diff options
49 files changed, 509 insertions, 203 deletions
diff --git a/actioncable/app/assets/javascripts/action_cable.js b/actioncable/app/assets/javascripts/action_cable.js index a68c76f299..280adbfa83 100644 --- a/actioncable/app/assets/javascripts/action_cable.js +++ b/actioncable/app/assets/javascripts/action_cable.js @@ -3,8 +3,8 @@ })(this, function(exports) { "use strict"; var adapters = { - logger: window.console, - WebSocket: window.WebSocket + logger: self.console, + WebSocket: self.WebSocket }; var logger = { log: function log() { @@ -49,7 +49,7 @@ this.startedAt = now(); delete this.stoppedAt; this.startPolling(); - document.addEventListener("visibilitychange", this.visibilityDidChange); + addEventListener("visibilitychange", this.visibilityDidChange); logger.log("ConnectionMonitor started. pollInterval = " + this.getPollInterval() + " ms"); } }; @@ -57,7 +57,7 @@ if (this.isRunning()) { this.stoppedAt = now(); this.stopPolling(); - document.removeEventListener("visibilitychange", this.visibilityDidChange); + removeEventListener("visibilitychange", this.visibilityDidChange); logger.log("ConnectionMonitor stopped"); } }; diff --git a/actioncable/app/javascript/action_cable/adapters.js b/actioncable/app/javascript/action_cable/adapters.js index 9ba6d338ee..4de8131438 100644 --- a/actioncable/app/javascript/action_cable/adapters.js +++ b/actioncable/app/javascript/action_cable/adapters.js @@ -1,4 +1,4 @@ export default { - logger: window.console, - WebSocket: window.WebSocket + logger: self.console, + WebSocket: self.WebSocket } diff --git a/actioncable/app/javascript/action_cable/connection_monitor.js b/actioncable/app/javascript/action_cable/connection_monitor.js index f0e75ae137..312a71d154 100644 --- a/actioncable/app/javascript/action_cable/connection_monitor.js +++ b/actioncable/app/javascript/action_cable/connection_monitor.js @@ -21,7 +21,7 @@ class ConnectionMonitor { this.startedAt = now() delete this.stoppedAt this.startPolling() - document.addEventListener("visibilitychange", this.visibilityDidChange) + addEventListener("visibilitychange", this.visibilityDidChange) logger.log(`ConnectionMonitor started. pollInterval = ${this.getPollInterval()} ms`) } } @@ -30,7 +30,7 @@ class ConnectionMonitor { if (this.isRunning()) { this.stoppedAt = now() this.stopPolling() - document.removeEventListener("visibilitychange", this.visibilityDidChange) + removeEventListener("visibilitychange", this.visibilityDidChange) logger.log("ConnectionMonitor stopped") } } diff --git a/actioncable/lib/rails/generators/test_unit/templates/channel_test.rb.tt b/actioncable/lib/rails/generators/test_unit/templates/channel_test.rb.tt index 301dc0b6fe..7307654611 100644 --- a/actioncable/lib/rails/generators/test_unit/templates/channel_test.rb.tt +++ b/actioncable/lib/rails/generators/test_unit/templates/channel_test.rb.tt @@ -1,5 +1,3 @@ -# frozen_string_literal: true - require "test_helper" class <%= class_name %>ChannelTest < ActionCable::Channel::TestCase diff --git a/actioncable/test/javascript/src/unit/action_cable_test.js b/actioncable/test/javascript/src/unit/action_cable_test.js index daad900aca..83426fa32e 100644 --- a/actioncable/test/javascript/src/unit/action_cable_test.js +++ b/actioncable/test/javascript/src/unit/action_cable_test.js @@ -6,14 +6,14 @@ const {module, test} = QUnit module("ActionCable", () => { module("Adapters", () => { module("WebSocket", () => { - test("default is window.WebSocket", assert => { - assert.equal(ActionCable.adapters.WebSocket, window.WebSocket) + test("default is self.WebSocket", assert => { + assert.equal(ActionCable.adapters.WebSocket, self.WebSocket) }) }) module("logger", () => { - test("default is window.console", assert => { - assert.equal(ActionCable.adapters.logger, window.console) + test("default is self.console", assert => { + assert.equal(ActionCable.adapters.logger, self.console) }) }) }) diff --git a/actionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb b/actionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb index ebe83a9ec0..bf0fd562fe 100644 --- a/actionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb +++ b/actionmailbox/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb @@ -38,7 +38,7 @@ module ActionMailbox # config.action_mailbox.ingress = :mailgun # # 3. {Configure Mailgun}[https://documentation.mailgun.com/en/latest/user_manual.html#receiving-forwarding-and-storing-messages] - # to forward inbound emails to `/rails/action_mailbox/mailgun/inbound_emails/mime`. + # to forward inbound emails to +/rails/action_mailbox/mailgun/inbound_emails/mime+. # # If your application lived at <tt>https://example.com</tt>, you would specify the fully-qualified URL # <tt>https://example.com/rails/action_mailbox/mailgun/inbound_emails/mime</tt>. diff --git a/actionmailbox/app/jobs/action_mailbox/incineration_job.rb b/actionmailbox/app/jobs/action_mailbox/incineration_job.rb index 224c9329a5..1579a3c7c8 100644 --- a/actionmailbox/app/jobs/action_mailbox/incineration_job.rb +++ b/actionmailbox/app/jobs/action_mailbox/incineration_job.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module ActionMailbox - # You can configure when this `IncinerationJob` will be run as a time-after-processing using the - # `config.action_mailbox.incinerate_after` or `ActionMailbox.incinerate_after` setting. + # You can configure when this +IncinerationJob+ will be run as a time-after-processing using the + # +config.action_mailbox.incinerate_after+ or +ActionMailbox.incinerate_after+ setting. # - # Since this incineration is set for the future, it'll automatically ignore any `InboundEmail`s + # Since this incineration is set for the future, it'll automatically ignore any <tt>InboundEmail</tt>s # that have already been deleted and discard itself if so. class IncinerationJob < ActiveJob::Base queue_as { ActionMailbox.queues[:incineration] } diff --git a/actionmailbox/app/models/action_mailbox/inbound_email.rb b/actionmailbox/app/models/action_mailbox/inbound_email.rb index 3a8dfd163c..023de19ccc 100644 --- a/actionmailbox/app/models/action_mailbox/inbound_email.rb +++ b/actionmailbox/app/models/action_mailbox/inbound_email.rb @@ -3,22 +3,22 @@ require "mail" module ActionMailbox - # The `InboundEmail` is an Active Record that keeps a reference to the raw email stored in Active Storage + # The +InboundEmail+ is an Active Record that keeps a reference to the raw email stored in Active Storage # and tracks the status of processing. By default, incoming emails will go through the following lifecycle: # # * Pending: Just received by one of the ingress controllers and scheduled for routing. # * Processing: During active processing, while a specific mailbox is running its #process method. # * Delivered: Successfully processed by the specific mailbox. - # * Failed: An exception was raised during the specific mailbox's execution of the `#process` method. + # * Failed: An exception was raised during the specific mailbox's execution of the +#process+ method. # * Bounced: Rejected processing by the specific mailbox and bounced to sender. # - # Once the `InboundEmail` has reached the status of being either `delivered`, `failed`, or `bounced`, - # it'll count as having been `#processed?`. Once processed, the `InboundEmail` will be scheduled for + # Once the +InboundEmail+ has reached the status of being either +delivered+, +failed+, or +bounced+, + # it'll count as having been +#processed?+. Once processed, the +InboundEmail+ will be scheduled for # automatic incineration at a later point. # - # When working with an `InboundEmail`, you'll usually interact with the parsed version of the source, - # which is available as a `Mail` object from `#mail`. But you can also access the raw source directly - # using the `#source` method. + # When working with an +InboundEmail+, you'll usually interact with the parsed version of the source, + # which is available as a +Mail+ object from +#mail+. But you can also access the raw source directly + # using the +#source+ method. # # Examples: # diff --git a/actionmailbox/app/models/action_mailbox/inbound_email/incineratable.rb b/actionmailbox/app/models/action_mailbox/inbound_email/incineratable.rb index 825e300648..697331ede4 100644 --- a/actionmailbox/app/models/action_mailbox/inbound_email/incineratable.rb +++ b/actionmailbox/app/models/action_mailbox/inbound_email/incineratable.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -# Ensure that the `InboundEmail` is automatically scheduled for later incineration if the status has been -# changed to `processed`. The later incineration will be invoked at the time specified by the -# `ActionMailbox.incinerate_after` time using the `IncinerationJob`. +# Ensure that the +InboundEmail+ is automatically scheduled for later incineration if the status has been +# changed to +processed+. The later incineration will be invoked at the time specified by the +# +ActionMailbox.incinerate_after+ time using the +IncinerationJob+. module ActionMailbox::InboundEmail::Incineratable extend ActiveSupport::Concern diff --git a/actionmailbox/app/models/action_mailbox/inbound_email/incineratable/incineration.rb b/actionmailbox/app/models/action_mailbox/inbound_email/incineratable/incineration.rb index 685f7fceb6..dabc83fae6 100644 --- a/actionmailbox/app/models/action_mailbox/inbound_email/incineratable/incineration.rb +++ b/actionmailbox/app/models/action_mailbox/inbound_email/incineratable/incineration.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module ActionMailbox - # Command class for carrying out the actual incineration of the `InboundMail` that's been scheduled - # for removal. Before the incineration – which really is just a call to `#destroy!` – is run, we verify + # Command class for carrying out the actual incineration of the +InboundMail+ that's been scheduled + # for removal. Before the incineration – which really is just a call to +#destroy!+ – is run, we verify # that it's both eligible (by virtue of having already been processed) and time to do so (that is, - # the `InboundEmail` was processed after the `incinerate_after` time). + # the +InboundEmail+ was processed after the +incinerate_after+ time). class InboundEmail::Incineratable::Incineration def initialize(inbound_email) @inbound_email = inbound_email diff --git a/actionmailbox/app/models/action_mailbox/inbound_email/message_id.rb b/actionmailbox/app/models/action_mailbox/inbound_email/message_id.rb index 2ad4525929..57b4a2445d 100644 --- a/actionmailbox/app/models/action_mailbox/inbound_email/message_id.rb +++ b/actionmailbox/app/models/action_mailbox/inbound_email/message_id.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -# The `Message-ID` as specified by rfc822 is supposed to be a unique identifier for that individual email. -# That makes it an ideal tracking token for debugging and forensics, just like `X-Request-Id` does for +# The +Message-ID+ as specified by rfc822 is supposed to be a unique identifier for that individual email. +# That makes it an ideal tracking token for debugging and forensics, just like +X-Request-Id+ does for # web request. # # If an inbound email does not, against the rfc822 mandate, specify a Message-ID, one will be generated -# using the approach from `Mail::MessageIdField`. +# using the approach from <tt>Mail::MessageIdField</tt>. module ActionMailbox::InboundEmail::MessageId extend ActiveSupport::Concern @@ -14,9 +14,9 @@ module ActionMailbox::InboundEmail::MessageId end class_methods do - # Create a new `InboundEmail` from the raw `source` of the email, which be uploaded as a Active Storage - # attachment called `raw_email`. Before the upload, extract the Message-ID from the `source` and set - # it as an attribute on the new `InboundEmail`. + # Create a new +InboundEmail+ from the raw +source+ of the email, which be uploaded as a Active Storage + # attachment called +raw_email+. Before the upload, extract the Message-ID from the +source+ and set + # it as an attribute on the new +InboundEmail+. def create_and_extract_message_id!(source, **options) create! options.merge(message_id: extract_message_id(source)) do |inbound_email| inbound_email.raw_email.attach io: StringIO.new(source), filename: "message.eml", content_type: "message/rfc822" diff --git a/actionmailbox/app/models/action_mailbox/inbound_email/routable.rb b/actionmailbox/app/models/action_mailbox/inbound_email/routable.rb index 58d67eb20c..39565df166 100644 --- a/actionmailbox/app/models/action_mailbox/inbound_email/routable.rb +++ b/actionmailbox/app/models/action_mailbox/inbound_email/routable.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -# A newly received `InboundEmail` will not be routed synchronously as part of ingress controller's receival. -# Instead, the routing will be done asynchronously, using a `RoutingJob`, to ensure maximum parallel capacity. +# A newly received +InboundEmail+ will not be routed synchronously as part of ingress controller's receival. +# Instead, the routing will be done asynchronously, using a +RoutingJob+, to ensure maximum parallel capacity. # -# By default, all newly created `InboundEmail` records that have the status of `pending`, which is the default, +# By default, all newly created +InboundEmail+ records that have the status of +pending+, which is the default, # will be scheduled for automatic, deferred routing. module ActionMailbox::InboundEmail::Routable extend ActiveSupport::Concern @@ -12,12 +12,12 @@ module ActionMailbox::InboundEmail::Routable after_create_commit :route_later, if: :pending? end - # Enqueue a `RoutingJob` for this `InboundEmail`. + # Enqueue a +RoutingJob+ for this +InboundEmail+. def route_later ActionMailbox::RoutingJob.perform_later self end - # Route this `InboundEmail` using the routing rules declared on the `ApplicationMailbox`. + # Route this +InboundEmail+ using the routing rules declared on the +ApplicationMailbox+. def route ApplicationMailbox.route self end diff --git a/actionmailbox/lib/action_mailbox/base.rb b/actionmailbox/lib/action_mailbox/base.rb index 4ac594b9f8..ff8587acd1 100644 --- a/actionmailbox/lib/action_mailbox/base.rb +++ b/actionmailbox/lib/action_mailbox/base.rb @@ -7,7 +7,7 @@ require "action_mailbox/routing" module ActionMailbox # The base class for all application mailboxes. Not intended to be inherited from directly. Inherit from - # `ApplicationMailbox` instead, as that's where the app-specific routing is configured. This routing + # +ApplicationMailbox+ instead, as that's where the app-specific routing is configured. This routing # is specified in the following ways: # # class ApplicationMailbox < ActionMailbox::Base @@ -27,15 +27,15 @@ module ActionMailbox # routing :all => :backstop # end # - # Application mailboxes need to overwrite the `#process` method, which is invoked by the framework after - # callbacks have been run. The callbacks available are: `before_processing`, `after_processing`, and - # `around_processing`. The primary use case is ensure certain preconditions to processing are fulfilled - # using `before_processing` callbacks. + # Application mailboxes need to overwrite the +#process+ method, which is invoked by the framework after + # callbacks have been run. The callbacks available are: +before_processing+, +after_processing+, and + # +around_processing+. The primary use case is ensure certain preconditions to processing are fulfilled + # using +before_processing+ callbacks. # - # If a precondition fails to be met, you can halt the processing using the `#bounced!` method, + # If a precondition fails to be met, you can halt the processing using the +#bounced!+ method, # which will silently prevent any further processing, but not actually send out any bounce notice. You # can also pair this behavior with the invocation of an Action Mailer class responsible for sending out - # an actual bounce email. This is done using the `#bounce_with` method, which takes the mail object returned + # an actual bounce email. This is done using the +#bounce_with+ method, which takes the mail object returned # by an Action Mailer method, like so: # # class ForwardsMailbox < ApplicationMailbox @@ -50,12 +50,12 @@ module ActionMailbox # end # # During the processing of the inbound email, the status will be tracked. Before processing begins, - # the email will normally have the `pending` status. Once processing begins, just before callbacks - # and the `#process` method is called, the status is changed to `processing`. If processing is allowed to - # complete, the status is changed to `delivered`. If a bounce is triggered, then `bounced`. If an unhandled - # exception is bubbled up, then `failed`. + # the email will normally have the +pending+ status. Once processing begins, just before callbacks + # and the +#process+ method is called, the status is changed to +processing+. If processing is allowed to + # complete, the status is changed to +delivered+. If a bounce is triggered, then +bounced+. If an unhandled + # exception is bubbled up, then +failed+. # - # Exceptions can be handled at the class level using the familiar `Rescuable` approach: + # Exceptions can be handled at the class level using the familiar +Rescuable+ approach: # # class ForwardsMailbox < ApplicationMailbox # rescue_from(ApplicationSpecificVerificationError) { bounced! } diff --git a/actionmailbox/lib/action_mailbox/router/route.rb b/actionmailbox/lib/action_mailbox/router/route.rb index b681eb7ea8..7e98e83382 100644 --- a/actionmailbox/lib/action_mailbox/router/route.rb +++ b/actionmailbox/lib/action_mailbox/router/route.rb @@ -2,7 +2,7 @@ module ActionMailbox # Encapsulates a route, which can then be matched against an inbound_email and provide a lookup of the matching - # mailbox class. See examples for the different route addresses and how to use them in the `ActionMailbox::Base` + # mailbox class. See examples for the different route addresses and how to use them in the +ActionMailbox::Base+ # documentation. class Router::Route attr_reader :address, :mailbox_name diff --git a/actionmailbox/lib/action_mailbox/routing.rb b/actionmailbox/lib/action_mailbox/routing.rb index 1ea96c8a9d..58462a44c6 100644 --- a/actionmailbox/lib/action_mailbox/routing.rb +++ b/actionmailbox/lib/action_mailbox/routing.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ActionMailbox - # See `ActionMailbox::Base` for how to specify routing. + # See +ActionMailbox::Base+ for how to specify routing. module Routing extend ActiveSupport::Concern diff --git a/actionmailbox/lib/action_mailbox/test_helper.rb b/actionmailbox/lib/action_mailbox/test_helper.rb index 02c52fb779..0ec9152844 100644 --- a/actionmailbox/lib/action_mailbox/test_helper.rb +++ b/actionmailbox/lib/action_mailbox/test_helper.rb @@ -4,38 +4,38 @@ require "mail" module ActionMailbox module TestHelper - # Create an `InboundEmail` record using an eml fixture in the format of message/rfc822 + # Create an +InboundEmail+ record using an eml fixture in the format of message/rfc822 # referenced with +fixture_name+ located in +test/fixtures/files/fixture_name+. def create_inbound_email_from_fixture(fixture_name, status: :processing) create_inbound_email_from_source file_fixture(fixture_name).read, status: status end - # Create an `InboundEmail` by specifying it using `Mail.new` options. Example: + # Create an +InboundEmail+ by specifying it using +Mail.new+ options. Example: # # create_inbound_email_from_mail(from: "david@loudthinking.com", subject: "Hello!") def create_inbound_email_from_mail(status: :processing, **mail_options) create_inbound_email_from_source Mail.new(mail_options).to_s, status: status end - # Create an `InboundEmail` using the raw rfc822 `source` as text. + # Create an +InboundEmail+ using the raw rfc822 +source+ as text. def create_inbound_email_from_source(source, status: :processing) ActionMailbox::InboundEmail.create_and_extract_message_id! source, status: status end - # Create an `InboundEmail` from fixture using the same arguments as `create_inbound_email_from_fixture` + # Create an +InboundEmail+ from fixture using the same arguments as +create_inbound_email_from_fixture+ # and immediately route it to processing. def receive_inbound_email_from_fixture(*args) create_inbound_email_from_fixture(*args).tap(&:route) end - # Create an `InboundEmail` from fixture using the same arguments as `create_inbound_email_from_mail` + # Create an +InboundEmail+ from fixture using the same arguments as +create_inbound_email_from_mail+ # and immediately route it to processing. def receive_inbound_email_from_mail(**kwargs) create_inbound_email_from_mail(**kwargs).tap(&:route) end - # Create an `InboundEmail` from fixture using the same arguments as `create_inbound_email_from_source` + # Create an +InboundEmail+ from fixture using the same arguments as +create_inbound_email_from_source+ # and immediately route it to processing. def receive_inbound_email_from_source(**kwargs) create_inbound_email_from_source(**kwargs).tap(&:route) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 95da4265a4..1457794354 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -85,10 +85,6 @@ *Yoshiyuki Kinjo* -* Remove undocumented `params` option from `url_for` helper. - - *Ilkka Oksanen* - * Encode Content-Disposition filenames on `send_data` and `send_file`. Previously, `send_data 'data', filename: "\u{3042}.txt"` sends `"filename=\"\u{3042}.txt\""` as Content-Disposition and it can be diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 118da11990..bf5e7a433f 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -124,6 +124,14 @@ module ActionController #:nodoc: # # render json: @people # + # +any+ can also be used with no arguments, in which case it will be used for any format requested by + # the user: + # + # respond_to do |format| + # format.html + # format.any { redirect_to support_path } + # end + # # Formats can have different variants. # # The request variant is a specialization of the request format, like <tt>:tablet</tt>, diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 2966c969f6..972953d4f3 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -820,6 +820,10 @@ module ActionDispatch path, params = generate(route_name, path_options, recall) + if options.key? :params + params.merge! options[:params] + end + options[:path] = path options[:script_name] = script_name options[:params] = params diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 1a31c7dbb8..fcb8ae296b 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -133,6 +133,7 @@ module ActionDispatch # <tt>ActionDispatch::Http::URL.tld_length</tt>, which in turn defaults to 1. # * <tt>:port</tt> - Optionally specify the port to connect to. # * <tt>:anchor</tt> - An anchor name to be appended to the path. + # * <tt>:params</tt> - The query parameters to be appended to the path. # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/" # * <tt>:script_name</tt> - Specifies application path relative to domain root. If provided, prepends application path. # diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 558e710df9..d8cea10153 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -193,7 +193,7 @@ class UrlOptionsTest < ActionController::TestCase action: "home", controller: "pages", only_path: true, - token: "secret" + params: { "token" => "secret" } } assert_equal "/home?token=secret", rs.url_for(options) diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index e381abee36..9222250b9c 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -354,6 +354,14 @@ module AbstractController assert_equal({ p2: "Y2" }.to_query, params[1]) end + def test_params_option + url = W.new.url_for(only_path: true, controller: "c", action: "a", params: { domain: "foo", id: "1" }) + params = extract_params(url) + assert_equal("/c/a?domain=foo&id=1", url) + assert_equal({ domain: "foo" }.to_query, params[0]) + assert_equal({ id: "1" }.to_query, params[1]) + end + def test_hash_parameter url = W.new.url_for(only_path: true, controller: "c", action: "a", query: { name: "Bob", category: "prof" }) params = extract_params(url) diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md index 2b110e604f..b9682b61e2 100644 --- a/actionview/app/assets/javascripts/README.md +++ b/actionview/app/assets/javascripts/README.md @@ -40,8 +40,7 @@ In a conventional Rails application that uses the asset pipeline, require `rails If you're using the Webpacker gem or some other JavaScript bundler, add the following to your main JS file: ```javascript -import Rails from "@rails/ujs" -Rails.start() +require("@rails/ujs").start() ``` ## How to run tests diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index fde3381df2..ea2ed7dff7 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/hash/keys" module ActiveModel # == Active \Model \Callbacks diff --git a/activestorage/README.md b/activestorage/README.md index 4a683dd8cd..f658b8d542 100644 --- a/activestorage/README.md +++ b/activestorage/README.md @@ -118,8 +118,7 @@ Active Storage, with its included JavaScript library, supports uploading directl ``` Using the npm package: ```js - import * as ActiveStorage from "@rails/activestorage" - ActiveStorage.start() + require("@rails/activestorage").start() ``` 2. Annotate file inputs with the direct upload URL. diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md index df02d5bd91..8f5c44849a 100644 --- a/guides/source/action_cable_overview.md +++ b/guides/source/action_cable_overview.md @@ -181,9 +181,9 @@ established using the following JavaScript, which is generated by default by Rai // Action Cable provides the framework to deal with WebSockets in Rails. // You can generate new channels where WebSocket features live using the `rails generate channel` command. -import ActionCable from "@rails/actioncable" +import { createConsumer } from "@rails/actioncable" -export default ActionCable.createConsumer() +export default createConsumer() ``` This will ready a consumer that'll connect against `/cable` on your server by default. diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md index 6d07d34dd7..474a93c83e 100644 --- a/guides/source/active_storage_overview.md +++ b/guides/source/active_storage_overview.md @@ -489,8 +489,7 @@ directly from the client to the cloud. Using the npm package: ```js - import * as ActiveStorage from "@rails/activestorage" - ActiveStorage.start() + require("@rails/activestorage").start() ``` 2. Annotate file inputs with the direct upload URL. diff --git a/guides/source/engines.md b/guides/source/engines.md index c4829299ca..f15383e3f1 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -1091,16 +1091,15 @@ main Rails application. Engine model and controller classes can be extended by open classing them in the main Rails application (since model and controller classes are just Ruby classes that inherit Rails specific functionality). Open classing an Engine class -redefines it for use in the main application. This is usually implemented by -using the decorator pattern. +redefines it for use in the main application. For simple class modifications, use `Class#class_eval`. For complex class modifications, consider using `ActiveSupport::Concern`. -#### A note on Decorators and Loading Code +#### A note on Overriding and Loading Code -Because these decorators are not referenced by your Rails application itself, -Rails' autoloading system will not kick in and load your decorators. This means +Because these overrides are not referenced by your Rails application itself, +Rails' autoloading system will not kick in and load your overrides. This means that you need to require them yourself. Here is some sample code to do this: @@ -1112,7 +1111,7 @@ module Blorgh isolate_namespace Blorgh config.to_prepare do - Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c| + Dir.glob(Rails.root + "app/overrides/**/*_override*.rb").each do |c| require_dependency(c) end end @@ -1120,15 +1119,15 @@ module Blorgh end ``` -This doesn't apply to just Decorators, but anything that you add in an engine +This doesn't apply to just overrides, but anything that you add in an engine that isn't referenced by your main application. -#### Implementing Decorator Pattern Using Class#class_eval +#### Reopening existing classes using Class#class_eval **Adding** `Article#time_since_created`: ```ruby -# MyApp/app/decorators/models/blorgh/article_decorator.rb +# MyApp/app/overrides/models/blorgh/article_override.rb Blorgh::Article.class_eval do def time_since_created @@ -1149,7 +1148,7 @@ end **Overriding** `Article#summary`: ```ruby -# MyApp/app/decorators/models/blorgh/article_decorator.rb +# MyApp/app/overrides/models/blorgh/article_override.rb Blorgh::Article.class_eval do def summary @@ -1169,7 +1168,7 @@ class Article < ApplicationRecord end ``` -#### Implementing Decorator Pattern Using ActiveSupport::Concern +#### Reopening existing classes using ActiveSupport::Concern Using `Class#class_eval` is great for simple adjustments, but for more complex class modifications, you might want to consider using [`ActiveSupport::Concern`] diff --git a/guides/source/testing.md b/guides/source/testing.md index 576c4d768c..1a2f480407 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -1735,14 +1735,14 @@ Testing Action Cable -------------------- Since Action Cable is used at different levels inside your application, -you'll need to test both the channels and connection classes themsleves and that other +you'll need to test both the channels, connection classes themselves, and that other entities broadcast correct messages. ### Connection Test Case By default, when you generate new Rails application with Action Cable, a test for the base connection class (`ApplicationCable::Connection`) is generated as well under `test/channels/application_cable` directory. -Connection tests aim to check whether a connection's identifiers gets assigned properly +Connection tests aim to check whether a connection's identifiers get assigned properly or that any improper connection requests are rejected. Here is an example: ```ruby @@ -1765,9 +1765,8 @@ end You can also specify request cookies the same way you do in integration tests: - ```ruby -test "connects with_cookies" do +test "connects with cookies" do cookies.signed[:user_id] = "42" connect @@ -1778,7 +1777,6 @@ end See the API documentation for [`AcionCable::Connection::TestCase`](http://api.rubyonrails.org/classes/ActionCable/Connection/TestCase.html) for more information. - ### Channel Test Case By default, when you generate a channel, an associated test will be generated as well @@ -1823,7 +1821,7 @@ See the API documentation for [`AcionCable::Channel::TestCase`](http://api.rubyo Action Cable ships with a bunch of custom assertions that can be used to lessen the verbosity of tests. For a full list of available assertions, see the API documentation for [`ActionCable::TestHelper`](http://api.rubyonrails.org/classes/ActionCable/TestHelper.html). -It's a good practice to ensure that the correct message has been broadcasted inside another components (e.g. inside your controllers). This is precisely where +It's a good practice to ensure that the correct message has been broadcasted inside other components (e.g. inside your controllers). This is precisely where the custom assertions provided by Action Cable are pretty useful. For instance, within a model: diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index aca55fae80..9c7e958b7c 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,20 @@ +* Add `rails db:system:change` command for changing databases. + + ``` + bin/rails db:system:change --to=postgresql + force config/database.yml + gsub Gemfile + ``` + + The change command copies a template `config/database.yml` with the target database adapter into your app, and replaces your database gem with the target database gem. + + *Gannon McGibbon* + +* Add `rails test:channels`. + + *bogdanvlviv* + + * Use original `bundler` environment variables during the process of generating a new rails project. *Marco Costa* diff --git a/railties/lib/rails/commands/db/system/change/change_command.rb b/railties/lib/rails/commands/db/system/change/change_command.rb new file mode 100644 index 0000000000..760c229c07 --- /dev/null +++ b/railties/lib/rails/commands/db/system/change/change_command.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "rails/generators" +require "rails/generators/rails/db/system/change/change_generator" + +module Rails + module Command + module Db + module System + class ChangeCommand < Base # :nodoc: + class_option :to, desc: "The database system to switch to." + + def perform + Rails::Generators::Db::System::ChangeGenerator.start + end + end + end + end + end +end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index caf8a33c3c..b835b3f3fd 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -23,6 +23,8 @@ module Rails autoload :ActiveModel, "rails/generators/active_model" autoload :Base, "rails/generators/base" autoload :Migration, "rails/generators/migration" + autoload :Database, "rails/generators/database" + autoload :AppName, "rails/generators/app_name" autoload :NamedBase, "rails/generators/named_base" autoload :ResourceHelpers, "rails/generators/resource_helpers" autoload :TestCase, "rails/generators/test_case" @@ -218,6 +220,7 @@ module Rails rails.delete("encryption_key_file") rails.delete("master_key") rails.delete("credentials") + rails.delete("db:system:change") hidden_namespaces.each { |n| groups.delete(n.to_s) } diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 8df2b32dd2..8278abcaeb 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -11,9 +11,8 @@ require "active_support/core_ext/array/extract_options" module Rails module Generators class AppBase < Base # :nodoc: - DATABASES = %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver ) - JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc ) - DATABASES.concat(JDBC_DATABASES) + include Database + include AppName attr_accessor :rails_template add_shebang_option! @@ -106,7 +105,6 @@ module Rails @gem_filter = lambda { |gem| true } @extra_entries = [] super - convert_database_option_for_jruby end private @@ -306,34 +304,6 @@ module Rails end end - def gem_for_database - # %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql ) - case options[:database] - when "mysql" then ["mysql2", [">= 0.4.4"]] - when "postgresql" then ["pg", [">= 0.18", "< 2.0"]] - when "oracle" then ["activerecord-oracle_enhanced-adapter", nil] - when "frontbase" then ["ruby-frontbase", nil] - when "sqlserver" then ["activerecord-sqlserver-adapter", nil] - when "jdbcmysql" then ["activerecord-jdbcmysql-adapter", nil] - when "jdbcsqlite3" then ["activerecord-jdbcsqlite3-adapter", nil] - when "jdbcpostgresql" then ["activerecord-jdbcpostgresql-adapter", nil] - when "jdbc" then ["activerecord-jdbc-adapter", nil] - else [options[:database], nil] - end - end - - def convert_database_option_for_jruby - if defined?(JRUBY_VERSION) - opt = options.dup - case opt[:database] - when "postgresql" then opt[:database] = "jdbcpostgresql" - when "mysql" then opt[:database] = "jdbcmysql" - when "sqlite3" then opt[:database] = "jdbcsqlite3" - end - self.options = opt.freeze - end - end - def assets_gemfile_entry return [] if options[:skip_sprockets] diff --git a/railties/lib/rails/generators/app_name.rb b/railties/lib/rails/generators/app_name.rb new file mode 100644 index 0000000000..c4f71694d8 --- /dev/null +++ b/railties/lib/rails/generators/app_name.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Rails + module Generators + module AppName # :nodoc: + RESERVED_NAMES = %w(application destroy plugin runner test) + + private + def app_name + @app_name ||= original_app_name.tr("-", "_") + end + + def original_app_name + @original_app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr('\\', "").tr(". ", "_") + end + + def defined_app_name + defined_app_const_base.underscore + end + + def defined_app_const_base + Rails.respond_to?(:application) && defined?(Rails::Application) && + Rails.application.is_a?(Rails::Application) && Rails.application.class.name.chomp("::Application") + end + + alias :defined_app_const_base? :defined_app_const_base + + def app_const_base + @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, "_").squeeze("_").camelize + end + alias :camelized :app_const_base + + def app_const + @app_const ||= "#{app_const_base}::Application" + end + + def valid_const? + if /^\d/.match?(app_const) + raise Error, "Invalid application name #{original_app_name}. Please give a name which does not start with numbers." + elsif RESERVED_NAMES.include?(original_app_name) + raise Error, "Invalid application name #{original_app_name}. Please give a " \ + "name which does not match one of the reserved rails " \ + "words: #{RESERVED_NAMES.join(", ")}" + elsif Object.const_defined?(app_const_base) + raise Error, "Invalid application name #{original_app_name}, constant #{app_const_base} is already in use. Please choose another application name." + end + end + end + end +end diff --git a/railties/lib/rails/generators/database.rb b/railties/lib/rails/generators/database.rb new file mode 100644 index 0000000000..be3e61bde8 --- /dev/null +++ b/railties/lib/rails/generators/database.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Rails + module Generators + module Database # :nodoc: + JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc ) + DATABASES = %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver ) + JDBC_DATABASES + + def initialize(*) + super + convert_database_option_for_jruby + end + + def gem_for_database(database = options[:database]) + case database + when "mysql" then ["mysql2", [">= 0.4.4"]] + when "postgresql" then ["pg", [">= 0.18", "< 2.0"]] + when "oracle" then ["activerecord-oracle_enhanced-adapter", nil] + when "frontbase" then ["ruby-frontbase", nil] + when "sqlserver" then ["activerecord-sqlserver-adapter", nil] + when "jdbcmysql" then ["activerecord-jdbcmysql-adapter", nil] + when "jdbcsqlite3" then ["activerecord-jdbcsqlite3-adapter", nil] + when "jdbcpostgresql" then ["activerecord-jdbcpostgresql-adapter", nil] + when "jdbc" then ["activerecord-jdbc-adapter", nil] + else [database, nil] + end + end + + def convert_database_option_for_jruby + if defined?(JRUBY_VERSION) + opt = options.dup + case opt[:database] + when "postgresql" then opt[:database] = "jdbcpostgresql" + when "mysql" then opt[:database] = "jdbcmysql" + when "sqlite3" then opt[:database] = "jdbcsqlite3" + end + self.options = opt.freeze + end + end + + private + def mysql_socket + @mysql_socket ||= [ + "/tmp/mysql.sock", # default + "/var/run/mysqld/mysqld.sock", # debian/gentoo + "/var/tmp/mysql.sock", # freebsd + "/var/lib/mysql/mysql.sock", # fedora + "/opt/local/lib/mysql/mysql.sock", # fedora + "/opt/local/var/run/mysqld/mysqld.sock", # mac + darwinports + mysql + "/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4 + "/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5 + "/opt/lampp/var/mysql/mysql.sock" # xampp for linux + ].find { |f| File.exist?(f) } unless Gem.win_platform? + end + end + end +end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 5c59566dc2..337a71ac06 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -242,7 +242,6 @@ module Rails # We need to store the RAILS_DEV_PATH in a constant, otherwise the path # can change in Ruby 1.8.7 when we FileUtils.cd. RAILS_DEV_PATH = File.expand_path("../../../../../..", __dir__) - RESERVED_NAMES = %w[application destroy plugin runner test] class AppGenerator < AppBase # :nodoc: WEBPACKS = %w( react vue angular elm stimulus ) @@ -269,7 +268,7 @@ module Rails super if !options[:skip_active_record] && !DATABASES.include?(options[:database]) - raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}." + raise Error, "Invalid value for --database option. Supported preconfigurations are: #{DATABASES.join(", ")}." end # Force sprockets and yarn to be skipped when generating API only apps. @@ -498,60 +497,6 @@ module Rails create_file(*args, &block) end - def app_name - @app_name ||= original_app_name.tr("-", "_") - end - - def original_app_name - @original_app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr('\\', "").tr(". ", "_") - end - - def defined_app_name - defined_app_const_base.underscore - end - - def defined_app_const_base - Rails.respond_to?(:application) && defined?(Rails::Application) && - Rails.application.is_a?(Rails::Application) && Rails.application.class.name.sub(/::Application$/, "") - end - - alias :defined_app_const_base? :defined_app_const_base - - def app_const_base - @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, "_").squeeze("_").camelize - end - alias :camelized :app_const_base - - def app_const - @app_const ||= "#{app_const_base}::Application" - end - - def valid_const? - if /^\d/.match?(app_const) - raise Error, "Invalid application name #{original_app_name}. Please give a name which does not start with numbers." - elsif RESERVED_NAMES.include?(original_app_name) - raise Error, "Invalid application name #{original_app_name}. Please give a " \ - "name which does not match one of the reserved rails " \ - "words: #{RESERVED_NAMES.join(", ")}" - elsif Object.const_defined?(app_const_base) - raise Error, "Invalid application name #{original_app_name}, constant #{app_const_base} is already in use. Please choose another application name." - end - end - - def mysql_socket - @mysql_socket ||= [ - "/tmp/mysql.sock", # default - "/var/run/mysqld/mysqld.sock", # debian/gentoo - "/var/tmp/mysql.sock", # freebsd - "/var/lib/mysql/mysql.sock", # fedora - "/opt/local/lib/mysql/mysql.sock", # fedora - "/opt/local/var/run/mysqld/mysqld.sock", # mac + darwinports + mysql - "/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4 - "/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5 - "/opt/lampp/var/mysql/mysql.sock" # xampp for linux - ].find { |f| File.exist?(f) } unless Gem.win_platform? - end - def get_builder_class defined?(::AppBuilder) ? ::AppBuilder : Rails::AppBuilder end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt index 1ad3a4b1f7..d39b5d311f 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt @@ -18,11 +18,11 @@ ruby <%= "'#{RUBY_VERSION}'" -%> <% end -%> <% end -%> -# Use ActiveModel has_secure_password +# Use Active Model has_secure_password # gem 'bcrypt', '~> 3.1.7' <% unless skip_active_storage? -%> -# Use ActiveStorage variant +# Use Active Storage variant # gem 'image_processing', '~> 1.2' <% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js b/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js index eec7e54b8a..0eceb59b18 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js +++ b/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js @@ -1,6 +1,6 @@ // Action Cable provides the framework to deal with WebSockets in Rails. // You can generate new channels where WebSocket features live using the `rails generate channel` command. -import ActionCable from "@rails/actioncable" +import { createConsumer } from "@rails/actioncable" -export default ActionCable.createConsumer() +export default createConsumer() diff --git a/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt index de91713546..908487d500 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt @@ -3,19 +3,13 @@ // a relevant structure within app/javascript and only use these pack files to reference // that code so it'll be compiled. -import Rails from "@rails/ujs" -Rails.start() +require("@rails/ujs").start() <%- unless options[:skip_turbolinks] -%> - -import Turbolinks from "turbolinks" -Turbolinks.start() +require("turbolinks").start() <%- end -%> <%- unless skip_active_storage? -%> - -import * as ActiveStorage from "@rails/activestorage" -ActiveStorage.start() +require("@rails/activestorage").start() <%- end -%> <%- unless options[:skip_action_cable] -%> - -import "channels" +require("channels") <%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt b/railties/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt index cc8337fc6d..800405f15e 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt @@ -1,5 +1,3 @@ -# frozen_string_literal: true - require "test_helper" class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase diff --git a/railties/lib/rails/generators/rails/db/system/change/change_generator.rb b/railties/lib/rails/generators/rails/db/system/change/change_generator.rb new file mode 100644 index 0000000000..63849eb18d --- /dev/null +++ b/railties/lib/rails/generators/rails/db/system/change/change_generator.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "rails/generators/base" + +module Rails + module Generators + module Db + module System + class ChangeGenerator < Base # :nodoc: + include Database + include AppName + + class_option :to, required: true, + desc: "The database system to switch to." + + def self.default_generator_root + path = File.expand_path(File.join(base_name, "app"), base_root) + path if File.exist?(path) + end + + def initialize(*) + super + + unless DATABASES.include?(options[:to]) + raise Error, "Invalid value for --to option. Supported preconfigurations are: #{DATABASES.join(", ")}." + end + + opt = options.dup + opt[:database] ||= opt[:to] + self.options = opt.freeze + end + + def edit_database_config + template("config/databases/#{options[:database]}.yml", "config/database.yml") + end + + def edit_gemfile + database_gem_name, _ = gem_for_database + gsub_file("Gemfile", all_database_gems_regex, database_gem_name) + end + + private + def all_database_gems + DATABASES.map { |database| gem_for_database(database) } + end + + def all_database_gems_regex + all_database_gem_names = all_database_gems.map(&:first) + /(\b#{all_database_gem_names.join('\b|\b')}\b)/ + end + end + end + end + end +end diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index ecc458b21e..3a1b62d9d1 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -28,7 +28,7 @@ namespace :test do desc "Run tests quickly, but also reset db" task db: %w[db:test:prepare test] - ["models", "helpers", "controllers", "mailers", "integration", "jobs", "mailboxes"].each do |name| + ["models", "helpers", "channels", "controllers", "mailers", "integration", "jobs", "mailboxes"].each do |name| task name => "test:prepare" do $: << "test" Rails::TestUnit::Runner.rake_run(["test/#{name}"]) diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 44e3b0f66b..830c36671c 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -118,7 +118,7 @@ module ApplicationTests end def test_code_statistics_sanity - assert_match "Code LOC: 32 Test LOC: 0 Code to Test Ratio: 1:0.0", + assert_match "Code LOC: 29 Test LOC: 0 Code to Test Ratio: 1:0.0", rails("stats") end diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index 6765eef9d0..fda6df500d 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -98,6 +98,17 @@ module ApplicationTests end end + def test_run_channels + create_test_file :channels, "foo_channel" + create_test_file :channels, "bar_channel" + + rails("test:channels").tap do |output| + assert_match "FooChannelTest", output + assert_match "BarChannelTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + def test_run_controllers create_test_file :controllers, "foo_controller" create_test_file :controllers, "bar_controller" @@ -167,11 +178,11 @@ module ApplicationTests end def test_run_all_suites - suites = [:models, :helpers, :unit, :controllers, :mailers, :functional, :integration, :jobs, :mailboxes] + suites = [:models, :helpers, :unit, :channels, :controllers, :mailers, :functional, :integration, :jobs, :mailboxes] suites.each { |suite| create_test_file suite, "foo_#{suite}" } run_test_command("") .tap do |output| suites.each { |suite| assert_match "Foo#{suite.to_s.camelize}Test", output } - assert_match "9 runs, 9 assertions, 0 failures", output + assert_match "10 runs, 10 assertions, 0 failures", output end end diff --git a/railties/test/commands/db_system_change_test.rb b/railties/test/commands/db_system_change_test.rb new file mode 100644 index 0000000000..2ff45a7878 --- /dev/null +++ b/railties/test/commands/db_system_change_test.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rails/command" +require "rails/commands/db/system/change/change_command" + +class Rails::Command::Db::System::ChangeCommandTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + setup { build_app } + + teardown { teardown_app } + + test "change to existing database" do + change_database(to: "sqlite3") + + output = change_database(to: "sqlite3") + + assert_match "identical config/database.yml", output + assert_match "gsub Gemfile", output + end + + test "change to invalid database" do + output = change_database(to: "invalid-db") + + assert_match <<~MSG.squish, output + Invalid value for --to option. + Supported preconfigurations are: + mysql, postgresql, sqlite3, oracle, frontbase, + ibm_db, sqlserver, jdbcmysql, jdbcsqlite3, + jdbcpostgresql, jdbc. + MSG + end + + test "change to postgresql" do + output = change_database(to: "postgresql") + + assert_match "force config/database.yml", output + assert_match "gsub Gemfile", output + end + + test "change to mysql" do + output = change_database(to: "mysql") + + assert_match "force config/database.yml", output + assert_match "gsub Gemfile", output + end + + test "change to sqlite3" do + change_database(to: "postgresql") + output = change_database(to: "sqlite3") + + assert_match "force config/database.yml", output + assert_match "gsub Gemfile", output + end + + private + def change_database(to:, **options) + args = ["--to", to] + rails "db:system:change", args, **options + end +end diff --git a/railties/test/generators/db_system_change_generator_test.rb b/railties/test/generators/db_system_change_generator_test.rb new file mode 100644 index 0000000000..d476bfd2dc --- /dev/null +++ b/railties/test/generators/db_system_change_generator_test.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "generators/generators_test_helper" +require "rails/generators/rails/db/system/change/change_generator" + +module Rails + module Generators + module Db + module System + class ChangeGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + + setup do + copy_gemfile( + GemfileEntry.new("sqlite3", nil, "Use sqlite3 as the database for Active Record") + ) + end + + test "change to invalid database" do + output = capture(:stderr) do + run_generator ["--to", "invalid-db"] + end + + assert_match <<~MSG.squish, output + Invalid value for --to option. + Supported preconfigurations are: + mysql, postgresql, sqlite3, oracle, frontbase, + ibm_db, sqlserver, jdbcmysql, jdbcsqlite3, + jdbcpostgresql, jdbc. + MSG + end + + test "change to postgresql" do + run_generator ["--to", "postgresql"] + + assert_file("config/database.yml") do |content| + assert_match "adapter: postgresql", content + assert_match "database: test_app", content + end + + assert_file("Gemfile") do |content| + assert_match "# Use pg as the database for Active Record", content + assert_match "gem 'pg'", content + end + end + + test "change to mysql" do + run_generator ["--to", "mysql"] + + assert_file("config/database.yml") do |content| + assert_match "adapter: mysql2", content + assert_match "database: test_app", content + end + + assert_file("Gemfile") do |content| + assert_match "# Use mysql2 as the database for Active Record", content + assert_match "gem 'mysql2'", content + end + end + + test "change to sqlite3" do + run_generator ["--to", "sqlite3"] + + assert_file("config/database.yml") do |content| + assert_match "adapter: sqlite3", content + assert_match "db/development.sqlite3", content + end + + assert_file("Gemfile") do |content| + assert_match "# Use sqlite3 as the database for Active Record", content + assert_match "gem 'sqlite3'", content + end + end + end + end + end + end +end diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 25d5dba1d8..975a204af4 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -30,6 +30,12 @@ module GeneratorsTestHelper include ActiveSupport::Testing::Stream include ActiveSupport::Testing::MethodCallAssertions + GemfileEntry = Struct.new(:name, :version, :comment, :options, :commented_out) do + def initialize(name, version, comment, options = {}, commented_out = false) + super + end + end + def self.included(base) base.class_eval do destination File.join(Rails.root, "tmp") @@ -63,4 +69,34 @@ module GeneratorsTestHelper FileUtils.mkdir_p(destination) FileUtils.cp routes, File.join(destination, "routes.rb") end + + def copy_gemfile(*gemfile_entries) + locals = gemfile_locals.merge(gemfile_entries: gemfile_entries) + gemfile = File.expand_path("../../lib/rails/generators/rails/app/templates/Gemfile.tt", __dir__) + gemfile = evaluate_template(gemfile, locals) + destination = File.join(destination_root) + File.write File.join(destination, "Gemfile"), gemfile + end + + def evaluate_template(file, locals = {}) + erb = ERB.new(File.read(file), nil, "-", "@output_buffer") + context = Class.new do + locals.each do |local, value| + class_attribute local, default: value + end + end + erb.result(context.new.instance_eval("binding")) + end + + private + def gemfile_locals + { + skip_active_storage: true, + depend_on_bootsnap: false, + depend_on_listen: false, + spring_install: false, + depends_on_system_test: false, + options: ActiveSupport::OrderedOptions.new, + } + end end diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index f673832caa..26ce487c5f 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -206,7 +206,7 @@ module SharedGeneratorTests unless generator_class.name == "Rails::Generators::PluginGenerator" assert_file "#{application_path}/app/javascript/packs/application.js" do |content| - assert_match(/^import \* as ActiveStorage from "@rails\/activestorage"\nActiveStorage.start\(\)/, content) + assert_match(/^require\("@rails\/activestorage"\)\.start\(\)/, content) end end @@ -267,7 +267,7 @@ module SharedGeneratorTests assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']active_storage\/engine["']/ assert_file "#{application_path}/app/javascript/packs/application.js" do |content| - assert_no_match(/^import * as ActiveStorage from "@rails\/activestorage"\nActiveStorage.start\(\)/, content) + assert_no_match(/^require\("@rails\/activestorage"\)\.start\(\)/, content) end assert_file "#{application_path}/config/environments/development.rb" do |content| diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 7d4a26ff9d..01b6cb0a43 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -476,7 +476,7 @@ Module.new do `yarn build` end - `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-gemfile --skip-listen --no-rc` + `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-bundle --skip-listen --no-rc` File.open("#{app_template_path}/config/boot.rb", "w") do |f| f.puts "require 'rails/all'" end |