aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md5
-rw-r--r--actionpack/lib/abstract_controller/base.rb2
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb2
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb12
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb4
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb2
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb13
-rw-r--r--actionview/lib/action_view/digestor.rb12
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/capture_helper.rb2
-rw-r--r--actionview/lib/action_view/lookup_context.rb6
-rw-r--r--actionview/lib/action_view/railtie.rb8
-rw-r--r--actionview/lib/action_view/template.rb8
-rw-r--r--actionview/lib/action_view/template/resolver.rb4
-rw-r--r--actionview/test/template/digestor_test.rb16
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb2
-rw-r--r--activemodel/lib/active_model/naming.rb2
-rw-r--r--activemodel/lib/active_model/validations/validates.rb2
-rw-r--r--activerecord/CHANGELOG.md58
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb16
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb9
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb12
-rw-r--r--activerecord/lib/active_record/locale/en.yml4
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb9
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/transactions.rb7
-rw-r--r--activerecord/test/cases/adapters/mysql/quoting_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql2/quoting_test.rb21
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb20
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb19
-rw-r--r--activerecord/test/cases/attributes_test.rb2
-rw-r--r--activerecord/test/cases/autosave_association_test.rb15
-rw-r--r--activerecord/test/cases/counter_cache_test.rb13
-rw-r--r--activerecord/test/cases/helper.rb3
-rw-r--r--activerecord/test/cases/locking_test.rb2
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb35
-rw-r--r--activerecord/test/cases/transactions_test.rb7
-rw-r--r--activerecord/test/models/aircraft.rb1
-rw-r--r--activerecord/test/models/ship.rb12
-rw-r--r--activerecord/test/schema/schema.rb5
-rw-r--r--activesupport/CHANGELOG.md8
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb43
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb6
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb20
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb2
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb2
-rw-r--r--activesupport/lib/active_support/message_verifier.rb4
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb2
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb4
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb2
-rw-r--r--guides/bug_report_templates/action_controller_master.rb2
-rw-r--r--guides/source/active_record_querying.md12
-rw-r--r--guides/source/testing.md2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile4
-rw-r--r--railties/test/application/per_request_digest_cache_test.rb63
-rw-r--r--railties/test/generators/api_app_generator_test.rb1
79 files changed, 545 insertions, 136 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 4b081591c0..6a68c057ac 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Fix `ActionController::Parameters#fetch` overwriting `KeyError` returned by
+ default block.
+
+ *Jonas Schuber Erlandsson*, *Roque Pinel*
+
* `ActionController::Parameters` no longer inherits from
`HashWithIndifferentAccess`
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index ebd02bd9a1..784092867c 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -96,7 +96,7 @@ module AbstractController
# ==== Returns
# * <tt>String</tt>
def controller_path
- @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
+ @controller_path ||= name.sub(/Controller$/, ''.freeze).underscore unless anonymous?
end
# Refresh the cached action_methods when a new action_method is added.
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 109eff10eb..d84c238a62 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -181,7 +181,7 @@ module AbstractController
end
def default_helper_module!
- module_name = name.sub(/Controller$/, '')
+ module_name = name.sub(/Controller$/, ''.freeze)
module_path = module_name.underscore
helper module_path
rescue LoadError => e
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 87609d8aa7..4c9f14e409 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -25,7 +25,7 @@ module ActionController
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
- message << " (#{additions.join(" | ")})" unless additions.blank?
+ message << " (#{additions.join(" | ".freeze)})" unless additions.blank?
message
end
end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index d920668184..e31d65aac2 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -55,10 +55,10 @@ module ActionController
# You can pass any of the following options to affect the before_action callback
# * <tt>only</tt> - The callback should be run only for this action
# * <tt>except</tt> - The callback should be run for all actions except this action
- # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a true value.
- # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a false value.
+ # * <tt>if</tt> - A symbol naming an instance method or a proc; the
+ # callback will be called only when it returns a true value.
+ # * <tt>unless</tt> - A symbol naming an instance method or a proc; the
+ # callback will be called only when it returns a false value.
def force_ssl(options = {})
action_options = options.slice(*ACTION_OPTIONS)
redirect_options = options.except(*ACTION_OPTIONS)
@@ -71,8 +71,8 @@ module ActionController
# Redirect the existing request to use the HTTPS protocol.
#
# ==== Parameters
- # * <tt>host_or_options</tt> - Either a host name or any of the url & redirect options
- # available to the <tt>force_ssl</tt> method.
+ # * <tt>host_or_options</tt> - Either a host name or any of the url &
+ # redirect options available to the <tt>force_ssl</tt> method.
def force_ssl_redirect(host_or_options = nil)
unless request.ssl?
options = {
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index b4da381d26..fcaf3e6425 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -73,7 +73,7 @@ module ActionController
# Provides a proxy to access helpers methods from outside the view.
def helpers
- @helper_proxy ||= begin
+ @helper_proxy ||= begin
proxy = ActionView::Base.new
proxy.config = config.inheritable_copy
proxy.extend(_helpers)
@@ -100,7 +100,7 @@ module ActionController
def all_helpers_from_path(path)
helpers = Array(path).flat_map do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
- names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1'.freeze) }
names.sort!
end
helpers.uniq!
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 06d625e4d5..cf6a64009f 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -380,11 +380,15 @@ module ActionController
def fetch(key, *args, &block)
convert_hashes_to_parameters(
key,
- @parameters.fetch(key, *args, &block),
+ @parameters.fetch(key) {
+ if block_given?
+ yield
+ else
+ args.fetch(0) { raise ActionController::ParameterMissing.new(key) }
+ end
+ },
false
)
- rescue KeyError
- raise ActionController::ParameterMissing.new(key)
end
# Returns a new <tt>ActionController::Parameters</tt> instance that
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index 6e058b829e..e826551f4b 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -34,11 +34,11 @@ module ActionDispatch
end
end
- deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.") }
- deep_strings, strings = strings.partition { |s| s.include?("\\.") }
+ deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.".freeze) }
+ deep_strings, strings = strings.partition { |s| s.include?("\\.".freeze) }
- regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
- deep_regexps << Regexp.new(deep_strings.join('|'), true) unless deep_strings.empty?
+ regexps << Regexp.new(strings.join('|'.freeze), true) unless strings.empty?
+ deep_regexps << Regexp.new(deep_strings.join('|'.freeze), true) unless deep_strings.empty?
new regexps, deep_regexps, blocks
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index f5b709ccd6..6fcf49030b 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -245,7 +245,7 @@ module ActionDispatch
# req = Request.new 'HTTP_HOST' => 'example.com:8080'
# req.host # => "example.com"
def host
- raw_host_with_port.sub(/:\d+$/, '')
+ raw_host_with_port.sub(/:\d+$/, ''.freeze)
end
# Returns a \host:\port string for this request, such as "example.com" or
diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
index bb01c087bc..cf6542b370 100644
--- a/actionpack/lib/action_dispatch/journey/nodes/node.rb
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -30,7 +30,7 @@ module ActionDispatch
end
def name
- left.tr '*:', ''
+ left.tr '*:'.freeze, ''.freeze
end
def type
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index d02ed96d0d..9793ca1c7a 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -14,10 +14,10 @@ module ActionDispatch
# normalize_path("/%ab") # => "/%AB"
def self.normalize_path(path)
path = "/#{path}"
- path.squeeze!('/')
- path.sub!(%r{/+\Z}, '')
+ path.squeeze!('/'.freeze)
+ path.sub!(%r{/+\Z}, ''.freeze)
path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
- path = '/' if path == ''
+ path = '/' if path == ''.freeze
path
end
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index b098ea389f..f38da4fdf6 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -35,7 +35,7 @@ module ActionDispatch
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
if match = paths.detect { |p|
- path = File.join(@root, p.force_encoding('UTF-8'))
+ path = File.join(@root, p.force_encoding('UTF-8'.freeze))
begin
File.file?(path) && File.readable?(path)
rescue SystemCallError
@@ -76,7 +76,7 @@ module ActionDispatch
end
def content_type(path)
- ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
+ ::Rack::Mime.mime_type(::File.extname(path), 'text/plain'.freeze)
end
def gzip_encoding_accepted?(env)
@@ -112,7 +112,7 @@ module ActionDispatch
def call(env)
case env['REQUEST_METHOD']
when 'GET', 'HEAD'
- path = env['PATH_INFO'].chomp('/')
+ path = env['PATH_INFO'].chomp('/'.freeze)
if match = @file_handler.match?(path)
env['PATH_INFO'] = match
return @file_handler.call(env)
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 7cfe4693c1..ec530c6e8a 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1538,7 +1538,7 @@ module ActionDispatch
path = path_for_action(action, options.delete(:path))
raise ArgumentError, "path is required" if path.blank?
- action = action.to_s.dup
+ action = action.to_s
if action =~ /^[\w\-\/]+$/
options[:action] ||= action.tr('-', '_') unless action.include?("/")
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 454593b59f..42512cad91 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -671,7 +671,7 @@ module ActionDispatch
# Remove leading slashes from controllers
def normalize_controller!
- @options[:controller] = controller.sub(%r{^/}, '') if controller
+ @options[:controller] = controller.sub(%r{^/}, ''.freeze) if controller
end
# Move 'index' action from options to recall
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 05532ec21b..9f7d14e85d 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -194,6 +194,19 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_equal "monkey", @params.fetch(:foo) { "monkey" }
end
+ test "fetch doesnt raise ParameterMissing exception if there is a default that is nil" do
+ assert_equal nil, @params.fetch(:foo, nil)
+ assert_equal nil, @params.fetch(:foo) { nil }
+ end
+
+ test 'KeyError in fetch block should not be covered up' do
+ params = ActionController::Parameters.new
+ e = assert_raises(KeyError) do
+ params.fetch(:missing_key) { {}.fetch(:also_missing) }
+ end
+ assert_match(/:also_missing$/, e.message)
+ end
+
test "not permitted is sticky beyond merges" do
assert !@params.merge(a: "b").permitted?
end
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index df8059d04e..4224346a2f 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -8,6 +8,13 @@ module ActionView
@@cache = ThreadSafe::Cache.new
@@digest_monitor = Monitor.new
+ class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc:
+ def call(env)
+ ActionView::Digestor.cache.clear
+ app.call(env)
+ end
+ end
+
class << self
# Supported options:
#
@@ -41,10 +48,7 @@ module ActionView
Digestor
end
- digest = klass.new(options).digest
- # Store the actual digest if config.cache_template_loading is true
- @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching?
- digest
+ @@cache[cache_key] = stored_digest = klass.new(options).digest
ensure
# something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
@@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index e32f8e219e..3041ad3ef7 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -237,7 +237,7 @@ module ActionView
# image_alt('underscored_file_name.png')
# # => Underscored file name
def image_alt(src)
- File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize
+ File.basename(src, '.*'.freeze).sub(/-[[:xdigit:]]{32}\z/, ''.freeze).tr('-_'.freeze, ' '.freeze).capitalize
end
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index b19dc25025..3beb7fefb1 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -127,7 +127,7 @@ module ActionView
return "" unless source.present?
return source if source =~ URI_REGEXP
- tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '')
+ tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, ''.freeze)
if extname = compute_asset_extname(source, options)
source = "#{source}#{extname}"
diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb
index a67ba580f1..93c7cba395 100644
--- a/actionview/lib/action_view/helpers/capture_helper.rb
+++ b/actionview/lib/action_view/helpers/capture_helper.rb
@@ -115,7 +115,7 @@ module ActionView
# <li><%= link_to 'Home', action: 'index' %></li>
# <% end %>
#
- # And in other place:
+ # And in another place:
#
# <% content_for :navigation do %>
# <li><%= link_to 'Login', action: 'login' %></li>
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 1c8359005b..f4a10aa393 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -173,13 +173,13 @@ module ActionView
# name instead of the prefix.
def normalize_name(name, prefixes) #:nodoc:
prefixes = prefixes.presence
- parts = name.to_s.split('/')
+ parts = name.to_s.split('/'.freeze)
parts.shift if parts.first.empty?
name = parts.pop
return name, prefixes || [""] if parts.empty?
- parts = parts.join('/')
+ parts = parts.join('/'.freeze)
prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
return name, prefixes
@@ -204,7 +204,7 @@ module ActionView
# add :html as fallback to :js.
def formats=(values)
if values
- values.concat(default_formats) if values.delete "*/*"
+ values.concat(default_formats) if values.delete "*/*".freeze
if values == [:js]
values << :html
@html_fallback_for_js = true
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index 5dc7950d6b..e829d86c99 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -42,6 +42,14 @@ module ActionView
end
end
+ initializer "action_view.per_request_digest_cache" do |app|
+ ActiveSupport.on_load(:action_view) do
+ if app.config.consider_all_requests_local
+ app.middleware.use ActionView::Digestor::PerRequestDigestCacheExpiry
+ end
+ end
+ end
+
initializer "action_view.setup_action_pack" do |app|
ActiveSupport.on_load(:action_controller) do
ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index d8585514d5..1ce9f94b13 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -190,7 +190,7 @@ module ActionView
end
def inspect
- @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier
+ @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", ''.freeze) : identifier
end
# This method is responsible for properly setting the encoding of the
@@ -337,13 +337,13 @@ module ActionView
def method_name #:nodoc:
@method_name ||= begin
m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
- m.tr!('-', '_')
+ m.tr!('-'.freeze, '_'.freeze)
m
end
end
def identifier_method_name #:nodoc:
- inspect.tr('^a-z_', '_')
+ inspect.tr('^a-z_'.freeze, '_'.freeze)
end
def instrument(action, &block)
@@ -366,7 +366,7 @@ module ActionView
end
def inferred_cache_name
- @inferred_cache_name ||= @virtual_path.split('/').last.sub('_', '')
+ @inferred_cache_name ||= @virtual_path.split('/'.freeze).last.sub('_'.freeze, ''.freeze)
end
end
end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 2b00b0303d..e7208cce49 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -222,7 +222,7 @@ module ActionView
end
def escape_entry(entry)
- entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&'.freeze)
end
# Returns the file mtime from the filesystem.
@@ -234,7 +234,7 @@ module ActionView
# from the path, or the handler, we should return the array of formats given
# to the resolver.
def extract_handler_and_format_and_variant(path, default_formats)
- pieces = File.basename(path).split('.')
+ pieces = File.basename(path).split('.'.freeze)
pieces.shift
extension = pieces.pop
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index f0afcdb5ae..24bc76cbbb 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -219,7 +219,7 @@ class TemplateDigestorTest < ActionView::TestCase
end
def test_variants
- assert_digest_difference("messages/new", false, variants: [:iphone]) do
+ assert_digest_difference("messages/new", variants: [:iphone]) do
change_template("messages/new", :iphone)
change_template("messages/_header", :iphone)
end
@@ -239,16 +239,6 @@ class TemplateDigestorTest < ActionView::TestCase
assert_not_equal digest_phone, digest_fridge_phone
end
- def test_cache_template_loading
- resolver_before = ActionView::Resolver.caching
- ActionView::Resolver.caching = false
- assert_digest_difference("messages/edit", true) do
- change_template("comments/_comment")
- end
- ensure
- ActionView::Resolver.caching = resolver_before
- end
-
def test_digest_cache_cleanup_with_recursion
first_digest = digest("level/_recursion")
second_digest = digest("level/_recursion")
@@ -291,9 +281,9 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
- def assert_digest_difference(template_name, persistent = false, options = {})
+ def assert_digest_difference(template_name, options = {})
previous_digest = digest(template_name, options)
- ActionView::Digestor.cache.clear unless persistent
+ ActionView::Digestor.cache.clear
yield
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 286cd6c206..9033ec0dca 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -372,7 +372,7 @@ module ActiveModel
"define_method(:'#{name}') do |*args|"
end
- extra = (extra.map!(&:inspect) << "*args").join(", ")
+ extra = (extra.map!(&:inspect) << "*args").join(", ".freeze)
target = if send =~ CALL_COMPILABLE_REGEXP
"#{"self." unless include_private}#{send}(#{extra})"
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 1f1749af4e..213f2d5b6a 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -192,7 +192,7 @@ module ActiveModel
private
def _singularize(string)
- ActiveSupport::Inflector.underscore(string).tr('/', '_')
+ ActiveSupport::Inflector.underscore(string).tr('/'.freeze, '_'.freeze)
end
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index bda436d8d0..1da4df21e7 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -115,7 +115,7 @@ module ActiveModel
key = "#{key.to_s.camelize}Validator"
begin
- validator = key.include?('::') ? key.constantize : const_get(key)
+ validator = key.include?('::'.freeze) ? key.constantize : const_get(key)
rescue NameError
raise ArgumentError, "Unknown validator: '#{key}'"
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 237b07f653..22f805fea4 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,61 @@
+* Deprecate the keys for association `restrict_dependent_destroy` errors in favor
+ of new key names.
+
+ Previously `has_one` and `has_many` associations were using the
+ `one` and `many` keys respectively. Both of these keys have special
+ meaning in I18n (they are considered to be pluralizations) so by
+ renaming them to `has_one` and `has_many` we make the messages more explicit
+ and most importantly they don't clash with linguistical systems that need to
+ validate translation keys (and their pluralizations).
+
+ The `:'restrict_dependent_destroy.one'` key should be replaced with
+ `:'restrict_dependent_destroy.has_one'`, and `:'restrict_dependent_destroy.many'`
+ with `:'restrict_dependent_destroy.has_many'`.
+
+ *Roque Pinel*, *Christopher Dell*
+
+* Fix state being carried over from previous transaction.
+
+ Considering the following example where `name` is a required attribute.
+ Before we had `new_record?` returning `true` for a persisted record:
+
+ author = Author.create! name: 'foo'
+ author.name = nil
+ author.save # => false
+ author.new_record? # => true
+
+ Fixes #20824.
+
+ *Roque Pinel*
+
+* Correctly ignore `mark_for_destruction` when `autosave` isn't set to `true`
+ when validating associations.
+
+ Fixes #20882.
+
+ *Sean Griffin*
+
+* Fix a bug where counter_cache doesn't always work with polymorphic
+ relations.
+
+ Fixes #16407.
+
+ *Stefan Kanev & Sean Griffin*
+
+* Ensure that cyclic associations with autosave don't cause duplicate errors
+ to be added to the parent record.
+
+ Fixes #20874.
+
+ *Sean Griffin*
+
+* Ensure that `ActionController::Parameters` can still be passed to nested
+ attributes.
+
+ Fixes #20922.
+
+ *Sean Griffin*
+
* Deprecate force association reload by passing a truthy argument to
association method.
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 97eb007f62..6e4a53f7fb 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -33,16 +33,24 @@ module ActiveRecord::Associations::Builder
if (@_after_create_counter_called ||= false)
@_after_create_counter_called = false
- elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable?
- model = reflection.klass
+ elsif attribute_changed?(foreign_key) && !new_record?
+ if reflection.polymorphic?
+ model = attribute(reflection.foreign_type).try(:constantize)
+ model_was = attribute_was(reflection.foreign_type).try(:constantize)
+ else
+ model = reflection.klass
+ model_was = reflection.klass
+ end
+
foreign_key_was = attribute_was foreign_key
foreign_key = attribute foreign_key
if foreign_key && model.respond_to?(:increment_counter)
model.increment_counter(cache_column, foreign_key)
end
- if foreign_key_was && model.respond_to?(:decrement_counter)
- model.decrement_counter(cache_column, foreign_key_was)
+
+ if foreign_key_was && model_was.respond_to?(:decrement_counter)
+ model_was.decrement_counter(cache_column, foreign_key_was)
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index ddeafb40ea..b5a8c81fe4 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -781,7 +781,7 @@ module ActiveRecord
# person.pets.any? # => false
#
# person.pets << Pet.new(name: 'Snoop')
- # person.pets.count # => 0
+ # person.pets.count # => 1
# person.pets.any? # => true
#
# You can also pass a +block+ to define criteria. The behavior
@@ -971,7 +971,7 @@ module ActiveRecord
alias_method :append, :<<
def prepend(*args)
- raise NoMethodError, "prepend on association is not defined. Please use << or append"
+ raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
end
# Equivalent to +delete_all+. The difference is that returns +self+, instead
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index ca27c9fdde..9f6c832c1b 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -16,7 +16,14 @@ module ActiveRecord
when :restrict_with_error
unless empty?
record = klass.human_attribute_name(reflection.name).downcase
- owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
+ message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.many', record: record, raise: true) rescue nil
+ if message
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ The error key `:'restrict_dependent_destroy.many'` has been deprecated and will be removed in Rails 5.1.
+ Please use `:'restrict_dependent_destroy.has_many'` instead.
+ MESSAGE
+ end
+ owner.errors.add(:base, message || :'restrict_dependent_destroy.has_many', record: record)
throw(:abort)
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 41a75b820e..5a92bc5e8a 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -12,7 +12,14 @@ module ActiveRecord
when :restrict_with_error
if load_target
record = klass.human_attribute_name(reflection.name).downcase
- owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
+ message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.one', record: record, raise: true) rescue nil
+ if message
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ The error key `:'restrict_dependent_destroy.one'` has been deprecated and will be removed in Rails 5.1.
+ Please use `:'restrict_dependent_destroy.has_one'` instead.
+ MESSAGE
+ end
+ owner.errors.add(:base, message || :'restrict_dependent_destroy.has_one', record: record)
throw(:abort)
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 9d58a19304..abe1d465a5 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -42,7 +42,7 @@ module ActiveRecord
def [](name)
@method_cache.compute_if_absent(name) do
- safe_name = name.unpack('h*').first
+ safe_name = name.unpack('h*'.freeze).first
temp_method = "__temp__#{safe_name}"
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
@module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 0d989c2eca..2363cf7608 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -37,7 +37,7 @@ module ActiveRecord
protected
def define_method_attribute(name)
- safe_name = name.unpack('h*').first
+ safe_name = name.unpack('h*'.freeze).first
temp_method = "__temp__#{safe_name}"
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index ab017c7b54..07d5e7d38e 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -24,7 +24,7 @@ module ActiveRecord
protected
def define_method_attribute=(name)
- safe_name = name.unpack('h*').first
+ safe_name = name.unpack('h*'.freeze).first
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 0792d19c3e..dbb0e2fab2 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -222,6 +222,7 @@ module ActiveRecord
true
end
validate validation_method
+ after_validation :_ensure_no_duplicate_errors
end
end
end
@@ -324,7 +325,7 @@ module ActiveRecord
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
# enabled records if they're marked_for_destruction? or destroyed.
def association_valid?(reflection, record)
- return true if record.destroyed? || record.marked_for_destruction?
+ return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
unless valid = record.valid?(validation_context)
@@ -456,5 +457,11 @@ module ActiveRecord
end
end
end
+
+ def _ensure_no_duplicate_errors
+ errors.messages.each_key do |attribute|
+ errors[attribute].uniq!
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 6d3a21a3dc..56227ddd80 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -107,6 +107,18 @@ module ActiveRecord
@prepared_statements = false
end
+ class Version
+ include Comparable
+
+ def initialize(version_string)
+ @version = version_string.split('.').map(&:to_i)
+ end
+
+ def <=>(version_string)
+ @version <=> version_string.split('.').map(&:to_i)
+ end
+ end
+
class BindCollector < Arel::Collectors::Bind
def compile(bvs, conn)
casted_binds = conn.prepare_binds_for_database(bvs)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 2027492f29..af156c9c78 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -307,7 +307,7 @@ module ActiveRecord
#
# http://bugs.mysql.com/bug.php?id=39170
def supports_transaction_isolation?
- version[0] >= 5
+ version >= '5.0.0'
end
def supports_indexes_in_create?
@@ -319,11 +319,11 @@ module ActiveRecord
end
def supports_views?
- version[0] >= 5
+ version >= '5.0.0'
end
def supports_datetime_with_precision?
- (version[0] == 5 && version[1] >= 6) || version[0] >= 6
+ version >= '5.6.4'
end
def native_database_types
@@ -386,6 +386,14 @@ module ActiveRecord
0
end
+ def quoted_date(value)
+ if supports_datetime_with_precision?
+ super
+ else
+ super.sub(/\.\d{6}\z/, '')
+ end
+ end
+
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity #:nodoc:
@@ -938,7 +946,7 @@ module ActiveRecord
end
def version
- @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
+ @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
end
def mariadb?
@@ -946,7 +954,7 @@ module ActiveRecord
end
def supports_rename_index?
- mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
+ mariadb? ? false : version >= '5.7.6'
end
def configure_connection
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
index 58715978f7..b2c49989a4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
@@ -12,7 +12,7 @@ module ActiveRecord
end
def sql_type
- super.gsub(/\[\]$/, "")
+ super.gsub(/\[\]$/, "".freeze)
end
def ==(other)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 7c809b088c..358039723f 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -65,18 +65,6 @@ module ActiveRecord
boolean: { name: "boolean" }
}
- class Version
- include Comparable
-
- def initialize(version_string)
- @version = version_string.split('.').map(&:to_i)
- end
-
- def <=>(version_string)
- @version <=> version_string.split('.').map(&:to_i)
- end
- end
-
class StatementPool < ConnectionAdapters::StatementPool
private
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index 8a3c27e6da..0b35027b2b 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -16,8 +16,8 @@ en:
messages:
record_invalid: "Validation failed: %{errors}"
restrict_dependent_destroy:
- one: "Cannot delete record because a dependent %{record} exists"
- many: "Cannot delete record because dependent %{record} exist"
+ has_one: "Cannot delete record because a dependent %{record} exists"
+ has_many: "Cannot delete record because dependent %{record} exist"
# Append your own errors here or at the model/attributes scope.
# You can define own errors for models or model attributes.
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index c942d0e265..a6b76b25bf 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -386,6 +386,9 @@ module ActiveRecord
# then the existing record will be marked for destruction.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
options = self.nested_attributes_options[association_name]
+ if attributes.respond_to?(:permitted?)
+ attributes = attributes.to_h
+ end
attributes = attributes.with_indifferent_access
existing_record = send(association_name)
@@ -442,6 +445,9 @@ module ActiveRecord
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
options = self.nested_attributes_options[association_name]
+ if attributes_collection.respond_to?(:permitted?)
+ attributes_collection = attributes_collection.to_h
+ end
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
@@ -468,6 +474,9 @@ module ActiveRecord
end
attributes_collection.each do |attributes|
+ if attributes.respond_to?(:permitted?)
+ attributes = attributes.to_h
+ end
attributes = attributes.with_indifferent_access
if attributes['id'].blank?
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 86f2c30168..d75ec72b1a 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -18,7 +18,7 @@ module ActiveRecord
delegate = Class.new(klass) {
include ClassSpecificRelation
}
- const_set klass.name.gsub('::', '_'), delegate
+ const_set klass.name.gsub('::'.freeze, '_'.freeze), delegate
cache[klass] = delegate
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 43e9afe853..d26db7d4cf 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -52,7 +52,7 @@ module ActiveRecord
key
else
key = key.to_s
- key.split('.').first if key.include?('.')
+ key.split('.'.freeze).first if key.include?('.'.freeze)
end
end.compact
end
@@ -123,10 +123,10 @@ module ActiveRecord
end
def convert_dot_notation_to_hash(attributes)
- dot_notation = attributes.keys.select { |s| s.include?(".") }
+ dot_notation = attributes.keys.select { |s| s.include?(".".freeze) }
dot_notation.each do |key|
- table_name, column_name = key.split(".")
+ table_name, column_name = key.split(".".freeze)
value = attributes.delete(key)
attributes[table_name] ||= {}
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 3131723828..887d7a5903 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -204,9 +204,8 @@ module ActiveRecord
#
# Note that "TRUNCATE" is also a MySQL DDL statement!
module ClassMethods
- # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
+ # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
def transaction(options = {}, &block)
- # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
connection.transaction(options, &block)
end
@@ -380,6 +379,10 @@ module ActiveRecord
raise ActiveRecord::Rollback unless status
end
status
+ ensure
+ if @transaction_state && @transaction_state.committed?
+ clear_transaction_record_state
+ end
end
protected
diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb
index a296cf9d31..ca476947fc 100644
--- a/activerecord/test/cases/adapters/mysql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb
@@ -12,4 +12,18 @@ class MysqlQuotingTest < ActiveRecord::MysqlTestCase
def test_type_cast_false
assert_equal 0, @conn.type_cast(false)
end
+
+ def test_quoted_date_precision_for_gte_564
+ @conn.stubs(:full_version).returns('5.6.4')
+ @conn.remove_instance_variable(:@version)
+ t = Time.now.change(usec: 1)
+ assert_match(/\.000001\z/, @conn.quoted_date(t))
+ end
+
+ def test_quoted_date_precision_for_lt_564
+ @conn.stubs(:full_version).returns('5.6.3')
+ @conn.remove_instance_variable(:@version)
+ t = Time.now.change(usec: 1)
+ refute_match(/\.000001\z/, @conn.quoted_date(t))
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/quoting_test.rb b/activerecord/test/cases/adapters/mysql2/quoting_test.rb
new file mode 100644
index 0000000000..a49cbba4b4
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/quoting_test.rb
@@ -0,0 +1,21 @@
+require "cases/helper"
+
+class Mysql2QuotingTest < ActiveRecord::Mysql2TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ test 'quoted date precision for gte 5.6.4' do
+ @connection.stubs(:full_version).returns('5.6.4')
+ @connection.remove_instance_variable(:@version)
+ t = Time.now.change(usec: 1)
+ assert_match(/\.000001\z/, @connection.quoted_date(t))
+ end
+
+ test 'quoted date precision for lt 5.6.4' do
+ @connection.stubs(:full_version).returns('5.6.3')
+ @connection.remove_instance_variable(:@version)
+ t = Time.now.change(usec: 1)
+ refute_match(/\.000001\z/, @connection.quoted_date(t))
+ end
+end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index f4bacbea2e..c2ef59d0f7 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1426,6 +1426,26 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert firm.companies.exists?(:name => 'child')
end
+ def test_restrict_with_error_is_deprecated_using_key_many
+ I18n.backend = I18n::Backend::Simple.new
+ I18n.backend.store_translations :en, activerecord: { errors: { messages: { restrict_dependent_destroy: { many: 'message for deprecated key' } } } }
+
+ firm = RestrictedWithErrorFirm.create!(name: 'restrict')
+ firm.companies.create(name: 'child')
+
+ assert !firm.companies.empty?
+
+ assert_deprecated { firm.destroy }
+
+ assert !firm.errors.empty?
+
+ assert_equal 'message for deprecated key', firm.errors[:base].first
+ assert RestrictedWithErrorFirm.exists?(name: 'restrict')
+ assert firm.companies.exists?(name: 'child')
+ ensure
+ I18n.backend.reload!
+ end
+
def test_restrict_with_error
firm = RestrictedWithErrorFirm.create!(:name => 'restrict')
firm.companies.create(:name => 'child')
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index b7cb5a064f..5a8afaf4d2 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -178,6 +178,25 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert firm.account.present?
end
+ def test_restrict_with_error_is_deprecated_using_key_one
+ I18n.backend = I18n::Backend::Simple.new
+ I18n.backend.store_translations :en, activerecord: { errors: { messages: { restrict_dependent_destroy: { one: 'message for deprecated key' } } } }
+
+ firm = RestrictedWithErrorFirm.create!(name: 'restrict')
+ firm.create_account(credit_limit: 10)
+
+ assert_not_nil firm.account
+
+ assert_deprecated { firm.destroy }
+
+ assert !firm.errors.empty?
+ assert_equal 'message for deprecated key', firm.errors[:base].first
+ assert RestrictedWithErrorFirm.exists?(name: 'restrict')
+ assert firm.account.present?
+ ensure
+ I18n.backend.reload!
+ end
+
def test_restrict_with_error
firm = RestrictedWithErrorFirm.create!(:name => 'restrict')
firm.create_account(:credit_limit => 10)
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index eeda9335ad..bd74821d87 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -142,7 +142,7 @@ module ActiveRecord
end
if current_adapter?(:PostgreSQLAdapter)
- test "arrays types can be specified" do
+ test "array types can be specified" do
klass = Class.new(OverloadedType) do
attribute :my_array, :string, limit: 50, array: true
attribute :my_int_array, :integer, array: true
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 676b29ed7c..37ec3f1106 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -67,6 +67,14 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
assert_no_difference_when_adding_callbacks_twice_for Pirate, :parrots
end
+ def test_cyclic_autosaves_do_not_add_multiple_validations
+ ship = ShipWithoutNestedAttributes.new
+ ship.prisoners.build
+
+ assert_not ship.valid?
+ assert_equal 1, ship.errors[:name].length
+ end
+
private
def assert_no_difference_when_adding_callbacks_twice_for(model, association_name)
@@ -1146,6 +1154,13 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_not_load_the_associated_model
assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
end
+
+ def test_mark_for_destruction_is_ignored_without_autosave_true
+ ship = ShipWithoutNestedAttributes.new(name: "The Black Flag")
+ ship.parts.build.mark_for_destruction
+
+ assert_not ship.valid?
+ end
end
class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 1f5055b2a2..922cb59280 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -1,6 +1,7 @@
require 'cases/helper'
require 'models/topic'
require 'models/car'
+require 'models/aircraft'
require 'models/wheel'
require 'models/engine'
require 'models/reply'
@@ -198,4 +199,16 @@ class CounterCacheTest < ActiveRecord::TestCase
assert_equal 2, car.engines_count
assert_equal 2, car.reload.engines_count
end
+
+ test "update counters in a polymorphic relationship" do
+ aircraft = Aircraft.create!
+
+ assert_difference 'aircraft.reload.wheels_count' do
+ aircraft.wheels << Wheel.create!
+ end
+
+ assert_difference 'aircraft.reload.wheels_count', -1 do
+ aircraft.wheels.first.destroy
+ end
+ end
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 12c793c408..d9d0f929db 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -47,7 +47,8 @@ end
def mysql_56?
current_adapter?(:MysqlAdapter, :Mysql2Adapter) &&
- ActiveRecord::Base.connection.send(:version).join(".") >= "5.6.0"
+ ActiveRecord::Base.connection.send(:version) >= '5.6.0' &&
+ ActiveRecord::Base.connection.send(:version) < '5.7.0'
end
def mysql_enforcing_gtid_consistency?
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index dbdcc84b7d..2e1363334d 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -270,7 +270,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
car.wheels << Wheel.create!
end
assert_difference 'car.wheels.count', -1 do
- car.destroy
+ car.reload.destroy
end
assert car.destroyed?
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index d72225f3d3..b8a0401fe3 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -1064,4 +1064,39 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
assert_not part.valid?
assert_equal ["Ship name can't be blank"], part.errors.full_messages
end
+
+ class ProtectedParameters
+ def initialize(hash)
+ @hash = hash
+ end
+
+ def permitted?
+ true
+ end
+
+ def [](key)
+ @hash[key]
+ end
+
+ def to_h
+ @hash
+ end
+ end
+
+ test "strong params style objects can be assigned for singular associations" do
+ params = { name: "Stern", ship_attributes:
+ ProtectedParameters.new(name: "The Black Rock") }
+ part = ShipPart.new(params)
+
+ assert_equal "Stern", part.name
+ assert_equal "The Black Rock", part.ship.name
+ end
+
+ test "strong params style objects can be assigned for collection associations" do
+ params = { trinkets_attributes: ProtectedParameters.new("0" => ProtectedParameters.new(name: "Necklace"), "1" => ProtectedParameters.new(name: "Spoon")) }
+ part = ShipPart.new(params)
+
+ assert_equal "Necklace", part.trinkets[0].name
+ assert_equal "Spoon", part.trinkets[1].name
+ end
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 4e163ca4a6..29a6ec7522 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -175,6 +175,13 @@ class TransactionTest < ActiveRecord::TestCase
assert topic.new_record?, "#{topic.inspect} should be new record"
end
+ def test_transaction_state_is_cleared_when_record_is_persisted
+ author = Author.create! name: 'foo'
+ author.name = nil
+ assert_not author.save
+ assert_not author.new_record?
+ end
+
def test_update_should_rollback_on_failure
author = Author.find(1)
posts_count = author.posts.size
diff --git a/activerecord/test/models/aircraft.rb b/activerecord/test/models/aircraft.rb
index 1f35ef45da..c4404a8094 100644
--- a/activerecord/test/models/aircraft.rb
+++ b/activerecord/test/models/aircraft.rb
@@ -1,4 +1,5 @@
class Aircraft < ActiveRecord::Base
self.pluralize_table_names = false
has_many :engines, :foreign_key => "car_id"
+ has_many :wheels, as: :wheelable
end
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb
index 312caef604..95172e4d3e 100644
--- a/activerecord/test/models/ship.rb
+++ b/activerecord/test/models/ship.rb
@@ -19,6 +19,18 @@ class Ship < ActiveRecord::Base
end
end
+class ShipWithoutNestedAttributes < ActiveRecord::Base
+ self.table_name = "ships"
+ has_many :prisoners, inverse_of: :ship, foreign_key: :ship_id
+ has_many :parts, class_name: "ShipPart", foreign_key: :ship_id
+
+ validates :name, presence: true
+end
+
+class Prisoner < ActiveRecord::Base
+ belongs_to :ship, autosave: true, class_name: "ShipWithoutNestedAttributes", inverse_of: :prisoners
+end
+
class FamousShip < ActiveRecord::Base
self.table_name = 'ships'
belongs_to :famous_pirate
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 6872b49ad9..7bab675b2a 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -37,6 +37,7 @@ ActiveRecord::Schema.define do
create_table :aircraft, force: true do |t|
t.string :name
+ t.integer :wheels_count, default: 0, null: false
end
create_table :articles, force: true do |t|
@@ -678,6 +679,10 @@ ActiveRecord::Schema.define do
t.datetime :updated_at
end
+ create_table :prisoners, force: true do |t|
+ t.belongs_to :ship
+ end
+
create_table :speedometers, force: true, id: false do |t|
t.string :speedometer_id
t.string :name
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 1c69e0eb12..7148f289bb 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,11 @@
+* Fix `TimeWithZone#eql?` to properly handle `TimeWithZone` created from `DateTime`:
+ twz = DateTime.now.in_time_zone
+ twz.eql?(twz.dup) => true
+
+ Fixes #14178.
+
+ *Roque Pinel*
+
* ActiveSupport::HashWithIndifferentAccess `select` and `reject` will now return
enumerator if called without block.
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index 01153606c9..40811dafc0 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -92,15 +92,28 @@ module DateAndTime
end
# Returns a new date/time at the start of the month.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Thu, 18 Jun 2015
+ # today.beginning_of_month # => Mon, 01 Jun 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Thu, 18 Jun 2015 15:23:13 +0000
+ # now.beginning_of_month # => Mon, 01 Jun 2015 00:00:00 +0000
def beginning_of_month
first_hour(change(:day => 1))
end
alias :at_beginning_of_month :beginning_of_month
# Returns a new date/time at the start of the quarter.
- # Example: 1st January, 1st July, 1st October.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.beginning_of_quarter # => Wed, 01 Jul 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000
def beginning_of_quarter
first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
beginning_of_month.change(:month => first_quarter_month)
@@ -108,8 +121,14 @@ module DateAndTime
alias :at_beginning_of_quarter :beginning_of_quarter
# Returns a new date/time at the end of the quarter.
- # Example: 31st March, 30th June, 30th September.
- # DateTime objects will have a time set to 23:59:59.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.end_of_quarter # => Wed, 30 Sep 2015
+ #
+ # +DateTime+ objects will have a time set to 23:59:59.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000
def end_of_quarter
last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
beginning_of_month.change(:month => last_quarter_month).end_of_month
@@ -117,8 +136,14 @@ module DateAndTime
alias :at_end_of_quarter :end_of_quarter
# Return a new date/time at the beginning of the year.
- # Example: 1st January.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.beginning_of_year # => Thu, 01 Jan 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.beginning_of_year # => Thu, 01 Jan 2015 00:00:00 +0000
def beginning_of_year
change(:month => 1).beginning_of_month
end
@@ -138,8 +163,8 @@ module DateAndTime
#
# +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
#
- # now = Time.current # => Thu, 07 May 2015 13:31:16 UTC +00:00
- # now.next_week # => Mon, 11 May 2015 00:00:00 UTC +00:00
+ # now = DateTime.current # => Thu, 07 May 2015 13:31:16 +0000
+ # now.next_week # => Mon, 11 May 2015 00:00:00 +0000
def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false)
result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)))
same_time ? copy_time_to(result) : result
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index d9fb392752..60732eb41a 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -23,7 +23,7 @@ class LoadError
# Returns true if the given path name (except perhaps for the ".rb"
# extension) is the missing file which caused the exception to be raised.
def is_missing?(location)
- location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '')
+ location.sub(/\.rb$/, ''.freeze) == path.sub(/\.rb$/, ''.freeze)
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 9b7a429db9..9dc0dee1bd 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -167,7 +167,7 @@ class Module
''
end
- file, line = caller(1, 1).first.split(':', 2)
+ file, line = caller(1, 1).first.split(':'.freeze, 2)
line = line.to_i
to = to.to_s
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 770c845435..fc6f822969 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -149,7 +149,7 @@ module ActiveSupport #:nodoc:
# Normalize the list of new constants, and add them to the list we will return
new_constants.each do |suffix|
- constants << ([namespace, suffix] - ["Object"]).join("::")
+ constants << ([namespace, suffix] - ["Object"]).join("::".freeze)
end
end
constants
@@ -431,7 +431,7 @@ module ActiveSupport #:nodoc:
# Search for a file in autoload_paths matching the provided suffix.
def search_for_file(path_suffix)
- path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb")
+ path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb".freeze)
autoload_paths.each do |root|
path = File.join(root, path_suffix)
@@ -516,7 +516,7 @@ module ActiveSupport #:nodoc:
if file_path
expanded = File.expand_path(file_path)
- expanded.sub!(/\.rb\z/, '')
+ expanded.sub!(/\.rb\z/, ''.freeze)
if loading.include?(expanded)
raise "Circular dependency detected while autoloading constant #{qualified_name}"
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index be369d21c6..60ef249e37 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -91,10 +91,10 @@ module ActiveSupport
def underscore(camel_cased_word)
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze)
- word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" }
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
- word.tr!("-", "_")
+ word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
+ word.tr!("-".freeze, "_".freeze)
word.downcase!
word
end
@@ -127,9 +127,9 @@ module ActiveSupport
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
- result.sub!(/\A_+/, '')
- result.sub!(/_id\z/, '')
- result.tr!('_', ' ')
+ result.sub!(/\A_+/, ''.freeze)
+ result.sub!(/_id\z/, ''.freeze)
+ result.tr!('_'.freeze, ' '.freeze)
result.gsub!(/([a-z\d]*)/i) do |match|
"#{inflections.acronyms[match] || match.downcase}"
@@ -178,14 +178,14 @@ module ActiveSupport
# classify('calculus') # => "Calculu"
def classify(table_name)
# strip out any leading schema name
- camelize(singularize(table_name.to_s.sub(/.*\./, '')))
+ camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
end
# Replaces underscores with dashes in the string.
#
# dasherize('puni_puni') # => "puni-puni"
def dasherize(underscored_word)
- underscored_word.tr('_', '-')
+ underscored_word.tr('_'.freeze, '-'.freeze)
end
# Removes the module part from the expression in the string.
@@ -248,7 +248,7 @@ module ActiveSupport
# NameError is raised when the name is not in CamelCase or the constant is
# unknown.
def constantize(camel_cased_word)
- names = camel_cased_word.split('::')
+ names = camel_cased_word.split('::'.freeze)
# Trigger a built-in NameError exception including the ill-formed constant in the message.
Object.const_get(camel_cased_word) if names.empty?
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index edea142e82..2c03956672 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -79,7 +79,7 @@ module ActiveSupport
# No more than one of the separator in a row.
parameterized_string.gsub!(/#{re_sep}{2,}/, sep)
# Remove leading/trailing separator.
- parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '')
+ parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, ''.freeze)
end
parameterized_string.downcase
end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 92ab6fe648..c82a13511e 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -82,7 +82,7 @@ module ActiveSupport
def _decrypt(encrypted_message)
cipher = new_cipher
- encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)}
+ encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)}
cipher.decrypt
cipher.key = @secret
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index b2a4404968..64c5232cf4 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -46,7 +46,7 @@ module ActiveSupport
def valid_message?(signed_message)
return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank?
- data, digest = signed_message.split("--")
+ data, digest = signed_message.split("--".freeze)
data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
end
@@ -74,7 +74,7 @@ module ActiveSupport
def verified(signed_message)
if valid_message?(signed_message)
begin
- data = signed_message.split("--")[0]
+ data = signed_message.split("--".freeze)[0]
@serializer.load(decode(data))
rescue ArgumentError => argument_error
return if argument_error.message =~ %r{invalid base64}
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 35efebc65f..f4de4a80d9 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -58,7 +58,7 @@ module ActiveSupport
# Returns a regular expression pattern that matches the passed Unicode
# codepoints.
def self.codepoints_to_pattern(array_of_codepoints) #:nodoc:
- array_of_codepoints.collect{ |e| [e].pack 'U*' }.join('|')
+ array_of_codepoints.collect{ |e| [e].pack 'U*'.freeze }.join('|'.freeze)
end
TRAILERS_PAT = /(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+\Z/u
LEADERS_PAT = /\A(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+/u
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 412c72d27c..f8f1b9ac2c 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -247,7 +247,7 @@ module ActiveSupport
end
def eql?(other)
- utc.eql?(other)
+ other.eql?(utc)
end
def hash
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 477a42114b..ccb7f02331 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -253,10 +253,14 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_eql?
+ assert_equal true, @twz.eql?(@twz.dup)
assert_equal true, @twz.eql?(Time.utc(2000))
assert_equal true, @twz.eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) )
assert_equal false, @twz.eql?( Time.utc(2000, 1, 1, 0, 0, 1) )
assert_equal false, @twz.eql?( DateTime.civil(1999, 12, 31, 23, 59, 59) )
+
+ other_twz = ActiveSupport::TimeWithZone.new(DateTime.now.utc, @time_zone)
+ assert_equal true, other_twz.eql?(other_twz.dup)
end
def test_hash
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 11561c55f9..58ba708a39 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -32,7 +32,7 @@ class TestController < ActionController::Base
include Rails.application.routes.url_helpers
def index
- render text: 'Home'
+ render plain: 'Home'
end
end
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index 66887398b9..1a4b736348 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -31,7 +31,7 @@ class TestController < ActionController::Base
include Rails.application.routes.url_helpers
def index
- render text: 'Home'
+ render plain: 'Home'
end
end
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index e3cfabb327..4b4d70d3ce 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1266,6 +1266,18 @@ class Client < ActiveRecord::Base
end
```
+NOTE: The `default_scope` is also applied while creating/building a record.
+It is not applied while updating a record. E.g.:
+
+```ruby
+class Client < ActiveRecord::Base
+ default_scope { where(active: true) }
+end
+
+Client.new # => #<Client id: nil, active: true>
+Client.unscoped.new # => #<Client id: nil, active: nil>
+```
+
### Merging of scopes
Just like `where` clauses scopes are merged using `AND` conditions.
diff --git a/guides/source/testing.md b/guides/source/testing.md
index d146df3dc6..f40e765242 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -1081,7 +1081,7 @@ Finally we can assert that our response was successful, template was rendered, a
#### Taking it further
-We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editting comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications.
+We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editing comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications.
Testing Your Mailers
--------------------
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
index c3b8ef1181..5f4904fee1 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -28,4 +28,4 @@
<br>
-<%%= link_to 'New <%= human_name %>', new_<%= singular_table_name %>_path %>
+<%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_table_name %>_path %>
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index b083381255..6a1c2faaab 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -36,13 +36,15 @@ group :development, :test do
end
group :development do
+<%- unless options.api? -%>
# Access an IRB console on exception pages or by using <%%= console %> in views
<%- if options.dev? || options.edge? -%>
gem 'web-console', github: 'rails/web-console'
<%- else -%>
gem 'web-console', '~> 2.0'
<%- end -%>
-<%- if spring_install? %>
+<%- end -%>
+<% if spring_install? -%>
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
<% end -%>
diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb
new file mode 100644
index 0000000000..3198e12662
--- /dev/null
+++ b/railties/test/application/per_request_digest_cache_test.rb
@@ -0,0 +1,63 @@
+require 'isolation/abstract_unit'
+require 'rack/test'
+require 'minitest/mock'
+
+require 'action_view'
+require 'active_support/testing/method_call_assertions'
+
+class PerRequestDigestCacheTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include ActiveSupport::Testing::MethodCallAssertions
+ include Rack::Test::Methods
+
+ setup do
+ build_app
+ add_to_config 'config.consider_all_requests_local = true'
+
+ app_file 'app/models/customer.rb', <<-RUBY
+ class Customer < Struct.new(:name, :id)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ resources :customers, only: :index
+ end
+ RUBY
+
+ app_file 'app/controllers/customers_controller.rb', <<-RUBY
+ class CustomersController < ApplicationController
+ def index
+ render [ Customer.new('david', 1), Customer.new('dingus', 2) ]
+ end
+ end
+ RUBY
+
+ app_file 'app/views/customers/_customer.html.erb', <<-RUBY
+ <% cache customer do %>
+ <%= customer.name %>
+ <% end %>
+ RUBY
+
+ require "#{app_path}/config/environment"
+ end
+
+ teardown :teardown_app
+
+ test "digests are reused when rendering the same template twice" do
+ get '/customers'
+ assert_equal 200, last_response.status
+
+ assert_equal [ '8ba099b7749542fe765ff34a6824d548' ], ActionView::Digestor.cache.values
+ assert_equal %w(david dingus), last_response.body.split.map(&:strip)
+ end
+
+ test "template digests are cleared before a request" do
+ assert_called(ActionView::Digestor.cache, :clear) do
+ get '/customers'
+ assert_equal 200, last_response.status
+ end
+ end
+end
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
index 9978ad0da1..998da3ef84 100644
--- a/railties/test/generators/api_app_generator_test.rb
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -38,6 +38,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/gem 'jquery-rails'/, content)
assert_no_match(/gem 'sass-rails'/, content)
assert_no_match(/gem 'jbuilder'/, content)
+ assert_no_match(/gem 'web-console'/, content)
assert_match(/gem 'active_model_serializers'/, content)
end