diff options
188 files changed, 932 insertions, 642 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index cb8dc513a9..8663034a1a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -89,8 +89,10 @@ Style/FrozenStringLiteralComment: Include: - 'activesupport/**/*' - 'activemodel/**/*' + - 'actioncable/**/*' - 'activejob/**/*' - 'activerecord/**/*' + - 'actionmailer/**/*' # Use `foo {}` not `foo{}`. Layout/SpaceBeforeBlockBraces: diff --git a/Gemfile.lock b/Gemfile.lock index 68b27bfc1e..3c6eafaf42 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -31,7 +31,7 @@ GIT GIT remote: https://github.com/rails/arel.git - revision: 67a51c62f4e19390cd8eb408596ca48bb0806362 + revision: 7a29220c689feb0581e21d5324b85fc2f201ac5e specs: arel (8.0.0) diff --git a/RELEASING_RAILS.md b/RELEASING_RAILS.md index 6e27d8ee5a..3ff28c29f5 100644 --- a/RELEASING_RAILS.md +++ b/RELEASING_RAILS.md @@ -1,35 +1,35 @@ # Releasing Rails -In this document, we'll cover the steps necessary to release Rails. Each -section contains steps to take during that time before the release. The times -suggested in each header are just that: suggestions. However, they should +In this document, we'll cover the steps necessary to release Rails. Each +section contains steps to take during that time before the release. The times +suggested in each header are just that: suggestions. However, they should really be considered as minimums. ## 10 Days before release -Today is mostly coordination tasks. Here are the things you must do today: +Today is mostly coordination tasks. Here are the things you must do today: -### Is the CI green? If not, make it green. (See "Fixing the CI") +### Is the CI green? If not, make it green. (See "Fixing the CI") -Do not release with a Red CI. You can find the CI status here: +Do not release with a Red CI. You can find the CI status here: ``` http://travis-ci.org/rails/rails ``` -### Is Sam Ruby happy? If not, make him happy. +### Is Sam Ruby happy? If not, make him happy. Sam Ruby keeps a [test suite](https://github.com/rubys/awdwr) that makes sure the code samples in his book ([Agile Web Development with Rails](https://pragprog.com/titles/rails5/agile-web-development-with-rails-5th-edition)) -all work. These are valuable system tests -for Rails. You can check the status of these tests here: +all work. These are valuable system tests +for Rails. You can check the status of these tests here: [http://intertwingly.net/projects/dashboard.html](http://intertwingly.net/projects/dashboard.html) Do not release with Red AWDwR tests. -### Do we have any Git dependencies? If so, contact those authors. +### Do we have any Git dependencies? If so, contact those authors. Having Git dependencies indicates that we depend on unreleased code. Obviously Rails cannot be released when it depends on unreleased code. @@ -38,12 +38,12 @@ suits them. ### Contact the security team (either tenderlove or rafaelfranca) -Let them know of your plans to release. There may be security issues to be +Let them know of your plans to release. There may be security issues to be addressed, and that can impact your release date. ### Notify implementors. -Ruby implementors have high stakes in making sure Rails works. Be kind and +Ruby implementors have high stakes in making sure Rails works. Be kind and give them a heads up that Rails will be released soonish. This is only required for major and minor releases, bugfix releases aren't a @@ -60,18 +60,18 @@ Implementors will love you and help you. ## 3 Days before release -This is when you should release the release candidate. Here are your tasks +This is when you should release the release candidate. Here are your tasks for today: -### Is the CI green? If not, make it green. +### Is the CI green? If not, make it green. -### Is Sam Ruby happy? If not, make him happy. +### Is Sam Ruby happy? If not, make him happy. -### Contact the security team. CVE emails must be sent on this day. +### Contact the security team. CVE emails must be sent on this day. ### Create a release branch. -From the stable branch, create a release branch. For example, if you're +From the stable branch, create a release branch. For example, if you're releasing Rails 3.0.10, do this: ``` @@ -82,7 +82,7 @@ Switched to a new branch '3-0-10' ### Update each CHANGELOG. -Many times commits are made without the CHANGELOG being updated. You should +Many times commits are made without the CHANGELOG being updated. You should review the commits since the last release, and fill in any missing information for each CHANGELOG. @@ -96,15 +96,15 @@ If you're doing a stable branch release, you should also ensure that all of the CHANGELOG entries in the stable branch are also synced to the master branch. -### Update the RAILS_VERSION file to include the RC. +### Put the new version in the RAILS_VERSION file. -### Build and test the gem. +Include an RC number if appropriate, e.g. `6.0.0.rc1`. -Run `rake install` to generate the gems and install them locally. Then try -generating a new app and ensure that nothing explodes. +### Build and test the gem. -Verify that Action Cable and Action View's package.json files are updated with -the RC version. +Run `rake verify` to generate the gems and install them locally. `verify` also +generates a Rails app with a migration and boots it to smoke test with in your +browser. This will stop you from looking silly when you push an RC to rubygems.org and then realize it is broken. @@ -117,37 +117,39 @@ as NPM packages, so you must have Node.js installed, have an NPM account check this via `npm owner ls actioncable` and `npm owner ls rails-ujs`) in order to do a full release. Do not release until you're set up with NPM! +The release task will sign the release tag. If you haven't got commit signing +set up, use https://git-scm.com/book/tr/v2/Git-Tools-Signing-Your-Work as a +guide. You can generate keys with the GPG suite from here: https://gpgtools.org. + +Run `rake changelog:header` to put a header with the new version in every +CHANGELOG. Don't commit this, the release task handles it. + Run `rake release`. This will populate the gemspecs and NPM package.json with the current RAILS_VERSION, commit the changes, tag it, and push the gems to rubygems.org. -Here are the commands that `rake release` uses so you can understand what to do -in case anything goes wrong: - -``` -$ rake all:build -$ git commit -am'updating RAILS_VERSION' -$ git tag -m 'v3.0.10.rc1 release' v3.0.10.rc1 -$ git push -$ git push --tags -$ for i in $(ls pkg); do gem push $i; npm publish; done -``` - ### Send Rails release announcements Write a release announcement that includes the version, changes, and links to -GitHub where people can find the specific commit list. Here are the mailing +GitHub where people can find the specific commit list. Here are the mailing lists where you should announce: * rubyonrails-core@googlegroups.com * rubyonrails-talk@googlegroups.com * ruby-talk@ruby-lang.org -Use Markdown format for your announcement. Remember to ask people to report +Use Markdown format for your announcement. Remember to ask people to report issues with the release candidate to the rails-core mailing list. +NOTE: For patch releases there's a `rake announce` task to generate the release +post. It supports multiple patch releases too: + +``` +VERSIONS="5.0.5.rc1,5.1.3.rc1" rake announce +``` + IMPORTANT: If any users experience regressions when using the release -candidate, you *must* postpone the release. Bugfix releases *should not* +candidate, you *must* postpone the release. Bugfix releases *should not* break existing applications. ### Post the announcement to the Rails blog. @@ -165,12 +167,12 @@ Check the rails-core mailing list and the GitHub issue list for regressions in the RC. If any regressions are found, fix the regressions and repeat the release -candidate process. We will not release the final until 72 hours after the -last release candidate has been pushed. This means that if users find +candidate process. We will not release the final until 72 hours after the +last release candidate has been pushed. This means that if users find regressions, the scheduled release date must be postponed. -When you fix the regressions, do not create a new branch. Fix them on the -stable branch, then cherry pick the commit to your release branch. No other +When you fix the regressions, do not create a new branch. Fix them on the +stable branch, then cherry pick the commit to your release branch. No other commits should be added to the release branch besides regression fixing commits. ## Day of release @@ -203,7 +205,7 @@ Email the security reports to: * oss-security@lists.openwall.com Be sure to note the security fixes in your announcement along with CVE numbers -and links to each patch. Some people may not be able to upgrade right away, +and links to each patch. Some people may not be able to upgrade right away, so we need to give them the security fixes in patch form. * Blog announcements @@ -7,6 +7,9 @@ require "railties/lib/rails/api/task" desc "Build gem files for all projects" task build: "all:build" +desc "Build, install and verify the gem files in a generated Rails app." +task verify: "all:verify" + desc "Prepare the release" task prep_release: "all:prep_release" diff --git a/actioncable/Rakefile b/actioncable/Rakefile index e21843bb44..226d171104 100644 --- a/actioncable/Rakefile +++ b/actioncable/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rake/testtask" require "pathname" require "open3" diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec index a72c0d2608..b5b98f1a6b 100644 --- a/actioncable/actioncable.gemspec +++ b/actioncable/actioncable.gemspec @@ -1,3 +1,5 @@ +# frozen_string_literal: true + version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip Gem::Specification.new do |s| diff --git a/actioncable/bin/test b/actioncable/bin/test index 470ce93f10..c53377cc97 100755 --- a/actioncable/bin/test +++ b/actioncable/bin/test @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true COMPONENT_ROOT = File.expand_path("..", __dir__) require_relative "../../tools/test" diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb index 9edd82e7b5..bd828b2d0f 100644 --- a/actioncable/lib/action_cable.rb +++ b/actioncable/lib/action_cable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- # Copyright (c) 2015-2017 Basecamp, LLC # diff --git a/actioncable/lib/action_cable/channel.rb b/actioncable/lib/action_cable/channel.rb index 7ae262ce5f..d2f6fbbbc7 100644 --- a/actioncable/lib/action_cable/channel.rb +++ b/actioncable/lib/action_cable/channel.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Channel extend ActiveSupport::Autoload diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb index 718f630f58..c5ad749bfe 100644 --- a/actioncable/lib/action_cable/channel/base.rb +++ b/actioncable/lib/action_cable/channel/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "set" module ActionCable @@ -205,7 +207,9 @@ module ActionCable # Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with # the proper channel identifier marked as the recipient. def transmit(data, via: nil) # :doc: - logger.debug "#{self.class.name} transmitting #{data.inspect.truncate(300)}".tap { |m| m << " (via #{via})" if via } + status = "#{self.class.name} transmitting #{data.inspect.truncate(300)}" + status += " (via #{via})" if via + logger.debug(status) payload = { channel_class: self.class.name, data: data, via: via } ActiveSupport::Notifications.instrument("transmit.action_cable", payload) do @@ -266,7 +270,7 @@ module ActionCable end def action_signature(action, data) - "#{self.class.name}##{action}".tap do |signature| + "#{self.class.name}##{action}".dup.tap do |signature| if (arguments = data.except("action")).any? signature << "(#{arguments.inspect})" end diff --git a/actioncable/lib/action_cable/channel/broadcasting.rb b/actioncable/lib/action_cable/channel/broadcasting.rb index 23ed4ec943..acc791817b 100644 --- a/actioncable/lib/action_cable/channel/broadcasting.rb +++ b/actioncable/lib/action_cable/channel/broadcasting.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/object/to_param" module ActionCable diff --git a/actioncable/lib/action_cable/channel/callbacks.rb b/actioncable/lib/action_cable/channel/callbacks.rb index c740132c94..4223c0d996 100644 --- a/actioncable/lib/action_cable/channel/callbacks.rb +++ b/actioncable/lib/action_cable/channel/callbacks.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/callbacks" module ActionCable diff --git a/actioncable/lib/action_cable/channel/naming.rb b/actioncable/lib/action_cable/channel/naming.rb index b565cb3cac..03a5dcd3a0 100644 --- a/actioncable/lib/action_cable/channel/naming.rb +++ b/actioncable/lib/action_cable/channel/naming.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Channel module Naming diff --git a/actioncable/lib/action_cable/channel/periodic_timers.rb b/actioncable/lib/action_cable/channel/periodic_timers.rb index 90c68cfe84..830b3efa3c 100644 --- a/actioncable/lib/action_cable/channel/periodic_timers.rb +++ b/actioncable/lib/action_cable/channel/periodic_timers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Channel module PeriodicTimers diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb index dbba333353..81c2c38064 100644 --- a/actioncable/lib/action_cable/channel/streams.rb +++ b/actioncable/lib/action_cable/channel/streams.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Channel # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pubsub queue where any data diff --git a/actioncable/lib/action_cable/connection.rb b/actioncable/lib/action_cable/connection.rb index 902efb07e2..804b89a707 100644 --- a/actioncable/lib/action_cable/connection.rb +++ b/actioncable/lib/action_cable/connection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Connection extend ActiveSupport::Autoload diff --git a/actioncable/lib/action_cable/connection/authorization.rb b/actioncable/lib/action_cable/connection/authorization.rb index 989a67d6df..a22179d988 100644 --- a/actioncable/lib/action_cable/connection/authorization.rb +++ b/actioncable/lib/action_cable/connection/authorization.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Connection module Authorization diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index ac5f405dea..8dbafe5105 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch" module ActionCable diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb index c7e30e78c8..ba33c8b982 100644 --- a/actioncable/lib/action_cable/connection/client_socket.rb +++ b/actioncable/lib/action_cable/connection/client_socket.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "websocket/driver" module ActionCable diff --git a/actioncable/lib/action_cable/connection/identification.rb b/actioncable/lib/action_cable/connection/identification.rb index ffab359429..4b5f9ca115 100644 --- a/actioncable/lib/action_cable/connection/identification.rb +++ b/actioncable/lib/action_cable/connection/identification.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "set" module ActionCable diff --git a/actioncable/lib/action_cable/connection/internal_channel.rb b/actioncable/lib/action_cable/connection/internal_channel.rb index 8f0ec766c3..f03904137b 100644 --- a/actioncable/lib/action_cable/connection/internal_channel.rb +++ b/actioncable/lib/action_cable/connection/internal_channel.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Connection # Makes it possible for the RemoteConnection to disconnect a specific connection. diff --git a/actioncable/lib/action_cable/connection/message_buffer.rb b/actioncable/lib/action_cable/connection/message_buffer.rb index 4ccd322644..f151a47072 100644 --- a/actioncable/lib/action_cable/connection/message_buffer.rb +++ b/actioncable/lib/action_cable/connection/message_buffer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Connection # Allows us to buffer messages received from the WebSocket before the Connection has been fully initialized, and is ready to receive them. diff --git a/actioncable/lib/action_cable/connection/stream.rb b/actioncable/lib/action_cable/connection/stream.rb index e620b93845..4873026b71 100644 --- a/actioncable/lib/action_cable/connection/stream.rb +++ b/actioncable/lib/action_cable/connection/stream.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "thread" module ActionCable diff --git a/actioncable/lib/action_cable/connection/stream_event_loop.rb b/actioncable/lib/action_cable/connection/stream_event_loop.rb index 2d1af0ff9f..d95afc50ba 100644 --- a/actioncable/lib/action_cable/connection/stream_event_loop.rb +++ b/actioncable/lib/action_cable/connection/stream_event_loop.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "nio" require "thread" diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb index 44bce1e195..faafd6d0a6 100644 --- a/actioncable/lib/action_cable/connection/subscriptions.rb +++ b/actioncable/lib/action_cable/connection/subscriptions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/hash/indifferent_access" module ActionCable diff --git a/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb b/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb index aef549aa86..85831806a9 100644 --- a/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb +++ b/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Connection # Allows the use of per-connection tags against the server logger. This wouldn't work using the traditional diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb index 27ae499f29..81233ace34 100644 --- a/actioncable/lib/action_cable/connection/web_socket.rb +++ b/actioncable/lib/action_cable/connection/web_socket.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "websocket/driver" module ActionCable diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb index d8c49ec6e1..c961f47342 100644 --- a/actioncable/lib/action_cable/engine.rb +++ b/actioncable/lib/action_cable/engine.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails" require "action_cable" require_relative "helpers/action_cable_helper" diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb index 5d6f9af0bb..af8277d06e 100644 --- a/actioncable/lib/action_cable/gem_version.rb +++ b/actioncable/lib/action_cable/gem_version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable # Returns the version of the currently loaded Action Cable as a <tt>Gem::Version</tt>. def self.gem_version diff --git a/actioncable/lib/action_cable/helpers/action_cable_helper.rb b/actioncable/lib/action_cable/helpers/action_cable_helper.rb index f53be0bc31..df16c02e83 100644 --- a/actioncable/lib/action_cable/helpers/action_cable_helper.rb +++ b/actioncable/lib/action_cable/helpers/action_cable_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Helpers module ActionCableHelper diff --git a/actioncable/lib/action_cable/remote_connections.rb b/actioncable/lib/action_cable/remote_connections.rb index e689fbf21b..a92b5e90b4 100644 --- a/actioncable/lib/action_cable/remote_connections.rb +++ b/actioncable/lib/action_cable/remote_connections.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable # If you need to disconnect a given connection, you can go through the # RemoteConnections. You can find the connections you're looking for by diff --git a/actioncable/lib/action_cable/server.rb b/actioncable/lib/action_cable/server.rb index 22f9353825..8d485a44f6 100644 --- a/actioncable/lib/action_cable/server.rb +++ b/actioncable/lib/action_cable/server.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Server extend ActiveSupport::Autoload diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb index 3b3a17a532..6c6f6c2936 100644 --- a/actioncable/lib/action_cable/server/base.rb +++ b/actioncable/lib/action_cable/server/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "monitor" module ActionCable diff --git a/actioncable/lib/action_cable/server/broadcasting.rb b/actioncable/lib/action_cable/server/broadcasting.rb index 7fcd6c6587..bc54d784b3 100644 --- a/actioncable/lib/action_cable/server/broadcasting.rb +++ b/actioncable/lib/action_cable/server/broadcasting.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Server # Broadcasting is how other parts of your application can send messages to a channel's subscribers. As explained in Channel, most of the time, these diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb index 17e0dee064..82fed81a18 100644 --- a/actioncable/lib/action_cable/server/configuration.rb +++ b/actioncable/lib/action_cable/server/configuration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Server # An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak Action Cable configuration diff --git a/actioncable/lib/action_cable/server/connections.rb b/actioncable/lib/action_cable/server/connections.rb index 5e61b4e335..39557d63a7 100644 --- a/actioncable/lib/action_cable/server/connections.rb +++ b/actioncable/lib/action_cable/server/connections.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Server # Collection class for all the connections that have been established on this specific server. Remember, usually you'll run many Action Cable servers, so diff --git a/actioncable/lib/action_cable/server/worker.rb b/actioncable/lib/action_cable/server/worker.rb index 43639c27af..c69cc4ac31 100644 --- a/actioncable/lib/action_cable/server/worker.rb +++ b/actioncable/lib/action_cable/server/worker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/callbacks" require "active_support/core_ext/module/attribute_accessors_per_thread" require "concurrent" diff --git a/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb b/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb index c1e4aa8103..2e378d4bf3 100644 --- a/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb +++ b/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module Server class Worker diff --git a/actioncable/lib/action_cable/subscription_adapter.rb b/actioncable/lib/action_cable/subscription_adapter.rb index 596269ab9b..bcece8d33b 100644 --- a/actioncable/lib/action_cable/subscription_adapter.rb +++ b/actioncable/lib/action_cable/subscription_adapter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module SubscriptionAdapter extend ActiveSupport::Autoload diff --git a/actioncable/lib/action_cable/subscription_adapter/async.rb b/actioncable/lib/action_cable/subscription_adapter/async.rb index 9169734471..96c18c4a2f 100644 --- a/actioncable/lib/action_cable/subscription_adapter/async.rb +++ b/actioncable/lib/action_cable/subscription_adapter/async.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "inline" module ActionCable diff --git a/actioncable/lib/action_cable/subscription_adapter/base.rb b/actioncable/lib/action_cable/subscription_adapter/base.rb index 796db5ffa3..34077707fd 100644 --- a/actioncable/lib/action_cable/subscription_adapter/base.rb +++ b/actioncable/lib/action_cable/subscription_adapter/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module SubscriptionAdapter class Base diff --git a/actioncable/lib/action_cable/subscription_adapter/channel_prefix.rb b/actioncable/lib/action_cable/subscription_adapter/channel_prefix.rb index 8b293cc785..df0aa040f5 100644 --- a/actioncable/lib/action_cable/subscription_adapter/channel_prefix.rb +++ b/actioncable/lib/action_cable/subscription_adapter/channel_prefix.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module SubscriptionAdapter module ChannelPrefix # :nodoc: diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb index ae71708240..07774810ce 100644 --- a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "thread" gem "em-hiredis", "~> 0.3.0" diff --git a/actioncable/lib/action_cable/subscription_adapter/inline.rb b/actioncable/lib/action_cable/subscription_adapter/inline.rb index 81357faead..d2c85c1c8d 100644 --- a/actioncable/lib/action_cable/subscription_adapter/inline.rb +++ b/actioncable/lib/action_cable/subscription_adapter/inline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module SubscriptionAdapter class Inline < Base # :nodoc: diff --git a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb index 487564c46c..a9c0949950 100644 --- a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb +++ b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + gem "pg", "~> 0.18" require "pg" require "thread" diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index 225609c236..c64f55f5b7 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "thread" gem "redis", "~> 3.0" diff --git a/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb b/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb index 4cce86dcca..01cdc2dfa1 100644 --- a/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb +++ b/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionCable module SubscriptionAdapter class SubscriberMap diff --git a/actioncable/lib/action_cable/version.rb b/actioncable/lib/action_cable/version.rb index d6081409f0..86115c6065 100644 --- a/actioncable/lib/action_cable/version.rb +++ b/actioncable/lib/action_cable/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "gem_version" module ActionCable diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb index 80f512c94c..c3528370c6 100644 --- a/actioncable/lib/rails/generators/channel/channel_generator.rb +++ b/actioncable/lib/rails/generators/channel/channel_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators class ChannelGenerator < NamedBase diff --git a/actioncable/test/channel/base_test.rb b/actioncable/test/channel/base_test.rb index 9a3a3581e6..866bd7c21b 100644 --- a/actioncable/test/channel/base_test.rb +++ b/actioncable/test/channel/base_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_connection" require "stubs/room" diff --git a/actioncable/test/channel/broadcasting_test.rb b/actioncable/test/channel/broadcasting_test.rb index 3476c1db31..ab58f33511 100644 --- a/actioncable/test/channel/broadcasting_test.rb +++ b/actioncable/test/channel/broadcasting_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_connection" require "stubs/room" diff --git a/actioncable/test/channel/naming_test.rb b/actioncable/test/channel/naming_test.rb index 08f0e7be48..6f094fbb5e 100644 --- a/actioncable/test/channel/naming_test.rb +++ b/actioncable/test/channel/naming_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" class ActionCable::Channel::NamingTest < ActiveSupport::TestCase diff --git a/actioncable/test/channel/periodic_timers_test.rb b/actioncable/test/channel/periodic_timers_test.rb index 17a8e45a35..500b984ca6 100644 --- a/actioncable/test/channel/periodic_timers_test.rb +++ b/actioncable/test/channel/periodic_timers_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_connection" require "stubs/room" diff --git a/actioncable/test/channel/rejection_test.rb b/actioncable/test/channel/rejection_test.rb index 99c4a7603a..a6da014a21 100644 --- a/actioncable/test/channel/rejection_test.rb +++ b/actioncable/test/channel/rejection_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_connection" require "stubs/room" diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb index 50fc7be30b..79c25d936f 100644 --- a/actioncable/test/channel/stream_test.rb +++ b/actioncable/test/channel/stream_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_connection" require "stubs/room" diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index 30ac1e9c38..56b3ef143b 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "concurrent" diff --git a/actioncable/test/connection/authorization_test.rb b/actioncable/test/connection/authorization_test.rb index dcdbe9c1d1..7d039336b8 100644 --- a/actioncable/test/connection/authorization_test.rb +++ b/actioncable/test/connection/authorization_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_server" diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb index 9bcd0700cf..99488e38c8 100644 --- a/actioncable/test/connection/base_test.rb +++ b/actioncable/test/connection/base_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_server" require "active_support/core_ext/object/json" diff --git a/actioncable/test/connection/client_socket_test.rb b/actioncable/test/connection/client_socket_test.rb index bc3ff6a3d7..2051216010 100644 --- a/actioncable/test/connection/client_socket_test.rb +++ b/actioncable/test/connection/client_socket_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_server" diff --git a/actioncable/test/connection/cross_site_forgery_test.rb b/actioncable/test/connection/cross_site_forgery_test.rb index 37bedfd734..3e21138ffc 100644 --- a/actioncable/test/connection/cross_site_forgery_test.rb +++ b/actioncable/test/connection/cross_site_forgery_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_server" diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb index f3d3bc0603..6b6c8cd31a 100644 --- a/actioncable/test/connection/identifier_test.rb +++ b/actioncable/test/connection/identifier_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_server" require "stubs/user" diff --git a/actioncable/test/connection/multiple_identifiers_test.rb b/actioncable/test/connection/multiple_identifiers_test.rb index ca1a08f4d6..230433a110 100644 --- a/actioncable/test/connection/multiple_identifiers_test.rb +++ b/actioncable/test/connection/multiple_identifiers_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_server" require "stubs/user" diff --git a/actioncable/test/connection/stream_test.rb b/actioncable/test/connection/stream_test.rb index 36e1d3c095..b0419b0994 100644 --- a/actioncable/test/connection/stream_test.rb +++ b/actioncable/test/connection/stream_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_server" diff --git a/actioncable/test/connection/string_identifier_test.rb b/actioncable/test/connection/string_identifier_test.rb index 6d53e249cb..6387fb792c 100644 --- a/actioncable/test/connection/string_identifier_test.rb +++ b/actioncable/test/connection/string_identifier_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_server" diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb index a1c8a4613c..149a40604a 100644 --- a/actioncable/test/connection/subscriptions_test.rb +++ b/actioncable/test/connection/subscriptions_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase diff --git a/actioncable/test/server/base_test.rb b/actioncable/test/server/base_test.rb index f0a51c5a7d..1312e45f49 100644 --- a/actioncable/test/server/base_test.rb +++ b/actioncable/test/server/base_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_server" require "active_support/core_ext/hash/indifferent_access" diff --git a/actioncable/test/server/broadcasting_test.rb b/actioncable/test/server/broadcasting_test.rb index ed377b7d5d..72cec26234 100644 --- a/actioncable/test/server/broadcasting_test.rb +++ b/actioncable/test/server/broadcasting_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_server" diff --git a/actioncable/test/stubs/global_id.rb b/actioncable/test/stubs/global_id.rb index 334f0d03e8..15fab6b8a7 100644 --- a/actioncable/test/stubs/global_id.rb +++ b/actioncable/test/stubs/global_id.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GlobalID attr_reader :uri delegate :to_param, :to_s, to: :uri diff --git a/actioncable/test/stubs/room.rb b/actioncable/test/stubs/room.rb index 1664b07d12..df7236f408 100644 --- a/actioncable/test/stubs/room.rb +++ b/actioncable/test/stubs/room.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Room attr_reader :id, :name diff --git a/actioncable/test/stubs/test_adapter.rb b/actioncable/test/stubs/test_adapter.rb index bbd142b287..c481046973 100644 --- a/actioncable/test/stubs/test_adapter.rb +++ b/actioncable/test/stubs/test_adapter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SuccessAdapter < ActionCable::SubscriptionAdapter::Base def broadcast(channel, payload) end diff --git a/actioncable/test/stubs/test_connection.rb b/actioncable/test/stubs/test_connection.rb index cd2e219d88..fdddd1159e 100644 --- a/actioncable/test/stubs/test_connection.rb +++ b/actioncable/test/stubs/test_connection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "stubs/user" class TestConnection diff --git a/actioncable/test/stubs/test_server.rb b/actioncable/test/stubs/test_server.rb index 5bf2a151dc..0bc4625e28 100644 --- a/actioncable/test/stubs/test_server.rb +++ b/actioncable/test/stubs/test_server.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "ostruct" class TestServer diff --git a/actioncable/test/stubs/user.rb b/actioncable/test/stubs/user.rb index a66b4f87d5..7894d1d9ae 100644 --- a/actioncable/test/stubs/user.rb +++ b/actioncable/test/stubs/user.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class User attr_reader :name diff --git a/actioncable/test/subscription_adapter/async_test.rb b/actioncable/test/subscription_adapter/async_test.rb index 8a447c7a4f..6e038259b5 100644 --- a/actioncable/test/subscription_adapter/async_test.rb +++ b/actioncable/test/subscription_adapter/async_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require_relative "common" diff --git a/actioncable/test/subscription_adapter/base_test.rb b/actioncable/test/subscription_adapter/base_test.rb index 5793415b5f..999dc0cba1 100644 --- a/actioncable/test/subscription_adapter/base_test.rb +++ b/actioncable/test/subscription_adapter/base_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "stubs/test_server" diff --git a/actioncable/test/subscription_adapter/channel_prefix.rb b/actioncable/test/subscription_adapter/channel_prefix.rb index 9ad659912e..3071facd9d 100644 --- a/actioncable/test/subscription_adapter/channel_prefix.rb +++ b/actioncable/test/subscription_adapter/channel_prefix.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" class ActionCable::Server::WithIndependentConfig < ActionCable::Server::Base diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb index 80baf2f771..c533a9f3eb 100644 --- a/actioncable/test/subscription_adapter/common.rb +++ b/actioncable/test/subscription_adapter/common.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "concurrent" diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb index 1c99031ab0..e3e0a0c72a 100644 --- a/actioncable/test/subscription_adapter/evented_redis_test.rb +++ b/actioncable/test/subscription_adapter/evented_redis_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require_relative "common" require_relative "channel_prefix" diff --git a/actioncable/test/subscription_adapter/inline_test.rb b/actioncable/test/subscription_adapter/inline_test.rb index eafa3df2df..6305626b2b 100644 --- a/actioncable/test/subscription_adapter/inline_test.rb +++ b/actioncable/test/subscription_adapter/inline_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require_relative "common" diff --git a/actioncable/test/subscription_adapter/postgresql_test.rb b/actioncable/test/subscription_adapter/postgresql_test.rb index 29c746733d..1c375188ba 100644 --- a/actioncable/test/subscription_adapter/postgresql_test.rb +++ b/actioncable/test/subscription_adapter/postgresql_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require_relative "common" diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb index 60596dd205..69120d5753 100644 --- a/actioncable/test/subscription_adapter/redis_test.rb +++ b/actioncable/test/subscription_adapter/redis_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require_relative "common" require_relative "channel_prefix" diff --git a/actioncable/test/subscription_adapter/subscriber_map_test.rb b/actioncable/test/subscription_adapter/subscriber_map_test.rb index 76b984c849..ed81099cbc 100644 --- a/actioncable/test/subscription_adapter/subscriber_map_test.rb +++ b/actioncable/test/subscription_adapter/subscriber_map_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" class SubscriberMapTest < ActionCable::TestCase diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb index 5d246c2b76..2a4611fb37 100644 --- a/actioncable/test/test_helper.rb +++ b/actioncable/test/test_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_cable" require "active_support/testing/autorun" diff --git a/actioncable/test/worker_test.rb b/actioncable/test/worker_test.rb index 3385593f74..bc1f3e415a 100644 --- a/actioncable/test/worker_test.rb +++ b/actioncable/test/worker_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" class WorkerTest < ActiveSupport::TestCase diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index 6f05d236d9..6ac408e1cb 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rake/testtask" desc "Default Task" diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index ae908ddda7..b8a2e80bd3 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -1,3 +1,5 @@ +# frozen_string_literal: true + version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip Gem::Specification.new do |s| diff --git a/actionmailer/bin/test b/actionmailer/bin/test index 470ce93f10..c53377cc97 100755 --- a/actionmailer/bin/test +++ b/actionmailer/bin/test @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true COMPONENT_ROOT = File.expand_path("..", __dir__) require_relative "../../tools/test" diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 8eba811ab6..a170eb7917 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- # Copyright (c) 2004-2017 David Heinemeier Hansson # diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 2b4992791d..6add4ec89c 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "mail" require_relative "collector" require "active_support/core_ext/string/inflections" diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb index d97a73d65a..888410fa75 100644 --- a/actionmailer/lib/action_mailer/collector.rb +++ b/actionmailer/lib/action_mailer/collector.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_controller/collector" require "active_support/core_ext/hash/reverse_merge" require "active_support/core_ext/array/extract_options" diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb index a617daa87e..40f26d8ad1 100644 --- a/actionmailer/lib/action_mailer/delivery_job.rb +++ b/actionmailer/lib/action_mailer/delivery_job.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_job" module ActionMailer diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb index 93ae10fd70..5cd62307e6 100644 --- a/actionmailer/lib/action_mailer/delivery_methods.rb +++ b/actionmailer/lib/action_mailer/delivery_methods.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "tmpdir" module ActionMailer diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb index f5594ef928..063d4580d8 100644 --- a/actionmailer/lib/action_mailer/gem_version.rb +++ b/actionmailer/lib/action_mailer/gem_version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionMailer # Returns the version of the currently loaded Action Mailer as a <tt>Gem::Version</tt>. def self.gem_version diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb index 980415afe0..4bef4a58d3 100644 --- a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb +++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "base64" module ActionMailer diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb index 2c058ccf66..87cfbfff28 100644 --- a/actionmailer/lib/action_mailer/log_subscriber.rb +++ b/actionmailer/lib/action_mailer/log_subscriber.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/log_subscriber" module ActionMailer diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb index e04fc08866..e7bed41f8d 100644 --- a/actionmailer/lib/action_mailer/mail_helper.rb +++ b/actionmailer/lib/action_mailer/mail_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionMailer # Provides helper methods for ActionMailer::Base that can be used for easily # formatting messages, accessing mailer or message instances, and the diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb index 0b54e12431..fe7265834f 100644 --- a/actionmailer/lib/action_mailer/message_delivery.rb +++ b/actionmailer/lib/action_mailer/message_delivery.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "delegate" module ActionMailer diff --git a/actionmailer/lib/action_mailer/parameterized.rb b/actionmailer/lib/action_mailer/parameterized.rb index 3acacc1f14..5e768e7106 100644 --- a/actionmailer/lib/action_mailer/parameterized.rb +++ b/actionmailer/lib/action_mailer/parameterized.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionMailer # Provides the option to parameterize mailers in order to share instance variable # setup, processing, and common headers. diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb index 4f72eca930..4a8d3659ec 100644 --- a/actionmailer/lib/action_mailer/preview.rb +++ b/actionmailer/lib/action_mailer/preview.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/descendants_tracker" module ActionMailer diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index 913df8cf93..36c2e5866d 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_job/railtie" require "action_mailer" require "rails" diff --git a/actionmailer/lib/action_mailer/rescuable.rb b/actionmailer/lib/action_mailer/rescuable.rb index f2eabfa057..28950eb834 100644 --- a/actionmailer/lib/action_mailer/rescuable.rb +++ b/actionmailer/lib/action_mailer/rescuable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionMailer #:nodoc: # Provides `rescue_from` for mailers. Wraps mailer action processing, # mail job processing, and mail delivery. diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb index 9ead03a40c..ee5a864847 100644 --- a/actionmailer/lib/action_mailer/test_case.rb +++ b/actionmailer/lib/action_mailer/test_case.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/test_case" require "rails-dom-testing" diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb index c30fb1fc18..ac8b944743 100644 --- a/actionmailer/lib/action_mailer/test_helper.rb +++ b/actionmailer/lib/action_mailer/test_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_job" module ActionMailer diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index 8452d6370e..4549d6eb57 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "gem_version" module ActionMailer diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb index bc21b07109..97eac30db1 100644 --- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb +++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators class MailerGenerator < NamedBase diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index dbfdb07e6e..45f69d5375 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/kernel/reporting" # These are the normal settings that will be set up by Railties diff --git a/actionmailer/test/assert_select_email_test.rb b/actionmailer/test/assert_select_email_test.rb index bf14fe0853..eb58ddd9c9 100644 --- a/actionmailer/test/assert_select_email_test.rb +++ b/actionmailer/test/assert_select_email_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class AssertSelectEmailTest < ActionMailer::TestCase diff --git a/actionmailer/test/asset_host_test.rb b/actionmailer/test/asset_host_test.rb index 812df01a34..2a14248488 100644 --- a/actionmailer/test/asset_host_test.rb +++ b/actionmailer/test/asset_host_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_controller" diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 06d4ea197c..2b0e2165e3 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "set" diff --git a/actionmailer/test/caching_test.rb b/actionmailer/test/caching_test.rb index e76466439e..e11e8d4676 100644 --- a/actionmailer/test/caching_test.rb +++ b/actionmailer/test/caching_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "fileutils" require "abstract_unit" require "mailers/base_mailer" diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb index f64a69019f..025f7152bb 100644 --- a/actionmailer/test/delivery_methods_test.rb +++ b/actionmailer/test/delivery_methods_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class MyCustomDelivery diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb index 4f09339800..6e75cff347 100644 --- a/actionmailer/test/i18n_with_controller_test.rb +++ b/actionmailer/test/i18n_with_controller_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_view" require "action_controller" diff --git a/actionmailer/test/log_subscriber_test.rb b/actionmailer/test/log_subscriber_test.rb index 799c6144d7..2e89758dfb 100644 --- a/actionmailer/test/log_subscriber_test.rb +++ b/actionmailer/test/log_subscriber_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "mailers/base_mailer" require "active_support/log_subscriber/test_helper" diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb index 6042548aef..51d6ccb10f 100644 --- a/actionmailer/test/mail_helper_test.rb +++ b/actionmailer/test/mail_helper_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class HelperMailer < ActionMailer::Base diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb index 73059d782d..16d77ed61d 100644 --- a/actionmailer/test/mail_layout_test.rb +++ b/actionmailer/test/mail_layout_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class AutoLayoutMailer < ActionMailer::Base diff --git a/actionmailer/test/mailers/asset_mailer.rb b/actionmailer/test/mailers/asset_mailer.rb index 1cf15128d2..7a9aba2629 100644 --- a/actionmailer/test/mailers/asset_mailer.rb +++ b/actionmailer/test/mailers/asset_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AssetMailer < ActionMailer::Base self.mailer_name = "asset_mailer" diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb index 2a8884959c..bfaecdb658 100644 --- a/actionmailer/test/mailers/base_mailer.rb +++ b/actionmailer/test/mailers/base_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BaseMailer < ActionMailer::Base self.mailer_name = "base_mailer" diff --git a/actionmailer/test/mailers/caching_mailer.rb b/actionmailer/test/mailers/caching_mailer.rb index 92d3cff7c9..02f0c6c103 100644 --- a/actionmailer/test/mailers/caching_mailer.rb +++ b/actionmailer/test/mailers/caching_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CachingMailer < ActionMailer::Base self.mailer_name = "caching_mailer" diff --git a/actionmailer/test/mailers/delayed_mailer.rb b/actionmailer/test/mailers/delayed_mailer.rb index cae4ec2f48..b0f5ecc2fb 100644 --- a/actionmailer/test/mailers/delayed_mailer.rb +++ b/actionmailer/test/mailers/delayed_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_job/arguments" class DelayedMailerError < StandardError; end diff --git a/actionmailer/test/mailers/params_mailer.rb b/actionmailer/test/mailers/params_mailer.rb index 4c0fae6d91..84aa336311 100644 --- a/actionmailer/test/mailers/params_mailer.rb +++ b/actionmailer/test/mailers/params_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ParamsMailer < ActionMailer::Base before_action { @inviter, @invitee = params[:inviter], params[:invitee] } diff --git a/actionmailer/test/mailers/proc_mailer.rb b/actionmailer/test/mailers/proc_mailer.rb index 2487db9eb9..b7cf53eb4a 100644 --- a/actionmailer/test/mailers/proc_mailer.rb +++ b/actionmailer/test/mailers/proc_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ProcMailer < ActionMailer::Base default to: "system@test.lindsaar.net", "X-Proc-Method" => Proc.new { Time.now.to_i.to_s }, diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb index 51f10b0bf1..89a3c7475e 100644 --- a/actionmailer/test/message_delivery_test.rb +++ b/actionmailer/test/message_delivery_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_job" require "mailers/delayed_mailer" diff --git a/actionmailer/test/parameterized_test.rb b/actionmailer/test/parameterized_test.rb index e988fffcb9..ec6c5e9e67 100644 --- a/actionmailer/test/parameterized_test.rb +++ b/actionmailer/test/parameterized_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_job" require "mailers/params_mailer" diff --git a/actionmailer/test/test_case_test.rb b/actionmailer/test/test_case_test.rb index 193d107b0a..7b9647d295 100644 --- a/actionmailer/test/test_case_test.rb +++ b/actionmailer/test/test_case_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class TestTestMailer < ActionMailer::Base diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb index 876e9b0634..abf50cf4da 100644 --- a/actionmailer/test/test_helper_test.rb +++ b/actionmailer/test/test_helper_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/testing/stream" diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb index 6dbfb3a1ff..0bd3371878 100644 --- a/actionmailer/test/url_test.rb +++ b/actionmailer/test/url_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_controller" diff --git a/actionview/lib/action_view/helpers/controller_helper.rb b/actionview/lib/action_view/helpers/controller_helper.rb index e86cdca4e4..65854e317f 100644 --- a/actionview/lib/action_view/helpers/controller_helper.rb +++ b/actionview/lib/action_view/helpers/controller_helper.rb @@ -7,8 +7,11 @@ module ActionView module ControllerHelper #:nodoc: attr_internal :controller, :request - delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers, - :flash, :action_name, :controller_name, :controller_path, to: :controller + CONTROLLER_DELEGATES = [:request_forgery_protection_token, :params, + :session, :cookies, :response, :headers, :flash, :action_name, + :controller_name, :controller_path] + + delegate *CONTROLLER_DELEGATES, to: :controller def assign_controller(controller) if @_controller = controller @@ -21,6 +24,11 @@ module ActionView def logger controller.logger if controller.respond_to?(:logger) end + + def respond_to?(method_name, include_private = false) + return controller.respond_to?(method_name) if CONTROLLER_DELEGATES.include?(method_name.to_sym) + super + end end end end diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb index 62ce985243..e54f9b8977 100644 --- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require "fiber" module ActionView @@ -31,7 +32,7 @@ module ActionView logger = ActionView::Base.logger return unless logger - message = "\n#{exception.class} (#{exception.message}):\n" + message = "\n#{exception.class} (#{exception.message}):\n".dup message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) message << " " << exception.backtrace.join("\n ") logger.fatal("#{message}\n\n") diff --git a/actionview/test/template/controller_helper_test.rb b/actionview/test/template/controller_helper_test.rb index 8dd0cedb75..ea0e790d9b 100644 --- a/actionview/test/template/controller_helper_test.rb +++ b/actionview/test/template/controller_helper_test.rb @@ -18,4 +18,15 @@ class ControllerHelperTest < ActionView::TestCase assert_nil default_form_builder end + + def test_respond_to + @controller = OpenStruct.new + assign_controller(@controller) + assert_not respond_to?(:params) + assert respond_to?(:assign_controller) + + @controller.params = {} + assert respond_to?(:params) + assert respond_to?(:assign_controller) + end end diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 7b303e923b..9904ee4bed 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -34,9 +34,7 @@ module ActiveRecord::Associations::Builder # :nodoc: foreign_key = reflection.foreign_key cache_column = reflection.counter_cache_column - if (@_after_create_counter_called ||= false) - @_after_create_counter_called = false - elsif (@_after_replace_counter_called ||= false) + if (@_after_replace_counter_called ||= false) @_after_replace_counter_called = false elsif saved_change_to_attribute?(foreign_key) && !new_record? if reflection.polymorphic? diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 938d2645ca..89ce00f98e 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -154,7 +154,7 @@ module ActiveRecord stmt.from scope.klass.arel_table stmt.wheres = arel.constraints - count = scope.klass.connection.delete(stmt, "SQL", scope.bound_attributes) + count = scope.klass.connection.delete(stmt, "SQL") end when :nullify count = scope.update_all(source_reflection.foreign_key => nil) diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index 42014653e8..30dbdb7fa5 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -23,11 +23,10 @@ module ActiveRecord super && reflection == other.reflection end - JoinInformation = Struct.new :joins, :binds + JoinInformation = Struct.new :joins def join_constraints(foreign_table, foreign_klass, join_type, tables, chain) joins = [] - binds = [] tables = tables.reverse # The chain starts with the target table, but we want to end with it here (makes @@ -43,7 +42,6 @@ module ActiveRecord join_scope = reflection.join_scope(table, foreign_klass) if join_scope.arel.constraints.any? - binds.concat join_scope.bound_attributes joins.concat join_scope.arel.join_sources right = joins.last.right right.expr = right.expr.and(join_scope.arel.constraints) @@ -53,7 +51,7 @@ module ActiveRecord foreign_table, foreign_klass = table, klass end - JoinInformation.new joins, binds + JoinInformation.new joins end def table diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb index c94897580c..b1937a3c68 100644 --- a/activerecord/lib/active_record/collection_cache_key.rb +++ b/activerecord/lib/active_record/collection_cache_key.rb @@ -29,7 +29,7 @@ module ActiveRecord arel = query.arel end - result = connection.select_one(arel, nil, query.bound_attributes) + result = connection.select_one(arel, nil) if result.blank? size = 0 diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index f2715cdab0..00e54edac0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -9,30 +9,37 @@ module ActiveRecord end # Converts an arel AST to SQL - def to_sql(arel, binds = []) - if arel.respond_to?(:ast) - collected = visitor.accept(arel.ast, collector) - collected.compile(binds, self).freeze + def to_sql(arel_or_sql_string, binds = []) + if arel_or_sql_string.respond_to?(:ast) + unless binds.empty? + raise "Passing bind parameters with an arel AST is forbidden. " \ + "The values must be stored on the AST directly" + end + sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value + [sql.freeze, binds || []] else - arel.dup.freeze + [arel_or_sql_string.dup.freeze, binds] end end # This is used in the StatementCache object. It returns an object that # can be used to query the database repeatedly. def cacheable_query(klass, arel) # :nodoc: - collected = visitor.accept(arel.ast, collector) if prepared_statements - klass.query(collected.value) + sql, binds = visitor.accept(arel.ast, collector).value + query = klass.query(sql) else - klass.partial_query(collected.value) + collector = PartialQueryCollector.new + parts, binds = visitor.accept(arel.ast, collector).value + query = klass.partial_query(parts) end + [query, binds] end # Returns an ActiveRecord::Result instance. def select_all(arel, name = nil, binds = [], preparable: nil) - arel, binds = binds_from_relation arel, binds - sql = to_sql(arel, binds) + arel = arel_from_relation(arel) + sql, binds = to_sql(arel, binds) if !prepared_statements || (arel.is_a?(String) && preparable.nil?) preparable = false else @@ -131,20 +138,23 @@ module ActiveRecord # # If the next id was calculated in advance (as in Oracle), it should be # passed in as +id_value+. - def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) - value = exec_insert(to_sql(arel, binds), name, binds, pk, sequence_name) + def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil) + sql, binds = to_sql(arel) + value = exec_insert(sql, name, binds, pk, sequence_name) id_value || last_inserted_id(value) end alias create insert # Executes the update statement and returns the number of rows affected. - def update(arel, name = nil, binds = []) - exec_update(to_sql(arel, binds), name, binds) + def update(arel, name = nil) + sql, binds = to_sql(arel) + exec_update(sql, name, binds) end # Executes the delete statement and returns the number of rows affected. - def delete(arel, name = nil, binds = []) - exec_delete(to_sql(arel, binds), name, binds) + def delete(arel, name = nil) + sql, binds = to_sql(arel) + exec_delete(sql, name, binds) end # Returns +true+ when the connection adapter supports prepared statement @@ -430,11 +440,12 @@ module ActiveRecord row && row.first end - def binds_from_relation(relation, binds) - if relation.is_a?(Relation) && binds.empty? - relation, binds = relation.arel, relation.bound_attributes + def arel_from_relation(relation) + if relation.is_a?(Relation) + relation.arel + else + relation end - [relation, binds] end # Fixture value is quoted by Arel, however scalar values @@ -447,6 +458,28 @@ module ActiveRecord value end end + + class PartialQueryCollector + def initialize + @parts = [] + @binds = [] + end + + def << str + @parts << str + self + end + + def add_bind obj + @binds << obj + @parts << Arel::Nodes::BindParam.new(1) + self + end + + def value + [@parts, @binds] + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 077e8beba9..ecf5201d12 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -92,8 +92,8 @@ module ActiveRecord def select_all(arel, name = nil, binds = [], preparable: nil) if @query_cache_enabled && !locked?(arel) - arel, binds = binds_from_relation arel, binds - sql = to_sql(arel, binds) + arel = arel_from_relation(arel) + sql, binds = to_sql(arel, binds) cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) } else super diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index b637518fc8..7b83bc319c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -24,6 +24,10 @@ module ActiveRecord return value.quoted_id end + if value.respond_to?(:value_for_database) + value = value.value_for_database + end + _quote(value) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 04e32d03e1..e699a77230 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -7,7 +7,9 @@ require_relative "sql_type_metadata" require_relative "abstract/schema_dumper" require_relative "abstract/schema_creation" require "arel/collectors/bind" +require "arel/collectors/composite" require "arel/collectors/sql_string" +require "arel/collectors/substitute_binds" module ActiveRecord module ConnectionAdapters # :nodoc: @@ -129,19 +131,6 @@ module ActiveRecord end end - class BindCollector < Arel::Collectors::Bind - def compile(bvs, conn) - casted_binds = bvs.map(&:value_for_database) - super(casted_binds.map { |value| conn.quote(value) }) - end - end - - class SQLString < Arel::Collectors::SQLString - def compile(bvs, conn) - super(bvs) - end - end - def valid_type?(type) # :nodoc: !native_database_types[type].nil? end @@ -432,14 +421,14 @@ module ActiveRecord end def case_sensitive_comparison(table, attribute, column, value) # :nodoc: - table[attribute].eq(value) + table[attribute].eq(Arel::Nodes::BindParam.new(value)) end def case_insensitive_comparison(table, attribute, column, value) # :nodoc: if can_perform_case_insensitive_comparison_for?(column) - table[attribute].lower.eq(table.lower(value)) + table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new(value))) else - table[attribute].eq(value) + table[attribute].eq(Arel::Nodes::BindParam.new(value)) end end @@ -457,24 +446,6 @@ module ActiveRecord visitor.accept(node, collector).value end - def combine_bind_parameters( - from_clause: [], - join_clause: [], - where_clause: [], - having_clause: [], - limit: nil, - offset: nil - ) # :nodoc: - result = from_clause + join_clause + where_clause + having_clause - if limit - result << limit - end - if offset - result << offset - end - result - end - def default_index_type?(index) # :nodoc: index.using.nil? end @@ -609,9 +580,15 @@ module ActiveRecord def collector if prepared_statements - SQLString.new + Arel::Collectors::Composite.new( + Arel::Collectors::SQLString.new, + Arel::Collectors::Bind.new, + ) else - BindCollector.new + Arel::Collectors::SubstituteBinds.new( + self, + Arel::Collectors::SQLString.new, + ) end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index cbbbf99318..e762abc00f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -177,7 +177,8 @@ module ActiveRecord #++ def explain(arel, binds = []) - sql = "EXPLAIN #{to_sql(arel, binds)}" + sql, binds = to_sql(arel, binds) + sql = "EXPLAIN #{sql}" start = Time.now result = exec_query(sql, "EXPLAIN", binds) elapsed = Time.now - start @@ -494,7 +495,7 @@ module ActiveRecord def case_sensitive_comparison(table, attribute, column, value) # :nodoc: if column.collation && !column.case_sensitive? - table[attribute].eq(Arel::Nodes::Bin.new(value)) + table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new(value))) else super end @@ -863,8 +864,8 @@ module ActiveRecord class MysqlString < Type::String # :nodoc: def serialize(value) case value - when true then MySQL::Quoting::QUOTED_TRUE - when false then MySQL::Quoting::QUOTED_FALSE + when true then "1" + when false then "0" else super end end @@ -873,8 +874,8 @@ module ActiveRecord def cast_value(value) case value - when true then MySQL::Quoting::QUOTED_TRUE - when false then MySQL::Quoting::QUOTED_FALSE + when true then "1" + when false then "0" else super end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb index 00b56ee9ae..a058a72872 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -5,7 +5,7 @@ module ActiveRecord module MySQL module DatabaseStatements # Returns an ActiveRecord::Result instance. - def select_all(arel, name = nil, binds = [], preparable: nil) # :nodoc: + def select_all(*) # :nodoc: result = if ExplainRegistry.collect? && prepared_statements unprepared_statement { super } else diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb index 803babf55d..be038403b8 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb @@ -4,8 +4,6 @@ module ActiveRecord module ConnectionAdapters module MySQL module Quoting # :nodoc: - QUOTED_TRUE, QUOTED_FALSE = "1".freeze, "0".freeze - def quote_column_name(name) @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`".freeze end @@ -14,18 +12,10 @@ module ActiveRecord @quoted_table_names[name] ||= super.gsub(".", "`.`").freeze end - def quoted_true - QUOTED_TRUE - end - def unquoted_true 1 end - def quoted_false - QUOTED_FALSE - end - def unquoted_false 0 end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 8db2a645af..0dd4aac463 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -5,7 +5,8 @@ module ActiveRecord module PostgreSQL module DatabaseStatements def explain(arel, binds = []) - sql = "EXPLAIN #{to_sql(arel, binds)}" + sql, binds = to_sql(arel, binds) + sql = "EXPLAIN #{sql}" PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds)) end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 10e80179ac..8c12cb09bd 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -203,7 +203,8 @@ module ActiveRecord #++ def explain(arel, binds = []) - sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" + sql, binds = to_sql(arel, binds) + sql = "EXPLAIN QUERY PLAN #{sql}" SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", [])) end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index ab2bfb63a6..5005d58f1c 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -182,7 +182,6 @@ module ActiveRecord each_counter_cached_associations do |association| if send(association.reflection.name) association.increment_counters - @_after_create_counter_called = true end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 7b4e44f61c..1c4011b75e 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -53,7 +53,7 @@ module ActiveRecord im = arel.create_insert im.into @table - substitutes, binds = substitute_values values + substitutes = substitute_values values if values.empty? # empty insert im.values = Arel.sql(connection.empty_insert_statement_value) @@ -67,11 +67,11 @@ module ActiveRecord primary_key || false, primary_key_value, nil, - binds) + ) end def _update_record(values, id, id_was) # :nodoc: - substitutes, binds = substitute_values values + substitutes = substitute_values values scope = @klass.unscoped @@ -80,7 +80,6 @@ module ActiveRecord end relation = scope.where(@klass.primary_key => (id_was || id)) - bvs = binds + relation.bound_attributes um = relation .arel .compile_update(substitutes, @klass.primary_key) @@ -88,20 +87,14 @@ module ActiveRecord @klass.connection.update( um, "SQL", - bvs, ) end def substitute_values(values) # :nodoc: - binds = [] - substitutes = [] - - values.each do |arel_attr, value| - binds.push QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name)) - substitutes.push [arel_attr, Arel::Nodes::BindParam.new] + values.map do |arel_attr, value| + bind = QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name)) + [arel_attr, Arel::Nodes::BindParam.new(bind)] end - - [substitutes, binds] end def arel_attribute(name) # :nodoc: @@ -380,7 +373,7 @@ module ActiveRecord stmt.wheres = arel.constraints end - @klass.connection.update stmt, "SQL", bound_attributes + @klass.connection.update stmt, "SQL" end # Updates an object (or multiple objects) and saves it to the database, if validations pass. @@ -510,7 +503,7 @@ module ActiveRecord stmt.wheres = arel.constraints end - affected = @klass.connection.delete(stmt, "SQL", bound_attributes) + affected = @klass.connection.delete(stmt, "SQL") reset affected @@ -578,7 +571,8 @@ module ActiveRecord conn = klass.connection conn.unprepared_statement { - conn.to_sql(relation.arel, relation.bound_attributes) + sql, _ = conn.to_sql(relation.arel) + sql } end end @@ -667,7 +661,7 @@ module ActiveRecord def exec_queries(&block) skip_query_cache_if_necessary do - @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes, &block).freeze + @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, &block).freeze preload = preload_values preload += includes_values unless eager_loading? diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index d281f8673f..d3b5be6bce 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -186,7 +186,7 @@ module ActiveRecord relation.select_values = column_names.map { |cn| @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn } - result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil, bound_attributes) } + result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) } result.cast_values(klass.attribute_types) end end @@ -267,7 +267,7 @@ module ActiveRecord query_builder = relation.arel end - result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil, bound_attributes) } + result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil) } row = result.first value = row && row.values.first type = result.column_types.fetch(column_alias) do @@ -318,7 +318,7 @@ module ActiveRecord relation.group_values = group_fields relation.select_values = select_values - calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil, relation.bound_attributes) } + calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) } if association key_ids = calculated_data.collect { |row| row[group_aliases.first] } diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 36d35b98fd..626c50470e 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -317,7 +317,7 @@ module ActiveRecord relation = construct_relation_for_exists(relation, conditions) - skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists", relation.bound_attributes) } ? true : false + skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false rescue ::RangeError false end @@ -378,7 +378,7 @@ module ActiveRecord if ActiveRecord::NullRelation === relation [] else - rows = skip_query_cache_if_necessary { connection.select_all(relation.arel, "SQL", relation.bound_attributes) } + rows = skip_query_cache_if_necessary { connection.select_all(relation.arel, "SQL") } join_dependency.instantiate(rows, aliases) end end @@ -426,7 +426,7 @@ module ActiveRecord relation = relation.except(:select).select(values).distinct! - id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL", relation.bound_attributes) } + id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL") } id_rows.map { |row| row[primary_key] } end diff --git a/activerecord/lib/active_record/relation/from_clause.rb b/activerecord/lib/active_record/relation/from_clause.rb index 03f1202470..c53a682aee 100644 --- a/activerecord/lib/active_record/relation/from_clause.rb +++ b/activerecord/lib/active_record/relation/from_clause.rb @@ -10,14 +10,6 @@ module ActiveRecord @name = name end - def binds - if value.is_a?(Relation) - value.bound_attributes - else - [] - end - end - def merge(other) self end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 3b8f8da634..5c42414072 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -8,10 +8,9 @@ module ActiveRecord @table = table @handlers = [] - register_handler(BasicObject, BasicObjectHandler.new) + register_handler(BasicObject, BasicObjectHandler.new(self)) register_handler(Base, BaseHandler.new(self)) - register_handler(Range, RangeHandler.new) - register_handler(RangeHandler::RangeWithBinds, RangeHandler.new) + register_handler(Range, RangeHandler.new(self)) register_handler(Relation, RelationHandler.new) register_handler(Array, ArrayHandler.new(self)) end @@ -21,11 +20,6 @@ module ActiveRecord expand_from_hash(attributes) end - def create_binds(attributes) - attributes = convert_dot_notation_to_hash(attributes) - create_binds_for_hash(attributes) - end - def self.references(attributes) attributes.map do |key, value| if value.is_a?(Hash) @@ -56,8 +50,11 @@ module ActiveRecord handler_for(value).call(attribute, value) end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. + def build_bind_attribute(column_name, value) + attr = Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) + Arel::Nodes::BindParam.new(attr) + end + protected attr_reader :table @@ -68,29 +65,13 @@ module ActiveRecord attributes.flat_map do |key, value| if value.is_a?(Hash) && !table.has_column?(key) associated_predicate_builder(key).expand_from_hash(value) - else - build(table.arel_attribute(key), value) - end - end - end - - def create_binds_for_hash(attributes) - result = attributes.dup - binds = [] - - attributes.each do |column_name, value| - case - when value.is_a?(Hash) && !table.has_column?(column_name) - attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value) - result[column_name] = attrs - binds += bvs - when table.associated_with?(column_name) + elsif table.associated_with?(key) # Find the foreign key when using queries such as: # Post.where(author: author) # # For polymorphic relationships, find the foreign key and type: # PriceEstimate.where(estimate_of: treasure) - associated_table = table.associated_table(column_name) + associated_table = table.associated_table(key) if associated_table.polymorphic_association? case value.is_a?(Array) ? value.first : value when Base, Relation @@ -100,40 +81,18 @@ module ActiveRecord end klass ||= AssociationQueryValue - result[column_name] = klass.new(associated_table, value).queries.map do |query| - attrs, bvs = create_binds_for_hash(query) - binds.concat(bvs) - attrs - end - when value.is_a?(Range) && !table.type(column_name).respond_to?(:subtype) - first = value.begin - last = value.end - unless first.respond_to?(:infinite?) && first.infinite? - binds << build_bind_attribute(column_name, first) - first = Arel::Nodes::BindParam.new + queries = klass.new(associated_table, value).queries.map do |query| + expand_from_hash(query).reduce(&:and) end - unless last.respond_to?(:infinite?) && last.infinite? - binds << build_bind_attribute(column_name, last) - last = Arel::Nodes::BindParam.new - end - - result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?) - when value.is_a?(Relation) - binds.concat(value.bound_attributes) + queries.reduce(&:or) + # FIXME: Deprecate this and provide a public API to force equality + elsif (value.is_a?(Range) || value.is_a?(Array)) && + table.type(key.to_s).respond_to?(:subtype) + BasicObjectHandler.new(self).call(table.arel_attribute(key), value) else - if can_be_bound?(column_name, value) - bind_attribute = build_bind_attribute(column_name, value) - if value.is_a?(StatementCache::Substitute) || !bind_attribute.value_for_database.nil? - result[column_name] = Arel::Nodes::BindParam.new - binds << bind_attribute - else - result[column_name] = nil - end - end + build(table.arel_attribute(key), value) end end - - [result, binds] end private @@ -161,19 +120,6 @@ module ActiveRecord def handler_for(object) @handlers.detect { |klass, _| klass === object }.last end - - def can_be_bound?(column_name, value) - case value - when Array, Range - table.type(column_name).respond_to?(:subtype) - else - !value.nil? && handler_for(value).is_a?(BasicObjectHandler) - end - end - - def build_bind_attribute(column_name, value) - Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) - end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb index 85ec7d1428..ad617365fc 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb @@ -19,7 +19,11 @@ module ActiveRecord case values.length when 0 then NullPredicate when 1 then predicate_builder.build(attribute, values.first) - else attribute.in(values) + else + bind_values = values.map do |v| + predicate_builder.build_bind_attribute(attribute.name, v) + end + attribute.in(bind_values) end unless nils.empty? @@ -31,8 +35,6 @@ module ActiveRecord array_predicates.inject(&:or) end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. protected attr_reader :predicate_builder diff --git a/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb index 632c68a103..112821135f 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb @@ -11,8 +11,6 @@ module ActiveRecord predicate_builder.build(attribute, value.id) end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. protected attr_reader :predicate_builder diff --git a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb index a17cf30ffd..34db266f05 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb @@ -3,9 +3,18 @@ module ActiveRecord class PredicateBuilder class BasicObjectHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + def call(attribute, value) - attribute.eq(value) + bind = predicate_builder.build_bind_attribute(attribute.name, value) + attribute.eq(bind) end + + protected + + attr_reader :predicate_builder end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb index 072e238306..6d16579708 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb @@ -3,25 +3,39 @@ module ActiveRecord class PredicateBuilder class RangeHandler # :nodoc: - RangeWithBinds = Struct.new(:begin, :end, :exclude_end?) + class RangeWithBinds < Struct.new(:begin, :end) + def exclude_end? + false + end + end + + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end def call(attribute, value) + begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin) + end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end) if value.begin.respond_to?(:infinite?) && value.begin.infinite? if value.end.respond_to?(:infinite?) && value.end.infinite? attribute.not_in([]) elsif value.exclude_end? - attribute.lt(value.end) + attribute.lt(end_bind) else - attribute.lteq(value.end) + attribute.lteq(end_bind) end elsif value.end.respond_to?(:infinite?) && value.end.infinite? - attribute.gteq(value.begin) + attribute.gteq(begin_bind) elsif value.exclude_end? - attribute.gteq(value.begin).and(attribute.lt(value.end)) + attribute.gteq(begin_bind).and(attribute.lt(end_bind)) else - attribute.between(value) + attribute.between(RangeWithBinds.new(begin_bind, end_bind)) end end + + protected + + attr_reader :predicate_builder end end end diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb index e6883acd90..5a9a7fd432 100644 --- a/activerecord/lib/active_record/relation/query_attribute.rb +++ b/activerecord/lib/active_record/relation/query_attribute.rb @@ -16,6 +16,11 @@ module ActiveRecord def with_cast_value(value) QueryAttribute.new(name, value, type) end + + def nil? + !value_before_type_cast.is_a?(StatementCache::Substitute) && + (value_before_type_cast.nil? || value_for_database.nil?) + end end end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index e2af62873c..812c8e7e3f 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -76,31 +76,6 @@ module ActiveRecord CODE end - def bound_attributes - if limit_value - limit_bind = Attribute.with_cast_value( - "LIMIT".freeze, - connection.sanitize_limit(limit_value), - Type.default_value, - ) - end - if offset_value - offset_bind = Attribute.with_cast_value( - "OFFSET".freeze, - offset_value.to_i, - Type.default_value, - ) - end - connection.combine_bind_parameters( - from_clause: from_clause.binds, - join_clause: arel.bind_values, - where_clause: where_clause.binds, - having_clause: having_clause.binds, - limit: limit_bind, - offset: offset_bind, - ) - end - alias extensions extending_values # Specify relationships to be included in the result set. For @@ -952,8 +927,22 @@ module ActiveRecord arel.where(where_clause.ast) unless where_clause.empty? arel.having(having_clause.ast) unless having_clause.empty? - arel.take(Arel::Nodes::BindParam.new) if limit_value - arel.skip(Arel::Nodes::BindParam.new) if offset_value + if limit_value + limit_attribute = Attribute.with_cast_value( + "LIMIT".freeze, + connection.sanitize_limit(limit_value), + Type.default_value, + ) + arel.take(Arel::Nodes::BindParam.new(limit_attribute)) + end + if offset_value + offset_attribute = Attribute.with_cast_value( + "OFFSET".freeze, + offset_value.to_i, + Type.default_value, + ) + arel.skip(Arel::Nodes::BindParam.new(offset_attribute)) + end arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty? build_order(arel) @@ -1029,7 +1018,6 @@ module ActiveRecord join_infos.each do |info| info.joins.each { |join| manager.from(join) } - manager.bind_values.concat info.binds end manager.join_sources.concat(join_list) diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index 0b4c7b7dfa..ef2bca9155 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -3,31 +3,26 @@ module ActiveRecord class Relation class WhereClause # :nodoc: - attr_reader :binds - delegate :any?, :empty?, to: :predicates - def initialize(predicates, binds) + def initialize(predicates) @predicates = predicates - @binds = binds end def +(other) WhereClause.new( predicates + other.predicates, - binds + other.binds, ) end def merge(other) WhereClause.new( predicates_unreferenced_by(other) + other.predicates, - non_conflicting_binds(other) + other.binds, ) end def except(*columns) - WhereClause.new(*except_predicates_and_binds(columns)) + WhereClause.new(except_predicates(columns)) end def or(other) @@ -38,7 +33,6 @@ module ActiveRecord else WhereClause.new( [ast.or(other.ast)], - binds + other.binds ) end end @@ -51,17 +45,10 @@ module ActiveRecord end end - binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h - equalities.map { |node| name = node.left.name.to_s - [name, binds.fetch(name) { - case node.right - when Array then node.right.map(&:val) - when Arel::Nodes::Casted, Arel::Nodes::Quoted - node.right.val - end - }] + value = extract_node_value(node.right) + [name, value] }.to_h end @@ -71,20 +58,17 @@ module ActiveRecord def ==(other) other.is_a?(WhereClause) && - predicates == other.predicates && - binds == other.binds + predicates == other.predicates end def invert - WhereClause.new(inverted_predicates, binds) + WhereClause.new(inverted_predicates) end def self.empty - @empty ||= new([], []) + @empty ||= new([]) end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. protected attr_reader :predicates @@ -108,12 +92,6 @@ module ActiveRecord node.respond_to?(:operator) && node.operator == :== end - def non_conflicting_binds(other) - conflicts = referenced_columns & other.referenced_columns - conflicts.map! { |node| node.name.to_s } - binds.reject { |attr| conflicts.include?(attr.name) } - end - def inverted_predicates predicates.map { |node| invert_predicate(node) } end @@ -133,44 +111,22 @@ module ActiveRecord end end - def except_predicates_and_binds(columns) - except_binds = [] - binds_index = 0 - - predicates = self.predicates.reject do |node| - binds_contains = node.grep(Arel::Nodes::BindParam).size if node.is_a?(Arel::Nodes::Node) - - except = \ - case node - when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual - subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) - columns.include?(subrelation.name.to_s) - end - - if except && binds_contains > 0 - (binds_index...(binds_index + binds_contains)).each do |i| - except_binds[i] = true - end + def except_predicates(columns) + self.predicates.reject do |node| + case node + when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual + subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) + columns.include?(subrelation.name.to_s) end - - binds_index += binds_contains if binds_contains - - except end - - binds = self.binds.reject.with_index do |_, i| - except_binds[i] - end - - [predicates, binds] end def predicates_with_wrapped_sql_literals non_empty_predicates.map do |node| - if Arel::Nodes::Equality === node - node - else + case node + when Arel::Nodes::SqlLiteral, ::String wrap_sql_literal(node) + else node end end end @@ -186,6 +142,22 @@ module ActiveRecord end Arel::Nodes::Grouping.new(node) end + + def extract_node_value(node) + case node + when Array + node.map { |v| extract_node_value(v) } + when Arel::Nodes::Casted, Arel::Nodes::Quoted + node.val + when Arel::Nodes::BindParam + value = node.value + if value.respond_to?(:value_before_type_cast) + value.value_before_type_cast + else + value + end + end + end end end end diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index 4a0868314d..1374785354 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -17,63 +17,19 @@ module ActiveRecord attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes) attributes.stringify_keys! - if perform_case_sensitive?(options = other.last) - parts, binds = build_for_case_sensitive(attributes, options) - else - attributes, binds = predicate_builder.create_binds(attributes) - parts = predicate_builder.build_from_hash(attributes) - end + parts = predicate_builder.build_from_hash(attributes) when Arel::Nodes::Node parts = [opts] else raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})" end - WhereClause.new(parts, binds || []) + WhereClause.new(parts) end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. protected attr_reader :klass, :predicate_builder - - private - - def perform_case_sensitive?(options) - options && options.key?(:case_sensitive) - end - - def build_for_case_sensitive(attributes, options) - parts, binds = [], [] - table = klass.arel_table - - attributes.each do |attribute, value| - if reflection = klass._reflect_on_association(attribute) - attribute = reflection.foreign_key.to_s - value = value[reflection.klass.primary_key] unless value.nil? - end - - if value.nil? - parts << table[attribute].eq(value) - else - column = klass.column_for_attribute(attribute) - - binds << predicate_builder.send(:build_bind_attribute, attribute, value) - value = Arel::Nodes::BindParam.new - - predicate = if options[:case_sensitive] - klass.connection.case_sensitive_comparison(table, attribute, column, value) - else - klass.connection.case_insensitive_comparison(table, attribute, column, value) - end - - parts << predicate - end - end - - [parts, binds] - end end end end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index dd8442a34f..27a1c89bd1 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -87,7 +87,7 @@ HEADER extensions = @connection.extensions if extensions.any? stream.puts " # These are extensions that must be enabled in order to support this database" - extensions.each do |extension| + extensions.sort.each do |extension| stream.puts " enable_extension #{extension.inspect}" end stream.puts diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index b03ff9bc6c..64657089b5 100644 --- a/activerecord/lib/active_record/statement_cache.rb +++ b/activerecord/lib/active_record/statement_cache.rb @@ -90,9 +90,9 @@ module ActiveRecord attr_reader :bind_map, :query_builder def self.create(connection, block = Proc.new) - relation = block.call Params.new - bind_map = BindMap.new relation.bound_attributes - query_builder = connection.cacheable_query(self, relation.arel) + relation = block.call Params.new + query_builder, binds = connection.cacheable_query(self, relation.arel) + bind_map = BindMap.new(binds) new query_builder, bind_map end diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 5c6a796857..0f3f84ca08 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -261,7 +261,7 @@ module ActiveRecord def check_schema_file(filename) unless File.exist?(filename) - message = %{#{filename} doesn't exist yet. Run `rails db:migrate` to create it, then try again.} + message = %{#{filename} doesn't exist yet. Run `rails db:migrate` to create it, then try again.}.dup message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails.root) Kernel.abort message end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 3a110b461d..4f5b157af9 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -52,7 +52,37 @@ module ActiveRecord end def build_relation(klass, attribute, value) - klass.unscoped.where!({ attribute => value }, options) + if reflection = klass._reflect_on_association(attribute) + attribute = reflection.foreign_key + value = value.attributes[reflection.klass.primary_key] unless value.nil? + end + + if value.nil? + return klass.unscoped.where!(attribute => value) + end + + # the attribute may be an aliased attribute + if klass.attribute_alias?(attribute) + attribute = klass.attribute_alias(attribute) + end + + attribute_name = attribute.to_s + + table = klass.arel_table + column = klass.columns_hash[attribute_name] + cast_type = klass.type_for_attribute(attribute_name) + + value = Relation::QueryAttribute.new(attribute_name, value, cast_type) + comparison = if !options[:case_sensitive] + # will use SQL LOWER function before comparison, unless it detects a case insensitive collation + klass.connection.case_insensitive_comparison(table, attribute, column, value) + else + klass.connection.case_sensitive_comparison(table, attribute, column, value) + end + klass.unscoped.tap do |scope| + parts = [comparison] + scope.where_clause += Relation::WhereClause.new(parts) + end end def scope_relation(record, relation) diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index e18f29bdef..abb7a696ce 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -244,7 +244,7 @@ module ActiveRecord def test_select_all_with_legacy_binds post = Post.create!(title: "foo", body: "bar") expected = @connection.select_all("SELECT * FROM posts WHERE id = #{post.id}") - result = @connection.select_all("SELECT * FROM posts WHERE id = #{Arel::Nodes::BindParam.new.to_sql}", nil, [[nil, post.id]]) + result = @connection.select_all("SELECT * FROM posts WHERE id = #{Arel::Nodes::BindParam.new(nil).to_sql}", nil, [[nil, post.id]]) assert_equal expected.to_hash, result.to_hash end end @@ -253,7 +253,6 @@ module ActiveRecord author = Author.create!(name: "john") Post.create!(author: author, title: "foo", body: "bar") query = author.posts.where(title: "foo").select(:title) - assert_equal({ "title" => "foo" }, @connection.select_one(query.arel, nil, query.bound_attributes)) assert_equal({ "title" => "foo" }, @connection.select_one(query)) assert @connection.select_all(query).is_a?(ActiveRecord::Result) assert_equal "foo", @connection.select_value(query) @@ -263,7 +262,6 @@ module ActiveRecord def test_select_methods_passing_a_relation Post.create!(title: "foo", body: "bar") query = Post.where(title: "foo").select(:title) - assert_equal({ "title" => "foo" }, @connection.select_one(query.arel, nil, query.bound_attributes)) assert_equal({ "title" => "foo" }, @connection.select_one(query)) assert @connection.select_all(query).is_a?(ActiveRecord::Result) assert_equal "foo", @connection.select_value(query) diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb deleted file mode 100644 index 00e989153f..0000000000 --- a/activerecord/test/cases/associations/association_scope_test.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require "cases/helper" -require "models/post" -require "models/author" - -module ActiveRecord - module Associations - class AssociationScopeTest < ActiveRecord::TestCase - test "does not duplicate conditions" do - scope = AssociationScope.scope(Author.new.association(:welcome_posts)) - binds = scope.where_clause.binds.map(&:value) - assert_equal binds.uniq, binds - end - end - end -end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 7e4ded9fc9..0f7a249bf3 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -1171,6 +1171,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase Column.create! record: record assert_equal 1, Column.count end + + def test_multiple_counter_cache_with_after_create_update + post = posts(:welcome) + parent = comments(:greetings) + + assert_difference "parent.reload.children_count", +1 do + assert_difference "post.reload.comments_count", +1 do + CommentWithAfterCreateUpdate.create(body: "foo", post: post, parent: parent) + end + end + end end class BelongsToWithForeignKeyTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 5dbb7f788a..1d33564989 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1054,29 +1054,15 @@ class BasicsTest < ActiveRecord::TestCase def test_count_with_join res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'" - res2 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count assert_equal res, res2 - res3 = nil - assert_nothing_raised do - res3 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count - end - assert_equal res, res3 - res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id" - res5 = nil - assert_nothing_raised do - res5 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count - end - + res5 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count assert_equal res4, res5 res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id" - res7 = nil - assert_nothing_raised do - res7 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").distinct.count - end + res7 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").distinct.count assert_equal res6, res7 end diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index 7ac3b5b197..91cc49385c 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -41,7 +41,7 @@ if ActiveRecord::Base.connection.prepared_statements end def test_binds_are_logged - sub = Arel::Nodes::BindParam.new + sub = Arel::Nodes::BindParam.new(1) binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)] sql = "select * from topics where id = #{sub.to_sql}" diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 239bf32068..a263106f6d 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -420,7 +420,7 @@ class InheritanceTest < ActiveRecord::TestCase def test_eager_load_belongs_to_primary_key_quoting con = Account.connection - bind_param = Arel::Nodes::BindParam.new + bind_param = Arel::Nodes::BindParam.new(nil) assert_sql(/#{con.quote_table_name('companies')}\.#{con.quote_column_name('id')} = (?:#{Regexp.quote(bind_param.to_sql)}|1)/) do Account.all.merge!(includes: :firm).find(1) end diff --git a/activerecord/test/cases/mixin_test.rb b/activerecord/test/cases/mixin_test.rb index 5f5205f3a0..fdb8ac6ab3 100644 --- a/activerecord/test/cases/mixin_test.rb +++ b/activerecord/test/cases/mixin_test.rb @@ -12,10 +12,6 @@ class TouchTest < ActiveRecord::TestCase travel_to Time.now end - teardown do - travel_back - end - def test_update stamped = Mixin.new diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index 44e2b16923..b68b3723f6 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -81,13 +81,11 @@ class RelationMergingTest < ActiveRecord::TestCase end test "merge collapses wheres from the LHS only" do - left = Post.where(title: "omg").where(comments_count: 1) + left = Post.where(title: "omg").where(comments_count: 1) right = Post.where(title: "wtf").where(title: "bbq") - expected = [left.bound_attributes[1]] + right.bound_attributes - merged = left.merge(right) + merged = left.merge(right) - assert_equal expected, merged.bound_attributes assert_not_includes merged.to_sql, "omg" assert_includes merged.to_sql, "wtf" assert_includes merged.to_sql, "bbq" diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb index 42c6718220..a68eb2b446 100644 --- a/activerecord/test/cases/relation/where_chain_test.rb +++ b/activerecord/test/cases/relation/where_chain_test.rb @@ -27,7 +27,7 @@ module ActiveRecord end def test_association_not_eq - expected = Arel::Nodes::Grouping.new(Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new)) + expected = Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new(1)) relation = Post.joins(:comments).where.not(comments: { title: "hello" }) assert_equal(expected.to_sql, relation.where_clause.ast.to_sql) end diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb index d43f32e1af..f3a81f3c70 100644 --- a/activerecord/test/cases/relation/where_clause_test.rb +++ b/activerecord/test/cases/relation/where_clause_test.rb @@ -5,76 +5,82 @@ require "cases/helper" class ActiveRecord::Relation class WhereClauseTest < ActiveRecord::TestCase test "+ combines two where clauses" do - first_clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]]) - second_clause = WhereClause.new([table["name"].eq(bind_param)], [["name", "Sean"]]) + first_clause = WhereClause.new([table["id"].eq(bind_param(1))]) + second_clause = WhereClause.new([table["name"].eq(bind_param("Sean"))]) combined = WhereClause.new( - [table["id"].eq(bind_param), table["name"].eq(bind_param)], - [["id", 1], ["name", "Sean"]], + [table["id"].eq(bind_param(1)), table["name"].eq(bind_param("Sean"))], ) assert_equal combined, first_clause + second_clause end test "+ is associative, but not commutative" do - a = WhereClause.new(["a"], ["bind a"]) - b = WhereClause.new(["b"], ["bind b"]) - c = WhereClause.new(["c"], ["bind c"]) + a = WhereClause.new(["a"]) + b = WhereClause.new(["b"]) + c = WhereClause.new(["c"]) assert_equal a + (b + c), (a + b) + c assert_not_equal a + b, b + a end test "an empty where clause is the identity value for +" do - clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]]) + clause = WhereClause.new([table["id"].eq(bind_param(1))]) assert_equal clause, clause + WhereClause.empty end test "merge combines two where clauses" do - a = WhereClause.new([table["id"].eq(1)], []) - b = WhereClause.new([table["name"].eq("Sean")], []) - expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], []) + a = WhereClause.new([table["id"].eq(1)]) + b = WhereClause.new([table["name"].eq("Sean")]) + expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")]) assert_equal expected, a.merge(b) end test "merge keeps the right side, when two equality clauses reference the same column" do - a = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], []) - b = WhereClause.new([table["name"].eq("Jim")], []) - expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Jim")], []) + a = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")]) + b = WhereClause.new([table["name"].eq("Jim")]) + expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Jim")]) assert_equal expected, a.merge(b) end test "merge removes bind parameters matching overlapping equality clauses" do a = WhereClause.new( - [table["id"].eq(bind_param), table["name"].eq(bind_param)], - [attribute("id", 1), attribute("name", "Sean")], + [table["id"].eq(bind_param(1)), table["name"].eq(bind_param("Sean"))], ) b = WhereClause.new( - [table["name"].eq(bind_param)], - [attribute("name", "Jim")] + [table["name"].eq(bind_param("Jim"))], ) expected = WhereClause.new( - [table["id"].eq(bind_param), table["name"].eq(bind_param)], - [attribute("id", 1), attribute("name", "Jim")], + [table["id"].eq(bind_param(1)), table["name"].eq(bind_param("Jim"))], ) assert_equal expected, a.merge(b) end test "merge allows for columns with the same name from different tables" do - skip "This is not possible as of 4.2, and the binds do not yet contain sufficient information for this to happen" - # We might be able to change the implementation to remove conflicts by index, rather than column name + table2 = Arel::Table.new("table2") + a = WhereClause.new( + [table["id"].eq(bind_param(1)), table2["id"].eq(bind_param(2))], + ) + b = WhereClause.new( + [table["id"].eq(bind_param(3))], + ) + expected = WhereClause.new( + [table2["id"].eq(bind_param(2)), table["id"].eq(bind_param(3))], + ) + + assert_equal expected, a.merge(b) end test "a clause knows if it is empty" do assert WhereClause.empty.empty? - assert_not WhereClause.new(["anything"], []).empty? + assert_not WhereClause.new(["anything"]).empty? end test "invert cannot handle nil" do - where_clause = WhereClause.new([nil], []) + where_clause = WhereClause.new([nil]) assert_raises ArgumentError do where_clause.invert @@ -88,13 +94,13 @@ class ActiveRecord::Relation table["id"].eq(1), "sql literal", random_object - ], []) + ]) expected = WhereClause.new([ table["id"].not_in([1, 2, 3]), table["id"].not_eq(1), Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new("sql literal")), Arel::Nodes::Not.new(random_object) - ], []) + ]) assert_equal expected, original.invert end @@ -102,20 +108,17 @@ class ActiveRecord::Relation test "except removes binary predicates referencing a given column" do where_clause = WhereClause.new([ table["id"].in([1, 2, 3]), - table["name"].eq(bind_param), - table["age"].gteq(bind_param), - ], [ - attribute("name", "Sean"), - attribute("age", 30), + table["name"].eq(bind_param("Sean")), + table["age"].gteq(bind_param(30)), ]) - expected = WhereClause.new([table["age"].gteq(bind_param)], [attribute("age", 30)]) + expected = WhereClause.new([table["age"].gteq(bind_param(30))]) assert_equal expected, where_clause.except("id", "name") end test "except jumps over unhandled binds (like with OR) correctly" do wcs = (0..9).map do |i| - WhereClause.new([table["id#{i}"].eq(bind_param)], [attribute("id#{i}", i)]) + WhereClause.new([table["id#{i}"].eq(bind_param(i))]) end wc = wcs[0] + wcs[1] + wcs[2].or(wcs[3]) + wcs[4] + wcs[5] + wcs[6].or(wcs[7]) + wcs[8] + wcs[9] @@ -123,18 +126,15 @@ class ActiveRecord::Relation expected = wcs[0] + wcs[2].or(wcs[3]) + wcs[5] + wcs[6].or(wcs[7]) + wcs[9] actual = wc.except("id1", "id2", "id4", "id7", "id8") - # Easier to read than the inspect of where_clause - assert_equal expected.ast.to_sql, actual.ast.to_sql - assert_equal expected.binds.map(&:value), actual.binds.map(&:value) assert_equal expected, actual end test "ast groups its predicates with AND" do predicates = [ table["id"].in([1, 2, 3]), - table["name"].eq(bind_param), + table["name"].eq(bind_param(nil)), ] - where_clause = WhereClause.new(predicates, []) + where_clause = WhereClause.new(predicates) expected = Arel::Nodes::And.new(predicates) assert_equal expected, where_clause.ast @@ -146,38 +146,36 @@ class ActiveRecord::Relation table["id"].in([1, 2, 3]), "foo = bar", random_object, - ], []) + ]) expected = Arel::Nodes::And.new([ table["id"].in([1, 2, 3]), Arel::Nodes::Grouping.new(Arel.sql("foo = bar")), - Arel::Nodes::Grouping.new(random_object), + random_object, ]) assert_equal expected, where_clause.ast end test "ast removes any empty strings" do - where_clause = WhereClause.new([table["id"].in([1, 2, 3])], []) - where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), ""], []) + where_clause = WhereClause.new([table["id"].in([1, 2, 3])]) + where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), ""]) assert_equal where_clause.ast, where_clause_with_empty.ast end test "or joins the two clauses using OR" do - where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)]) - other_clause = WhereClause.new([table["name"].eq(bind_param)], [attribute("name", "Sean")]) + where_clause = WhereClause.new([table["id"].eq(bind_param(1))]) + other_clause = WhereClause.new([table["name"].eq(bind_param("Sean"))]) expected_ast = Arel::Nodes::Grouping.new( - Arel::Nodes::Or.new(table["id"].eq(bind_param), table["name"].eq(bind_param)) + Arel::Nodes::Or.new(table["id"].eq(bind_param(1)), table["name"].eq(bind_param("Sean"))) ) - expected_binds = where_clause.binds + other_clause.binds assert_equal expected_ast.to_sql, where_clause.or(other_clause).ast.to_sql - assert_equal expected_binds, where_clause.or(other_clause).binds end test "or returns an empty where clause when either side is empty" do - where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)]) + where_clause = WhereClause.new([table["id"].eq(bind_param(1))]) assert_equal WhereClause.empty, where_clause.or(WhereClause.empty) assert_equal WhereClause.empty, WhereClause.empty.or(where_clause) @@ -189,12 +187,8 @@ class ActiveRecord::Relation Arel::Table.new("table") end - def bind_param - Arel::Nodes::BindParam.new - end - - def attribute(name, value) - ActiveRecord::Attribute.with_cast_value(name, value, ActiveRecord::Type::Value.new) + def bind_param(value) + Arel::Nodes::BindParam.new(value) end end end diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index 977165cf2e..c45fd38bc9 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -127,7 +127,7 @@ module ActiveRecord car = cars(:honda) expected = [price_estimates(:diamond), price_estimates(:sapphire_1), price_estimates(:sapphire_2), price_estimates(:honda)].sort - actual = PriceEstimate.where(estimate_of: [treasure_1, treasure_2, car]).to_a.sort + actual = PriceEstimate.where(estimate_of: [treasure_1, treasure_2, car]).to_a.sort assert_equal expected, actual end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index b1e14f8dc4..f22fcd7b5a 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -185,7 +185,7 @@ module ActiveRecord relation = Relation.new(klass, :b, nil) relation.merge!(where: ["foo = ?", "bar"]) - assert_equal Relation::WhereClause.new(["foo = bar"], []), relation.where_clause + assert_equal Relation::WhereClause.new(["foo = bar"]), relation.where_clause end def test_merging_readonly_false diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 9775912f2a..ae1dc35bff 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1594,7 +1594,11 @@ class RelationTest < ActiveRecord::TestCase assert_equal ["comments"], scope.references_values scope = Post.order("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}") - assert_equal ["comments"], scope.references_values + if current_adapter?(:OracleAdapter) + assert_equal ["COMMENTS"], scope.references_values + else + assert_equal ["comments"], scope.references_values + end scope = Post.order("comments.body", "yaks.body") assert_equal ["comments", "yaks"], scope.references_values @@ -1615,7 +1619,11 @@ class RelationTest < ActiveRecord::TestCase assert_equal %w(comments), scope.references_values scope = Post.reorder("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}") - assert_equal ["comments"], scope.references_values + if current_adapter?(:OracleAdapter) + assert_equal ["COMMENTS"], scope.references_values + else + assert_equal ["comments"], scope.references_values + end scope = Post.reorder("comments.body", "yaks.body") assert_equal %w(comments yaks), scope.references_values @@ -1753,7 +1761,7 @@ class RelationTest < ActiveRecord::TestCase test "relations with cached arel can't be mutated [internal API]" do relation = Post.all - relation.count + relation.arel assert_raises(ActiveRecord::ImmutableRelation) { relation.limit!(5) } assert_raises(ActiveRecord::ImmutableRelation) { relation.where!("1 = 2") } @@ -1852,33 +1860,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal 1, posts.unscope(where: :body).count end - def test_unscope_removes_binds - left = Post.where(id: 20) - - assert_equal 1, left.bound_attributes.length - - relation = left.unscope(where: :id) - assert_equal [], relation.bound_attributes - end - - def test_merging_removes_rhs_binds - left = Post.where(id: 20) - right = Post.where(id: [1, 2, 3, 4]) - - assert_equal 1, left.bound_attributes.length - - merged = left.merge(right) - assert_equal [], merged.bound_attributes - end - - def test_merging_keeps_lhs_binds - right = Post.where(id: 20) - left = Post.where(id: 10) - - merged = left.merge(right) - assert_equal [20], merged.bound_attributes.map(&:value) - end - def test_locked_should_not_build_arel posts = Post.locked assert posts.locked? @@ -1889,24 +1870,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal "Thank you for the welcome,Thank you again for the welcome", Post.first.comments.join(",") end - def test_connection_adapters_can_reorder_binds - posts = Post.limit(1).offset(2) - - stubbed_connection = Post.connection.dup - def stubbed_connection.combine_bind_parameters(**kwargs) - offset = kwargs[:offset] - kwargs[:offset] = kwargs[:limit] - kwargs[:limit] = offset - super(**kwargs) - end - - posts.define_singleton_method(:connection) do - stubbed_connection - end - - assert_equal 2, posts.to_a.length - end - test "#skip_query_cache!" do Post.cache do assert_queries(1) do diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 194fceb0f3..01ec3e06ad 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -303,6 +303,20 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match "# These are extensions that must be enabled", output assert_no_match %r{enable_extension}, output end + + def test_schema_dump_includes_extensions_in_alphabetic_order + connection = ActiveRecord::Base.connection + + connection.stubs(:extensions).returns(["hstore", "uuid-ossp", "xml2"]) + output = perform_schema_dump + enabled_extensions = output.scan(%r{enable_extension "(.+)"}).flatten + assert_equal ["hstore", "uuid-ossp", "xml2"], enabled_extensions + + connection.stubs(:extensions).returns(["uuid-ossp", "xml2", "hstore"]) + output = perform_schema_dump + enabled_extensions = output.scan(%r{enable_extension "(.+)"}).flatten + assert_equal ["hstore", "uuid-ossp", "xml2"], enabled_extensions + end end end diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index e1e2a5e0e9..61c54a77a7 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -77,3 +77,9 @@ class CommentWithDefaultScopeReferencesAssociation < Comment default_scope -> { includes(:developer).order("developers.name").references(:developer) } belongs_to :developer end + +class CommentWithAfterCreateUpdate < Comment + after_create do + update_attributes(body: "bar") + end +end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index f5542c6d25..457eb84a62 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,25 @@ +* Add purpose and expiry support to `ActiveSupport::MessageVerifier` & + `ActiveSupport::MessageEncryptor`. + + For instance, to ensure a message is only usable for one intended purpose: + + token = @verifier.generate("x", purpose: :shipping) + + @verifier.verified(token, purpose: :shipping) # => "x" + @verifier.verified(token) # => nil + + Or make it expire after a set time: + + @verifier.generate("x", expires_in: 1.month) + @verifier.generate("y", expires_at: Time.now.end_of_year) + + Showcased with `ActiveSupport::MessageVerifier`, but works the same for + `ActiveSupport::MessageEncryptor`'s `encrypt_and_sign` and `decrypt_and_verify`. + + Pull requests: #29599, #29854 + + *Assain Jaleel* + * Make the order of `Hash#reverse_merge!` consistent with `HashWithIndifferentAccess`. *Erol Fornoles* diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 9ceb3a3a7f..090d51933a 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -22,6 +22,38 @@ module ActiveSupport # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...> # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." # crypt.decrypt_and_verify(encrypted_data) # => "my secret data" + # + # === Confining messages to a specific purpose + # + # By default any message can be used throughout your app. But they can also be + # confined to a specific +:purpose+. + # + # token = crypt.encrypt_and_sign("this is the chair", purpose: :login) + # + # Then that same purpose must be passed when verifying to get the data back out: + # + # crypt.decrypt_and_verify(token, purpose: :login) # => "this is the chair" + # crypt.decrypt_and_verify(token, purpose: :shipping) # => nil + # crypt.decrypt_and_verify(token) # => nil + # + # Likewise, if a message has no purpose it won't be returned when verifying with + # a specific purpose. + # + # token = crypt.encrypt_and_sign("the conversation is lively") + # crypt.decrypt_and_verify(token, purpose: :scare_tactics) # => nil + # crypt.decrypt_and_verify(token) # => "the conversation is lively" + # + # === Making messages expire + # + # By default messages last forever and verifying one year from now will still + # return the original value. But messages can be set to expire at a given + # time with +:expires_in+ or +:expires_at+. + # + # crypt.encrypt_and_sign(parcel, expires_in: 1.month) + # crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year) + # + # Then the messages can be verified and returned upto the expire time. + # Thereafter, verifying returns +nil+. class MessageEncryptor class << self attr_accessor :use_authenticated_message_encryption #:nodoc: diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 76b3865bf2..fdd2185f7f 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -33,6 +33,46 @@ module ActiveSupport # `:digest` key as an option while initializing the verifier: # # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256') + # + # === Confining messages to a specific purpose + # + # By default any message can be used throughout your app. But they can also be + # confined to a specific +:purpose+. + # + # token = @verifier.generate("this is the chair", purpose: :login) + # + # Then that same purpose must be passed when verifying to get the data back out: + # + # @verifier.verified(token, purpose: :login) # => "this is the chair" + # @verifier.verified(token, purpose: :shipping) # => nil + # @verifier.verified(token) # => nil + # + # @verifier.verify(token, purpose: :login) # => "this is the chair" + # @verifier.verify(token, purpose: :shipping) # => ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.verify(token) # => ActiveSupport::MessageVerifier::InvalidSignature + # + # Likewise, if a message has no purpose it won't be returned when verifying with + # a specific purpose. + # + # token = @verifier.generate("the conversation is lively") + # @verifier.verified(token, purpose: :scare_tactics) # => nil + # @verifier.verified(token) # => "the conversation is lively" + # + # @verifier.verify(token, purpose: :scare_tactics) # => ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.verify(token) # => "the conversation is lively" + # + # === Making messages expire + # + # By default messages last forever and verifying one year from now will still + # return the original value. But messages can be set to expire at a given + # time with +:expires_in+ or +:expires_at+. + # + # @verifier.generate(parcel, expires_in: 1.month) + # @verifier.generate(doowad, expires_at: Time.now.end_of_year) + # + # Then the messages can be verified and returned upto the expire time. + # Thereafter, the +verified+ method returns +nil+ while +verify+ raises + # <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>. class MessageVerifier class InvalidSignature < StandardError; end diff --git a/activesupport/lib/active_support/messages/metadata.rb b/activesupport/lib/active_support/messages/metadata.rb index e35086fb77..db14ac0b1c 100644 --- a/activesupport/lib/active_support/messages/metadata.rb +++ b/activesupport/lib/active_support/messages/metadata.rb @@ -5,13 +5,13 @@ module ActiveSupport module Messages #:nodoc: class Metadata #:nodoc: def initialize(expires_at, purpose) - @expires_at, @purpose = expires_at, purpose + @expires_at, @purpose = expires_at, purpose.to_s end class << self def wrap(message, expires_at: nil, expires_in: nil, purpose: nil) if expires_at || expires_in || purpose - { "value" => message, "_rails" => { "exp" => pick_expiry(expires_at, expires_in), "pur" => purpose.to_s } } + { "value" => message, "_rails" => { "exp" => pick_expiry(expires_at, expires_in), "pur" => purpose } } else message end @@ -22,7 +22,7 @@ module ActiveSupport if metadata.nil? message if purpose.nil? - elsif metadata.match?(purpose.to_s) && metadata.fresh? + elsif metadata.match?(purpose) && metadata.fresh? message["value"] end end @@ -32,7 +32,7 @@ module ActiveSupport if expires_at expires_at.utc.iso8601(3) elsif expires_in - expires_in.from_now.utc.iso8601(3) + Time.now.utc.advance(seconds: expires_in).iso8601(3) end end @@ -44,7 +44,7 @@ module ActiveSupport end def match?(purpose) - @purpose == purpose + @purpose == purpose.to_s end def fresh? diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index b529592910..fa5f46736c 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -51,8 +51,14 @@ module ActiveSupport # Contains helpers that help you test passage of time. module TimeHelpers + def after_teardown + travel_back + super + end + # Changes current time to the time in the future or in the past by a given time difference by - # stubbing +Time.now+, +Date.today+, and +DateTime.now+. + # stubbing +Time.now+, +Date.today+, and +DateTime.now+. The stubs are automatically removed + # at the end of the test. # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 # travel 1.day @@ -74,6 +80,7 @@ module ActiveSupport # Changes current time to the given time by stubbing +Time.now+, # +Date.today+, and +DateTime.now+ to return the time or date passed into this method. + # The stubs are automatically removed at the end of the test. # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44) diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index d4a8ce762a..f626ab745f 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -19,7 +19,7 @@ class MessageVerifierTest < ActiveSupport::TestCase def setup @verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!") - @data = { some: "data", now: Time.local(2010) } + @data = { some: "data", now: Time.utc(2010) } end def test_valid_message @@ -99,6 +99,21 @@ class MessageVerifierMetadataTest < ActiveSupport::TestCase @verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", verifier_options) end + def test_verify_raises_when_purpose_differs + assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do + @verifier.verify(@verifier.generate(@message, purpose: "payment"), purpose: "shipping") + end + end + + def test_verify_raises_when_expired + signed_message = @verifier.generate(@message, expires_in: 1.month) + + travel 2.months + assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do + @verifier.verify(signed_message) + end + end + private def generate(message, **options) @verifier.generate(message, options) diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 04f1a53dd9..acb0ecd226 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -103,7 +103,6 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].today travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].today - travel_back end def test_tomorrow @@ -115,7 +114,6 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].tomorrow travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST assert_equal Date.new(2000, 1, 3), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].tomorrow - travel_back end def test_yesterday @@ -127,7 +125,6 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].yesterday travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].yesterday - travel_back end def test_travel_to_a_date diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index b607a63176..ce258341f6 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -64,9 +64,9 @@ module Rails end def print_boot_information - url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}" + url = "on #{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}" unless use_puma? puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" - puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}" + puts "=> Rails #{Rails.version} application starting in #{Rails.env} #{url}" puts "=> Run `rails server -h` for more startup options" end @@ -91,6 +91,10 @@ module Rails def restart_command "bin/rails server #{ARGV.join(' ')}" end + + def use_puma? + server.to_s == "Rack::Handler::Puma" + end end module Command diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index e7f51dba99..9705af8b40 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -16,6 +16,9 @@ module Rails include Thor::Actions include Rails::Generators::Actions + class_option :skip_namespace, type: :boolean, default: false, + desc: "Skip namespace (affects only isolated applications)" + add_runtime_options! strict_args_position! @@ -271,6 +274,40 @@ module Rails end end + # Wrap block with namespace of current application + # if namespace exists and is not skipped + def module_namespacing(&block) # :doc: + content = capture(&block) + content = wrap_with_namespace(content) if namespaced? + concat(content) + end + + def indent(content, multiplier = 2) # :doc: + spaces = " " * multiplier + content.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join + end + + def wrap_with_namespace(content) # :doc: + content = indent(content).chomp + "module #{namespace.name}\n#{content}\nend\n" + end + + def namespace # :doc: + Rails::Generators.namespace + end + + def namespaced? # :doc: + !options[:skip_namespace] && namespace + end + + def namespace_dirs + @namespace_dirs ||= namespace.name.split("::").map(&:underscore) + end + + def namespaced_path # :doc: + @namespaced_path ||= namespace_dirs.join("/") + end + # Use Rails default banner. def self.banner # :doc: "rails generate #{namespace.sub(/^rails:/, '')} #{arguments.map(&:usage).join(' ')} [options]".gsub(/\s+/, " ") diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index d63a5b0c30..dc1d6cab9d 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -6,8 +6,6 @@ module Rails module Generators class NamedBase < Base argument :name, type: :string - class_option :skip_namespace, type: :boolean, default: false, - desc: "Skip namespace (affects only isolated applications)" def initialize(args, *options) #:nodoc: @inside_template = nil @@ -45,24 +43,6 @@ module Rails file_name end - # Wrap block with namespace of current application - # if namespace exists and is not skipped - def module_namespacing(&block) # :doc: - content = capture(&block) - content = wrap_with_namespace(content) if namespaced? - concat(content) - end - - def indent(content, multiplier = 2) # :doc: - spaces = " " * multiplier - content.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join - end - - def wrap_with_namespace(content) # :doc: - content = indent(content).chomp - "module #{namespace.name}\n#{content}\nend\n" - end - def inside_template # :doc: @inside_template = true yield @@ -74,18 +54,6 @@ module Rails @inside_template end - def namespace # :doc: - Rails::Generators.namespace - end - - def namespaced? # :doc: - !options[:skip_namespace] && namespace - end - - def namespace_dirs - @namespace_dirs ||= namespace.name.split("::").map(&:underscore) - end - def file_path # :doc: @file_path ||= (class_path + [file_name]).join("/") end @@ -102,10 +70,6 @@ module Rails @namespaced_class_path ||= namespace_dirs + @class_path end - def namespaced_path # :doc: - @namespaced_path ||= namespace_dirs.join("/") - end - def class_name # :doc: (class_path + [file_name]).map!(&:camelize).join("::") end diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index c63f23fa0a..9e612f1526 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -259,6 +259,13 @@ module ApplicationTests end end + test "db:schema:load fails if schema.rb doesn't exist yet" do + Dir.chdir(app_path) do + stderr_output = capture(:stderr) { `bin/rails db:schema:load` } + assert_match /Run `rails db:migrate` to create it/, stderr_output + end + end + def db_test_load_structure Dir.chdir(app_path) do `bin/rails generate model book title:string; diff --git a/tasks/release.rb b/tasks/release.rb index 038fdc584a..ac13612b91 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -142,7 +142,7 @@ namespace :all do task push: FRAMEWORKS.map { |f| "#{f}:push" } + ["rails:push"] task :ensure_clean_state do - unless `git status -s | grep -v 'RAILS_VERSION\\|CHANGELOG\\|Gemfile.lock\\|package.json\\|version.rb'`.strip.empty? + unless `git status -s | grep -v 'RAILS_VERSION\\|CHANGELOG\\|Gemfile.lock\\|package.json\\|version.rb\\|tasks/release.rb'`.strip.empty? abort "[ABORTING] `git status` reports a dirty tree. Make sure all changes are committed" end @@ -152,6 +152,27 @@ namespace :all do end end + task verify: :install do + app_name = "pkg/verify-#{version}-#{Time.now.to_i}" + sh "rails new #{app_name}" + cd app_name + sh "rails generate scaffold user name admin:boolean && rails db:migrate" + + puts "Booting a Rails server. Verify the release by:" + puts + puts "- Seeing the correct release number on the root page" + puts "- Viewing /users" + puts "- Creating a user" + puts "- Updating a user (e.g. disable the admin flag)" + puts "- Deleting a user on /users" + puts "- Whatever else you want." + begin + sh "rails server" + rescue Interrupt + # Server passes along interrupt. Prevent halting verify task. + end + end + task :bundle do sh "bundle check" end @@ -179,69 +200,47 @@ namespace :all do task release: %w(prep_release tag push) end -task :announce do - Dir.chdir("pkg/") do - if gem_version.segments[2] == 0 || gem_version.segments[3].is_a?(Integer) - # Not major releases, and not security releases - raise "Only valid for patch releases" +module Announcement + class Version + def initialize(version) + @version, @gem_version = version, Gem::Version.new(version) + end + + def to_s + @version end - sums = "$ shasum -a 256 *-#{version}.gem\n" + `shasum -a 256 *-#{version}.gem` + def previous + @gem_version.segments[0, 3].tap { |v| v[2] -= 1 }.join(".") + end - puts "Hi everyone," - puts + def major_or_security? + @gem_version.segments[2].zero? || @gem_version.segments[3].is_a?(Integer) + end - puts "I am happy to announce that Rails #{version} has been released." - puts + def rc? + @version =~ /rc/ + end + end +end - previous_version = gem_version.segments[0, 3] - previous_version[2] -= 1 - previous_version = previous_version.join(".") +task :announce do + Dir.chdir("pkg/") do + versions = ENV["VERSIONS"] ? ENV["VERSIONS"].split(",") : [ version ] + versions = versions.sort.map { |v| Announcement::Version.new(v) } - if version =~ /rc/ + raise "Only valid for patch releases" if versions.any?(&:major_or_security?) + + if versions.any?(&:rc?) require "date" future_date = Date.today + 5 future_date += 1 while future_date.saturday? || future_date.sunday? github_user = `git config github.user`.chomp - - puts <<MSG -If no regressions are found, expect the final release on #{future_date.strftime('%A, %B %-d, %Y')}. -If you find one, please open an [issue on GitHub](https://github.com/rails/rails/issues/new) -#{"and mention me (@#{github_user}) on it, " unless github_user.empty?}so that we can fix it before the final release. - -MSG - end - - puts <<MSG -## CHANGES since #{previous_version} - -To view the changes for each gem, please read the changelogs on GitHub: - -MSG - FRAMEWORKS.sort.each do |framework| - puts "* [#{FRAMEWORK_NAMES[framework]} CHANGELOG](https://github.com/rails/rails/blob/v#{version}/#{framework}/CHANGELOG.md)" end - puts <<MSG - -*Full listing* - -To see the full list of changes, [check out all the commits on -GitHub](https://github.com/rails/rails/compare/v#{previous_version}...v#{version}). - -## SHA-256 - -If you'd like to verify that your gem is the same as the one I've uploaded, -please use these SHA-256 hashes. - -Here are the checksums for #{version}: - -``` -#{sums} -``` - -As always, huge thanks to the many contributors who helped with this release. -MSG + require "erb" + template = File.read("../tasks/release_announcement_draft.erb") + puts ERB.new(template, nil, "<>").result(binding) end end diff --git a/tasks/release_announcement_draft.erb b/tasks/release_announcement_draft.erb new file mode 100644 index 0000000000..65d121fd4b --- /dev/null +++ b/tasks/release_announcement_draft.erb @@ -0,0 +1,38 @@ +Hi everyone, + +I am happy to announce that Rails <%= versions.join(" and ") %> <%= versions.size > 1 ? "have" : "has" %> been released. + +<% if future_date %> +If no regressions are found, expect the final release on <%= future_date.strftime("%A, %B %-d, %Y") %>. +If you find one, please open an [issue on GitHub](https://github.com/rails/rails/issues/new) +<%= "and mention me (@github_user}) on it, " unless github_user.empty? %>so that we can fix it before the final release. +<% end %> +<% versions.each do |version| %> + +## CHANGES since <%= version.previous %> + +To view the changes for each gem, please read the changelogs on GitHub: + <% FRAMEWORKS.sort.each do |framework| %> +<%= "* [#{FRAMEWORK_NAMES[framework]} CHANGELOG](https://github.com/rails/rails/blob/v#{version}/#{framework}/CHANGELOG.md)" %> + <% end %> + +*Full listing* + +To see the full list of changes, [check out all the commits on +GitHub](https://github.com/rails/rails/compare/v<%= "#{version.previous}...v#{version}" %>). + <% end %> +## SHA-256 + +If you'd like to verify that your gem is the same as the one I've uploaded, +please use these SHA-256 hashes. + +<% versions.each do |version| %> +Here are the checksums for <%= version %>: + +``` +$ shasum -a 256 *-<%= version %>.gem +<%= `shasum -a 256 *-#{version}.gem` %> +``` + +<% end %> +As always, huge thanks to the many contributors who helped with this release. |