aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md6
-rw-r--r--actionpack/lib/abstract_controller/caching/fragments.rb6
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb4
-rw-r--r--actionpack/lib/action_controller/metal/live.rb4
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb2
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb22
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb3
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb4
-rw-r--r--actionpack/test/controller/parameters/always_permitted_parameters_test.rb2
-rw-r--r--actionpack/test/controller/redirect_test.rb16
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb4
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb14
-rw-r--r--activejob/lib/active_job/arguments.rb20
-rw-r--r--activejob/test/cases/argument_serialization_test.rb6
-rw-r--r--activemodel/CHANGELOG.md6
-rw-r--r--activemodel/lib/active_model/type/helpers/time_value.rb8
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb15
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb10
-rw-r--r--activemodel/test/models/topic.rb14
-rw-r--r--activerecord/CHANGELOG.md47
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb32
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb19
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb70
-rw-r--r--activerecord/lib/active_record/connection_handling.rb41
-rw-r--r--activerecord/lib/active_record/core.rb22
-rw-r--r--activerecord/lib/active_record/database_configurations.rb185
-rw-r--r--activerecord/lib/active_record/database_configurations/database_config.rb29
-rw-r--r--activerecord/lib/active_record/database_configurations/hash_config.rb36
-rw-r--r--activerecord/lib/active_record/database_configurations/url_config.rb60
-rw-r--r--activerecord/lib/active_record/persistence.rb9
-rw-r--r--activerecord/lib/active_record/railties/databases.rake42
-rw-r--r--activerecord/lib/active_record/reflection.rb20
-rw-r--r--activerecord/lib/active_record/relation.rb8
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb4
-rw-r--r--activerecord/lib/active_record/result.rb4
-rw-r--r--activerecord/lib/active_record/sanitization.rb4
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb32
-rw-r--r--activerecord/lib/active_record/test_databases.rb27
-rw-r--r--activerecord/lib/rails/generators/active_record/migration.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb1
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb12
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb5
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb7
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb8
-rw-r--r--activerecord/test/cases/dirty_test.rb20
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb2
-rw-r--r--activerecord/test/cases/legacy_configurations_test.rb43
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb6
-rw-r--r--activerecord/test/cases/relations_test.rb18
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb151
-rw-r--r--activerecord/test/cases/tasks/legacy_database_tasks_test.rb591
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb2
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb2
-rw-r--r--activerecord/test/models/post.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb18
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb12
-rw-r--r--guides/source/2_3_release_notes.md2
-rw-r--r--guides/source/3_0_release_notes.md2
-rw-r--r--guides/source/6_0_release_notes.md175
-rw-r--r--guides/source/configuring.md35
-rw-r--r--guides/source/documents.yaml5
-rw-r--r--guides/source/form_helpers.md214
-rw-r--r--guides/source/layout.html.erb4
-rw-r--r--guides/source/upgrading_ruby_on_rails.md15
-rw-r--r--railties/CHANGELOG.md15
-rw-r--r--railties/lib/rails/application/configuration.rb12
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt4
-rw-r--r--railties/test/application/rake/dbs_test.rb16
-rw-r--r--railties/test/application/rake/multi_dbs_test.rb20
-rw-r--r--railties/test/generators/migration_generator_test.rb13
-rw-r--r--railties/test/generators/scaffold_generator_test.rb2
78 files changed, 1826 insertions, 486 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index a30f178190..0f5afc0416 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -10,8 +10,6 @@
Enable `action_dispatch.use_cookies_with_metadata` to use this feature, which
writes cookies with the new purpose and expiry metadata embedded.
- Pull Request: #32937
-
*Assain Jaleel*
* Raises `ActionController::RespondToMismatchError` with confliciting `respond_to` invocations.
@@ -39,7 +37,7 @@
*Aaron Kromer*
-* Pass along arguments to underlying `get` method in `follow_redirect!`
+* Pass along arguments to underlying `get` method in `follow_redirect!`.
Now all arguments passed to `follow_redirect!` are passed to the underlying
`get` method. This for example allows to set custom headers for the
@@ -56,7 +54,7 @@
*Vinicius Stock*
-* Introduce ActionDispatch::DebugExceptions.register_interceptor
+* Introduce `ActionDispatch::DebugExceptions.register_interceptor`.
Exception aware plugin authors can use the newly introduced
`.register_interceptor` method to get the processed exception, instead of
diff --git a/actionpack/lib/abstract_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb
index febd8a67a6..95078a2a28 100644
--- a/actionpack/lib/abstract_controller/caching/fragments.rb
+++ b/actionpack/lib/abstract_controller/caching/fragments.rb
@@ -88,7 +88,11 @@ module AbstractController
def combined_fragment_cache_key(key)
head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
- [ :views, (ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]), *head, *tail ].compact
+
+ cache_key = [:views, ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"], head, tail]
+ cache_key.flatten!(1)
+ cache_key.compact!
+ cache_key
end
# Writes +content+ to the location signified by
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index 8d53a30e93..26e6f72b66 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -5,8 +5,8 @@ require "active_support/core_ext/hash/slice"
module ActionController
# This module is deprecated in favor of +config.force_ssl+ in your environment
- # config file. This will ensure all communication to non-whitelisted endpoints
- # served by your application occurs over HTTPS.
+ # config file. This will ensure all endpoints not explicitly marked otherwise
+ # will have all communication served over HTTPS.
module ForceSSL # :nodoc:
extend ActiveSupport::Concern
include AbstractController::Callbacks
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 2f4c8fb83c..b1c2391afe 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -86,7 +86,7 @@ module ActionController
# Note: SSEs are not currently supported by IE. However, they are supported
# by Chrome, Firefox, Opera, and Safari.
class SSE
- WHITELISTED_OPTIONS = %w( retry event id )
+ PERMITTED_OPTIONS = %w( retry event id )
def initialize(stream, options = {})
@stream = stream
@@ -111,7 +111,7 @@ module ActionController
def perform_write(json, options)
current_options = @options.merge(options).stringify_keys
- WHITELISTED_OPTIONS.each do |option_name|
+ PERMITTED_OPTIONS.each do |option_name|
if (option_value = current_options[option_name])
@stream.write "#{option_name}: #{option_value}\n"
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 9e63e3e7b6..118da11990 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -11,7 +11,7 @@ module ActionController #:nodoc:
# @people = Person.all
# end
#
- # That action implicitly responds to all formats, but formats can also be whitelisted:
+ # That action implicitly responds to all formats, but formats can also be explicitly enumerated:
#
# def index
# @people = Person.all
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 4c2b5120eb..2804a06a58 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -105,7 +105,7 @@ module ActionController
when String
request.protocol + request.host_with_port + options
when Proc
- _compute_redirect_to_location request, options.call
+ _compute_redirect_to_location request, instance_eval(&options)
else
url_for(options)
end.delete("\0\r\n")
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 7ed7b9d546..cb109c6ad8 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -45,7 +45,7 @@ module ActionController #:nodoc:
# the same origin. Note however that any cross-origin third party domain
# allowed via {CORS}[https://en.wikipedia.org/wiki/Cross-origin_resource_sharing]
# will also be able to create XHR requests. Be sure to check your
- # CORS whitelist before disabling forgery protection for XHR.
+ # CORS configuration before disabling forgery protection for XHR.
#
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method.
# By default <tt>protect_from_forgery</tt> protects your session with
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 7af29f8dca..21859e5356 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -58,7 +58,7 @@ module ActionController
# == Action Controller \Parameters
#
- # Allows you to choose which attributes should be whitelisted for mass updating
+ # Allows you to choose which attributes should be permitted for mass updating
# and thus prevent accidentally exposing that which shouldn't be exposed.
# Provides two methods for this purpose: #require and #permit. The former is
# used to mark parameters as required. The latter is used to set the parameter
@@ -505,7 +505,7 @@ module ActionController
#
# Note that if you use +permit+ in a key that points to a hash,
# it won't allow all the hash. You also need to specify which
- # attributes inside the hash should be whitelisted.
+ # attributes inside the hash should be permitted.
#
# params = ActionController::Parameters.new({
# person: {
@@ -904,6 +904,16 @@ module ActionController
PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) }
end
+ # Adds existing keys to the params if their values are scalar.
+ #
+ # For example:
+ #
+ # puts self.keys #=> ["zipcode(90210i)"]
+ # params = {}
+ #
+ # permitted_scalar_filter(params, "zipcode")
+ #
+ # puts params.keys # => ["zipcode"]
def permitted_scalar_filter(params, key)
if has_key?(key) && permitted_scalar?(self[key])
params[key] = self[key]
@@ -997,8 +1007,8 @@ module ActionController
#
# It provides an interface for protecting attributes from end-user
# assignment. This makes Action Controller parameters forbidden
- # to be used in Active Model mass assignment until they have been
- # whitelisted.
+ # to be used in Active Model mass assignment until they have been explicitly
+ # enumerated.
#
# In addition, parameters can be marked as required and flow through a
# predefined raise/rescue flow to end up as a <tt>400 Bad Request</tt> with no
@@ -1034,7 +1044,7 @@ module ActionController
# end
#
# In order to use <tt>accepts_nested_attributes_for</tt> with Strong \Parameters, you
- # will need to specify which nested attributes should be whitelisted. You might want
+ # will need to specify which nested attributes should be permitted. You might want
# to allow +:id+ and +:_destroy+, see ActiveRecord::NestedAttributes for more information.
#
# class Person
@@ -1052,7 +1062,7 @@ module ActionController
# private
#
# def person_params
- # # It's mandatory to specify the nested attributes that should be whitelisted.
+ # # It's mandatory to specify the nested attributes that should be permitted.
# # If you use `permit` with just the key that points to the nested attributes hash,
# # it will return an empty hash.
# params.require(:person).permit(:name, :age, pets_attributes: [ :id, :name, :category ])
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index d7435fa8df..be129965d1 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -85,10 +85,7 @@ module ActionDispatch
if variant.all? { |v| v.is_a?(Symbol) }
@variant = ActiveSupport::ArrayInquirer.new(variant)
else
- raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols. " \
- "For security reasons, never directly set the variant to a user-provided value, " \
- "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
- "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
+ raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols."
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
index 1fa0691303..0242b706b2 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -197,4 +197,7 @@
setupMatchPaths();
setupRouteToggleHelperLinks();
+
+ // Focus the search input after page has loaded
+ document.getElementById('search').focus();
</script>
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index ff325afc54..07e3be4db8 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -553,10 +553,10 @@ module ActionDispatch
#
# match 'json_only', constraints: { format: 'json' }, via: :get
#
- # class Whitelist
+ # class PermitList
# def matches?(request) request.remote_ip == '1.2.3.4' end
# end
- # match 'path', to: 'c#a', constraints: Whitelist.new, via: :get
+ # match 'path', to: 'c#a', constraints: PermitList.new, via: :get
#
# See <tt>Scoping#constraints</tt> for more examples with its scope
# equivalent.
diff --git a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
index fe0e5e368d..974612fb7b 100644
--- a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
+++ b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
@@ -20,7 +20,7 @@ class AlwaysPermittedParametersTest < ActiveSupport::TestCase
end
end
- test "permits parameters that are whitelisted" do
+ test "allows both explicitly listed and always-permitted parameters" do
params = ActionController::Parameters.new(
book: { pages: 65 },
format: "json")
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 2959dc3e4d..461e627154 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -5,6 +5,12 @@ require "abstract_unit"
class Workshop
extend ActiveModel::Naming
include ActiveModel::Conversion
+
+ OUT_OF_SCOPE_BLOCK = proc do
+ raise "Not executed in controller's context" unless RedirectController === self
+ request.original_url
+ end
+
attr_accessor :id
def initialize(id)
@@ -119,6 +125,10 @@ class RedirectController < ActionController::Base
redirect_to proc { { action: "hello_world" } }
end
+ def redirect_to_out_of_scope_block
+ redirect_to Workshop::OUT_OF_SCOPE_BLOCK
+ end
+
def redirect_with_header_break
redirect_to "/lol\r\nwat"
end
@@ -326,6 +336,12 @@ class RedirectTest < ActionController::TestCase
assert_redirected_to "http://www.rubyonrails.org/"
end
+ def test_redirect_to_out_of_scope_block
+ get :redirect_to_out_of_scope_block
+ assert_response :redirect
+ assert_redirected_to "http://test.host/redirect/redirect_to_out_of_scope_block"
+ end
+
def test_redirect_to_with_block_and_accepted_options
with_routing do |set|
set.draw do
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index cb0c99c4cf..f4fa133f55 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -10,7 +10,7 @@ module ActionView
# These helper methods extend Action View making them callable within your template files.
module SanitizeHelper
extend ActiveSupport::Concern
- # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted.
+ # Sanitizes HTML input, stripping all but known-safe tags and attributes.
#
# It also strips href/src attributes with unsafe protocols like
# <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
@@ -40,7 +40,7 @@ module ActionView
#
# <%= sanitize @comment.body %>
#
- # Providing custom whitelisted tags and attributes:
+ # Providing custom lists of permitted tags and attributes:
#
# <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
#
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index b7b749f9da..270be0a380 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -14,7 +14,17 @@ module ActionView
class_attribute :erb_implementation, default: Erubi
# Do not escape templates of these mime types.
- class_attribute :escape_whitelist, default: ["text/plain"]
+ class_attribute :escape_ignore_list, default: ["text/plain"]
+
+ [self, singleton_class].each do |base|
+ base.send(:alias_method, :escape_whitelist, :escape_ignore_list)
+ base.send(:alias_method, :escape_whitelist=, :escape_ignore_list=)
+
+ base.deprecate(
+ escape_whitelist: "use #escape_ignore_list instead",
+ :escape_whitelist= => "use #escape_ignore_list= instead"
+ )
+ end
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
@@ -47,7 +57,7 @@ module ActionView
self.class.erb_implementation.new(
erb,
- escape: (self.class.escape_whitelist.include? template.type),
+ escape: (self.class.escape_ignore_list.include? template.type),
trim: (self.class.erb_trim_mode == "-")
).src
end
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index 86bb0c5540..ba7f9456f9 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -24,18 +24,20 @@ module ActiveJob
module Arguments
extend self
# :nodoc:
- TYPE_WHITELIST = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ]
+ PERMITTED_TYPES = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ]
- # Serializes a set of arguments. Whitelisted types are returned
- # as-is. Arrays/Hashes are serialized element by element.
- # All other types are serialized using GlobalID.
+ # Serializes a set of arguments. Intrinsic types that can safely be
+ # serialized without mutation are returned as-is. Arrays/Hashes are
+ # serialized element by element. All other types are serialized using
+ # GlobalID.
def serialize(arguments)
arguments.map { |argument| serialize_argument(argument) }
end
- # Deserializes a set of arguments. Whitelisted types are returned
- # as-is. Arrays/Hashes are deserialized element by element.
- # All other types are deserialized using GlobalID.
+ # Deserializes a set of arguments. Instrinsic types that can safely be
+ # deserialized without mutation are returned as-is. Arrays/Hashes are
+ # deserialized element by element. All other types are deserialized using
+ # GlobalID.
def deserialize(arguments)
arguments.map { |argument| deserialize_argument(argument) }
rescue
@@ -64,7 +66,7 @@ module ActiveJob
def serialize_argument(argument)
case argument
- when *TYPE_WHITELIST
+ when *PERMITTED_TYPES
argument
when GlobalID::Identification
convert_to_global_id_hash(argument)
@@ -88,7 +90,7 @@ module ActiveJob
case argument
when String
GlobalID::Locator.locate(argument) || argument
- when *TYPE_WHITELIST
+ when *PERMITTED_TYPES
argument
when Array
argument.map { |arg| deserialize_argument(arg) }
diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb
index e5f1f087fe..f07529d743 100644
--- a/activejob/test/cases/argument_serialization_test.rb
+++ b/activejob/test/cases/argument_serialization_test.rb
@@ -121,8 +121,10 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
end
test "should not allow reserved hash keys" do
- ["_aj_globalid", :_aj_globalid, "_aj_symbol_keys", :_aj_symbol_keys,
- "_aj_hash_with_indifferent_access", :_aj_hash_with_indifferent_access].each do |key|
+ ["_aj_globalid", :_aj_globalid,
+ "_aj_symbol_keys", :_aj_symbol_keys,
+ "_aj_hash_with_indifferent_access", :_aj_hash_with_indifferent_access,
+ "_aj_serialized", :_aj_serialized].each do |key|
assert_raises ActiveJob::SerializationError do
ActiveJob::Arguments.serialize [key => 1]
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 4bf96e11b0..0b2fa37787 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,9 @@
+* Fix numericality validator to still use value before type cast except Active Record.
+
+ Fixes #33651, #33686.
+
+ *Ryuta Kamizono*
+
* Fix `ActiveModel::Serializers::JSON#as_json` method for timestamps.
Before:
diff --git a/activemodel/lib/active_model/type/helpers/time_value.rb b/activemodel/lib/active_model/type/helpers/time_value.rb
index cb6aa67a9d..da56073436 100644
--- a/activemodel/lib/active_model/type/helpers/time_value.rb
+++ b/activemodel/lib/active_model/type/helpers/time_value.rb
@@ -70,7 +70,13 @@ module ActiveModel
# Doesn't handle time zones.
def fast_string_to_time(string)
if string =~ ISO_DATETIME
- microsec = ($7.to_r * 1_000_000).to_i
+ microsec_part = $7
+ if microsec_part && microsec_part.start_with?(".") && microsec_part.length == 7
+ microsec_part[0] = ""
+ microsec = microsec_part.to_i
+ else
+ microsec = (microsec_part.to_r * 1_000_000).to_i
+ end
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
end
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 3753040316..126a87ac6e 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -21,10 +21,17 @@ module ActiveModel
def validate_each(record, attr_name, value)
came_from_user = :"#{attr_name}_came_from_user?"
- if record.respond_to?(came_from_user) && record.public_send(came_from_user)
- raw_value = record.read_attribute_before_type_cast(attr_name)
- elsif record.respond_to?(:read_attribute)
- raw_value = record.read_attribute(attr_name)
+ if record.respond_to?(came_from_user)
+ if record.public_send(came_from_user)
+ raw_value = record.read_attribute_before_type_cast(attr_name)
+ elsif record.respond_to?(:read_attribute)
+ raw_value = record.read_attribute(attr_name)
+ end
+ else
+ before_type_cast = :"#{attr_name}_before_type_cast"
+ if record.respond_to?(before_type_cast)
+ raw_value = record.public_send(before_type_cast)
+ end
end
raw_value ||= value
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 01b78ae72e..ca3c3bc40d 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -262,6 +262,16 @@ class NumericalityValidationTest < ActiveModel::TestCase
Person.clear_validators!
end
+ def test_validates_numericality_using_value_before_type_cast_if_possible
+ Topic.validates_numericality_of :price
+
+ topic = Topic.new(price: 50)
+
+ assert_equal "$50.00", topic.price
+ assert_equal 50, topic.price_before_type_cast
+ assert_predicate topic, :valid?
+ end
+
def test_validates_numericality_with_exponent_number
base = 10_000_000_000_000_000
Topic.validates_numericality_of :approved, less_than_or_equal_to: base
diff --git a/activemodel/test/models/topic.rb b/activemodel/test/models/topic.rb
index b0af00ee45..db3284f833 100644
--- a/activemodel/test/models/topic.rb
+++ b/activemodel/test/models/topic.rb
@@ -3,6 +3,11 @@
class Topic
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
+ include ActiveModel::AttributeMethods
+ include ActiveSupport::NumberHelper
+
+ attribute_method_suffix "_before_type_cast"
+ define_attribute_method :price
def self._validates_default_keys
super | [ :message ]
@@ -10,6 +15,7 @@ class Topic
attr_accessor :title, :author_name, :content, :approved, :created_at
attr_accessor :after_validation_performed
+ attr_writer :price
after_validation :perform_after_validation
@@ -38,4 +44,12 @@ class Topic
def my_validation_with_arg(attr)
errors.add attr, "is missing" unless send(attr)
end
+
+ def price
+ number_to_currency @price
+ end
+
+ def attribute_before_type_cast(attr)
+ instance_variable_get(:"@#{attr}")
+ end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 71dcecd346..a7c2680015 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,50 @@
+* ActiveRecord::Base.configurations now returns an object.
+
+ ActiveRecord::Base.configurations used to return a hash, but this
+ is an inflexible data model. In order to improve multiple-database
+ handling in Rails, we've changed this to return an object. Some methods
+ are provided to make the object behave hash-like in order to ease the
+ transition process. Since most applications don't manipulate the hash
+ we've decided to add backwards-compatible functionality that will throw
+ a deprecation warning if used, however calling `ActiveRecord::Base.configurations`
+ will use the new version internally and externally.
+
+ For example, the following database.yml...
+
+ ```
+ development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ ```
+
+ Used to become a hash:
+
+ ```
+ { "development" => { "adapter" => "sqlite3", "database" => "db/development.sqlite3" } }
+ ```
+
+ Is now converted into the following object:
+
+ ```
+ #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
+ #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
+ @spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>
+ ]
+ ```
+
+ Iterating over the database configurations has also changed. Instead of
+ calling hash methods on the `configurations` hash directly, a new method `configs_for` has
+ been provided that allows you to select the correct configuration. `env_name` is a required
+ argument, `spec_name` is optional as well as passing a block. These return an array of
+ database config objects for the requested environment and specification name respectively.
+
+ ```
+ ActiveRecord::Base.configurations.configs_for("development")
+ ActiveRecord::Base.configurations.configs_for("development", "primary")
+ ```
+
+ *Eileen M. Uchitelle*, *Aaron Patterson*
+
* Add database configuration to disable advisory locks.
```
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index d198466dbf..d43378c64f 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -40,7 +40,6 @@ module ActiveRecord
autoload :Core
autoload :ConnectionHandling
autoload :CounterCache
- autoload :DatabaseConfigurations
autoload :DynamicMatchers
autoload :Enum
autoload :InternalMetadata
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index e4b8b1a330..701f19a6ae 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -31,7 +31,7 @@ module ActiveRecord
end
}
- BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
+ RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
class GeneratedAttributeMethods < Module #:nodoc:
include Mutex_m
@@ -123,7 +123,7 @@ module ActiveRecord
# A class method is 'dangerous' if it is already (re)defined by Active Record, but
# not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
def dangerous_class_method?(method_name)
- BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
+ RESTRICTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
end
def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
@@ -167,12 +167,14 @@ module ActiveRecord
end
end
- # Regexp whitelist. Matches the following:
+ # Regexp for column names (with or without a table name prefix). Matches
+ # the following:
# "#{table_name}.#{column_name}"
# "#{column_name}"
- COLUMN_NAME_WHITELIST = /\A(?:\w+\.)?\w+\z/i
+ COLUMN_NAME = /\A(?:\w+\.)?\w+\z/i
- # Regexp whitelist. Matches the following:
+ # Regexp for column names with order (with or without a table name
+ # prefix, with or without various order modifiers). Matches the following:
# "#{table_name}.#{column_name}"
# "#{table_name}.#{column_name} #{direction}"
# "#{table_name}.#{column_name} #{direction} NULLS FIRST"
@@ -181,7 +183,7 @@ module ActiveRecord
# "#{column_name} #{direction}"
# "#{column_name} #{direction} NULLS FIRST"
# "#{column_name} NULLS LAST"
- COLUMN_NAME_ORDER_WHITELIST = /
+ COLUMN_NAME_WITH_ORDER = /
\A
(?:\w+\.)?
\w+
@@ -190,12 +192,12 @@ module ActiveRecord
\z
/ix
- def enforce_raw_sql_whitelist(args, whitelist: COLUMN_NAME_WHITELIST) # :nodoc:
+ def disallow_raw_sql!(args, permit: COLUMN_NAME) # :nodoc:
unexpected = args.reject do |arg|
arg.kind_of?(Arel::Node) ||
arg.is_a?(Arel::Nodes::SqlLiteral) ||
arg.is_a?(Arel::Attributes::Attribute) ||
- arg.to_s.split(/\s*,\s*/).all? { |part| whitelist.match?(part) }
+ arg.to_s.split(/\s*,\s*/).all? { |part| permit.match?(part) }
end
return if unexpected.none?
@@ -449,14 +451,6 @@ module ActiveRecord
defined?(@attributes) && @attributes.key?(attr_name)
end
- def attributes_with_values_for_create(attribute_names)
- attributes_with_values(attributes_for_create(attribute_names))
- end
-
- def attributes_with_values_for_update(attribute_names)
- attributes_with_values(attributes_for_update(attribute_names))
- end
-
def attributes_with_values(attribute_names)
attribute_names.each_with_object({}) do |name, attrs|
attrs[name] = _read_attribute(name)
@@ -465,7 +459,8 @@ module ActiveRecord
# Filters the primary keys and readonly attributes from the attribute names.
def attributes_for_update(attribute_names)
- attribute_names.reject do |name|
+ attribute_names &= self.class.column_names
+ attribute_names.delete_if do |name|
readonly_attribute?(name)
end
end
@@ -473,7 +468,8 @@ module ActiveRecord
# Filters out the primary keys, from the attribute names, when the primary
# key is to be generated (e.g. the id attribute has no value).
def attributes_for_create(attribute_names)
- attribute_names.reject do |name|
+ attribute_names &= self.class.column_names
+ attribute_names.delete_if do |name|
pk_attribute?(name) && id.nil?
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 233ee29fac..ebc2252c50 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -16,9 +16,6 @@ module ActiveRecord
class_attribute :partial_writes, instance_writer: false, default: true
- after_create { changes_applied }
- after_update { changes_applied }
-
# Attribute methods for "changed in last call to save?"
attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
attribute_method_prefix("saved_change_to_")
@@ -167,16 +164,20 @@ module ActiveRecord
result
end
- def _update_record(*)
- partial_writes? ? super(keys_for_partial_write) : super
+ def _update_record(attribute_names = attribute_names_for_partial_writes)
+ affected_rows = super
+ changes_applied
+ affected_rows
end
- def _create_record(*)
- partial_writes? ? super(keys_for_partial_write) : super
+ def _create_record(attribute_names = attribute_names_for_partial_writes)
+ id = super
+ changes_applied
+ id
end
- def keys_for_partial_write
- changed_attribute_names_to_save & self.class.column_names
+ def attribute_names_for_partial_writes
+ partial_writes? ? changed_attribute_names_to_save : attribute_names
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 5169f312f5..db097cb930 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -22,6 +22,7 @@ require "active_record/explain_subscriber"
require "active_record/relation/delegation"
require "active_record/attributes"
require "active_record/type_caster"
+require "active_record/database_configurations"
module ActiveRecord #:nodoc:
# = Active Record
@@ -291,7 +292,6 @@ module ActiveRecord #:nodoc:
extend Aggregations::ClassMethods
include Core
- include DatabaseConfigurations
include Persistence
include ReadonlyAttributes
include ModelSchema
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 4702de1964..84ea7a0c33 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -522,6 +522,9 @@ module ActiveRecord
# Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
# * <tt>:scale</tt> -
# Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
+ # * <tt>:collation</tt> -
+ # Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column. If not specified, the
+ # column will have the same collation as the table.
# * <tt>:comment</tt> -
# Specifies the comment for the column. This option is ignored by some backends.
#
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 204691006c..2e7a78215a 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -114,8 +114,7 @@ module ActiveRecord
class Resolver # :nodoc:
attr_reader :configurations
- # Accepts a hash two layers deep, keys on the first layer represent
- # environments such as "production". Keys must be strings.
+ # Accepts a list of db config objects.
def initialize(configurations)
@configurations = configurations
end
@@ -136,33 +135,14 @@ module ActiveRecord
# Resolver.new(configurations).resolve(:production)
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
#
- def resolve(config)
- if config
- resolve_connection config
- elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
- resolve_symbol_connection env.to_sym
+ def resolve(config_or_env, pool_name = nil)
+ if config_or_env
+ resolve_connection config_or_env, pool_name
else
raise AdapterNotSpecified
end
end
- # Expands each key in @configurations hash into fully resolved hash
- def resolve_all
- config = configurations.dup
-
- if env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
- env_config = config[env] if config[env].is_a?(Hash) && !(config[env].key?("adapter") || config[env].key?("url"))
- end
-
- config.merge! env_config if env_config
-
- config.each do |key, value|
- config[key] = resolve(value) if value
- end
-
- config
- end
-
# Returns an instance of ConnectionSpecification for a given adapter.
# Accepts a hash one layer deep that contains all connection information.
#
@@ -176,7 +156,9 @@ module ActiveRecord
# # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
#
def spec(config)
- spec = resolve(config).symbolize_keys
+ pool_name = config if config.is_a?(Symbol)
+
+ spec = resolve(config, pool_name).symbolize_keys
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
@@ -211,7 +193,6 @@ module ActiveRecord
end
private
-
# Returns fully resolved connection, accepts hash, string or symbol.
# Always returns a hash.
#
@@ -232,29 +213,42 @@ module ActiveRecord
# Resolver.new({}).resolve_connection("postgresql://localhost/foo")
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
#
- def resolve_connection(spec)
- case spec
+ def resolve_connection(config_or_env, pool_name = nil)
+ case config_or_env
when Symbol
- resolve_symbol_connection spec
+ resolve_symbol_connection config_or_env, pool_name
when String
- resolve_url_connection spec
+ resolve_url_connection config_or_env
when Hash
- resolve_hash_connection spec
+ resolve_hash_connection config_or_env
+ else
+ resolve_connection config_or_env
end
end
- # Takes the environment such as +:production+ or +:development+.
+ # Takes the environment such as +:production+ or +:development+ and a
+ # pool name the corresponds to the name given by the connection pool
+ # to the connection. That pool name is merged into the hash with the
+ # name key.
+ #
# This requires that the @configurations was initialized with a key that
# matches.
#
- # Resolver.new("production" => {}).resolve_symbol_connection(:production)
- # # => {}
+ # configurations = #<ActiveRecord::DatabaseConfigurations:0x00007fd9fdace3e0
+ # @configurations=[
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd9fdace250
+ # @env_name="production", @spec_name="primary", @config={"database"=>"my_db"}>
+ # ]>
#
- def resolve_symbol_connection(spec)
- if config = configurations[spec.to_s]
- resolve_connection(config).merge("name" => spec.to_s)
+ # Resolver.new(configurations).resolve_symbol_connection(:production, "primary")
+ # # => { "database" => "my_db" }
+ def resolve_symbol_connection(env_name, pool_name)
+ db_config = configurations.find_db_config(env_name)
+
+ if db_config
+ resolve_connection(db_config.config).merge("name" => pool_name.to_s)
else
- raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}")
+ raise(AdapterNotSpecified, "'#{env_name}' database is not configured. Available: #{configurations.configurations.map(&:env_name).join(", ")}")
end
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index ee0e651912..18114f9e1c 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -46,45 +46,18 @@ module ActiveRecord
#
# The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
# may be returned on an error.
- def establish_connection(config = nil)
+ def establish_connection(config_or_env = nil)
raise "Anonymous class is not allowed." unless name
- config ||= DEFAULT_ENV.call.to_sym
- spec_name = self == Base ? "primary" : name
- self.connection_specification_name = spec_name
+ config_or_env ||= DEFAULT_ENV.call.to_sym
+ pool_name = self == Base ? "primary" : name
+ self.connection_specification_name = pool_name
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
- spec = resolver.resolve(config).symbolize_keys
- spec[:name] = spec_name
+ config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
+ config_hash[:name] = pool_name
- # use the primary config if a config is not passed in and
- # it's a three tier config
- spec = spec[spec_name.to_sym] if spec[spec_name.to_sym]
-
- connection_handler.establish_connection(spec)
- end
-
- class MergeAndResolveDefaultUrlConfig # :nodoc:
- def initialize(raw_configurations)
- @raw_config = raw_configurations.dup
- @env = DEFAULT_ENV.call.to_s
- end
-
- # Returns fully resolved connection hashes.
- # Merges connection information from `ENV['DATABASE_URL']` if available.
- def resolve
- ConnectionAdapters::ConnectionSpecification::Resolver.new(config).resolve_all
- end
-
- private
- def config
- @raw_config.dup.tap do |cfg|
- if url = ENV["DATABASE_URL"]
- cfg[@env] ||= {}
- cfg[@env]["url"] ||= url
- end
- end
- end
+ connection_handler.establish_connection(config_hash)
end
# Returns the connection currently associated with the class. This can
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index c983bc0d93..9ec6ba14fd 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -26,7 +26,7 @@ module ActiveRecord
##
# Contains the database configuration - as is typically stored in config/database.yml -
- # as a Hash.
+ # as an ActiveRecord::DatabaseConfigurations object.
#
# For example, the following database.yml...
#
@@ -40,22 +40,18 @@ module ActiveRecord
#
# ...would result in ActiveRecord::Base.configurations to look like this:
#
- # {
- # 'development' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/development.sqlite3'
- # },
- # 'production' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/production.sqlite3'
- # }
- # }
+ # #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
+ # @spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>,
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
+ # @spec_name="primary", @config={"adapter"=>"mysql2", "database"=>"db/production.sqlite3"}>
+ # ]>
def self.configurations=(config)
- @@configurations = ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
+ @@configurations = ActiveRecord::DatabaseConfigurations.new(config)
end
self.configurations = {}
- # Returns fully resolved configurations hash
+ # Returns fully resolved ActiveRecord::DatabaseConfigurations object
def self.configurations
@@configurations
end
diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb
index ffeed45030..14b7cb040f 100644
--- a/activerecord/lib/active_record/database_configurations.rb
+++ b/activerecord/lib/active_record/database_configurations.rb
@@ -1,63 +1,168 @@
# frozen_string_literal: true
+require "active_record/database_configurations/database_config"
+require "active_record/database_configurations/hash_config"
+require "active_record/database_configurations/url_config"
+
module ActiveRecord
- module DatabaseConfigurations # :nodoc:
- class DatabaseConfig
- attr_reader :env_name, :spec_name, :config
+ # ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig
+ # objects (either a HashConfig or UrlConfig) that are constructed from the
+ # application's database configuration hash or url string.
+ class DatabaseConfigurations
+ attr_reader :configurations
+
+ def initialize(configurations = {})
+ @configurations = build_configs(configurations)
+ end
+
+ # Collects the configs for the environment and optionally the specification
+ # name passed in.
+ #
+ # If a spec name is provided a single DatabaseConfiguration object will be
+ # returned, otherwise an array of DatabaseConfiguration objects will be
+ # returned that corresponds with the environment requested.
+ def configs_for(env = nil, spec = nil, &blk)
+ configs = env_with_configs(env)
- def initialize(env_name, spec_name, config)
- @env_name = env_name
- @spec_name = spec_name
- @config = config
+ if spec
+ configs.find do |db_config|
+ db_config.spec_name == spec
+ end
+ else
+ configs
end
end
- # Selects the config for the specified environment and specification name
+ # Returns the config hash that corresponds with the environment
+ #
+ # If the application has multiple databases `default_hash` will
+ # the first config hash for the environment.
#
- # For example if passed :development, and :animals it will select the database
- # under the :development and :animals configuration level
- def self.config_for_env_and_spec(environment, specification_name, configs = ActiveRecord::Base.configurations) # :nodoc:
- configs_for(environment, configs).find do |db_config|
- db_config.spec_name == specification_name
+ # { database: "my_db", adapter: "mysql2" }
+ def default_hash(env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s)
+ default = find_db_config(env)
+ default.config if default
+ end
+ alias :[] :default_hash
+
+ # Returns a single DatabaseConfig object based on the requested environment.
+ #
+ # If the application has multiple databases `select_db_config` will return
+ # the first DatabaseConfig for the environment.
+ def find_db_config(env)
+ configurations.find do |db_config|
+ db_config.env_name == env.to_s ||
+ (db_config.for_current_env? && db_config.spec_name == env.to_s)
end
end
- # Collects the configs for the environment passed in.
+ # Returns the DatabaseConfig object as a Hash.
+ def to_h
+ configs = configurations.reverse.inject({}) do |memo, db_config|
+ memo.merge(db_config.to_legacy_hash)
+ end
+
+ Hash[configs.to_a.reverse]
+ end
+
+ # Checks if the application's configurations are empty.
#
- # If a block is given returns the specification name and configuration
- # otherwise returns an array of DatabaseConfig structs for the environment.
- def self.configs_for(env, configs = ActiveRecord::Base.configurations, &blk) # :nodoc:
- env_with_configs = db_configs(configs).select do |db_config|
- db_config.env_name == env
+ # Aliased to blank?
+ def empty?
+ configurations.empty?
+ end
+ alias :blank? :empty?
+
+ private
+ def env_with_configs(env = nil)
+ if env
+ configurations.select { |db_config| db_config.env_name == env }
+ else
+ configurations
+ end
+ end
+
+ def build_configs(configs)
+ return configs.configurations if configs.is_a?(DatabaseConfigurations)
+
+ build_db_config = configs.each_pair.flat_map do |env_name, config|
+ walk_configs(env_name, "primary", config)
+ end.compact
+
+ if url = ENV["DATABASE_URL"]
+ build_url_config(url, build_db_config)
+ else
+ build_db_config
+ end
end
- if block_given?
- env_with_configs.each do |env_with_config|
- yield env_with_config.spec_name, env_with_config.config
+ def walk_configs(env_name, spec_name, config)
+ case config
+ when String
+ build_db_config_from_string(env_name, spec_name, config)
+ when Hash
+ build_db_config_from_hash(env_name, spec_name, config)
end
- else
- env_with_configs
end
- end
- # Given an env, spec and config creates DatabaseConfig structs with
- # each attribute set.
- def self.walk_configs(env_name, spec_name, config) # :nodoc:
- if config["database"] || config["url"] || config["adapter"]
- DatabaseConfig.new(env_name, spec_name, config)
- else
- config.each_pair.map do |sub_spec_name, sub_config|
- walk_configs(env_name, sub_spec_name, sub_config)
+ def build_db_config_from_string(env_name, spec_name, config)
+ begin
+ url = config
+ uri = URI.parse(url)
+ if uri.try(:scheme)
+ ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url)
+ end
+ rescue URI::InvalidURIError
+ ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
end
end
- end
- # Walks all the configs passed in and returns an array
- # of DatabaseConfig structs for each configuration.
- def self.db_configs(configs = ActiveRecord::Base.configurations) # :nodoc:
- configs.each_pair.flat_map do |env_name, config|
- walk_configs(env_name, "primary", config)
+ def build_db_config_from_hash(env_name, spec_name, config)
+ if url = config["url"]
+ config_without_url = config.dup
+ config_without_url.delete "url"
+ ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url, config_without_url)
+ elsif config["database"] || (config.size == 1 && config.values.all? { |v| v.is_a? String })
+ ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
+ else
+ config.each_pair.map do |sub_spec_name, sub_config|
+ walk_configs(env_name, sub_spec_name, sub_config)
+ end
+ end
+ end
+
+ def build_url_config(url, configs)
+ env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
+
+ if original_config = configs.find(&:for_current_env?)
+ if original_config.url_config?
+ configs
+ else
+ configs.map do |config|
+ ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, config.spec_name, url, config.config)
+ end
+ end
+ else
+ configs + [ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, "primary", url)]
+ end
+ end
+
+ def method_missing(method, *args, &blk)
+ if Hash.method_defined?(method)
+ ActiveSupport::Deprecation.warn \
+ "Returning a hash from ActiveRecord::Base.configurations is deprecated. Therefore calling `#{method}` on the hash is also deprecated. Please switch to using the `configs_for` method instead to collect and iterate over database configurations."
+ end
+
+ case method
+ when :each, :first
+ configurations.send(method, *args, &blk)
+ when :fetch
+ configs_for(args.first)
+ when :values
+ configurations.map(&:config)
+ else
+ super
+ end
end
- end
end
end
diff --git a/activerecord/lib/active_record/database_configurations/database_config.rb b/activerecord/lib/active_record/database_configurations/database_config.rb
new file mode 100644
index 0000000000..4a58115cd5
--- /dev/null
+++ b/activerecord/lib/active_record/database_configurations/database_config.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module ActiveRecord
+ class DatabaseConfigurations
+ # ActiveRecord::Base.configurations will return either a HashConfig or
+ # UrlConfig respectively. It will never return a DatabaseConfig object,
+ # as this is the parent class for the types of database configuration objects.
+ class DatabaseConfig # :nodoc:
+ attr_reader :env_name, :spec_name
+
+ def initialize(env_name, spec_name)
+ @env_name = env_name
+ @spec_name = spec_name
+ end
+
+ def url_config?
+ false
+ end
+
+ def to_legacy_hash
+ { env_name => config }
+ end
+
+ def for_current_env?
+ env_name == ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/database_configurations/hash_config.rb b/activerecord/lib/active_record/database_configurations/hash_config.rb
new file mode 100644
index 0000000000..2ee218c730
--- /dev/null
+++ b/activerecord/lib/active_record/database_configurations/hash_config.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module ActiveRecord
+ class DatabaseConfigurations
+ # A HashConfig object is created for each database configuration entry that
+ # is created from a hash.
+ #
+ # A hash config:
+ #
+ # { "development" => { "database" => "db_name" } }
+ #
+ # Becomes:
+ #
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
+ # @env_name="development", @spec_name="primary", @config={"db_name"}>
+ #
+ # Options are:
+ #
+ # <tt>:env_name</tt> - The Rails environment, ie "development"
+ # <tt>:spec_name</tt> - The specification name. In a standard two-tier
+ # database configuration this will default to "primary". In a multiple
+ # database three-tier database configuration this corresponds to the name
+ # used in the second tier, for example "primary_readonly".
+ # <tt>:config</tt> - The config hash. This is the hash that contains the
+ # database adapter, name, and other important information for database
+ # connections.
+ class HashConfig < DatabaseConfig
+ attr_reader :config
+
+ def initialize(env_name, spec_name, config)
+ super(env_name, spec_name)
+ @config = config
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/database_configurations/url_config.rb b/activerecord/lib/active_record/database_configurations/url_config.rb
new file mode 100644
index 0000000000..c3d9798c37
--- /dev/null
+++ b/activerecord/lib/active_record/database_configurations/url_config.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module ActiveRecord
+ class DatabaseConfigurations
+ # A UrlConfig object is created for each database configuration
+ # entry that is created from a URL. This can either be a URL string
+ # or a hash with a URL in place of the config hash.
+ #
+ # A URL config:
+ #
+ # postgres://localhost/foo
+ #
+ # Becomes:
+ #
+ # #<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fdc3238f340
+ # @env_name="default_env", @spec_name="primary",
+ # @config={"adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost"},
+ # @url="postgres://localhost/foo">
+ #
+ # Options are:
+ #
+ # <tt>:env_name</tt> - The Rails environment, ie "development"
+ # <tt>:spec_name</tt> - The specification name. In a standard two-tier
+ # database configuration this will default to "primary". In a multiple
+ # database three-tier database configuration this corresponds to the name
+ # used in the second tier, for example "primary_readonly".
+ # <tt>:url</tt> - The database URL.
+ # <tt>:config</tt> - The config hash. This is the hash that contains the
+ # database adapter, name, and other important information for database
+ # connections.
+ class UrlConfig < DatabaseConfig
+ attr_reader :url, :config
+
+ def initialize(env_name, spec_name, url, config = {})
+ super(env_name, spec_name)
+ @config = build_config(config, url)
+ @url = url
+ end
+
+ def url_config? # :nodoc:
+ true
+ end
+
+ private
+ def build_config(original_config, url)
+ if /^jdbc:/.match?(url)
+ hash = { "url" => url }
+ else
+ hash = ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url).to_hash
+ end
+
+ if original_config[env_name]
+ original_config[env_name].merge(hash)
+ else
+ original_config.merge(hash)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 05963e5546..24af56cf0b 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -718,7 +718,6 @@ module ActiveRecord
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
def _update_record(attribute_names = self.attribute_names)
- attribute_names &= self.class.column_names
attribute_names = attributes_for_update(attribute_names)
if attribute_names.empty?
@@ -737,10 +736,12 @@ module ActiveRecord
# Creates a record with values matching those of the instance attributes
# and returns its id.
def _create_record(attribute_names = self.attribute_names)
- attribute_names &= self.class.column_names
- attributes_values = attributes_with_values_for_create(attribute_names)
+ attribute_names = attributes_for_create(attribute_names)
+
+ new_id = self.class._insert_record(
+ attributes_with_values(attribute_names)
+ )
- new_id = self.class._insert_record(attributes_values)
self.id ||= new_id if self.class.primary_key
@new_record = false
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 8b7d18fb3d..15b0459422 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -26,7 +26,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
desc "Create #{spec_name} database for current environment"
task spec_name => :load_config do
- db_config = ActiveRecord::DatabaseConfigurations.config_for_env_and_spec(Rails.env, spec_name)
+ db_config = ActiveRecord::Base.configurations.configs_for(Rails.env, spec_name)
ActiveRecord::Tasks::DatabaseTasks.create(db_config.config)
end
end
@@ -45,7 +45,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
desc "Drop #{spec_name} database for current environment"
task spec_name => [:load_config, :check_protected_environments] do
- db_config = ActiveRecord::DatabaseConfigurations.config_for_env_and_spec(Rails.env, spec_name)
+ db_config = ActiveRecord::Base.configurations.configs_for(Rails.env, spec_name)
ActiveRecord::Tasks::DatabaseTasks.drop(db_config.config)
end
end
@@ -73,8 +73,8 @@ db_namespace = namespace :db do
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
task migrate: :load_config do
- ActiveRecord::DatabaseConfigurations.configs_for(Rails.env) do |spec_name, config|
- ActiveRecord::Base.establish_connection(config)
+ ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
+ ActiveRecord::Base.establish_connection(db_config.config)
ActiveRecord::Tasks::DatabaseTasks.migrate
end
db_namespace["_dump"].invoke
@@ -99,7 +99,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
desc "Migrate #{spec_name} database for current environment"
task spec_name => :load_config do
- db_config = ActiveRecord::DatabaseConfigurations.config_for_env_and_spec(Rails.env, spec_name)
+ db_config = ActiveRecord::Base.configurations.configs_for(Rails.env, spec_name)
ActiveRecord::Base.establish_connection(db_config.config)
ActiveRecord::Tasks::DatabaseTasks.migrate
end
@@ -274,11 +274,10 @@ db_namespace = namespace :db do
desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record"
task dump: :load_config do
require "active_record/schema_dumper"
-
- ActiveRecord::DatabaseConfigurations.configs_for(Rails.env) do |spec_name, config|
- filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(spec_name, :ruby)
+ ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
+ filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :ruby)
File.open(filename, "w:utf-8") do |file|
- ActiveRecord::Base.establish_connection(config)
+ ActiveRecord::Base.establish_connection(db_config.config)
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
end
@@ -314,11 +313,10 @@ db_namespace = namespace :db do
namespace :structure do
desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql"
task dump: :load_config do
- ActiveRecord::DatabaseConfigurations.configs_for(Rails.env) do |spec_name, config|
- ActiveRecord::Base.establish_connection(config)
- filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(spec_name, :sql)
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(config, filename)
-
+ ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
+ ActiveRecord::Base.establish_connection(db_config.config)
+ filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :sql)
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(db_config.config, filename)
if ActiveRecord::SchemaMigration.table_exists?
File.open(filename, "a") do |f|
f.puts ActiveRecord::Base.connection.dump_schema_information
@@ -356,22 +354,30 @@ db_namespace = namespace :db do
begin
should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
ActiveRecord::Schema.verbose = false
- ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :ruby, ENV["SCHEMA"], "test"
+ ActiveRecord::Base.configurations.configs_for("test").each do |db_config|
+ filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :ruby)
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config.config, :ruby, filename, "test")
+ end
ensure
if should_reconnect
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations.default_hash(ActiveRecord::Tasks::DatabaseTasks.env))
end
end
end
# desc "Recreate the test database from an existent structure.sql file"
task load_structure: %w(db:test:purge) do
- ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :sql, ENV["SCHEMA"], "test"
+ ActiveRecord::Base.configurations.configs_for("test").each do |db_config|
+ filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :sql)
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config.config, :sql, filename, "test")
+ end
end
# desc "Empty the test database"
task purge: %w(load_config check_protected_environments) do
- ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations["test"]
+ ActiveRecord::Base.configurations.configs_for("test").each do |db_config|
+ ActiveRecord::Tasks::DatabaseTasks.purge(db_config.config)
+ end
end
# desc 'Load the test schema'
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 6d2f75a3ae..b2110f727c 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -612,9 +612,21 @@ module ActiveRecord
# returns either +nil+ or the inverse association name that it finds.
def automatic_inverse_of
- if can_find_inverse_of_automatically?(self)
- inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
+ return unless can_find_inverse_of_automatically?(self)
+ inverse_name_candidates =
+ if options[:as]
+ [options[:as]]
+ else
+ active_record_name = active_record.name.demodulize
+ [active_record_name, ActiveSupport::Inflector.pluralize(active_record_name)]
+ end
+
+ inverse_name_candidates.map! do |candidate|
+ ActiveSupport::Inflector.underscore(candidate).to_sym
+ end
+
+ inverse_name_candidates.detect do |inverse_name|
begin
reflection = klass._reflect_on_association(inverse_name)
rescue NameError
@@ -623,9 +635,7 @@ module ActiveRecord
reflection = false
end
- if valid_inverse_reflection?(reflection)
- return inverse_name
- end
+ valid_inverse_reflection?(reflection)
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 2d3e1eaa08..29a3ceab7d 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -375,8 +375,12 @@ module ActiveRecord
@klass.connection.update stmt, "#{@klass} Update All"
end
- def update(attributes) # :nodoc:
- each { |record| record.update(attributes) }
+ def update(id = :all, attributes) # :nodoc:
+ if id == :all
+ each { |record| record.update(attributes) }
+ else
+ klass.update(id, attributes)
+ end
end
def update_counters(counters) # :nodoc:
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 40fe39fa9d..0fa5ba2e50 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -190,7 +190,7 @@ module ActiveRecord
relation = apply_join_dependency
relation.pluck(*column_names)
else
- enforce_raw_sql_whitelist(column_names)
+ disallow_raw_sql!(column_names)
relation = spawn
relation.select_values = column_names.map { |cn|
@klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 52405f21a1..56497e11cb 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1133,9 +1133,9 @@ module ActiveRecord
end
order_args.flatten!
- @klass.enforce_raw_sql_whitelist(
+ @klass.disallow_raw_sql!(
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
- whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
+ permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
)
validate_order_args(order_args)
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 7f1c2fd7eb..3b2556b1c8 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -140,6 +140,8 @@ module ActiveRecord
# We freeze the strings to prevent them getting duped when
# used as keys in ActiveRecord::Base's @attributes hash
columns = @columns.map(&:-@)
+ length = columns.length
+
@rows.map { |row|
# In the past we used Hash[columns.zip(row)]
# though elegant, the verbose way is much more efficient
@@ -148,8 +150,6 @@ module ActiveRecord
hash = {}
index = 0
- length = columns.length
-
while index < length
hash[columns[index]] = row[index]
index += 1
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index c6c268855e..3485d9e557 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -61,8 +61,8 @@ module ActiveRecord
# # => "id ASC"
def sanitize_sql_for_order(condition)
if condition.is_a?(Array) && condition.first.to_s.include?("?")
- enforce_raw_sql_whitelist([condition.first],
- whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
+ disallow_raw_sql!([condition.first],
+ permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
)
# Ensure we aren't dealing with a subclass of String that might
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index fd36c0abd2..3a1791f9c7 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_record/database_configurations"
+
module ActiveRecord
module Tasks # :nodoc:
class DatabaseAlreadyExists < StandardError; end # :nodoc:
@@ -101,16 +103,21 @@ module ActiveRecord
@env ||= Rails.env
end
+ def spec
+ @spec ||= "primary"
+ end
+
def seed_loader
@seed_loader ||= Rails.application
end
def current_config(options = {})
options.reverse_merge! env: env
+ options[:spec] ||= "primary"
if options.has_key?(:config)
@current_config = options[:config]
else
- @current_config ||= ActiveRecord::Base.configurations[options[:env]]
+ @current_config ||= ActiveRecord::Base.configurations.configs_for(options[:env], options[:spec]).config
end
end
@@ -122,7 +129,7 @@ module ActiveRecord
$stderr.puts "Database '#{configuration['database']}' already exists" if verbose?
rescue Exception => error
$stderr.puts error
- $stderr.puts "Couldn't create database for #{configuration.inspect}"
+ $stderr.puts "Couldn't create '#{configuration['database']}' database. Please check your configuration."
raise
end
@@ -135,8 +142,8 @@ module ActiveRecord
end
def for_each
- databases = Rails.application.config.load_database_yaml
- database_configs = ActiveRecord::DatabaseConfigurations.configs_for(Rails.env, databases)
+ databases = Rails.application.config.database_configuration
+ database_configs = ActiveRecord::DatabaseConfigurations.new(databases).configs_for(Rails.env)
# if this is a single database application we don't want tasks for each primary database
return if database_configs.count == 1
@@ -180,9 +187,11 @@ module ActiveRecord
scope = ENV["SCOPE"]
verbose_was, Migration.verbose = Migration.verbose, verbose?
+
Base.connection.migration_context.migrate(target_version) do |migration|
scope.blank? || scope == migration.scope
end
+
ActiveRecord::Base.clear_cache!
ensure
Migration.verbose = verbose_was
@@ -198,8 +207,8 @@ module ActiveRecord
ENV["VERSION"].to_i if ENV["VERSION"] && !ENV["VERSION"].empty?
end
- def charset_current(environment = env)
- charset ActiveRecord::Base.configurations[environment]
+ def charset_current(environment = env, specification_name = spec)
+ charset ActiveRecord::Base.configurations.configs_for(environment, specification_name).config
end
def charset(*arguments)
@@ -207,8 +216,8 @@ module ActiveRecord
class_for_adapter(configuration["adapter"]).new(*arguments).charset
end
- def collation_current(environment = env)
- collation ActiveRecord::Base.configurations[environment]
+ def collation_current(environment = env, specification_name = spec)
+ collation ActiveRecord::Base.configurations.configs_for(environment, specification_name).config
end
def collation(*arguments)
@@ -342,14 +351,15 @@ module ActiveRecord
environments << "test" if environment == "development"
environments.each do |env|
- ActiveRecord::DatabaseConfigurations.configs_for(env) do |spec_name, configuration|
- yield configuration, spec_name, env
+ ActiveRecord::Base.configurations.configs_for(env).each do |db_config|
+ yield db_config.config, db_config.spec_name, env
end
end
end
def each_local_configuration
- ActiveRecord::Base.configurations.each_value do |configuration|
+ ActiveRecord::Base.configurations.configs_for.each do |db_config|
+ configuration = db_config.config
next unless configuration["database"]
if local_database?(configuration)
diff --git a/activerecord/lib/active_record/test_databases.rb b/activerecord/lib/active_record/test_databases.rb
index 5b4efe22c9..16113eb04e 100644
--- a/activerecord/lib/active_record/test_databases.rb
+++ b/activerecord/lib/active_record/test_databases.rb
@@ -5,31 +5,32 @@ require "active_support/testing/parallelization"
module ActiveRecord
module TestDatabases # :nodoc:
ActiveSupport::Testing::Parallelization.after_fork_hook do |i|
- create_and_load_schema(i, spec_name: Rails.env)
+ create_and_load_schema(i, env_name: Rails.env)
end
- ActiveSupport::Testing::Parallelization.run_cleanup_hook do |_|
- drop(spec_name: Rails.env)
+ ActiveSupport::Testing::Parallelization.run_cleanup_hook do
+ drop(env_name: Rails.env)
end
- def self.create_and_load_schema(i, spec_name:)
+ def self.create_and_load_schema(i, env_name:)
old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
- connection_spec = ActiveRecord::Base.configurations[spec_name]
-
- connection_spec["database"] += "-#{i}"
- ActiveRecord::Tasks::DatabaseTasks.create(connection_spec)
- ActiveRecord::Tasks::DatabaseTasks.load_schema(connection_spec)
+ ActiveRecord::Base.configurations.configs_for(env_name).each do |db_config|
+ db_config.config["database"] += "-#{i}"
+ ActiveRecord::Tasks::DatabaseTasks.create(db_config.config)
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config.config, ActiveRecord::Base.schema_format, nil, env_name, db_config.spec_name)
+ end
ensure
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env])
+ ActiveRecord::Base.establish_connection(Rails.env.to_sym)
ENV["VERBOSE"] = old
end
- def self.drop(spec_name:)
+ def self.drop(env_name:)
old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
- connection_spec = ActiveRecord::Base.configurations[spec_name]
- ActiveRecord::Tasks::DatabaseTasks.drop(connection_spec)
+ ActiveRecord::Base.configurations.configs_for(env_name).each do |db_config|
+ ActiveRecord::Tasks::DatabaseTasks.drop(db_config.config)
+ end
ensure
ENV["VERBOSE"] = old
end
diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb
index 4ceb502c5d..4a17082d66 100644
--- a/activerecord/lib/rails/generators/active_record/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration.rb
@@ -24,7 +24,9 @@ module ActiveRecord
end
def db_migrate_path
- if defined?(Rails.application) && Rails.application
+ if migrations_paths = options[:migrations_paths]
+ migrations_paths
+ elsif defined?(Rails.application) && Rails.application
Rails.application.config.paths["db/migrate"].to_ary.first
else
"db/migrate"
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index a07b00ef79..281b7afb50 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -8,6 +8,7 @@ module ActiveRecord
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
class_option :primary_key_type, type: :string, desc: "The type for primary key"
+ class_option :migrations_paths, type: :string, desc: "The migration path for your generated migrations. If this is not set it will default to db/migrate"
def create_migration_file
set_local_assigns!
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index da3a42e2b5..eb4dc73423 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -20,6 +20,8 @@ require "models/company"
require "models/project"
require "models/author"
require "models/post"
+require "models/department"
+require "models/hotel"
class AutomaticInverseFindingTests < ActiveRecord::TestCase
fixtures :ratings, :comments, :cars
@@ -724,6 +726,16 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
# fails because Interest does have the correct inverse_of
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Interest.first }
end
+
+ def test_favors_has_one_associations_for_inverse_of
+ inverse_name = Post.reflect_on_association(:author).inverse_of.name
+ assert_equal :post, inverse_name
+ end
+
+ def test_finds_inverse_of_for_plural_associations
+ inverse_name = Department.reflect_on_association(:hotel).inverse_of.name
+ assert_equal :departments, inverse_name
+ end
end
# NOTE - these tests might not be meaningful, ripped as they were from the parental_control plugin
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index b8e623f17b..8c204a2692 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -28,13 +28,16 @@ module ActiveRecord
end
def test_establish_connection_uses_spec_name
+ old_config = ActiveRecord::Base.configurations
config = { "readonly" => { "adapter" => "sqlite3" } }
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config)
+ ActiveRecord::Base.configurations = config
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(ActiveRecord::Base.configurations)
spec = resolver.spec(:readonly)
@handler.establish_connection(spec.to_hash)
assert_not_nil @handler.retrieve_connection_pool("readonly")
ensure
+ ActiveRecord::Base.configurations = old_config
@handler.remove_connection("readonly")
end
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
index 1b64324cc4..06c1c51724 100644
--- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -18,11 +18,14 @@ module ActiveRecord
end
def resolve_config(config)
- ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ configs.to_h
end
def resolve_spec(spec, config)
- ConnectionSpecification::Resolver.new(resolve_config(config)).resolve(spec)
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
+ resolver.resolve(spec, spec)
end
def test_resolver_with_database_uri_and_current_env_symbol_key
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index 5b80f16a44..72be14f507 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -7,11 +7,15 @@ module ActiveRecord
class ConnectionSpecification
class ResolverTest < ActiveRecord::TestCase
def resolve(spec, config = {})
- Resolver.new(config).resolve(spec)
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
+ resolver.resolve(spec, spec)
end
def spec(spec, config = {})
- Resolver.new(config).spec(spec)
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
+ resolver.spec(spec)
end
def test_url_invalid_adapter
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 83cc2aa319..1f0e770a93 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -879,6 +879,26 @@ class DirtyTest < ActiveRecord::TestCase
raise "changed? should be false" if changed?
raise "has_changes_to_save? should be false" if has_changes_to_save?
raise "saved_changes? should be true" unless saved_changes?
+ raise "id_in_database should not be nil" if id_in_database.nil?
+ end
+ end
+
+ person = klass.create!(first_name: "Sean")
+ assert_not_predicate person, :changed?
+ end
+
+ test "changed? in around callbacks after yield returns false" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "people"
+
+ around_create :check_around
+
+ def check_around
+ yield
+ raise "changed? should be false" if changed?
+ raise "has_changes_to_save? should be false" if has_changes_to_save?
+ raise "saved_changes? should be true" unless saved_changes?
+ raise "id_in_database should not be nil" if id_in_database.nil?
end
end
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index 82cc891970..79a0630193 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -40,7 +40,7 @@ if ActiveRecord::Base.connection.supports_explain?
assert_equal binds, queries[0][1]
end
- def test_collects_nothing_if_the_statement_is_not_whitelisted
+ def test_collects_nothing_if_the_statement_is_not_explainable
SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "SHOW max_identifier_length")
assert_empty queries
end
diff --git a/activerecord/test/cases/legacy_configurations_test.rb b/activerecord/test/cases/legacy_configurations_test.rb
new file mode 100644
index 0000000000..c36feb5116
--- /dev/null
+++ b/activerecord/test/cases/legacy_configurations_test.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+
+module ActiveRecord
+ class LegacyConfigurationsTest < ActiveRecord::TestCase
+ def test_can_turn_configurations_into_a_hash
+ assert ActiveRecord::Base.configurations.to_h.is_a?(Hash), "expected to be a hash but was not."
+ assert_equal ["arunit", "arunit2", "arunit_without_prepared_statements"].sort, ActiveRecord::Base.configurations.to_h.keys.sort
+ end
+
+ def test_each_is_deprecated
+ assert_deprecated do
+ ActiveRecord::Base.configurations.each do |db_config|
+ assert_equal "primary", db_config.spec_name
+ end
+ end
+ end
+
+ def test_first_is_deprecated
+ assert_deprecated do
+ db_config = ActiveRecord::Base.configurations.first
+ assert_equal "arunit", db_config.env_name
+ assert_equal "primary", db_config.spec_name
+ end
+ end
+
+ def test_fetch_is_deprecated
+ assert_deprecated do
+ db_config = ActiveRecord::Base.configurations.fetch("arunit").first
+ assert_equal "arunit", db_config.env_name
+ assert_equal "primary", db_config.spec_name
+ end
+ end
+
+ def test_values_are_deprecated
+ config_hashes = ActiveRecord::Base.configurations.configurations.map(&:config)
+ assert_deprecated do
+ assert_equal config_hashes, ActiveRecord::Base.configurations.values
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index 3f3d41980c..a8030c2d64 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -5,7 +5,7 @@ require "models/post"
require "models/comment"
module ActiveRecord
- module DelegationWhitelistTests
+ module ArrayDelegationTests
ARRAY_DELEGATES = [
:+, :-, :|, :&, :[], :shuffle,
:all?, :collect, :compact, :detect, :each, :each_cons, :each_with_index,
@@ -38,7 +38,7 @@ module ActiveRecord
end
class DelegationAssociationTest < ActiveRecord::TestCase
- include DelegationWhitelistTests
+ include ArrayDelegationTests
include DeprecatedArelDelegationTests
def target
@@ -47,7 +47,7 @@ module ActiveRecord
end
class DelegationRelationTest < ActiveRecord::TestCase
- include DelegationWhitelistTests
+ include ArrayDelegationTests
include DeprecatedArelDelegationTests
def target
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 5412ab5def..d03b412efb 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1587,6 +1587,24 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "David", topic2.reload.author_name
end
+ def test_update_with_ids_on_relation
+ topic1 = TopicWithCallbacks.create!(title: "arel", author_name: nil)
+ topic2 = TopicWithCallbacks.create!(title: "activerecord", author_name: nil)
+ topics = TopicWithCallbacks.none
+ topics.update(
+ [topic1.id, topic2.id],
+ [{ title: "adequaterecord" }, { title: "adequaterecord" }]
+ )
+
+ assert_equal TopicWithCallbacks.count, TopicWithCallbacks.topic_count
+
+ assert_equal "adequaterecord", topic1.reload.title
+ assert_equal "adequaterecord", topic2.reload.title
+ # Testing that the before_update callbacks have run
+ assert_equal "David", topic1.reload.author_name
+ assert_equal "David", topic2.reload.author_name
+ end
+
def test_update_on_relation_passing_active_record_object_is_not_permitted
topic = Topic.create!(title: "Foo", author_name: nil)
assert_raises(ArgumentError) do
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index ee53d576a1..a39a7371fe 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -156,20 +156,24 @@ module ActiveRecord
class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
def setup
+ @old_configurations = ActiveRecord::Base.configurations
@configurations = { "development" => { "database" => "my-db" } }
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
+
+ ActiveRecord::Base.configurations = @configurations
end
def teardown
$stdout, $stderr = @original_stdout, @original_stderr
+ ActiveRecord::Base.configurations = @old_configurations
end
def test_ignores_configurations_without_databases
@configurations["development"]["database"] = nil
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all
end
@@ -179,7 +183,7 @@ module ActiveRecord
def test_ignores_remote_databases
@configurations["development"]["host"] = "my.server.tld"
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all
end
@@ -189,7 +193,7 @@ module ActiveRecord
def test_warning_for_remote_databases
@configurations["development"]["host"] = "my.server.tld"
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
ActiveRecord::Tasks::DatabaseTasks.create_all
assert_match "This task only modifies local databases. my-db is on a remote host.",
@@ -200,7 +204,7 @@ module ActiveRecord
def test_creates_configurations_with_local_ip
@configurations["development"]["host"] = "127.0.0.1"
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all
end
@@ -210,7 +214,7 @@ module ActiveRecord
def test_creates_configurations_with_local_host
@configurations["development"]["host"] = "localhost"
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all
end
@@ -220,40 +224,33 @@ module ActiveRecord
def test_creates_configurations_with_blank_hosts
@configurations["development"]["host"] = nil
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all
end
end
end
-
- private
-
- def with_stubbed_configurations_establish_connection
- ActiveRecord::Base.stub(:configurations, @configurations) do
- # To refrain from connecting to a newly created empty DB in
- # sqlite3_mem tests
- ActiveRecord::Base.connection_handler.stub(
- :establish_connection,
- nil
- ) do
- yield
- end
- end
- end
end
class DatabaseTasksCreateCurrentTest < ActiveRecord::TestCase
def setup
+ @old_configurations = ActiveRecord::Base.configurations
+
@configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
- "production" => { "url" => "prod-db-url" }
+ "production" => { "url" => "abstract://prod-db-url" }
}
+
+ ActiveRecord::Base.configurations = @configurations
+ end
+
+ def teardown
+ ActiveRecord::Base.configurations = @old_configurations
end
def test_creates_current_environment_database
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
@@ -267,7 +264,7 @@ module ActiveRecord
end
def test_creates_current_environment_database_with_url
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
@@ -281,7 +278,7 @@ module ActiveRecord
end
def test_creates_test_and_development_databases_when_env_was_not_specified
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
@@ -301,7 +298,7 @@ module ActiveRecord
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
@@ -328,29 +325,27 @@ module ActiveRecord
end
end
end
-
- private
-
- def with_stubbed_configurations_establish_connection
- ActiveRecord::Base.stub(:configurations, @configurations) do
- ActiveRecord::Base.stub(:establish_connection, nil) do
- yield
- end
- end
- end
end
class DatabaseTasksCreateCurrentThreeTierTest < ActiveRecord::TestCase
def setup
+ @old_configurations = ActiveRecord::Base.configurations
+
@configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
- "production" => { "primary" => { "url" => "prod-db-url" }, "secondary" => { "url" => "secondary-prod-db-url" } }
+ "production" => { "primary" => { "url" => "abstract://prod-db-url" }, "secondary" => { "url" => "abstract://secondary-prod-db-url" } }
}
+
+ ActiveRecord::Base.configurations = @configurations
+ end
+
+ def teardown
+ ActiveRecord::Base.configurations = @old_configurations
end
def test_creates_current_environment_database
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
@@ -367,7 +362,7 @@ module ActiveRecord
end
def test_creates_current_environment_database_with_url
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
@@ -384,7 +379,7 @@ module ActiveRecord
end
def test_creates_test_and_development_databases_when_env_was_not_specified
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
@@ -406,7 +401,7 @@ module ActiveRecord
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- with_stubbed_configurations_establish_connection do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:create,
@@ -439,16 +434,6 @@ module ActiveRecord
end
end
end
-
- private
-
- def with_stubbed_configurations_establish_connection
- ActiveRecord::Base.stub(:configurations, @configurations) do
- ActiveRecord::Base.stub(:establish_connection, nil) do
- yield
- end
- end
- end
end
class DatabaseTasksDropTest < ActiveRecord::TestCase
@@ -467,20 +452,24 @@ module ActiveRecord
class DatabaseTasksDropAllTest < ActiveRecord::TestCase
def setup
+ @old_configurations = ActiveRecord::Base.configurations
@configurations = { development: { "database" => "my-db" } }
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
+
+ ActiveRecord::Base.configurations = @configurations
end
def teardown
$stdout, $stderr = @original_stdout, @original_stderr
+ ActiveRecord::Base.configurations = @old_configurations
end
def test_ignores_configurations_without_databases
@configurations[:development]["database"] = nil
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -490,7 +479,7 @@ module ActiveRecord
def test_ignores_remote_databases
@configurations[:development]["host"] = "my.server.tld"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -500,7 +489,7 @@ module ActiveRecord
def test_warning_for_remote_databases
@configurations[:development]["host"] = "my.server.tld"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
ActiveRecord::Tasks::DatabaseTasks.drop_all
assert_match "This task only modifies local databases. my-db is on a remote host.",
@@ -511,7 +500,7 @@ module ActiveRecord
def test_drops_configurations_with_local_ip
@configurations[:development]["host"] = "127.0.0.1"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -521,7 +510,7 @@ module ActiveRecord
def test_drops_configurations_with_local_host
@configurations[:development]["host"] = "localhost"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -531,7 +520,7 @@ module ActiveRecord
def test_drops_configurations_with_blank_hosts
@configurations[:development]["host"] = nil
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
@@ -541,15 +530,22 @@ module ActiveRecord
class DatabaseTasksDropCurrentTest < ActiveRecord::TestCase
def setup
+ @old_configurations = ActiveRecord::Base.configurations
@configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
- "production" => { "url" => "prod-db-url" }
+ "production" => { "url" => "abstract://prod-db-url" }
}
+
+ ActiveRecord::Base.configurations = @configurations
+ end
+
+ def teardown
+ ActiveRecord::Base.configurations = @old_configurations
end
def test_drops_current_environment_database
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
["database" => "test-db"]) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
@@ -560,7 +556,7 @@ module ActiveRecord
end
def test_drops_current_environment_database_with_url
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
["url" => "prod-db-url"]) do
ActiveRecord::Tasks::DatabaseTasks.drop_current(
@@ -571,7 +567,7 @@ module ActiveRecord
end
def test_drops_test_and_development_databases_when_env_was_not_specified
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -591,7 +587,7 @@ module ActiveRecord
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -612,15 +608,22 @@ module ActiveRecord
class DatabaseTasksDropCurrentThreeTierTest < ActiveRecord::TestCase
def setup
+ @old_configurations = ActiveRecord::Base.configurations
@configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
- "production" => { "primary" => { "url" => "prod-db-url" }, "secondary" => { "url" => "secondary-prod-db-url" } }
+ "production" => { "primary" => { "url" => "abstract://prod-db-url" }, "secondary" => { "url" => "abstract://secondary-prod-db-url" } }
}
+
+ ActiveRecord::Base.configurations = @configurations
+ end
+
+ def teardown
+ ActiveRecord::Base.configurations = @old_configurations
end
def test_drops_current_environment_database
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -637,7 +640,7 @@ module ActiveRecord
end
def test_drops_current_environment_database_with_url
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -654,7 +657,7 @@ module ActiveRecord
end
def test_drops_test_and_development_databases_when_env_was_not_specified
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -676,7 +679,7 @@ module ActiveRecord
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:drop,
@@ -848,12 +851,16 @@ module ActiveRecord
class DatabaseTasksPurgeCurrentTest < ActiveRecord::TestCase
def test_purges_current_environment_database
+ old_configurations = ActiveRecord::Base.configurations
configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
"production" => { "database" => "prod-db" }
}
- ActiveRecord::Base.stub(:configurations, configurations) do
+
+ ActiveRecord::Base.configurations = configurations
+
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:purge,
@@ -864,13 +871,17 @@ module ActiveRecord
end
end
end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
class DatabaseTasksPurgeAllTest < ActiveRecord::TestCase
def test_purge_all_local_configurations
+ old_configurations = ActiveRecord::Base.configurations
configurations = { development: { "database" => "my-db" } }
- ActiveRecord::Base.stub(:configurations, configurations) do
+ ActiveRecord::Base.configurations = configurations
+ ActiveRecord::Base.configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:purge,
@@ -879,6 +890,8 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.purge_all
end
end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
diff --git a/activerecord/test/cases/tasks/legacy_database_tasks_test.rb b/activerecord/test/cases/tasks/legacy_database_tasks_test.rb
new file mode 100644
index 0000000000..ffa55be878
--- /dev/null
+++ b/activerecord/test/cases/tasks/legacy_database_tasks_test.rb
@@ -0,0 +1,591 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "active_record/tasks/database_tasks"
+
+module ActiveRecord
+ class LegacyDatabaseTasksCreateAllTest < ActiveRecord::TestCase
+ def setup
+ @old_configurations = ActiveRecord::Base.configurations.to_h
+
+ @configurations = { "development" => { "database" => "my-db" } }
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+
+ ActiveRecord::Base.configurations = @configurations
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
+ ActiveRecord::Base.configurations = @old_configurations
+ end
+
+ def test_ignores_configurations_without_databases
+ @configurations["development"]["database"] = nil
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
+ end
+
+ def test_ignores_remote_databases
+ @configurations["development"]["host"] = "my.server.tld"
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
+ end
+
+ def test_warning_for_remote_databases
+ @configurations["development"]["host"] = "my.server.tld"
+
+ ActiveRecord::Base.configurations.to_h do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+
+ assert_match "This task only modifies local databases. my-db is on a remote host.",
+ $stderr.string
+ end
+ end
+
+ def test_creates_configurations_with_local_ip
+ @configurations["development"]["host"] = "127.0.0.1"
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
+ end
+
+ def test_creates_configurations_with_local_host
+ @configurations["development"]["host"] = "localhost"
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
+ end
+
+ def test_creates_configurations_with_blank_hosts
+ @configurations["development"]["host"] = nil
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
+ end
+ end
+
+ class LegacyDatabaseTasksCreateCurrentTest < ActiveRecord::TestCase
+ def setup
+ @old_configurations = ActiveRecord::Base.configurations.to_h
+
+ @configurations = {
+ "development" => { "database" => "dev-db" },
+ "test" => { "database" => "test-db" },
+ "production" => { "url" => "abstract://prod-db-url" }
+ }
+
+ ActiveRecord::Base.configurations = @configurations
+ end
+
+ def teardown
+ ActiveRecord::Base.configurations = @old_configurations
+ end
+
+ def test_creates_current_environment_database
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ ["database" => "test-db"],
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
+ end
+ end
+
+ def test_creates_current_environment_database_with_url
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ ["url" => "prod-db-url"],
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
+ end
+ end
+
+ def test_creates_test_and_development_databases_when_env_was_not_specified
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "dev-db"],
+ ["database" => "test-db"]
+ ],
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ end
+
+ def test_creates_test_and_development_databases_when_rails_env_is_development
+ old_env = ENV["RAILS_ENV"]
+ ENV["RAILS_ENV"] = "development"
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "dev-db"],
+ ["database" => "test-db"]
+ ],
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ ensure
+ ENV["RAILS_ENV"] = old_env
+ end
+
+ def test_establishes_connection_for_the_given_environments
+ ActiveRecord::Tasks::DatabaseTasks.stub(:create, nil) do
+ assert_called_with(ActiveRecord::Base, :establish_connection, [:development]) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ end
+ end
+
+ class LegacyDatabaseTasksCreateCurrentThreeTierTest < ActiveRecord::TestCase
+ def setup
+ @old_configurations = ActiveRecord::Base.configurations.to_h
+
+ @configurations = {
+ "development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
+ "test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
+ "production" => { "primary" => { "url" => "abstract://prod-db-url" }, "secondary" => { "url" => "abstract://secondary-prod-db-url" } }
+ }
+
+ ActiveRecord::Base.configurations = @configurations
+ end
+
+ def teardown
+ ActiveRecord::Base.configurations = @old_configurations
+ end
+
+ def test_creates_current_environment_database
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
+ end
+ end
+
+ def test_creates_current_environment_database_with_url
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["url" => "prod-db-url"],
+ ["url" => "secondary-prod-db-url"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
+ end
+ end
+
+ def test_creates_test_and_development_databases_when_env_was_not_specified
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"],
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ end
+
+ def test_creates_test_and_development_databases_when_rails_env_is_development
+ old_env = ENV["RAILS_ENV"]
+ ENV["RAILS_ENV"] = "development"
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"],
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ ensure
+ ENV["RAILS_ENV"] = old_env
+ end
+
+ def test_establishes_connection_for_the_given_environments_config
+ ActiveRecord::Tasks::DatabaseTasks.stub(:create, nil) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [:development]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ end
+ end
+
+ class LegacyDatabaseTasksDropAllTest < ActiveRecord::TestCase
+ def setup
+ @old_configurations = ActiveRecord::Base.configurations.to_h
+
+ @configurations = { development: { "database" => "my-db" } }
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+
+ ActiveRecord::Base.configurations = @configurations
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
+ ActiveRecord::Base.configurations = @old_configurations
+ end
+
+ def test_ignores_configurations_without_databases
+ @configurations[:development]["database"] = nil
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
+ end
+
+ def test_ignores_remote_databases
+ @configurations[:development]["host"] = "my.server.tld"
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
+ end
+
+ def test_warning_for_remote_databases
+ @configurations[:development]["host"] = "my.server.tld"
+
+ ActiveRecord::Base.configurations.to_h do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+
+ assert_match "This task only modifies local databases. my-db is on a remote host.",
+ $stderr.string
+ end
+ end
+
+ def test_drops_configurations_with_local_ip
+ @configurations[:development]["host"] = "127.0.0.1"
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
+ end
+
+ def test_drops_configurations_with_local_host
+ @configurations[:development]["host"] = "localhost"
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
+ end
+
+ def test_drops_configurations_with_blank_hosts
+ @configurations[:development]["host"] = nil
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
+ end
+ end
+
+ class LegacyDatabaseTasksDropCurrentTest < ActiveRecord::TestCase
+ def setup
+ @old_configurations = ActiveRecord::Base.configurations.to_h
+
+ @configurations = {
+ "development" => { "database" => "dev-db" },
+ "test" => { "database" => "test-db" },
+ "production" => { "url" => "abstract://prod-db-url" }
+ }
+
+ ActiveRecord::Base.configurations = @configurations
+ end
+
+ def teardown
+ ActiveRecord::Base.configurations = @old_configurations
+ end
+
+ def test_drops_current_environment_database
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
+ ["database" => "test-db"]) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
+ end
+ end
+
+ def test_drops_current_environment_database_with_url
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
+ ["url" => "prod-db-url"]) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
+ end
+ end
+
+ def test_drops_test_and_development_databases_when_env_was_not_specified
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "dev-db"],
+ ["database" => "test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ end
+
+ def test_drops_testand_development_databases_when_rails_env_is_development
+ old_env = ENV["RAILS_ENV"]
+ ENV["RAILS_ENV"] = "development"
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "dev-db"],
+ ["database" => "test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ ensure
+ ENV["RAILS_ENV"] = old_env
+ end
+ end
+
+ class LegacyDatabaseTasksDropCurrentThreeTierTest < ActiveRecord::TestCase
+ def setup
+ @old_configurations = ActiveRecord::Base.configurations.to_h
+
+ @configurations = {
+ "development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
+ "test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
+ "production" => { "primary" => { "url" => "abstract://prod-db-url" }, "secondary" => { "url" => "abstract://secondary-prod-db-url" } }
+ }
+
+ ActiveRecord::Base.configurations = @configurations
+ end
+
+ def teardown
+ ActiveRecord::Base.configurations = @old_configurations
+ end
+
+ def test_drops_current_environment_database
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
+ end
+ end
+
+ def test_drops_current_environment_database_with_url
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["url" => "prod-db-url"],
+ ["url" => "secondary-prod-db-url"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
+ end
+ end
+
+ def test_drops_test_and_development_databases_when_env_was_not_specified
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"],
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ end
+
+ def test_drops_testand_development_databases_when_rails_env_is_development
+ old_env = ENV["RAILS_ENV"]
+ ENV["RAILS_ENV"] = "development"
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"],
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ ensure
+ ENV["RAILS_ENV"] = old_env
+ end
+ end
+
+ class LegacyDatabaseTasksPurgeCurrentTest < ActiveRecord::TestCase
+ def test_purges_current_environment_database
+ @old_configurations = ActiveRecord::Base.configurations.to_h
+
+ configurations = {
+ "development" => { "database" => "dev-db" },
+ "test" => { "database" => "test-db" },
+ "production" => { "database" => "prod-db" }
+ }
+
+ ActiveRecord::Base.configurations = configurations
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :purge,
+ ["database" => "prod-db"]
+ ) do
+ assert_called_with(ActiveRecord::Base, :establish_connection, [:production]) do
+ ActiveRecord::Tasks::DatabaseTasks.purge_current("production")
+ end
+ end
+ end
+ ensure
+ ActiveRecord::Base.configurations = @old_configurations
+ end
+ end
+
+ class LegacyDatabaseTasksPurgeAllTest < ActiveRecord::TestCase
+ def test_purge_all_local_configurations
+ @old_configurations = ActiveRecord::Base.configurations.to_h
+
+ configurations = { development: { "database" => "my-db" } }
+ ActiveRecord::Base.configurations = configurations
+
+ ActiveRecord::Base.configurations.to_h do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :purge,
+ ["database" => "my-db"]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge_all
+ end
+ end
+ ensure
+ ActiveRecord::Base.configurations = @old_configurations
+ end
+ end
+end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index e36c2b1e3f..8c6e8d79f3 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -112,7 +112,7 @@ if current_adapter?(:PostgreSQLAdapter)
ActiveRecord::Base.stub(:connection, @connection) do
ActiveRecord::Base.stub(:establish_connection, -> * { raise Exception }) do
assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration }
- assert_match "Couldn't create database for #{@configuration.inspect}", $stderr.string
+ assert_match "Couldn't create '#{@configuration['database']}' database. Please check your configuration.", $stderr.string
end
end
end
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index c42afd0e42..c1092b97c1 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -62,7 +62,7 @@ if current_adapter?(:SQLite3Adapter)
def test_db_create_with_error_prints_message
ActiveRecord::Base.stub(:establish_connection, proc { raise Exception }) do
assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" }
- assert_match "Couldn't create database for #{@configuration.inspect}", $stderr.string
+ assert_match "Couldn't create '#{@configuration['database']}' database. Please check your configuration.", $stderr.string
end
end
end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 640cdb33b4..528585fb75 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -324,7 +324,7 @@ class FakeKlass
table[name]
end
- def enforce_raw_sql_whitelist(*args)
+ def disallow_raw_sql!(*args)
# noop
end
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index c874691629..aa6896af32 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -4,19 +4,27 @@ require "delegate"
module ActiveSupport
module Tryable #:nodoc:
- def try(*a, &b)
- try!(*a, &b) if a.empty? || respond_to?(a.first)
+ def try(method_name = nil, *args, &b)
+ if method_name.nil? && block_given?
+ if b.arity == 0
+ instance_eval(&b)
+ else
+ yield self
+ end
+ elsif respond_to?(method_name)
+ public_send(method_name, *args, &b)
+ end
end
- def try!(*a, &b)
- if a.empty? && block_given?
+ def try!(method_name = nil, *args, &b)
+ if method_name.nil? && block_given?
if b.arity == 0
instance_eval(&b)
else
yield self
end
else
- public_send(*a, &b)
+ public_send(method_name, *args, &b)
end
end
end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 7e71318404..fb6956f64f 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -286,8 +286,10 @@ module ActiveSupport
alias_method :since, :+
alias_method :in, :+
- # Returns a new TimeWithZone object that represents the difference between
- # the current object's time and the +other+ time.
+ # Subtracts an interval of time and returns a new TimeWithZone object unless
+ # the other value `acts_like?` time. Then it will return a Float of the difference
+ # between the two times that represents the difference between the current
+ # object's time and the +other+ time.
#
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
# now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00
@@ -302,6 +304,12 @@ module ActiveSupport
#
# now - 24.hours # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
# now - 1.day # => Sun, 02 Nov 2014 00:26:28 EDT -04:00
+ #
+ # If both the TimeWithZone object and the other value act like Time, a Float
+ # will be returned.
+ #
+ # Time.zone.now - 1.day.ago # => 86399.999967
+ #
def -(other)
if other.acts_like?(:time)
to_time - other.to_time
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index f85415ee42..ee9a499953 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -52,7 +52,7 @@ After some versions without an upgrade, Rails 2.3 offers some new features for R
Documentation
-------------
-The [Ruby on Rails guides](https://guides.rubyonrails.org/) project has published several additional guides for Rails 2.3. In addition, a [separate site](http://edgeguides.rubyonrails.org/) maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the [Rails wiki](http://newwiki.rubyonrails.org/) and early planning for a Rails Book.
+The [Ruby on Rails guides](https://guides.rubyonrails.org/) project has published several additional guides for Rails 2.3. In addition, a [separate site](https://edgeguides.rubyonrails.org/) maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the [Rails wiki](http://newwiki.rubyonrails.org/) and early planning for a Rails Book.
* More Information: [Rails Documentation Projects](https://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects)
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index 9d15dfb2aa..e793146c2c 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -153,7 +153,7 @@ More information: - [New Action Mailer API in Rails 3](http://lindsaar.net/2010/
Documentation
-------------
-The documentation in the Rails tree is being updated with all the API changes, additionally, the [Rails Edge Guides](http://edgeguides.rubyonrails.org/) are being updated one by one to reflect the changes in Rails 3.0. The guides at [guides.rubyonrails.org](https://guides.rubyonrails.org/) however will continue to contain only the stable version of Rails (at this point, version 2.3.5, until 3.0 is released).
+The documentation in the Rails tree is being updated with all the API changes, additionally, the [Rails Edge Guides](https://edgeguides.rubyonrails.org/) are being updated one by one to reflect the changes in Rails 3.0. The guides at [guides.rubyonrails.org](https://guides.rubyonrails.org/) however will continue to contain only the stable version of Rails (at this point, version 2.3.5, until 3.0 is released).
More Information: - [Rails Documentation Projects](https://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects)
diff --git a/guides/source/6_0_release_notes.md b/guides/source/6_0_release_notes.md
new file mode 100644
index 0000000000..f3ed21dc45
--- /dev/null
+++ b/guides/source/6_0_release_notes.md
@@ -0,0 +1,175 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
+
+Ruby on Rails 6.0 Release Notes
+===============================
+
+Highlights in Rails 6.0:
+
+* Parallel Testing
+
+These release notes cover only the major changes. To learn about various bug
+fixes and changes, please refer to the change logs or check out the [list of
+commits](https://github.com/rails/rails/commits/6-0-stable) in the main Rails
+repository on GitHub.
+
+--------------------------------------------------------------------------------
+
+Upgrading to Rails 6.0
+----------------------
+
+If you're upgrading an existing application, it's a great idea to have good test
+coverage before going in. You should also first upgrade to Rails 5.2 in case you
+haven't and make sure your application still runs as expected before attempting
+an update to Rails 6.0. A list of things to watch out for when upgrading is
+available in the
+[Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-5-2-to-rails-6-0)
+guide.
+
+Major Features
+--------------
+
+### Parallel Testing
+
+[Pull Request](https://github.com/rails/rails/pull/31900)
+
+[Parallel Testing](testing.html#parallel-testing) allows you to parallelize your
+test suite. While forking processes is the default method, threading is
+supported as well. Running tests in parallel reduces the time it takes
+your entire test suite to run.
+
+Railties
+--------
+
+Please refer to the [Changelog][railties] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Action Cable
+------------
+
+Please refer to the [Changelog][action-cable] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Action Pack
+-----------
+
+Please refer to the [Changelog][action-pack] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Action View
+-----------
+
+Please refer to the [Changelog][action-view] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Action Mailer
+-------------
+
+Please refer to the [Changelog][action-mailer] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Active Record
+-------------
+
+Please refer to the [Changelog][active-record] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Active Storage
+--------------
+
+Please refer to the [Changelog][active-storage] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Active Model
+------------
+
+Please refer to the [Changelog][active-model] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Active Support
+--------------
+
+Please refer to the [Changelog][active-support] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Active Job
+----------
+
+Please refer to the [Changelog][active-job] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Ruby on Rails Guides
+--------------------
+
+Please refer to the [Changelog][guides] for detailed changes.
+
+### Notable changes
+
+Credits
+-------
+
+See the
+[full list of contributors to Rails](http://contributors.rubyonrails.org/)
+for the many people who spent many hours making Rails, the stable and robust
+framework it is. Kudos to all of them.
+
+[railties]: https://github.com/rails/rails/blob/6-0-stable/railties/CHANGELOG.md
+[action-pack]: https://github.com/rails/rails/blob/6-0-stable/actionpack/CHANGELOG.md
+[action-view]: https://github.com/rails/rails/blob/6-0-stable/actionview/CHANGELOG.md
+[action-mailer]: https://github.com/rails/rails/blob/6-0-stable/actionmailer/CHANGELOG.md
+[action-cable]: https://github.com/rails/rails/blob/6-0-stable/actioncable/CHANGELOG.md
+[active-record]: https://github.com/rails/rails/blob/6-0-stable/activerecord/CHANGELOG.md
+[active-storage]: https://github.com/rails/rails/blob/6-0-stable/activestorage/CHANGELOG.md
+[active-model]: https://github.com/rails/rails/blob/6-0-stable/activemodel/CHANGELOG.md
+[active-support]: https://github.com/rails/rails/blob/6-0-stable/activesupport/CHANGELOG.md
+[active-job]: https://github.com/rails/rails/blob/6-0-stable/activejob/CHANGELOG.md
+[guides]: https://github.com/rails/rails/blob/6-0-stable/guides/CHANGELOG.md
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index b20a2bb0d2..8c95187fa4 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -516,6 +516,9 @@ Defaults to `'signed cookie'`.
signed and encrypted cookies use the AES-256-GCM cipher or
the older AES-256-CBC cipher. It defaults to `true`.
+* `config.action_dispatch.use_cookies_with_metadata` enables writing
+ cookies with the purpose and expiry metadata embedded. It defaults to `true`.
+
* `config.action_dispatch.perform_deep_munge` configures whether `deep_munge`
method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation)
for more information. It defaults to `true`.
@@ -908,7 +911,15 @@ $ echo $DATABASE_URL
postgresql://localhost/my_database
$ rails runner 'puts ActiveRecord::Base.configurations'
-{"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database"}}
+#<ActiveRecord::DatabaseConfigurations:0x00007fd50e209a28>
+
+$ rails runner 'puts ActiveRecord::Base.configurations.inspect'
+#<ActiveRecord::DatabaseConfigurations:0x00007fc8eab02880 @configurations=[
+ #<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fc8eab020b0
+ @env_name="development", @spec_name="primary",
+ @config={"adapter"=>"postgresql", "database"=>"my_database", "host"=>"localhost"}
+ @url="postgresql://localhost/my_database">
+ ]
```
Here the adapter, host, and database match the information in `ENV['DATABASE_URL']`.
@@ -925,7 +936,15 @@ $ echo $DATABASE_URL
postgresql://localhost/my_database
$ rails runner 'puts ActiveRecord::Base.configurations'
-{"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database", "pool"=>5}}
+#<ActiveRecord::DatabaseConfigurations:0x00007fd50e209a28>
+
+$ rails runner 'puts ActiveRecord::Base.configurations.inspect'
+#<ActiveRecord::DatabaseConfigurations:0x00007fc8eab02880 @configurations=[
+ #<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fc8eab020b0
+ @env_name="development", @spec_name="primary",
+ @config={"adapter"=>"postgresql", "database"=>"my_database", "host"=>"localhost", "pool"=>5}
+ @url="postgresql://localhost/my_database">
+ ]
```
Since pool is not in the `ENV['DATABASE_URL']` provided connection information its information is merged in. Since `adapter` is duplicate, the `ENV['DATABASE_URL']` connection information wins.
@@ -935,13 +954,21 @@ The only way to explicitly not use the connection information in `ENV['DATABASE_
```
$ cat config/database.yml
development:
- url: sqlite3:NOT_my_database
+ url: sqlite3://NOT_my_database
$ echo $DATABASE_URL
postgresql://localhost/my_database
$ rails runner 'puts ActiveRecord::Base.configurations'
-{"development"=>{"adapter"=>"sqlite3", "database"=>"NOT_my_database"}}
+#<ActiveRecord::DatabaseConfigurations:0x00007fd50e209a28>
+
+$ rails runner 'puts ActiveRecord::Base.configurations.inspect'
+#<ActiveRecord::DatabaseConfigurations:0x00007fc8eab02880 @configurations=[
+ #<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fc8eab020b0
+ @env_name="development", @spec_name="primary",
+ @config={"adapter"=>"sqlite3", "database"=>"NOT_my_database", "host"=>"localhost"}
+ @url="sqlite3://NOT_my_database">
+ ]
```
Here the connection information in `ENV['DATABASE_URL']` is ignored, note the different adapter and database name.
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 4dee34b1e7..8f2312458d 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -202,6 +202,11 @@
url: upgrading_ruby_on_rails.html
description: This guide helps in upgrading applications to latest Ruby on Rails versions.
-
+ name: Ruby on Rails 6.0 Release Notes
+ work_in_progress: true
+ url: 6_0_release_notes.html
+ description: Release notes for Rails 6.0.
+ -
name: Ruby on Rails 5.2 Release Notes
url: 5_2_release_notes.html
description: Release notes for Rails 5.2.
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 6e6752c82c..12cfe249b6 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -30,7 +30,7 @@ The main form helper is `form_with`.
<% end %>
```
-When called without arguments like this, it creates a `<form>` tag which, when submitted, will POST to the current page. For instance, assuming the current page is `/home/index`, the generated HTML will look like this (some line breaks added for readability):
+When called without arguments like this, it creates a form tag which, when submitted, will POST to the current page. For instance, assuming the current page is a home page, the generated HTML will look like this:
```html
<form accept-charset="UTF-8" action="/" data-remote="true" method="post">
@@ -39,8 +39,8 @@ When called without arguments like this, it creates a `<form>` tag which, when s
</form>
```
-You'll notice that the HTML contains an `input` element with type `hidden`. This `input` is important, because the form cannot be successfully submitted without it.
-The hidden input element with the name `authenticity_token` is a security feature of Rails called **cross-site request forgery protection**, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the [Security Guide](security.html#cross-site-request-forgery-csrf).
+You'll notice that the HTML contains an `input` element with type `hidden`. This `input` is important, because non-GET form cannot be successfully submitted without it.
+The hidden input element with the name `authenticity_token` is a security feature of Rails called **cross-site request forgery protection**, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the [Security Guide](security.html#cross-site-request-forgery-csrf) guide.
### A Generic Search Form
@@ -75,9 +75,7 @@ TIP: Passing `url: my_specified_path` to `form_with` tells the form where to mak
TIP: For every form input, an ID attribute is generated from its name (`"q"` in above example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript.
-Besides `text_field_tag` and `submit_tag`, there is a similar helper for _every_ form control in HTML.
-
-IMPORTANT: Always use "GET" as the method for search forms. This allows users to bookmark a specific search and get back to it. More generally Rails encourages you to use the right HTTP verb for an action.
+IMPORTANT: Use "GET" as the method for search forms. This allows users to bookmark a specific search and get back to it. More generally Rails encourages you to use the right HTTP verb for an action.
### Helpers for Generating Form Elements
@@ -91,7 +89,7 @@ value entered by the user for that field. For example, if the form contains
`<%= text_field_tag(:query) %>`, then you would be able to get the value of this
field in the controller with `params[:query]`.
-When naming inputs, Rails uses certain conventions that make it possible to submit parameters with non-scalar values such as arrays or hashes, which will also be accessible in `params`. You can read more about them in [chapter 7 of this guide](#understanding-parameter-naming-conventions). For details on the precise usage of these helpers, please refer to the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html).
+When naming inputs, Rails uses certain conventions that make it possible to submit parameters with non-scalar values such as arrays or hashes, which will also be accessible in `params`. You can read more about them in chapter [Understanding Parameter Naming Conventions](#understanding-parameter-naming-conventions) of this guide. For details on the precise usage of these helpers, please refer to the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html).
#### Checkboxes
@@ -123,7 +121,7 @@ Radio buttons, while similar to checkboxes, are controls that specify a set of o
<%= radio_button_tag(:age, "child") %>
<%= label_tag(:age_child, "I am younger than 21") %>
<%= radio_button_tag(:age, "adult") %>
-<%= label_tag(:age_adult, "I'm over 21") %>
+<%= label_tag(:age_adult, "I am over 21") %>
```
Output:
@@ -132,7 +130,7 @@ Output:
<input id="age_child" name="age" type="radio" value="child" />
<label for="age_child">I am younger than 21</label>
<input id="age_adult" name="age" type="radio" value="adult" />
-<label for="age_adult">I'm over 21</label>
+<label for="age_adult">I am over 21</label>
```
As with `check_box_tag`, the second parameter to `radio_button_tag` is the value of the input. Because these two radio buttons share the same name (`age`), the user will only be able to select one of them, and `params[:age]` will contain either `"child"` or `"adult"`.
@@ -196,7 +194,7 @@ There is definitely [no shortage of solutions for this](https://github.com/Moder
[Modernizr](https://modernizr.com/), which provides a simple way to add functionality based on the presence of
detected HTML5 features.
-TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the [Security Guide](security.html#logging).
+TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the [Securing Rails Applications](security.html#logging) guide.
Dealing with Model Objects
--------------------------
@@ -214,10 +212,10 @@ For these helpers the first argument is the name of an instance variable and the
will produce output similar to
```erb
-<input id="person_name" name="person[name]" type="text" value="Henry"/>
+<input id="person_name" name="person[name]" type="text" value="Henry" />
```
-Upon form submission the value entered by the user will be stored in `params[:person][:name]`. The `params[:person]` hash is suitable for passing to `Person.new` or, if `@person` is an instance of Person, `@person.update`. While the name of an attribute is the most common second parameter to these helpers this is not compulsory. In the example above, as long as person objects have a `name` and a `name=` method Rails will be happy.
+Upon form submission the value entered by the user will be stored in `params[:person][:name]`.
WARNING: You must pass the name of an instance variable, i.e. `:person` or `"person"`, not an actual instance of your model object.
@@ -250,7 +248,7 @@ There are a few things to note here:
* `@article` is the actual object being edited.
* There is a single hash of options. HTML options (except `id` and `class`) are passed in the `:html` hash. Also you can provide a `:namespace` option for your form to ensure uniqueness of id attributes on form elements. The scope attribute will be prefixed with underscore on the generated HTML id.
* The `form_with` method yields a **form builder** object (the `f` variable).
-* If you wish to direct your form request to a particular url, you would use `form_with url: my_nifty_url_path` instead. To see more in depth options on what `form_with` accepts be sure to [check out the API documentation](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with)
+* If you wish to direct your form request to a particular url, you would use `form_with url: my_nifty_url_path` instead. To see more in depth options on what `form_with` accepts be sure to [check out the API documentation](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with).
* Methods to create form controls are called **on** the form builder object `f`.
The resulting HTML is:
@@ -264,7 +262,7 @@ The resulting HTML is:
</form>
```
-The name passed to `:model` in `form_with` controls the key used in `params` to access the form's values. Here the name is `article` and so all the inputs have names of the form `article[attribute_name]`. Accordingly, in the `create` action `params[:article]` will be a hash with keys `:title` and `:body`. You can read more about the significance of input names in the [parameter_names section](#understanding-parameter-naming-conventions).
+The object passed as `:model` in `form_with` controls the key used in `params` to access the form's values. Here the name is `article` and so all the inputs have names of the form `article[attribute_name]`. Accordingly, in the `create` action `params[:article]` will be a hash with keys `:title` and `:body`. You can read more about the significance of input names in chapter [Understanding Parameter Naming Conventions](#understanding-parameter-naming-conventions) of this guide.
TIP: Conventionally your inputs will mirror model attributes. However, they don't have to! If there is other information you need you can include it in your form just as with attributes and access it via `params[:article][:my_nifty_non_attribute_input]`.
@@ -275,7 +273,7 @@ You can create a similar binding without actually creating `<form>` tags with th
```erb
<%= form_with model: @person do |person_form| %>
<%= person_form.text_field :name %>
- <%= fields_for @person.contact_detail do |contact_detail_form| %>
+ <%= fields_for :contact_detail, @person.contact_detail do |contact_detail_form| %>
<%= contact_detail_form.text_field :phone_number %>
<% end %>
<% end %>
@@ -301,7 +299,7 @@ The Article model is directly available to users of the application, so - follow
resources :articles
```
-TIP: Declaring a resource has a number of side effects. See [Rails Routing From the Outside In](routing.html#resource-routing-the-rails-default) for more information on setting up and using resources.
+TIP: Declaring a resource has a number of side effects. See [Rails Routing from the Outside In](routing.html#resource-routing-the-rails-default) guide for more information on setting up and using resources.
When dealing with RESTful resources, calls to `form_with` can get significantly easier if you rely on **record identification**. In short, you can just pass the model instance and have Rails figure out model name and the rest:
@@ -309,21 +307,19 @@ When dealing with RESTful resources, calls to `form_with` can get significantly
## Creating a new article
# long-style:
form_with(model: @article, url: articles_path)
-# same thing, short-style (record identification gets used):
+short-style:
form_with(model: @article)
## Editing an existing article
# long-style:
-form_with(model: @article, url: article_path(@article), html: {method: "patch"})
+form_with(model: @article, url: article_path(@article), method: "patch")
# short-style:
form_with(model: @article)
```
-Notice how the short-style `form_with` invocation is conveniently the same, regardless of the record being new or existing. Record identification is smart enough to figure out if the record is new by asking `record.new_record?`. It also selects the correct path to submit to and the name based on the class of the object.
-
-Rails will also automatically set the `class` and `id` of the form appropriately: a form creating an article would have `id` and `class` `new_article`. If you were editing the article with id 23, the `class` would be set to `edit_article` and the id to `edit_article_23`. These attributes will be omitted for brevity in the rest of this guide.
+Notice how the short-style `form_with` invocation is conveniently the same, regardless of the record being new or existing. Record identification is smart enough to figure out if the record is new by asking `record.new_record?`. It also selects the correct path to submit to, and the name based on the class of the object.
-WARNING: When you're using STI (single-table inheritance) with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify the model name, `:url`, and `:method` explicitly.
+WARNING: When you're using STI (single-table inheritance) with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify `:url`, and `:scope` (the model name) explicitly.
#### Dealing with Namespaces
@@ -339,11 +335,11 @@ will create a form that submits to the `ArticlesController` inside the admin nam
form_with model: [:admin, :management, @article]
```
-For more information on Rails' routing system and the associated conventions, please see the [routing guide](routing.html).
+For more information on Rails' routing system and the associated conventions, please see [Rails Routing from the Outside In](routing.html) guide.
### How do forms with PATCH, PUT, or DELETE methods work?
-The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PATCH" and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms.
+The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PATCH", "PUT", and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms.
Rails works around this issue by emulating other methods over POST with a hidden input named `"_method"`, which is set to reflect the desired method:
@@ -351,10 +347,10 @@ Rails works around this issue by emulating other methods over POST with a hidden
form_with(url: search_path, method: "patch")
```
-output:
+Output:
```html
-<form accept-charset="UTF-8" action="/search" data-remote="true" method="post">
+<form accept-charset="UTF-8" action="/search" data-remote="true" method="post">
<input name="_method" type="hidden" value="patch" />
<input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
...
@@ -363,7 +359,7 @@ output:
When parsing POSTed data, Rails will take into account the special `_method` parameter and act as if the HTTP method was the one specified inside it ("PATCH" in this example).
-IMPORTANT: All forms using `form_with` implement `remote: true` by default. These forms will submit data using an XHR (Ajax) request. To disable this include `local: true`. To dive deeper see [working with Javascript in Rails](https://guides.rubyonrails.org/working_with_javascript_in_rails.html#remote-elements).
+IMPORTANT: All forms using `form_with` implement `remote: true` by default. These forms will submit data using an XHR (Ajax) request. To disable this include `local: true`. To dive deeper see [Working with JavaScript in Rails](working_with_javascript_in_rails.html#remote-elements) guide.
Making Select Boxes with Ease
-----------------------------
@@ -376,8 +372,7 @@ Here is what the markup might look like:
<select name="city_id" id="city_id">
<option value="1">Lisbon</option>
<option value="2">Madrid</option>
- ...
- <option value="12">Berlin</option>
+ <option value="3">Berlin</option>
</select>
```
@@ -388,19 +383,21 @@ Here you have a list of cities whose names are presented to the user. Internally
The most generic helper is `select_tag`, which - as the name implies - simply generates the `SELECT` tag that encapsulates an options string:
```erb
-<%= select_tag(:city_id, '<option value="1">Lisbon</option>...') %>
+<%= select_tag(:city_id, raw('<option value="1">Lisbon</option><option value="2">Madrid</option><option value="3">Berlin</option>')) %>
```
This is a start, but it doesn't dynamically create the option tags. You can generate option tags with the `options_for_select` helper:
```html+erb
-<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...]) %>
+<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ['Berlin', 3]]) %>
+```
-output:
+Output:
+```html
<option value="1">Lisbon</option>
<option value="2">Madrid</option>
-...
+<option value="3">Berlin</option>
```
The first argument to `options_for_select` is a nested array where each element has two elements: option text (city name) and option value (city id). The option value is what will be submitted to your controller. Often this will be the id of a corresponding database object but this does not have to be the case.
@@ -414,48 +411,61 @@ Knowing this, you can combine `select_tag` and `options_for_select` to achieve t
`options_for_select` allows you to pre-select an option by passing its value.
```html+erb
-<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...], 2) %>
+<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ['Berlin', 3]], 2) %>
+```
-output:
+Output:
+```html
<option value="1">Lisbon</option>
<option value="2" selected="selected">Madrid</option>
-...
+<option value="3">Berlin</option>
```
Whenever Rails sees that the internal value of an option being generated matches this value, it will add the `selected` attribute to that option.
-WARNING: When `:include_blank` or `:prompt` are not present, `:include_blank` is forced true if the select attribute `required` is true, display `size` is one, and `multiple` is not true.
-
You can add arbitrary attributes to the options using hashes:
```html+erb
<%= options_for_select(
[
['Lisbon', 1, { 'data-size' => '2.8 million' }],
- ['Madrid', 2, { 'data-size' => '3.2 million' }]
+ ['Madrid', 2, { 'data-size' => '3.2 million' }],
+ ['Berlin', 3, { 'data-size' => '3.4 million' }]
], 2
) %>
+```
-output:
+Output:
+```html
<option value="1" data-size="2.8 million">Lisbon</option>
<option value="2" selected="selected" data-size="3.2 million">Madrid</option>
-...
+<option value="3" data-size="3.4 million">Berlin</option>
```
-### Select Boxes for Dealing with Models
+### Select Boxes for Dealing with Model Objects
-In most cases form controls will be tied to a specific database model and as you might expect Rails provides helpers tailored for that purpose. Consistent with other form helpers, when dealing with models you drop the `_tag` suffix from `select_tag`:
+In most cases form controls will be tied to a specific model and as you might expect Rails provides helpers tailored for that purpose. Consistent with other form helpers, when dealing with a model object drop the `_tag` suffix from `select_tag`:
+
+If your controller has defined `@person` and that person's city_id is 2:
```ruby
-# controller:
@person = Person.new(city_id: 2)
```
```erb
-# view:
-<%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ...]) %>
+<%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ['Berlin', 3]]) %>
+```
+
+will produce output similar to
+
+```html
+<select name="person[city_id]" id="person_city_id">
+ <option value="1">Lisbon</option>
+ <option value="2" selected="selected">Madrid</option>
+ <option value="3">Berlin</option>
+</select>
```
Notice that the third parameter, the options array, is the same kind of argument you pass to `options_for_select`. One advantage here is that you don't have to worry about pre-selecting the correct city if the user already has one - Rails will do this for you by reading from the `@person.city_id` attribute.
@@ -463,21 +473,26 @@ Notice that the third parameter, the options array, is the same kind of argument
As with other helpers, if you were to use the `select` helper on a form builder scoped to the `@person` object, the syntax would be:
```erb
-# select on a form builder
-<%= f.select(:city_id, ...) %>
+<%= form_with model: @person do |person_form| %>
+ <%= person_form.select(:city_id, [['Lisbon', 1], ['Madrid', 2], ['Berlin', 3]]) %>
+<% end %>
```
You can also pass a block to `select` helper:
```erb
-<%= f.select(:city_id) do %>
- <% [['Lisbon', 1], ['Madrid', 2]].each do |c| -%>
- <%= content_tag(:option, c.first, value: c.last) %>
+<%= form_with model: @person do |person_form| %>
+ <%= person_form.select(:city_id) do %>
+ <% [['Lisbon', 1], ['Madrid', 2], ['Berlin', 3]].each do |c| %>
+ <%= content_tag(:option, c.first, value: c.last) %>
+ <% end %>
<% end %>
<% end %>
```
-WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of `ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750)` when you pass the `params` hash to `Person.new` or `update`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly.
+WARNING: If you are using `select` or similar helpers to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself.
+
+WARNING: When `:include_blank` or `:prompt` are not present, `:include_blank` is forced true if the select attribute `required` is true, display `size` is one, and `multiple` is not true.
### Option Tags from a Collection of Arbitrary Objects
@@ -494,7 +509,7 @@ This is a perfectly valid solution, but Rails provides a less verbose alternativ
<%= options_from_collection_for_select(City.all, :id, :name) %>
```
-As the name implies, this only generates option tags. To generate a working select box you would need to use it in conjunction with `select_tag`, just as you would with `options_for_select`. When working with model objects, just as `select` combines `select_tag` and `options_for_select`, `collection_select` combines `select_tag` with `options_from_collection_for_select`.
+As the name implies, this only generates option tags. To generate a working select box you would need to use `collection_select`:
```erb
<%= collection_select(:person, :city_id, City.all, :id, :name) %>
@@ -503,16 +518,16 @@ As the name implies, this only generates option tags. To generate a working sele
As with other helpers, if you were to use the `collection_select` helper on a form builder scoped to the `@person` object, the syntax would be:
```erb
-<%= f.collection_select(:city_id, City.all, :id, :name) %>
+<%= form_with model: @person do |person_form| %>
+ <%= person_form.collection_select(:city_id, City.all, :id, :name) %>
+<% end %>
```
-To recap, `options_from_collection_for_select` is to `collection_select` what `options_for_select` is to `select`.
-
-NOTE: Pairs passed to `options_for_select` should have the name first and the id second, however with `options_from_collection_for_select` the first argument is the value method and the second the text method.
+NOTE: Pairs passed to `options_for_select` should have the text first and the value second, however with `options_from_collection_for_select` should have the value method first and the text method second.
### Time Zone and Country Select
-To leverage time zone support in Rails, you have to ask your users what time zone they are in. Doing so would require generating select options from a list of pre-defined TimeZone objects using `collection_select`, but you can simply use the `time_zone_select` helper that already wraps this:
+To leverage time zone support in Rails, you have to ask your users what time zone they are in. Doing so would require generating select options from a list of pre-defined [`ActiveSupport::TimeZone`](http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html) objects using `collection_select`, but you can simply use the `time_zone_select` helper that already wraps this:
```erb
<%= time_zone_select(:person, :time_zone) %>
@@ -520,21 +535,21 @@ To leverage time zone support in Rails, you have to ask your users what time zon
There is also `time_zone_options_for_select` helper for a more manual (therefore more customizable) way of doing this. Read the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-time_zone_options_for_select) to learn about the possible arguments for these two methods.
-Rails _used_ to have a `country_select` helper for choosing countries, but this has been extracted to the [country_select plugin](https://github.com/stefanpenner/country_select). When using this, be aware that the exclusion or inclusion of certain names from the list can be somewhat controversial (and was the reason this functionality was extracted from Rails).
+Rails _used_ to have a `country_select` helper for choosing countries, but this has been extracted to the [country_select plugin](https://github.com/stefanpenner/country_select).
Using Date and Time Form Helpers
--------------------------------
You can choose not to use the form helpers generating HTML5 date and time input fields and use the alternative date and time helpers. These date and time helpers differ from all the other form helpers in two important respects:
-* Dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc.) and so there is no single value in your `params` hash with your date or time.
+* Dates and times are not representable by a single input element. Instead, you have several, one for each component (year, month, day etc.) and so there is no single value in your `params` hash with your date or time.
* Other helpers use the `_tag` suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, `select_date`, `select_time` and `select_datetime` are the barebones helpers, `date_select`, `time_select` and `datetime_select` are the equivalent model object helpers.
Both of these families of helpers will create a series of select boxes for the different components (year, month, day etc.).
### Barebones Helpers
-The `select_*` family of helpers take as their first argument an instance of `Date`, `Time` or `DateTime` that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example:
+The `select_*` family of helpers take as their first argument an instance of `Date`, `Time`, or `DateTime` that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example:
```erb
<%= select_date Date.today, prefix: :start_date %>
@@ -543,12 +558,15 @@ The `select_*` family of helpers take as their first argument an instance of `Da
outputs (with actual option values omitted for brevity)
```html
-<select id="start_date_year" name="start_date[year]"> ... </select>
-<select id="start_date_month" name="start_date[month]"> ... </select>
-<select id="start_date_day" name="start_date[day]"> ... </select>
+<select id="start_date_year" name="start_date[year]">
+</select>
+<select id="start_date_month" name="start_date[month]">
+</select>
+<select id="start_date_day" name="start_date[day]">
+</select>
```
-The above inputs would result in `params[:start_date]` being a hash with keys `:year`, `:month`, `:day`. To get an actual `Date`, `Time` or `DateTime` object you would have to extract these values and pass them to the appropriate constructor, for example:
+The above inputs would result in `params[:start_date]` being a hash with keys `:year`, `:month`, `:day`. To get an actual `Date`, `Time`, or `DateTime` object you would have to extract these values and pass them to the appropriate constructor, for example:
```ruby
Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i)
@@ -568,9 +586,12 @@ The model object helpers for dates and times submit parameters with special name
outputs (with actual option values omitted for brevity)
```html
-<select id="person_birth_date_1i" name="person[birth_date(1i)]"> ... </select>
-<select id="person_birth_date_2i" name="person[birth_date(2i)]"> ... </select>
-<select id="person_birth_date_3i" name="person[birth_date(3i)]"> ... </select>
+<select id="person_birth_date_1i" name="person[birth_date(1i)]">
+</select>
+<select id="person_birth_date_2i" name="person[birth_date(2i)]">
+</select>
+<select id="person_birth_date_3i" name="person[birth_date(3i)]">
+</select>
```
which results in a `params` hash like
@@ -587,20 +608,18 @@ Both families of helpers use the same core set of functions to generate the indi
As a rule of thumb you should be using `date_select` when working with model objects and `select_date` in other cases, such as a search form which filters results by date.
-NOTE: In many cases the built-in date pickers are clumsy as they do not aid the user in working out the relationship between the date and the day of the week.
-
### Individual Components
Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example, "year" for `select_year`, "month" for `select_month` etc.) although this can be overridden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value.
-The first parameter specifies which value should be selected and can either be an instance of a `Date`, `Time` or `DateTime`, in which case the relevant component will be extracted, or a numerical value. For example:
+The first parameter specifies which value should be selected and can either be an instance of a `Date`, `Time`, or `DateTime`, in which case the relevant component will be extracted, or a numerical value. For example:
```erb
<%= select_year(2009) %>
-<%= select_year(Time.now) %>
+<%= select_year(Time.new(2009)) %>
```
-will produce the same output if the current year is 2009 and the value chosen by the user can be retrieved by `params[:date][:year]`.
+will produce the same output and the value chosen by the user can be retrieved by `params[:date][:year]`.
Uploading Files
---------------
@@ -619,27 +638,27 @@ The following two forms both upload a file.
<% end %>
```
-Rails provides the usual pair of helpers: the barebones `file_field_tag` and the model oriented `file_field`. The only difference with other helpers is that you cannot set a default value for file inputs as this would have no meaning. As you would expect in the first case the uploaded file is in `params[:picture]` and in the second case in `params[:person][:picture]`.
+Rails provides the usual pair of helpers: the barebones `file_field_tag` and the model oriented `file_field`. As you would expect in the first case the uploaded file is in `params[:picture]` and in the second case in `params[:person][:picture]`.
### What Gets Uploaded
-The object in the `params` hash is an instance of a subclass of `IO`. Depending on the size of the uploaded file it may in fact be a `StringIO` or an instance of `File` backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#{Rails.root}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example).
+The object in the `params` hash is an instance of [`ActionDispatch::Http::UploadedFile`](http://api.rubyonrails.org/classes/ActionDispatch/Http/UploadedFile.html). The following snippet saves the uploaded file in `#{Rails.root}/public/uploads` under the same name as the original file.
```ruby
def upload
- uploaded_io = params[:person][:picture]
- File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'wb') do |file|
- file.write(uploaded_io.read)
+ uploaded_file = params[:picture]
+ File.open(Rails.root.join('public', 'uploads', uploaded_file.original_filename), 'wb') do |file|
+ file.write(uploaded_file.read)
end
end
```
-Once a file has been uploaded, there are a multitude of potential tasks, ranging from where to store the files (on Disk, Amazon S3, etc), associating them with models, resizing image files, and generating thumbnails, etc. [Active Storage](https://guides.rubyonrails.org/active_storage_overview.html) is designed to assist with these tasks.
+Once a file has been uploaded, there are a multitude of potential tasks, ranging from where to store the files (on Disk, Amazon S3, etc), associating them with models, resizing image files, and generating thumbnails, etc. [Active Storage](active_storage_overview.html) is designed to assist with these tasks.
Customizing Form Builders
-------------------------
-As mentioned previously the object yielded by `form_with` and `fields_for` is an instance of `FormBuilder` (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way, you can also subclass `FormBuilder` and add the helpers there. For example:
+The object yielded by `form_with` and `fields_for` is an instance of [`ActionView::Helpers::FormBuilder`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html). Form builders encapsulate the notion of displaying form elements for a single object. While you can write helpers for your forms in the usual way, you can also create subclass `ActionView::Helpers::FormBuilder` and add the helpers there. For example:
```erb
<%= form_with model: @person do |f| %>
@@ -680,13 +699,12 @@ The form builder used also determines what happens when you do
<%= render partial: f %>
```
-If `f` is an instance of `FormBuilder` then this will render the `form` partial, setting the partial's object to the form builder. If the form builder is of class `LabellingFormBuilder` then the `labelling_form` partial would be rendered instead.
+If `f` is an instance of `ActionView::Helpers::FormBuilder` then this will render the `form` partial, setting the partial's object to the form builder. If the form builder is of class `LabellingFormBuilder` then the `labelling_form` partial would be rendered instead.
Understanding Parameter Naming Conventions
------------------------------------------
-As you've seen in the previous sections, values from forms can be at the top level of the `params` hash or nested in another hash. For example, in a standard `create`
-action for a Person model, `params[:person]` would usually be a hash of all the attributes for the person to create. The `params` hash can also contain arrays, arrays of hashes, and so on.
+Values from forms can be at the top level of the `params` hash or nested in another hash. For example, in a standard `create` action for a Person model, `params[:person]` would usually be a hash of all the attributes for the person to create. The `params` hash can also contain arrays, arrays of hashes, and so on.
Fundamentally HTML forms don't know about any sort of structured data, all they generate is name-value pairs, where pairs are just plain strings. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses.
@@ -733,16 +751,19 @@ This would result in `params[:person][:phone_number]` being an array containing
We can mix and match these two concepts. One element of a hash might be an array as in the previous example, or you can have an array of hashes. For example, a form might let you create any number of addresses by repeating the following form fragment
```html
-<input name="addresses[][line1]" type="text"/>
-<input name="addresses[][line2]" type="text"/>
-<input name="addresses[][city]" type="text"/>
+<input name="person[addresses][][line1]" type="text"/>
+<input name="person[addresses][][line2]" type="text"/>
+<input name="person[addresses][][city]" type="text"/>
+<input name="person[addresses][][line1]" type="text"/>
+<input name="person[addresses][][line2]" type="text"/>
+<input name="person[addresses][][city]" type="text"/>
```
-This would result in `params[:addresses]` being an array of hashes with keys `line1`, `line2` and `city`. Rails decides to start accumulating values in a new hash whenever it encounters an input name that already exists in the current hash.
+This would result in `params[:person][:addresses]` being an array of hashes with keys `line1`, `line2`, and `city`.
There's a restriction, however, while hashes can be nested arbitrarily, only one level of "arrayness" is allowed. Arrays can usually be replaced by hashes; for example, instead of having an array of model objects, one can have a hash of model objects keyed by their id, an array index, or some other parameter.
-WARNING: Array parameters do not play well with the `check_box` helper. According to the HTML specification unchecked checkboxes submit no value. However it is often convenient for a checkbox to always submit a value. The `check_box` helper fakes this by creating an auxiliary hidden input with the same name. If the checkbox is unchecked only the hidden input is submitted and if it is checked then both are submitted but the value submitted by the checkbox takes precedence. When working with array parameters this duplicate submission will confuse Rails since duplicate input names are how it decides when to start a new array element. It is preferable to either use `check_box_tag` or to use hashes instead of arrays.
+WARNING: Array parameters do not play well with the `check_box` helper. According to the HTML specification unchecked checkboxes submit no value. However it is often convenient for a checkbox to always submit a value. The `check_box` helper fakes this by creating an auxiliary hidden input with the same name. If the checkbox is unchecked only the hidden input is submitted and if it is checked then both are submitted but the value submitted by the checkbox takes precedence.
### Using Form Helpers
@@ -765,6 +786,7 @@ Assuming the person had two addresses, with ids 23 and 45 this would create outp
```html
<form accept-charset="UTF-8" action="/people/1" data-remote="true" method="post">
+ <input name="_method" type="hidden" value="patch" />
<input id="person_name" name="person[name]" type="text" />
<input id="person_address_23_city" name="person[address][23][city]" type="text" />
<input id="person_address_45_city" name="person[address][45][city]" type="text" />
@@ -789,7 +811,7 @@ To create more intricate nestings, you can specify the first part of the input
name (`person[address]` in the previous example) explicitly:
```erb
-<%= fields_for 'person[address][primary]', address, index: address do |address_form| %>
+<%= fields_for 'person[address][primary]', address, index: address.id do |address_form| %>
<%= address_form.text_field :city %>
<% end %>
```
@@ -797,12 +819,12 @@ name (`person[address]` in the previous example) explicitly:
will create inputs like
```html
-<input id="person_address_primary_1_city" name="person[address][primary][1][city]" type="text" value="bologna" />
+<input id="person_address_primary_1_city" name="person[address][primary][1][city]" type="text" value="Bologna" />
```
As a general rule the final input name is the concatenation of the name given to `fields_for`/`form_with`, the index value, and the name of the attribute. You can also pass an `:index` option directly to helpers such as `text_field`, but it is usually less repetitive to specify this at the form builder level rather than on individual input controls.
-As a shortcut you can append [] to the name and omit the `:index` option. This is the same as specifying `index: address` so
+As a shortcut you can append [] to the name and omit the `:index` option. This is the same as specifying `index: address.id` so
```erb
<%= fields_for 'person[address][primary][]', address do |address_form| %>
@@ -909,7 +931,7 @@ The `fields_for` yields a form builder. The parameters' name will be what
The keys of the `:addresses_attributes` hash are unimportant, they need merely be different for each address.
-If the associated object is already saved, `fields_for` autogenerates a hidden input with the `id` of the saved record. You can disable this by passing `include_id: false` to `fields_for`. You may wish to do this if the autogenerated input is placed in a location where an input tag is not valid HTML or when using an ORM where children do not have an `id`.
+If the associated object is already saved, `fields_for` autogenerates a hidden input with the `id` of the saved record. You can disable this by passing `include_id: false` to `fields_for`.
### The Controller
@@ -940,9 +962,9 @@ class Person < ApplicationRecord
end
```
-If the hash of attributes for an object contains the key `_destroy` with a value
-of `1` or `true` then the object will be destroyed. This form allows users to
-remove addresses:
+If the hash of attributes for an object contains the key `_destroy` with a value that
+evaluates to `true` (eg. 1, '1', true, or 'true') then the object will be destroyed.
+This form allows users to remove addresses:
```erb
<%= form_with model: @person do |f| %>
@@ -985,7 +1007,7 @@ As a convenience you can instead pass the symbol `:all_blank` which will create
### Adding Fields on the Fly
-Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new address' button. Rails does not provide any built-in support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current JavaScript date (milliseconds after the epoch) is a common choice.
+Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new address' button. Rails does not provide any built-in support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current JavaScript date (milliseconds since the [epoch](https://en.wikipedia.org/wiki/Unix_time)) is a common choice.
Using form_for and form_tag
---------------------------
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index e8a1bd4f3d..8fbd4d4ed4 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -95,12 +95,12 @@
</p>
<p>
Please contribute if you see any typos or factual errors.
- To get started, you can read our <%= link_to 'documentation contributions', 'http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation' %> section.
+ To get started, you can read our <%= link_to 'documentation contributions', 'https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation' %> section.
</p>
<p>
You may also find incomplete content or stuff that is not up to date.
Please do add any missing documentation for master. Make sure to check
- <%= link_to 'Edge Guides', 'http://edgeguides.rubyonrails.org' %> first to verify
+ <%= link_to 'Edge Guides', 'https://edgeguides.rubyonrails.org' %> first to verify
if the issues are already fixed or not on the master branch.
Check the <%= link_to 'Ruby on Rails Guides Guidelines', 'ruby_on_rails_guides_guidelines.html' %>
for style and conventions.
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 89de180508..befd4e08c0 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -69,13 +69,15 @@ Don't forget to review the difference, to see if there were any unexpected chang
### Configure Framework Defaults
The new Rails version might have different configuration defaults than the previous version. However, after following the steps described above, your application would still run with configuration defaults from the *previous* Rails version. That's because the value for `config.load_defaults` in `config/application.rb` has not been changed yet.
-
+
To allow you to upgrade to new defaults one by one, the update task has created a file `config/initializers/new_framework_defaults.rb`. Once your application is ready to run with new defaults, you can remove this file and flip the `config.load_defaults` value.
Upgrading from Rails 5.2 to Rails 6.0
-------------------------------------
+For more information on changes made to Rails 6.0 please see the [release notes](6_0_release_notes.html).
+
### Force SSL
The `force_ssl` method on controllers has been deprecated and will be removed in
@@ -83,6 +85,17 @@ Rails 6.1. You are encouraged to enable `config.force_ssl` to enforce HTTPS
connections throughout your application. If you need to exempt certain endpoints
from redirection, you can use `config.ssl_options` to configure that behavior.
+### Purpose in signed or encrypted cookie is now embedded in the cookies values
+
+To improve security, Rails now embeds the purpose information in encrypted or signed cookies value.
+Rails can now thwart attacks that attempt to copy signed/encrypted value
+of a cookie and use it as the value of another cookie.
+
+This new embed information make those cookies incompatible with versions of Rails older than 6.0.
+
+If you require your cookies to be read by 5.2 and older, or you are still validating your 6.0 deploy and want
+to allow you to rollback set
+`Rails.application.config.action_dispatch.use_cookies_with_metadata` to `false`.
Upgrading from Rails 5.1 to Rails 5.2
-------------------------------------
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 2a8882171f..b855fb7da2 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,18 @@
+* Add `--migrations_paths` option to migration generator.
+
+ If you're using multiple databases and have a folder for each database
+ for migrations (ex db/migrate and db/new_db_migrate) you can now pass the
+ `--migrations_paths` option to the generator to make sure the the migration
+ is inserted into the correct folder.
+
+ ```
+ rails g migration CreateHouses --migrations_paths=db/kingston_migrate
+ invoke active_record
+ create db/kingston_migrate/20180830151055_create_houses.rb
+ ```
+
+ *Eileen M. Uchitelle*
+
* Deprecate `rake routes` in favor of `rails routes`.
*Yuji Yaginuma*
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 9eb07219e0..f4cbd2b9d0 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -168,18 +168,6 @@ module Rails
end
end
- # Loads the database YAML without evaluating ERB. People seem to
- # write ERB that makes the database configuration depend on
- # Rails configuration. But we want Rails configuration (specifically
- # `rake` and `rails` tasks) to be generated based on information in
- # the database yaml, so we need a method that loads the database
- # yaml *without* the context of the Rails application.
- def load_database_yaml # :nodoc:
- path = paths["config/database"].existent.first
- return {} unless path
- YAML.load_file(path.to_s)
- end
-
# Loads and returns the entire raw configuration of database from
# values stored in <tt>config/database.yml</tt>.
def database_configuration
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt b/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt
index f83f5a5c62..15bd7956b6 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt
@@ -16,7 +16,7 @@ class <%= class_name.pluralize %>Test < ApplicationSystemTestCase
click_on "New <%= class_name.titleize %>"
<%- attributes_hash.each do |attr, value| -%>
- fill_in "<%= attr.humanize.titleize %>", with: <%= value %>
+ fill_in "<%= attr.humanize %>", with: <%= value %>
<%- end -%>
click_on "Create <%= human_name %>"
@@ -29,7 +29,7 @@ class <%= class_name.pluralize %>Test < ApplicationSystemTestCase
click_on "Edit", match: :first
<%- attributes_hash.each do |attr, value| -%>
- fill_in "<%= attr.humanize.titleize %>", with: <%= value %>
+ fill_in "<%= attr.humanize %>", with: <%= value %>
<%- end -%>
click_on "Update <%= human_name %>"
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index 0594236b1f..b1038ba5d6 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -26,12 +26,12 @@ module ApplicationTests
FileUtils.rm_rf("#{app_path}/config/database.yml")
end
- def db_create_and_drop(expected_database, environment_loaded: true)
+ def db_create_and_drop(expected_database)
Dir.chdir(app_path) do
output = rails("db:create")
assert_match(/Created database/, output)
assert File.exist?(expected_database)
- assert_equal expected_database, ActiveRecord::Base.connection_config[:database] if environment_loaded
+ assert_equal expected_database, ActiveRecord::Base.connection_config[:database]
output = rails("db:drop")
assert_match(/Dropped database/, output)
assert_not File.exist?(expected_database)
@@ -52,17 +52,21 @@ module ApplicationTests
test "db:create and db:drop respect environment setting" do
app_file "config/database.yml", <<-YAML
development:
- database: <%= Rails.application.config.database %>
+ database: db/development.sqlite3
adapter: sqlite3
YAML
app_file "config/environments/development.rb", <<-RUBY
Rails.application.configure do
- config.database = "db/development.sqlite3"
+ config.read_encrypted_secrets = true
end
RUBY
- db_create_and_drop "db/development.sqlite3", environment_loaded: false
+ app "development"
+
+ assert_equal true, Rails.application.config.read_encrypted_secrets
+
+ db_create_and_drop "db/development.sqlite3"
end
def with_database_existing
@@ -93,7 +97,7 @@ module ApplicationTests
test "db:create failure because bad permissions" do
with_bad_permissions do
output = rails("db:create", allow_failure: true)
- assert_match(/Couldn't create database/, output)
+ assert_match("Couldn't create '#{database_url_db_name}' database. Please check your configuration.", output)
assert_equal 1, $?.exitstatus
end
end
diff --git a/railties/test/application/rake/multi_dbs_test.rb b/railties/test/application/rake/multi_dbs_test.rb
index 07d96fcb56..f86bb9641f 100644
--- a/railties/test/application/rake/multi_dbs_test.rb
+++ b/railties/test/application/rake/multi_dbs_test.rb
@@ -127,36 +127,36 @@ EOS
test "db:create and db:drop works on all databases for env" do
require "#{app_path}/config/environment"
- ActiveRecord::Base.configurations[Rails.env].each do |namespace, config|
- db_create_and_drop namespace, config["database"]
+ ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
+ db_create_and_drop db_config.spec_name, db_config.config["database"]
end
end
test "db:create:namespace and db:drop:namespace works on specified databases" do
require "#{app_path}/config/environment"
- ActiveRecord::Base.configurations[Rails.env].each do |namespace, config|
- db_create_and_drop_namespace namespace, config["database"]
+ ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
+ db_create_and_drop_namespace db_config.spec_name, db_config.config["database"]
end
end
test "db:migrate and db:schema:dump and db:schema:load works on all databases" do
require "#{app_path}/config/environment"
- ActiveRecord::Base.configurations[Rails.env].each do |namespace, config|
- db_migrate_and_schema_dump_and_load namespace, config["database"], "schema"
+ ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
+ db_migrate_and_schema_dump_and_load db_config.spec_name, db_config.config["database"], "schema"
end
end
test "db:migrate and db:structure:dump and db:structure:load works on all databases" do
require "#{app_path}/config/environment"
- ActiveRecord::Base.configurations[Rails.env].each do |namespace, config|
- db_migrate_and_schema_dump_and_load namespace, config["database"], "structure"
+ ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
+ db_migrate_and_schema_dump_and_load db_config.spec_name, db_config.config["database"], "structure"
end
end
test "db:migrate:namespace works" do
require "#{app_path}/config/environment"
- ActiveRecord::Base.configurations[Rails.env].each do |namespace, config|
- db_migrate_namespaced namespace, config["database"]
+ ActiveRecord::Base.configurations.configs_for(Rails.env).each do |db_config|
+ db_migrate_namespaced db_config.spec_name, db_config.config["database"]
end
end
end
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index 88a939a55a..5c57d607fc 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -51,12 +51,12 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
def test_add_migration_with_table_having_from_in_title
- migration = "add_email_address_to_blacklisted_from_campaign"
+ migration = "add_email_address_to_excluded_from_campaign"
run_generator [migration, "email_address:string"]
assert_migration "db/migrate/#{migration}.rb" do |content|
assert_method :change, content do |change|
- assert_match(/add_column :blacklisted_from_campaigns, :email_address, :string/, change)
+ assert_match(/add_column :excluded_from_campaigns, :email_address, :string/, change)
end
end
end
@@ -254,6 +254,15 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_migrations_paths_puts_migrations_in_that_folder
+ run_generator ["create_books", "--migrations_paths=db/test_migrate"]
+ assert_migration "db/test_migrate/create_books.rb" do |content|
+ assert_method :change, content do |change|
+ assert_match(/create_table :books/, change)
+ end
+ end
+ end
+
def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove_or_create
migration = "delete_books"
run_generator [migration, "title:string", "content:text"]
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 3e631f6021..e90834bc2b 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -514,7 +514,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "test/system/users_test.rb" do |content|
assert_match(/fill_in "Password", with: 'secret'/, content)
- assert_match(/fill_in "Password Confirmation", with: 'secret'/, content)
+ assert_match(/fill_in "Password confirmation", with: 'secret'/, content)
end
assert_file "test/fixtures/users.yml" do |content|