aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionmailer/CHANGELOG.md8
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb42
-rw-r--r--actionmailer/test/test_helper_test.rb42
-rw-r--r--actionpack/CHANGELOG.md6
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb5
-rw-r--r--actionview/test/abstract_unit.rb53
-rw-r--r--activerecord/CHANGELOG.md25
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb32
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb5
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb8
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb1
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb51
-rw-r--r--activerecord/test/cases/adapters/postgresql/type_lookup_test.rb2
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb8
-rw-r--r--activerecord/test/cases/relations_test.rb17
-rw-r--r--activerecord/test/models/post.rb2
-rw-r--r--activestorage/Rakefile2
-rw-r--r--activestorage/package.json2
-rw-r--r--activesupport/CHANGELOG.md16
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb5
-rw-r--r--activesupport/lib/active_support/security_utils.rb2
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb7
-rw-r--r--activesupport/test/message_encryptor_test.rb2
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb4
-rw-r--r--guides/bug_report_templates/action_controller_master.rb4
-rw-r--r--guides/bug_report_templates/active_job_gem.rb3
-rw-r--r--guides/bug_report_templates/active_job_master.rb3
-rw-r--r--guides/bug_report_templates/active_record_gem.rb3
-rw-r--r--guides/bug_report_templates/active_record_master.rb3
-rw-r--r--guides/bug_report_templates/active_record_migrations_gem.rb3
-rw-r--r--guides/bug_report_templates/active_record_migrations_master.rb3
-rw-r--r--guides/bug_report_templates/benchmark.rb3
-rw-r--r--guides/bug_report_templates/generic_gem.rb3
-rw-r--r--guides/bug_report_templates/generic_master.rb3
-rw-r--r--guides/source/active_record_querying.md2
-rw-r--r--guides/source/engines.md2
-rw-r--r--guides/source/security.md38
-rw-r--r--railties/lib/rails/application.rb6
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb8
-rw-r--r--railties/lib/rails/generators/named_base.rb35
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update.tt5
-rw-r--r--railties/lib/rails/generators/rails/master_key/master_key_generator.rb26
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb4
-rw-r--r--railties/test/application/configuration_test.rb26
-rw-r--r--railties/test/application/middleware/cookies_test.rb64
-rw-r--r--railties/test/application/middleware/session_test.rb2
-rw-r--r--railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml1
-rw-r--r--railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb3
-rw-r--r--railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb3
-rw-r--r--railties/test/generators/app_generator_test.rb7
-rw-r--r--railties/test/generators/named_base_test.rb13
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb23
61 files changed, 482 insertions, 203 deletions
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 9993a11c9d..fe60f8a6a1 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,11 @@
+* Add `assert_enqueued_email_with` test helper.
+
+ assert_enqueued_email_with ContactMailer, :welcome do
+ ContactMailer.welcome.deliver_later
+ end
+
+ *Mikkel Malmberg*
+
* Allow Action Mailer classes to configure their delivery job.
class MyMailer < ApplicationMailer
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index ac8b944743..8ee4d06915 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -93,6 +93,48 @@ module ActionMailer
assert_enqueued_jobs number, only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block
end
+ # Asserts that a specific email has been enqueued, optionally
+ # matching arguments.
+ #
+ # def test_email
+ # ContactMailer.welcome.deliver_later
+ # assert_enqueued_email_with ContactMailer, :welcome
+ # end
+ #
+ # def test_email_with_arguments
+ # ContactMailer.welcome("Hello", "Goodbye").deliver_later
+ # assert_enqueued_email_with ContactMailer, :welcome, args: ["Hello", "Goodbye"]
+ # end
+ #
+ # If a block is passed, that block should cause the specified email
+ # to be enqueued.
+ #
+ # def test_email_in_block
+ # assert_enqueued_email_with ContactMailer, :welcome do
+ # ContactMailer.welcome.deliver_later
+ # end
+ # end
+ #
+ # If `args` is provided as a Hash, a parameterized email is matched.
+ #
+ # def test_parameterized_email
+ # assert_enqueued_email_with ContactMailer, :welcome,
+ # args: {email: 'user@example.com} do
+ # ContactMailer.with(email: 'user@example.com').welcome.deliver_later
+ # end
+ # end
+ def assert_enqueued_email_with(mailer, method, args: nil, queue: "mailers", &block)
+ if args.is_a? Hash
+ job = ActionMailer::Parameterized::DeliveryJob
+ args = [mailer.to_s, method.to_s, "deliver_now", args]
+ else
+ job = ActionMailer::DeliveryJob
+ args = [mailer.to_s, method.to_s, "deliver_now", *args]
+ end
+
+ assert_enqueued_with(job: job, args: args, queue: queue, &block)
+ end
+
# Asserts that no emails are enqueued for later delivery.
#
# def test_no_emails
diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb
index abf50cf4da..5470d51599 100644
--- a/actionmailer/test/test_helper_test.rb
+++ b/actionmailer/test/test_helper_test.rb
@@ -10,6 +10,18 @@ class TestHelperMailer < ActionMailer::Base
to: "test@example.com",
from: "tester@example.com"
end
+
+ def test_args(recipient, name)
+ mail body: render(inline: "Hello, #{name}"),
+ to: recipient,
+ from: "tester@example.com"
+ end
+
+ def test_parameter_args
+ mail body: render(inline: "All is #{params[:all]}"),
+ to: "test@example.com",
+ from: "tester@example.com"
+ end
end
class TestHelperMailerTest < ActionMailer::TestCase
@@ -207,6 +219,36 @@ class TestHelperMailerTest < ActionMailer::TestCase
assert_match(/0 .* but 1/, error.message)
end
+
+ def test_assert_enqueued_email_with
+ assert_nothing_raised do
+ assert_enqueued_email_with TestHelperMailer, :test do
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ end
+ end
+ end
+ end
+
+ def test_assert_enqueued_email_with_args
+ assert_nothing_raised do
+ assert_enqueued_email_with TestHelperMailer, :test_args, args: ["some_email", "some_name"] do
+ silence_stream($stdout) do
+ TestHelperMailer.test_args("some_email", "some_name").deliver_later
+ end
+ end
+ end
+ end
+
+ def test_assert_enqueued_email_with_parameterized_args
+ assert_nothing_raised do
+ assert_enqueued_email_with TestHelperMailer, :test_parameter_args, args: { all: "good" } do
+ silence_stream($stdout) do
+ TestHelperMailer.with(all: "good").test_parameter_args.deliver_later
+ end
+ end
+ end
+ end
end
class AnotherTestHelperMailerTest < ActionMailer::TestCase
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 1d4b27a0f9..16090e7946 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -7,14 +7,14 @@
*Michael J Coyne*
-* Use Capybara registered `:puma` server config.
+* Use Capybara registered `:puma` server config.
The Capybara registered `:puma` server ensures the puma server is run in process so
connection sharing and open request detection work correctly by default.
*Thomas Walpole*
-* Cookies `:expires` option supports `ActiveSupport::Duration` object.
+* Cookies `:expires` option supports `ActiveSupport::Duration` object.
cookies[:user_name] = { value: "assain", expires: 1.hour }
cookies[:key] = { value: "a yummy cookie", expires: 6.months }
@@ -23,7 +23,7 @@
*Assain Jaleel*
-* Enforce signed/encrypted cookie expiry server side.
+* Enforce signed/encrypted cookie expiry server side.
Rails can thwart attacks by malicious clients that don't honor a cookie's expiry.
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index eb193fcbfb..86a070c6ad 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -615,10 +615,11 @@ module ActionDispatch
end
if upgrade_legacy_hmac_aes_cbc_cookies?
- secret = request.key_generator.generate_key(request.encrypted_cookie_salt)
+ legacy_cipher = "aes-256-cbc"
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(legacy_cipher))
sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
- @encryptor.rotate secret, sign_secret, cipher: "aes-256-cbc", digest: digest, serializer: SERIALIZER
+ @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
end
if upgrade_legacy_signed_cookies?
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index c98270bd12..f20a66c2d2 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -26,14 +26,6 @@ require "active_record"
require "pp" # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
-module Rails
- class << self
- def env
- @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "test")
- end
- end
-end
-
ActiveSupport::Dependencies.hook!
Thread.abort_on_exception = true
@@ -110,12 +102,6 @@ module ActionDispatch
end
end
-module ActiveSupport
- class TestCase
- include ActionDispatch::DrawOnce
- end
-end
-
class RoutedRackApp
attr_reader :routes
@@ -162,29 +148,6 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
self.app = build_app
- # Stub Rails dispatcher so it does not get controller references and
- # simply return the controller#action as Rack::Body.
- class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher
- private
- def controller_reference(controller_param)
- controller_param
- end
-
- def dispatch(controller, action, env)
- [200, { "Content-Type" => "text/html" }, ["#{controller}##{action}"]]
- end
- end
-
- def self.stub_controllers
- old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher
- ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
- ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher }
- yield ActionDispatch::Routing::RouteSet.new
- ensure
- ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
- ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher }
- end
-
def with_routing(&block)
temporary_routes = ActionDispatch::Routing::RouteSet.new
old_app, self.class.app = self.class.app, self.class.build_app(temporary_routes)
@@ -196,21 +159,6 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
self.class.app = old_app
silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) }
end
-
- def with_autoload_path(path)
- path = File.join(File.expand_path("fixtures", __dir__), path)
- if ActiveSupport::Dependencies.autoload_paths.include?(path)
- yield
- else
- begin
- ActiveSupport::Dependencies.autoload_paths << path
- yield
- ensure
- ActiveSupport::Dependencies.autoload_paths.reject! { |p| p == path }
- ActiveSupport::Dependencies.clear
- end
- end
- end
end
ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
@@ -274,6 +222,7 @@ module ActionDispatch
end
class ActiveSupport::TestCase
+ include ActionDispatch::DrawOnce
include ActiveSupport::Testing::MethodCallAssertions
# Skips the current run on Rubinius using Minitest::Assertions#skip
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index c34236d4be..f73e27b91f 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,28 @@
+* PostgreSQL `tsrange` now preserves subsecond precision
+
+ PostgreSQL 9.1+ introduced range types, and Rails added support for using
+ this datatype in ActiveRecord. However, the serialization of
+ PostgreSQL::OID::Range was incomplete, because it did not properly
+ cast the bounds that make up the range. This led to subseconds being
+ dropped in SQL commands:
+
+ (byebug) from = type_cast_single_for_database(range.first)
+ 2010-01-01 13:30:00 UTC
+
+ (byebug) to = type_cast_single_for_database(range.last)
+ 2011-02-02 19:30:00 UTC
+
+ (byebug) "[#{from},#{to}#{value.exclude_end? ? ')' : ']'}"
+ "[2010-01-01 13:30:00 UTC,2011-02-02 19:30:00 UTC)"
+
+ (byebug) "[#{type_cast(from)},#{type_cast(to)}#{value.exclude_end? ? ')' : ']'}"
+ "['2010-01-01 13:30:00.670277','2011-02-02 19:30:00.745125')"
+
+* Passing a `Set` to `Relation#where` now behaves the same as passing an
+ array.
+
+ *Sean Griffin*
+
* Use given algorithm while removing index from database.
Fixes #24190.
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index fe696e0d6e..607d376a08 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -50,20 +50,14 @@ module ActiveRecord
end
def owner_keys
- unless defined?(@owner_keys)
- @owner_keys = owners.map do |owner|
- owner[owner_key_name]
- end
- @owner_keys.uniq!
- @owner_keys.compact!
- end
- @owner_keys
+ @owner_keys ||= owners_by_key.keys
end
def owners_by_key
unless defined?(@owners_by_key)
@owners_by_key = owners.each_with_object({}) do |owner, h|
- h[convert_key(owner[owner_key_name])] = owner
+ key = convert_key(owner[owner_key_name])
+ h[key] = owner if key
end
end
@owners_by_key
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 4f957ac3ca..06598439d8 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -34,7 +34,7 @@ module ActiveRecord
def reload(*)
super.tap do
@mutations_before_last_save = nil
- clear_mutation_trackers
+ @mutations_from_database = nil
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
end
@@ -44,22 +44,21 @@ module ActiveRecord
@attributes = self.class._default_attributes.map do |attr|
attr.with_value_from_user(@attributes.fetch_value(attr.name))
end
- clear_mutation_trackers
+ @mutations_from_database = nil
end
def changes_applied # :nodoc:
- @mutations_before_last_save = mutation_tracker
- @mutations_from_database = AttributeMutationTracker.new(@attributes)
+ @mutations_before_last_save = mutations_from_database
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
- clear_mutation_trackers
+ @mutations_from_database = nil
end
def clear_changes_information # :nodoc:
@mutations_before_last_save = nil
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
- clear_mutation_trackers
+ @mutations_from_database = nil
end
def clear_attribute_changes(attr_names) # :nodoc:
@@ -75,7 +74,7 @@ module ActiveRecord
if defined?(@cached_changed_attributes)
@cached_changed_attributes
else
- super.reverse_merge(mutation_tracker.changed_values).freeze
+ super.reverse_merge(mutations_from_database.changed_values).freeze
end
end
@@ -90,7 +89,7 @@ module ActiveRecord
end
def attribute_changed_in_place?(attr_name) # :nodoc:
- mutation_tracker.changed_in_place?(attr_name)
+ mutations_from_database.changed_in_place?(attr_name)
end
# Did this attribute change when we last saved? This method can be invoked
@@ -183,26 +182,18 @@ module ActiveRecord
result
end
- def mutation_tracker
- unless defined?(@mutation_tracker)
- @mutation_tracker = nil
- end
- @mutation_tracker ||= AttributeMutationTracker.new(@attributes)
- end
-
def mutations_from_database
unless defined?(@mutations_from_database)
@mutations_from_database = nil
end
- @mutations_from_database ||= mutation_tracker
+ @mutations_from_database ||= AttributeMutationTracker.new(@attributes)
end
def changes_include?(attr_name)
- super || mutation_tracker.changed?(attr_name)
+ super || mutations_from_database.changed?(attr_name)
end
def clear_attribute_change(attr_name)
- mutation_tracker.forget_change(attr_name)
mutations_from_database.forget_change(attr_name)
end
@@ -227,11 +218,6 @@ module ActiveRecord
@attributes = @attributes.map(&:forgetting_assignment)
end
- def clear_mutation_trackers
- @mutation_tracker = nil
- @mutations_from_database = nil
- end
-
def mutations_before_last_save
@mutations_before_last_save ||= NullMutationTracker.instance
end
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 63c059e291..d8fc046e10 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -17,13 +17,15 @@ module ActiveRecord
# Returns the primary key value.
def id
sync_with_transaction_state
- _read_attribute(self.class.primary_key) if self.class.primary_key
+ primary_key = self.class.primary_key
+ _read_attribute(primary_key) if primary_key
end
# Sets the primary key value.
def id=(value)
sync_with_transaction_state
- _write_attribute(self.class.primary_key, value) if self.class.primary_key
+ primary_key = self.class.primary_key
+ _write_attribute(primary_key, value) if primary_key
end
# Queries the primary key value.
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index b070235684..4077250583 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -58,8 +58,9 @@ module ActiveRecord
attr_name.to_s
end
- name = self.class.primary_key if name == "id".freeze && self.class.primary_key
- sync_with_transaction_state if name == self.class.primary_key
+ primary_key = self.class.primary_key
+ name = primary_key if name == "id".freeze && primary_key
+ sync_with_transaction_state if name == primary_key
_read_attribute(name, &block)
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 37891ce2ef..bb0ec6a8c3 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -39,8 +39,9 @@ module ActiveRecord
attr_name.to_s
end
- name = self.class.primary_key if name == "id".freeze && self.class.primary_key
- sync_with_transaction_state if name == self.class.primary_key
+ primary_key = self.class.primary_key
+ name = primary_key if name == "id".freeze && primary_key
+ sync_with_transaction_state if name == primary_key
_write_attribute(name, value)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index c9607df28c..4f0c1890be 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -2,7 +2,7 @@
require_relative "../../migration/join_table"
require "active_support/core_ext/string/access"
-require "digest"
+require "digest/sha2"
module ActiveRecord
module ConnectionAdapters # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
index 7d5d7d91e6..a89aa5ea09 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -35,7 +35,7 @@ module ActiveRecord
if value.is_a?(::Range)
from = type_cast_single_for_database(value.begin)
to = type_cast_single_for_database(value.end)
- "[#{from},#{to}#{value.exclude_end? ? ')' : ']'}"
+ ::Range.new(from, to, value.exclude_end?)
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index a0a22ba0f1..9fdeab06c1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -101,6 +101,8 @@ module ActiveRecord
end
when OID::Array::Data
_quote(encode_array(value))
+ when Range
+ _quote(encode_range(value))
else
super
end
@@ -117,6 +119,8 @@ module ActiveRecord
value.to_s
when OID::Array::Data
encode_array(value)
+ when Range
+ encode_range(value)
else
super
end
@@ -133,6 +137,10 @@ module ActiveRecord
result
end
+ def encode_range(range)
+ "[#{type_cast(range.first)},#{type_cast(range.last)}#{range.exclude_end? ? ')' : ']'}"
+ end
+
def determine_encoding_of_strings_in_array(value)
case value
when ::Array then determine_encoding_of_strings_in_array(value.first)
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index b48a137a73..a57c60ffac 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -322,7 +322,7 @@ module ActiveRecord
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
- became.instance_variable_set("@mutation_tracker", @mutation_tracker) if defined?(@mutation_tracker)
+ became.instance_variable_set("@mutations_from_database", @mutations_from_database) if defined?(@mutations_from_database)
became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 032df925bf..97adfb4352 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -426,7 +426,7 @@ module ActiveRecord
def initialize(name, scope, options, active_record)
super
@type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
- @foreign_type = options[:foreign_type] || "#{name}_type"
+ @foreign_type = options[:polymorphic] && (options[:foreign_type] || "#{name}_type")
@constructable = calculate_constructable(macro, options)
@association_scope_cache = Concurrent::Map.new
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 5c42414072..be4b169f67 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -13,6 +13,7 @@ module ActiveRecord
register_handler(Range, RangeHandler.new(self))
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new(self))
+ register_handler(Set, ArrayHandler.new(self))
end
def build_from_hash(attributes)
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index b4a776d04d..a75fdef698 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -232,6 +232,57 @@ _SQL
end
end
+ def test_create_tstzrange_preserve_usec
+ tstzrange = Time.parse("2010-01-01 14:30:00.670277 +0100")...Time.parse("2011-02-02 14:30:00.745125 CDT")
+ round_trip(@new_range, :tstz_range, tstzrange)
+ assert_equal @new_range.tstz_range, tstzrange
+ assert_equal @new_range.tstz_range, Time.parse("2010-01-01 13:30:00.670277 UTC")...Time.parse("2011-02-02 19:30:00.745125 UTC")
+ end
+
+ def test_update_tstzrange_preserve_usec
+ assert_equal_round_trip(@first_range, :tstz_range,
+ Time.parse("2010-01-01 14:30:00.245124 CDT")...Time.parse("2011-02-02 14:30:00.451274 CET"))
+ assert_nil_round_trip(@first_range, :tstz_range,
+ Time.parse("2010-01-01 14:30:00.245124 +0100")...Time.parse("2010-01-01 13:30:00.245124 +0000"))
+ end
+
+ def test_create_tsrange_preseve_usec
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal_round_trip(@new_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0, 125435)...Time.send(tz, 2011, 2, 2, 14, 30, 0, 225435))
+ end
+
+ def test_update_tsrange_preserve_usec
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal_round_trip(@first_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)...Time.send(tz, 2011, 2, 2, 14, 30, 0, 224242))
+ assert_nil_round_trip(@first_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)...Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432))
+ end
+
+ def test_timezone_awareness_tsrange_preserve_usec
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ PostgresqlRange.reset_column_information
+ time_string = "2017-09-26 07:30:59.132451 -0700"
+ time = Time.zone.parse(time_string)
+ assert time.usec > 0
+
+ record = PostgresqlRange.new(ts_range: time_string..time_string)
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+ assert_equal time.usec, record.ts_range.begin.usec
+
+ record.save!
+ record.reload
+
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+ assert_equal time.usec, record.ts_range.begin.usec
+ end
+ end
+
def test_create_numrange
assert_equal_round_trip(@new_range, :num_range,
BigDecimal.new("0.5")...BigDecimal.new("1"))
diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
index 449023b6eb..8212ed4263 100644
--- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
@@ -30,6 +30,6 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase
big_range = 0..123456789123456789
assert_raises(ActiveModel::RangeError) { int_range.serialize(big_range) }
- assert_equal "[0,123456789123456789]", bigint_range.serialize(big_range)
+ assert_equal "[0,123456789123456789]", @connection.type_cast(bigint_range.serialize(big_range))
end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 0ea8ef5cea..2d67c57cfb 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -1019,14 +1019,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase
ActiveRecord::Base.time_zone_aware_types = old_types
end
- def cached_columns
- Topic.columns.map(&:name)
- end
-
- def time_related_columns_on_topic
- Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) }
- end
-
def privatize(method_signature)
@target.class_eval(<<-private_method, __FILE__, __LINE__ + 1)
private
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index ae1dc35bff..4edaf79e9a 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1806,6 +1806,10 @@ class RelationTest < ActiveRecord::TestCase
assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).take
end
+ test "arel_attribute respects a custom table" do
+ assert_equal [posts(:welcome)], custom_post_relation.ranked_by_comments.limit_by(1).to_a
+ end
+
test "#load" do
relation = Post.all
assert_queries(1) do
@@ -1912,6 +1916,19 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ test "#where with set" do
+ david = authors(:david)
+ mary = authors(:mary)
+
+ authors = Author.where(name: ["David", "Mary"].to_set)
+ assert_equal [david, mary], authors
+ end
+
+ test "#where with empty set" do
+ authors = Author.where(name: Set.new)
+ assert_empty authors
+ end
+
private
def custom_post_relation
table_alias = Post.arel_table.alias("omg_posts")
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 4c8e847354..935a11e811 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -21,7 +21,7 @@ class Post < ActiveRecord::Base
scope :containing_the_letter_a, -> { where("body LIKE '%a%'") }
scope :titled_with_an_apostrophe, -> { where("title LIKE '%''%'") }
- scope :ranked_by_comments, -> { order("comments_count DESC") }
+ scope :ranked_by_comments, -> { order(arel_attribute(:comments_count).desc) }
scope :limit_by, lambda { |l| limit(l) }
scope :locked, -> { lock }
diff --git a/activestorage/Rakefile b/activestorage/Rakefile
index aa71a65f6e..2aa4d2a76f 100644
--- a/activestorage/Rakefile
+++ b/activestorage/Rakefile
@@ -11,4 +11,6 @@ Rake::TestTask.new do |test|
test.warning = false
end
+task :package
+
task default: :test
diff --git a/activestorage/package.json b/activestorage/package.json
index b481295a8d..8e6dd1c57f 100644
--- a/activestorage/package.json
+++ b/activestorage/package.json
@@ -1,6 +1,6 @@
{
"name": "activestorage",
- "version": "5.2.0.alpha",
+ "version": "5.2.0-alpha",
"description": "Attach cloud and local files in Rails applications",
"main": "app/assets/javascripts/activestorage.js",
"files": [
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 487984cbd3..c95f95d076 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,19 @@
+* Deprecate `secrets.secret_token`.
+
+ The architecture for secrets had a big upgrade between Rails 3 and Rails 4,
+ when the default changed from using `secret_token` to `secret_key_base`.
+
+ `secret_token` has been soft deprecated in documentation for four years
+ but is still in place to support apps created before Rails 4.
+ Deprecation warnings have been added to help developers upgrade their
+ applications to `secret_key_base`.
+
+ *claudiob*, *Kasper Timm Hansen*
+
+* Return an instance of `HashWithIndifferentAccess` from `HashWithIndifferentAccess#transform_keys`.
+
+ *Yuji Yaginuma*
+
* Add key rotation support to `MessageEncryptor` and `MessageVerifier`
This change introduces a `rotate` method to both the `MessageEncryptor` and
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 12291af443..fcc13feb8c 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -306,6 +306,11 @@ module ActiveSupport
dup.tap { |hash| hash.transform_values!(*args, &block) }
end
+ def transform_keys(*args, &block)
+ return to_enum(:transform_keys) unless block_given?
+ dup.tap { |hash| hash.transform_keys!(*args, &block) }
+ end
+
def compact
dup.tap(&:compact!)
end
diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb
index 51870559ec..b6b31ef140 100644
--- a/activesupport/lib/active_support/security_utils.rb
+++ b/activesupport/lib/active_support/security_utils.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "digest"
+require "digest/sha2"
module ActiveSupport
module SecurityUtils
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
index b3788ee65c..b878ac20fa 100644
--- a/activesupport/test/hash_with_indifferent_access_test.rb
+++ b/activesupport/test/hash_with_indifferent_access_test.rb
@@ -399,6 +399,13 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase
assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
end
+ def test_indifferent_transform_keys
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).transform_keys { |k| k * 2 }
+
+ assert_equal({ "aa" => 1, "bb" => 2 }, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
def test_indifferent_compact
hash_contain_nil_value = @strings.merge("z" => nil)
hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value)
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index 8fde3928dc..9edf07f762 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -201,7 +201,7 @@ class MessageEncryptorTest < ActiveSupport::TestCase
end
def secrets
- @secrets ||= Hash.new { |h,k| h[k] = SecureRandom.random_bytes(32) }
+ @secrets ||= Hash.new { |h, k| h[k] = SecureRandom.random_bytes(32) }
end
def munge(base64_string)
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 4d8d8db3e5..557b1d7bef 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
# Activate the gem you are reporting the issue against.
gem "rails", "5.1.0"
end
@@ -19,7 +22,6 @@ require "action_controller/railtie"
class TestApp < Rails::Application
config.root = __dir__
config.session_store :cookie_store, key: "cookie_store_key"
- secrets.secret_token = "secret_token"
secrets.secret_key_base = "secret_key_base"
config.logger = Logger.new($stdout)
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index 1f862e07da..cf76de80d2 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
gem "rails", github: "rails/rails"
gem "arel", github: "rails/arel"
end
@@ -17,7 +20,6 @@ require "action_controller/railtie"
class TestApp < Rails::Application
config.root = __dir__
- secrets.secret_token = "secret_token"
secrets.secret_key_base = "secret_key_base"
config.logger = Logger.new($stdout)
diff --git a/guides/bug_report_templates/active_job_gem.rb b/guides/bug_report_templates/active_job_gem.rb
index af777a86ef..013d1f8602 100644
--- a/guides/bug_report_templates/active_job_gem.rb
+++ b/guides/bug_report_templates/active_job_gem.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
# Activate the gem you are reporting the issue against.
gem "activejob", "5.1.0"
end
diff --git a/guides/bug_report_templates/active_job_master.rb b/guides/bug_report_templates/active_job_master.rb
index 39fb3f60a6..ce480cbb52 100644
--- a/guides/bug_report_templates/active_job_master.rb
+++ b/guides/bug_report_templates/active_job_master.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
gem "rails", github: "rails/rails"
gem "arel", github: "rails/arel"
end
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index 168e2dcc66..921917fbe9 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
# Activate the gem you are reporting the issue against.
gem "activerecord", "5.1.0"
gem "sqlite3"
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index cbd2cff2b8..78411e2d57 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
gem "rails", github: "rails/rails"
gem "arel", github: "rails/arel"
gem "sqlite3"
diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb
index b931ed0beb..f75b6fd932 100644
--- a/guides/bug_report_templates/active_record_migrations_gem.rb
+++ b/guides/bug_report_templates/active_record_migrations_gem.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
# Activate the gem you are reporting the issue against.
gem "activerecord", "5.1.0"
gem "sqlite3"
diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb
index 2c009c0563..60416ed42f 100644
--- a/guides/bug_report_templates/active_record_migrations_master.rb
+++ b/guides/bug_report_templates/active_record_migrations_master.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
gem "rails", github: "rails/rails"
gem "arel", github: "rails/arel"
gem "sqlite3"
diff --git a/guides/bug_report_templates/benchmark.rb b/guides/bug_report_templates/benchmark.rb
index d0f5a634bc..fb51273e3e 100644
--- a/guides/bug_report_templates/benchmark.rb
+++ b/guides/bug_report_templates/benchmark.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
gem "rails", github: "rails/rails"
gem "arel", github: "rails/arel"
gem "benchmark-ips"
diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb
index c990bda005..60e8322c2a 100644
--- a/guides/bug_report_templates/generic_gem.rb
+++ b/guides/bug_report_templates/generic_gem.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
# Activate the gem you are reporting the issue against.
gem "activesupport", "5.1.0"
end
diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb
index 1a9b99b624..384c8b1833 100644
--- a/guides/bug_report_templates/generic_master.rb
+++ b/guides/bug_report_templates/generic_master.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
gem "rails", github: "rails/rails"
gem "arel", github: "rails/arel"
end
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 678b80516f..3573c3c77b 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -414,7 +414,7 @@ end
`find_in_batches` works on model classes, as seen above, and also on relations:
```ruby
-Invoice.pending.find_in_batches do |invoice|
+Invoice.pending.find_in_batches do |invoices|
pending_invoices_export.add_invoices(invoices)
end
```
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 343c224a7c..188620a683 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -63,7 +63,7 @@ authentication for its parent applications, or
[Thredded](https://github.com/thredded/thredded), an engine that provides forum
functionality. There's also [Spree](https://github.com/spree/spree) which
provides an e-commerce platform, and
-[RefineryCMS](https://github.com/refinery/refinerycms), a CMS engine.
+[Refinery CMS](https://github.com/refinery/refinerycms), a CMS engine.
Finally, engines would not have been possible without the work of James Adam,
Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever
diff --git a/guides/source/security.md b/guides/source/security.md
index 9e1dc518d2..a07d583f15 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -152,37 +152,39 @@ In test and development applications get a `secret_key_base` derived from the ap
If you have received an application where the secret was exposed (e.g. an application whose source was shared), strongly consider changing the secret.
-### Rotating Keys for Encrypted and Signed Cookies
+### Rotating Encrypted and Signed Cookies Configurations
-It is possible to rotate the `secret_key_base` as well as the salts,
-ciphers, and digests used for both encrypted and signed cookies. Rotating
-the `secret_key_base` is necessary if the value was exposed or leaked.
-It is also useful to rotate this value for other more benign reasons,
-such as an employee leaving your organization or changing hosting
-environments.
+Rotation is ideal for changing cookie configurations and ensuring old cookies
+aren't immediately invalid. Your users then have a chance to visit your site,
+get their cookie read with an old configuration and have it rewritten with the
+new change. The rotation can then be removed once you're comfortable enough
+users have had their chance to get their cookies upgraded.
-For example to rotate out an old `secret_key_base`, we can define signed and
-encrypted rotations as follows:
+It's possible to rotate the ciphers and digests used for encrypted and signed cookies.
+
+For instance to change the digest used for signed cookies from SHA1 to SHA256,
+you would first assign the new configuration value:
```ruby
-Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
- cookies.rotate :encrypted, secret: Rails.application.credentials.old_secret_key_base
- cookies.rotate :signed, secret: Rails.application.credentials.old_secret_key_base
-end
+Rails.application.config.action_dispatch.signed_cookie_digest = "SHA256"
```
-It's also possible to set up multiple rotations. For instance to use `SHA512`
-for signed cookies while rotating out SHA256 and SHA1 digests, we'd do:
+Then you'd set up a rotation with the old configuration to keep it alive.
```ruby
-Rails.application.config.action_dispatch.signed_cookie_digest = "SHA512"
-
Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
cookies.rotate :signed, digest: "SHA256"
- cookies.rotate :signed, digest: "SHA1"
end
```
+Then any written signed cookies will be digested with SHA256. Old cookies
+that were written with SHA1 can still be read, and if accessed will be written
+with the new digest so they're upgraded and won't be invalid when you remove the
+rotation.
+
+Once users with SHA1 digested signed cookies should no longer have a chance to
+have their cookies rewritten, remove the rotation.
+
While you can setup as many rotations as you'd like it's not common to have many
rotations going at any one time.
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 24f5eeae87..4fd20185b1 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -6,6 +6,7 @@ require "active_support/core_ext/object/blank"
require "active_support/key_generator"
require "active_support/message_verifier"
require "active_support/encrypted_configuration"
+require "active_support/deprecation"
require_relative "engine"
require_relative "secrets"
@@ -398,6 +399,11 @@ module Rails
# Fallback to config.secret_token if secrets.secret_token isn't set
secrets.secret_token ||= config.secret_token
+ if secrets.secret_token.present?
+ ActiveSupport::Deprecation.warn \
+ "`secrets.secret_token` is deprecated in favor of `secret_key_base` and will be removed in Rails 6.0."
+ end
+
secrets
end
end
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index 4f2e84f924..0eb9d82bbb 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -1,4 +1,4 @@
-<%%= form_with(model: <%= singular_table_name %>, local: true) do |form| %>
+<%%= form_with(model: <%= model_resource_name %>, local: true) do |form| %>
<%% if <%= singular_table_name %>.errors.any? %>
<div id="error_explanation">
<h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
index 5f4904fee1..e1ede7c713 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -18,9 +18,9 @@
<% attributes.reject(&:password_digest?).each do |attribute| -%>
<td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
<% end -%>
- <td><%%= link_to 'Show', <%= singular_table_name %> %></td>
- <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td>
- <td><%%= link_to 'Destroy', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ <td><%%= link_to 'Show', <%= model_resource_name %> %></td>
+ <td><%%= link_to 'Edit', edit_<%= singular_route_name %>_path(<%= singular_table_name %>) %></td>
+ <td><%%= link_to 'Destroy', <%= model_resource_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<%% end %>
</tbody>
@@ -28,4 +28,4 @@
<br>
-<%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_table_name %>_path %>
+<%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_route_name %>_path %>
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index fe8447be23..5f602f1d52 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -100,11 +100,11 @@ module Rails
end
def index_helper # :doc:
- uncountable? ? "#{plural_table_name}_index" : plural_table_name
+ uncountable? ? "#{plural_route_name}_index" : plural_route_name
end
def show_helper # :doc:
- "#{singular_table_name}_url(@#{singular_table_name})"
+ "#{singular_route_name}_url(@#{singular_table_name})"
end
def edit_helper # :doc:
@@ -112,7 +112,7 @@ module Rails
end
def new_helper # :doc:
- "new_#{singular_table_name}_url"
+ "new_#{singular_route_name}_url"
end
def field_id(attribute_name)
@@ -152,6 +152,35 @@ module Rails
end
end
+ def redirect_resource_name # :doc:
+ model_resource_name(prefix: "@")
+ end
+
+ def model_resource_name(prefix: "") # :doc:
+ resource_name = "#{prefix}#{singular_table_name}"
+ if controller_class_path.empty?
+ resource_name
+ else
+ "[#{controller_class_path.map { |name| ":" + name }.join(", ")}, #{resource_name}]"
+ end
+ end
+
+ def singular_route_name # :doc:
+ if controller_class_path.empty?
+ singular_table_name
+ else
+ "#{controller_class_path.join('_')}_#{singular_table_name}"
+ end
+ end
+
+ def plural_route_name # :doc:
+ if controller_class_path.empty?
+ plural_table_name
+ else
+ "#{controller_class_path.join('_')}_#{plural_table_name}"
+ end
+ end
+
def assign_names!(name)
@class_path = name.include?("/") ? name.split("/") : name.split("::")
@class_path.map!(&:underscore)
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index ac82ff6633..23fdf03b05 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -69,7 +69,7 @@ module Rails
def version_control
if !options[:skip_git] && !options[:pretend]
- run "git init"
+ run "git init", capture: options[:quiet]
end
end
@@ -164,7 +164,7 @@ module Rails
require_relative "../master_key/master_key_generator"
after_bundle do
- Rails::Generators::MasterKeyGenerator.new.add_master_key_file
+ Rails::Generators::MasterKeyGenerator.new([], quiet: options[:quiet]).add_master_key_file
end
end
@@ -174,7 +174,7 @@ module Rails
require_relative "../credentials/credentials_generator"
after_bundle do
- Rails::Generators::CredentialsGenerator.new.add_credentials_file_silently
+ Rails::Generators::CredentialsGenerator.new([], quiet: options[:quiet]).add_credentials_file_silently
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update.tt b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
index d744bec32f..70cc71d83b 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/update.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
@@ -15,6 +15,11 @@ chdir APP_ROOT do
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
+<% unless options.skip_yarn? -%>
+
+ # Install JavaScript dependencies if using Yarn
+ # system('bin/yarn')
+<% end -%>
<% unless options.skip_active_record? -%>
puts "\n== Updating database =="
diff --git a/railties/lib/rails/generators/rails/master_key/master_key_generator.rb b/railties/lib/rails/generators/rails/master_key/master_key_generator.rb
index e49d3b39e0..395687974a 100644
--- a/railties/lib/rails/generators/rails/master_key/master_key_generator.rb
+++ b/railties/lib/rails/generators/rails/master_key/master_key_generator.rb
@@ -13,15 +13,15 @@ module Rails
unless MASTER_KEY_PATH.exist?
key = ActiveSupport::EncryptedFile.generate_key
- say "Adding #{MASTER_KEY_PATH} to store the master encryption key: #{key}"
- say ""
- say "Save this in a password manager your team can access."
- say ""
- say "If you lose the key, no one, including you, can access anything encrypted with it."
+ log "Adding #{MASTER_KEY_PATH} to store the master encryption key: #{key}"
+ log ""
+ log "Save this in a password manager your team can access."
+ log ""
+ log "If you lose the key, no one, including you, can access anything encrypted with it."
- say ""
+ log ""
create_file MASTER_KEY_PATH, key
- say ""
+ log ""
ignore_master_key_file
end
@@ -31,15 +31,15 @@ module Rails
def ignore_master_key_file
if File.exist?(".gitignore")
unless File.read(".gitignore").include?(key_ignore)
- say "Ignoring #{MASTER_KEY_PATH} so it won't end up in Git history:"
- say ""
+ log "Ignoring #{MASTER_KEY_PATH} so it won't end up in Git history:"
+ log ""
append_to_file ".gitignore", key_ignore
- say ""
+ log ""
end
else
- say "IMPORTANT: Don't commit #{MASTER_KEY_PATH}. Add this to your ignore file:"
- say key_ignore, :on_green
- say ""
+ log "IMPORTANT: Don't commit #{MASTER_KEY_PATH}. Add this to your ignore file:"
+ log key_ignore, :on_green
+ log ""
end
end
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
index 42b9e34274..05f1c2b2d3 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -29,7 +29,7 @@ class <%= controller_class_name %>Controller < ApplicationController
@<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
if @<%= orm_instance.save %>
- redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %>
+ redirect_to <%= redirect_resource_name %>, notice: <%= "'#{human_name} was successfully created.'" %>
else
render :new
end
@@ -38,7 +38,7 @@ class <%= controller_class_name %>Controller < ApplicationController
# PATCH/PUT <%= route_url %>/1
def update
if @<%= orm_instance.update("#{singular_table_name}_params") %>
- redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %>
+ redirect_to <%= redirect_resource_name %>, notice: <%= "'#{human_name} was successfully updated.'" %>
else
render :edit
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index c1a80eaeaf..bb8cc0876c 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -487,6 +487,32 @@ module ApplicationTests
assert_equal "some_value", Rails.application.message_verifier(:sensitive_value).verify(message)
end
+ test "config.secret_token is deprecated" do
+ app_file "config/initializers/secret_token.rb", <<-RUBY
+ Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33"
+ RUBY
+
+ app "production"
+
+ assert_deprecated(/secret_token/) do
+ app.secrets
+ end
+ end
+
+ test "secrets.secret_token is deprecated" do
+ app_file "config/secrets.yml", <<-YAML
+ production:
+ secret_token: "b3c631c314c0bbca50c1b2843150fe33"
+ YAML
+
+ app "production"
+
+ assert_deprecated(/secret_token/) do
+ app.secrets
+ end
+ end
+
+
test "raises when secret_key_base is blank" do
app_file "config/initializers/secret_token.rb", <<-RUBY
Rails.application.credentials.secret_key_base = nil
diff --git a/railties/test/application/middleware/cookies_test.rb b/railties/test/application/middleware/cookies_test.rb
index 092f7a1099..ecb4ee3446 100644
--- a/railties/test/application/middleware/cookies_test.rb
+++ b/railties/test/application/middleware/cookies_test.rb
@@ -52,14 +52,6 @@ module ApplicationTests
end
test "signed cookies with SHA512 digest and rotated out SHA256 and SHA1 digests" do
- skip "@kaspth will fix this"
-
- key_gen_sha1 = ActiveSupport::KeyGenerator.new("legacy sha1 secret", iterations: 1000)
- key_gen_sha256 = ActiveSupport::KeyGenerator.new("legacy sha256 secret", iterations: 1000)
-
- verifer_sha1 = ActiveSupport::MessageVerifier.new(key_gen_sha1.generate_key("sha1 salt"), digest: :SHA1)
- verifer_sha256 = ActiveSupport::MessageVerifier.new(key_gen_sha256.generate_key("sha256 salt"), digest: :SHA256)
-
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get ':controller(/:action)'
@@ -72,12 +64,12 @@ module ApplicationTests
protect_from_forgery with: :null_session
def write_raw_cookie_sha1
- cookies[:signed_cookie] = "#{verifer_sha1.generate("signed cookie")}"
+ cookies[:signed_cookie] = TestVerifiers.sha1.generate("signed cookie")
head :ok
end
def write_raw_cookie_sha256
- cookies[:signed_cookie] = "#{verifer_sha256.generate("signed cookie")}"
+ cookies[:signed_cookie] = TestVerifiers.sha256.generate("signed cookie")
head :ok
end
@@ -92,44 +84,43 @@ module ApplicationTests
RUBY
add_to_config <<-RUBY
- config.action_dispatch.cookies_rotations.rotate :signed,
- digest: "SHA1", secret: "legacy sha1 secret", salt: "sha1 salt"
+ sha1_secret = Rails.application.key_generator.generate_key("sha1")
+ sha256_secret = Rails.application.key_generator.generate_key("sha256")
- config.action_dispatch.cookies_rotations.rotate :signed,
- digest: "SHA256", secret: "legacy sha256 secret", salt: "sha256 salt"
+ ::TestVerifiers = Class.new do
+ class_attribute :sha1, default: ActiveSupport::MessageVerifier.new(sha1_secret, digest: "SHA1")
+ class_attribute :sha256, default: ActiveSupport::MessageVerifier.new(sha256_secret, digest: "SHA256")
+ end
config.action_dispatch.signed_cookie_digest = "SHA512"
config.action_dispatch.signed_cookie_salt = "sha512 salt"
+
+ config.action_dispatch.cookies_rotations.tap do |cookies|
+ cookies.rotate :signed, sha1_secret, digest: "SHA1"
+ cookies.rotate :signed, sha256_secret, digest: "SHA256"
+ end
RUBY
require "#{app_path}/config/environment"
- verifer_sha512 = ActiveSupport::MessageVerifier.new(app.key_generator.generate_key("sha512 salt"), digest: :SHA512)
+ verifier_sha512 = ActiveSupport::MessageVerifier.new(app.key_generator.generate_key("sha512 salt"), digest: :SHA512)
get "/foo/write_raw_cookie_sha1"
get "/foo/read_signed"
assert_equal "signed cookie".inspect, last_response.body
get "/foo/read_raw_cookie"
- assert_equal "signed cookie", verifer_sha512.verify(last_response.body)
+ assert_equal "signed cookie", verifier_sha512.verify(last_response.body)
get "/foo/write_raw_cookie_sha256"
get "/foo/read_signed"
assert_equal "signed cookie".inspect, last_response.body
get "/foo/read_raw_cookie"
- assert_equal "signed cookie", verifer_sha512.verify(last_response.body)
+ assert_equal "signed cookie", verifier_sha512.verify(last_response.body)
end
- test "encrypted cookies with multiple rotated out ciphers" do
- skip "@kaspth will fix this"
-
- key_gen_one = ActiveSupport::KeyGenerator.new("legacy secret one", iterations: 1000)
- key_gen_two = ActiveSupport::KeyGenerator.new("legacy secret two", iterations: 1000)
-
- encryptor_one = ActiveSupport::MessageEncryptor.new(key_gen_one.generate_key("salt one", 32), cipher: "aes-256-gcm")
- encryptor_two = ActiveSupport::MessageEncryptor.new(key_gen_two.generate_key("salt two", 32), cipher: "aes-256-gcm")
-
+ test "encrypted cookies rotating multiple encryption keys" do
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get ':controller(/:action)'
@@ -142,12 +133,12 @@ module ApplicationTests
protect_from_forgery with: :null_session
def write_raw_cookie_one
- cookies[:encrypted_cookie] = "#{encryptor_one.encrypt_and_sign("encrypted cookie")}"
+ cookies[:encrypted_cookie] = TestEncryptors.first_gcm.encrypt_and_sign("encrypted cookie")
head :ok
end
def write_raw_cookie_two
- cookies[:encrypted_cookie] = "#{encryptor_two.encrypt_and_sign("encrypted cookie")}"
+ cookies[:encrypted_cookie] = TestEncryptors.second_gcm.encrypt_and_sign("encrypted cookie")
head :ok
end
@@ -162,15 +153,22 @@ module ApplicationTests
RUBY
add_to_config <<-RUBY
+ first_secret = Rails.application.key_generator.generate_key("first", 32)
+ second_secret = Rails.application.key_generator.generate_key("second", 32)
+
+ ::TestEncryptors = Class.new do
+ class_attribute :first_gcm, default: ActiveSupport::MessageEncryptor.new(first_secret, cipher: "aes-256-gcm")
+ class_attribute :second_gcm, default: ActiveSupport::MessageEncryptor.new(second_secret, cipher: "aes-256-gcm")
+ end
+
config.action_dispatch.use_authenticated_cookie_encryption = true
config.action_dispatch.encrypted_cookie_cipher = "aes-256-gcm"
config.action_dispatch.authenticated_encrypted_cookie_salt = "salt"
- config.action_dispatch.cookies_rotations.rotate :encrypted,
- cipher: "aes-256-gcm", secret: "legacy secret one", salt: "salt one"
-
- config.action_dispatch.cookies_rotations.rotate :encrypted,
- cipher: "aes-256-gcm", secret: "legacy secret two", salt: "salt two"
+ config.action_dispatch.cookies_rotations.tap do |cookies|
+ cookies.rotate :encrypted, first_secret
+ cookies.rotate :encrypted, second_secret
+ end
RUBY
require "#{app_path}/config/environment"
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index 36d1bf5bf2..a17988235a 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -301,8 +301,6 @@ module ApplicationTests
end
test "session upgrading from AES-CBC-HMAC encryption to AES-GCM encryption" do
- skip "@kaspth will fix this"
-
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get ':controller(/:action)'
diff --git a/railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml b/railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml
deleted file mode 100644
index fe80872a16..0000000000
--- a/railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml
+++ /dev/null
@@ -1 +0,0 @@
-# an empty YAML file - any content in here seems to get parsed as a string \ No newline at end of file
diff --git a/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb b/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb
deleted file mode 100644
index 1a82a2bdd4..0000000000
--- a/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-# frozen_string_literal: true
-
-# intentionally empty
diff --git a/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb b/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb
deleted file mode 100644
index 1a82a2bdd4..0000000000
--- a/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-# frozen_string_literal: true
-
-# intentionally empty
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 904e2a5c84..20f593f25c 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -560,6 +560,11 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/run git init/, output)
end
+ def test_quiet_option
+ output = run_generator [File.join(destination_root, "myapp"), "--quiet"]
+ assert_empty output
+ end
+
def test_application_name_with_spaces
path = File.join(destination_root, "foo bar")
@@ -737,7 +742,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
sequence = ["git init", "install", "exec spring binstub --all", "echo ran after_bundle"]
@sequence_step ||= 0
- ensure_bundler_first = -> command do
+ ensure_bundler_first = -> command, options = nil do
assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}"
@sequence_step += 1
end
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index 67f05926e3..64e9909859 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -131,6 +131,19 @@ class NamedBaseTest < Rails::Generators::TestCase
assert_name g, "admin/foos", :controller_file_path
assert_name g, "foos", :controller_file_name
assert_name g, "admin.foos", :controller_i18n_scope
+ assert_name g, "admin_user", :singular_route_name
+ assert_name g, "admin_users", :plural_route_name
+ assert_name g, "[:admin, @user]", :redirect_resource_name
+ assert_name g, "[:admin, user]", :model_resource_name
+ assert_name g, "admin_users", :index_helper
+ end
+
+ def test_scaffold_plural_names
+ g = generator ["User"]
+ assert_name g, "@user", :redirect_resource_name
+ assert_name g, "user", :model_resource_name
+ assert_name g, "user", :singular_route_name
+ assert_name g, "users", :plural_route_name
end
private
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 384524aba9..513b037043 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -174,6 +174,29 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_instance_method :index, content do |m|
assert_match("@users = User.all", m)
end
+
+ assert_instance_method :create, content do |m|
+ assert_match("redirect_to [:admin, @user]", m)
+ end
+
+ assert_instance_method :update, content do |m|
+ assert_match("redirect_to [:admin, @user]", m)
+ end
+ end
+
+ assert_file "app/views/admin/users/index.html.erb" do |content|
+ assert_match("'Show', [:admin, user]", content)
+ assert_match("'Edit', edit_admin_user_path(user)", content)
+ assert_match("'Destroy', [:admin, user]", content)
+ assert_match("'New User', new_admin_user_path", content)
+ end
+
+ assert_file "app/views/admin/users/new.html.erb" do |content|
+ assert_match("'Back', admin_users_path", content)
+ end
+
+ assert_file "app/views/admin/users/_form.html.erb" do |content|
+ assert_match("model: [:admin, user]", content)
end
end