aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock6
-rw-r--r--actioncable/Rakefile36
-rw-r--r--actionmailer/test/message_delivery_test.rb6
-rw-r--r--actionpack/CHANGELOG.md4
-rw-r--r--actionpack/lib/action_controller/base.rb8
-rw-r--r--actionpack/lib/action_controller/metal.rb4
-rw-r--r--actionpack/lib/action_controller/metal/etag_with_flash.rb4
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb14
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb2
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb6
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb2
-rw-r--r--actionpack/lib/action_controller/metal/live.rb8
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb4
-rw-r--r--actionpack/lib/action_controller/metal/parameter_encoding.rb2
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb8
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb4
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb10
-rw-r--r--actionpack/lib/action_controller/metal/rescue.rb2
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb16
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb2
-rw-r--r--actionpack/lib/action_controller/railtie.rb2
-rw-r--r--actionpack/lib/action_controller/renderer.rb4
-rw-r--r--actionpack/lib/action_controller/test_case.rb10
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb3
-rw-r--r--actionpack/lib/action_dispatch/system_test_case.rb2
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb39
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb11
-rw-r--r--actionpack/test/dispatch/routing_test.rb61
-rw-r--r--actionview/CHANGELOG.md23
-rw-r--r--activejob/CHANGELOG.md4
-rw-r--r--activejob/lib/active_job/queue_adapters/test_adapter.rb11
-rw-r--r--activejob/test/cases/test_helper_test.rb11
-rw-r--r--activemodel/lib/active_model/type.rb10
-rw-r--r--activerecord/CHANGELOG.md19
-rw-r--r--activerecord/lib/active_record/associations.rb4
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb79
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb33
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb146
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb43
-rw-r--r--activerecord/lib/active_record/core.rb1
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb8
-rw-r--r--activerecord/lib/active_record/type/serialized.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb6
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb20
-rw-r--r--activerecord/test/cases/dirty_test.rb3
-rw-r--r--activerecord/test/cases/locking_test.rb34
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb70
-rw-r--r--activesupport/CHANGELOG.md41
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/compatibility.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/time/compatibility.rb11
-rw-r--r--activesupport/lib/active_support/duration.rb88
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb5
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb13
-rw-r--r--activesupport/test/core_ext/date_and_time_compatibility_test.rb166
-rw-r--r--activesupport/test/core_ext/duration_test.rb142
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb29
-rw-r--r--guides/source/active_record_querying.md3
-rw-r--r--guides/source/active_record_validations.md3
-rw-r--r--guides/source/asset_pipeline.md2
-rw-r--r--guides/source/debugging_rails_applications.md1
-rw-r--r--railties/CHANGELOG.md8
-rw-r--r--railties/lib/rails/commands/destroy/destroy_command.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile2
-rw-r--r--railties/test/commands/secrets_test.rb13
-rw-r--r--railties/test/generators/app_generator_test.rb14
-rw-r--r--railties/test/generators/scaffold_generator_test.rb55
79 files changed, 1029 insertions, 489 deletions
diff --git a/Gemfile b/Gemfile
index a0da9d391f..6f0bc534df 100644
--- a/Gemfile
+++ b/Gemfile
@@ -14,7 +14,7 @@ gem "rake", ">= 11.1"
# be loaded after loading the test library.
gem "mocha", "~> 0.14", require: false
-gem "capybara", "~> 2.7.0"
+gem "capybara", "~> 2.13.0"
gem "rack-cache", "~> 1.2"
gem "jquery-rails"
@@ -84,6 +84,7 @@ group :cable do
gem "blade", require: false, platforms: [:ruby]
gem "blade-sauce_labs_plugin", require: false, platforms: [:ruby]
+ gem "sprockets-export", require: false
end
# Add your own local bundler stuff.
diff --git a/Gemfile.lock b/Gemfile.lock
index 74f76f9e7c..580333f6e6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -120,7 +120,7 @@ GEM
bunny (2.6.2)
amq-protocol (>= 2.0.1)
byebug (9.0.6)
- capybara (2.7.1)
+ capybara (2.13.0)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
@@ -319,6 +319,7 @@ GEM
sprockets (3.7.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
+ sprockets-export (0.9.1)
sprockets-rails (3.2.0)
actionpack (>= 4.0)
activesupport (>= 4.0)
@@ -376,7 +377,7 @@ DEPENDENCIES
blade
blade-sauce_labs_plugin
byebug
- capybara (~> 2.7.0)
+ capybara (~> 2.13.0)
coffee-rails
dalli (>= 2.2.1)
delayed_job
@@ -414,6 +415,7 @@ DEPENDENCIES
sequel
sidekiq
sneakers
+ sprockets-export
sqlite3 (~> 1.3.6)
stackprof
sucker_punch
diff --git a/actioncable/Rakefile b/actioncable/Rakefile
index 87d443919c..bda8c7b6c8 100644
--- a/actioncable/Rakefile
+++ b/actioncable/Rakefile
@@ -1,12 +1,13 @@
require "rake/testtask"
require "pathname"
+require "open3"
require "action_cable"
dir = File.dirname(__FILE__)
task default: :test
-task package: "assets:compile"
+task package: %w( assets:compile assets:verify )
Rake::TestTask.new do |t|
t.libs << "test"
@@ -37,6 +38,39 @@ namespace :assets do
desc "Compile Action Cable assets"
task :compile do
require "blade"
+ require "sprockets"
+ require "sprockets/export"
Blade.build
end
+
+ desc "Verify compiled Action Cable assets"
+ task :verify do
+ file = "lib/assets/compiled/action_cable.js"
+ pathname = Pathname.new("#{dir}/#{file}")
+
+ print "[verify] #{file} exists "
+ if pathname.exist?
+ puts "[OK]"
+ else
+ $stderr.puts "[FAIL]"
+ fail
+ end
+
+ print "[verify] #{file} is a UMD module "
+ if pathname.read =~ /module\.exports.*define\.amd/m
+ puts "[OK]"
+ else
+ $stderr.puts "[FAIL]"
+ fail
+ end
+
+ print "[verify] #{dir} can be required as a module "
+ stdout, stderr, status = Open3.capture3("node", "--print", "window = {}; require('#{dir}');")
+ if status.success?
+ puts "[OK]"
+ else
+ $stderr.puts "[FAIL]\n#{stderr}"
+ fail
+ end
+ end
end
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index f4c4f43bdc..c0683be94d 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -76,14 +76,14 @@ class MessageDeliveryTest < ActiveSupport::TestCase
test "should enqueue a delivery with a delay" do
travel_to Time.new(2004, 11, 24, 01, 04, 44) do
- assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f + 600, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
- @mail.deliver_later wait: 600.seconds
+ assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current + 10.minutes, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
+ @mail.deliver_later wait: 10.minutes
end
end
end
test "should enqueue a delivery at a specific time" do
- later_time = Time.now.to_f + 3600
+ later_time = Time.current + 1.hour
assert_performed_with(job: ActionMailer::DeliveryJob, at: later_time, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
@mail.deliver_later wait_until: later_time
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index a0cddbe709..d0662bdae2 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,6 +1,4 @@
-* Fix `ActionController::Renderer#with_defaults` does not work
-
- Fixes `NameError: undefined local variable or method `env'`.
+* Fix `NameError` raised in `ActionController::Renderer#with_defaults`
*Hiroyuki Ishii*
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index ca8066cd82..0fe0853da3 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -8,7 +8,7 @@ module ActionController
# on the controller, which will automatically be made accessible to the web-server through \Rails Routes.
#
# By default, only the ApplicationController in a \Rails application inherits from <tt>ActionController::Base</tt>. All other
- # controllers in turn inherit from ApplicationController. This gives you one class to configure things such as
+ # controllers inherit from ApplicationController. This gives you one class to configure things such as
# request forgery protection and filtering of sensitive request parameters.
#
# A sample controller could look like this:
@@ -30,7 +30,7 @@ module ActionController
#
# Unlike index, the create action will not render a template. After performing its main purpose (creating a
# new post), it initiates a redirect instead. This redirect works by returning an external
- # "302 Moved" HTTP response that takes the user to the index action.
+ # <tt>302 Moved</tt> HTTP response that takes the user to the index action.
#
# These two methods represent the two basic action archetypes used in Action Controllers: Get-and-show and do-and-redirect.
# Most actions are variations on these themes.
@@ -59,7 +59,7 @@ module ActionController
# <input type="text" name="post[name]" value="david">
# <input type="text" name="post[address]" value="hyacintvej">
#
- # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
+ # A request coming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
# If the address input had been named <tt>post[address][street]</tt>, the <tt>params</tt> would have included
# <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
#
@@ -74,7 +74,7 @@ module ActionController
#
# session[:person] = Person.authenticate(user_name, password)
#
- # And retrieved again through the same hash:
+ # You can retrieve it again through the same hash:
#
# Hello #{session[:person]}
#
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 337718afc0..74c4153cd2 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -138,7 +138,7 @@ module ActionController
false
end
- # Delegates to the class' <tt>controller_name</tt>
+ # Delegates to the class' <tt>controller_name</tt>.
def controller_name
self.class.controller_name
end
@@ -244,7 +244,7 @@ module ActionController
end
end
- # Direct dispatch to the controller. Instantiates the controller, then
+ # Direct dispatch to the controller. Instantiates the controller, then
# executes the action named +name+.
def self.dispatch(name, req, res)
if middleware_stack.any?
diff --git a/actionpack/lib/action_controller/metal/etag_with_flash.rb b/actionpack/lib/action_controller/metal/etag_with_flash.rb
index 474d75f02e..7bd338bd7c 100644
--- a/actionpack/lib/action_controller/metal/etag_with_flash.rb
+++ b/actionpack/lib/action_controller/metal/etag_with_flash.rb
@@ -1,9 +1,9 @@
module ActionController
# When you're using the flash, it's generally used as a conditional on the view.
# This means the content of the view depends on the flash. Which in turn means
- # that the etag for a response should be computed with the content of the flash
+ # that the ETag for a response should be computed with the content of the flash
# in mind. This does that by including the content of the flash as a component
- # in the etag that's generated for a response.
+ # in the ETag that's generated for a response.
module EtagWithFlash
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index 9d43e752ac..73e67573ca 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -2,17 +2,17 @@ require "active_support/core_ext/hash/except"
require "active_support/core_ext/hash/slice"
module ActionController
- # This module provides a method which will redirect the browser to use HTTPS
- # protocol. This will ensure that user's sensitive information will be
+ # This module provides a method which will redirect the browser to use the secured HTTPS
+ # protocol. This will ensure that users' sensitive information will be
# transferred safely over the internet. You _should_ always force the browser
# to use HTTPS when you're transferring sensitive information such as
# user authentication, account information, or credit card information.
#
# Note that if you are really concerned about your application security,
# you might consider using +config.force_ssl+ in your config file instead.
- # That will ensure all the data transferred via HTTPS protocol and prevent
- # the user from getting their session hijacked when accessing the site over
- # unsecured HTTP protocol.
+ # That will ensure all the data is transferred via HTTPS, and will
+ # prevent the user from getting their session hijacked when accessing the
+ # site over unsecured HTTP protocol.
module ForceSSL
extend ActiveSupport::Concern
include AbstractController::Callbacks
@@ -23,7 +23,7 @@ module ActionController
module ClassMethods
# Force the request to this particular controller or specified actions to be
- # under HTTPS protocol.
+ # through the HTTPS protocol.
#
# If you need to disable this for any reason (e.g. development) then you can use
# an +:if+ or +:unless+ condition.
@@ -71,7 +71,7 @@ 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 &
+ # * <tt>host_or_options</tt> - Either a host name or any of the url and
# redirect options available to the <tt>force_ssl</tt> method.
def force_ssl_redirect(host_or_options = nil)
unless request.ssl?
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 0575360068..d8bc895265 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -445,7 +445,7 @@ module ActionController
end
end
- # Parses the token and options out of the token authorization header.
+ # Parses the token and options out of the token Authorization header.
# The value for the Authorization header is expected to have the prefix
# <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
# Authorization: Token token="abc", nonce="def"
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index dde924e682..eeb27f99f4 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -2,11 +2,11 @@ module ActionController
# Handles implicit rendering for a controller action that does not
# explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
#
- # For API controllers, the implicit response is always 204 No Content.
+ # For API controllers, the implicit response is always <tt>204 No Content</tt>.
#
# For all other controllers, we use these heuristics to decide whether to
# render a template, raise an error for a missing template, or respond with
- # 204 No Content:
+ # <tt>204 No Content</tt>:
#
# First, if we DO find a template, it's rendered. Template lookup accounts
# for the action name, locales, format, variant, template handlers, and more
@@ -23,7 +23,7 @@ module ActionController
# <tt>ActionView::UnknownFormat</tt> with an explanation.
#
# Finally, if we DON'T find a template AND the request isn't a browser page
- # load, then we implicitly respond with 204 No Content.
+ # load, then we implicitly respond with <tt>204 No Content</tt>.
module ImplicitRender
# :stopdoc:
include BasicImplicitRender
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 924686218f..2485d27cec 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -3,7 +3,7 @@ require "abstract_controller/logger"
module ActionController
# Adds instrumentation to several ends in ActionController::Base. It also provides
- # some hooks related with process_action, this allows an ORM like Active Record
+ # some hooks related with process_action. This allows an ORM like Active Record
# and/or DataMapper to plug in ActionController and show related information.
#
# Check ActiveRecord::Railties::ControllerRuntime for an example.
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index fed99e6c82..a607ee2309 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -239,8 +239,8 @@ module ActionController
error = nil
# This processes the action in a child thread. It lets us return the
- # response code and headers back up the rack stack, and still process
- # the body in parallel with sending data to the client
+ # response code and headers back up the Rack stack, and still process
+ # the body in parallel with sending data to the client.
new_controller_thread {
ActiveSupport::Dependencies.interlock.running do
t2 = Thread.current
@@ -278,9 +278,9 @@ module ActionController
raise error if error
end
- # Spawn a new thread to serve up the controller in. This is to get
+ # Spawn a new thread to serve up the controller in. This is to get
# around the fact that Rack isn't based around IOs and we need to use
- # a thread to stream data from the response bodies. Nobody should call
+ # a thread to stream data from the response bodies. Nobody should call
# this method except in Rails internals. Seriously!
def new_controller_thread # :nodoc:
Thread.new {
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index f6aabcb102..7b4c7b923e 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -181,8 +181,8 @@ module ActionController #:nodoc:
#
# request.variant = [:tablet, :phone]
#
- # which will work similarly to formats and MIME types negotiation. If there will be no
- # +:tablet+ variant declared, +:phone+ variant will be picked:
+ # This will work similarly to formats and MIME types negotiation. If there
+ # is no +:tablet+ variant declared, +:phone+ variant will be picked:
#
# respond_to do |format|
# format.html.none
diff --git a/actionpack/lib/action_controller/metal/parameter_encoding.rb b/actionpack/lib/action_controller/metal/parameter_encoding.rb
index 962532ff09..ecc691619e 100644
--- a/actionpack/lib/action_controller/metal/parameter_encoding.rb
+++ b/actionpack/lib/action_controller/metal/parameter_encoding.rb
@@ -39,7 +39,7 @@ module ActionController
# end
#
# The show action in the above controller would have all parameter values
- # encoded as ASCII-8BIT. This is useful in the case where an application
+ # encoded as ASCII-8BIT. This is useful in the case where an application
# must handle data but encoding of the data is unknown, like file system data.
def skip_parameter_encoding(action)
@_parameter_encodings[action.to_s] = true
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 7fc898f034..3cca5e8906 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -213,7 +213,7 @@ module ActionController
end
# Sets the default wrapper key or model which will be used to determine
- # wrapper key and attribute names. Will be called automatically when the
+ # wrapper key and attribute names. Called automatically when the
# module is inherited.
def inherited(klass)
if klass._wrapper_options.format.any?
@@ -225,7 +225,7 @@ module ActionController
end
end
- # Performs parameters wrapping upon the request. Will be called automatically
+ # Performs parameters wrapping upon the request. Called automatically
# by the metal call stack.
def process_action(*args)
if _wrapper_enabled?
@@ -238,11 +238,11 @@ module ActionController
wrapped_keys = request.request_parameters.keys
wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
- # This will make the wrapped hash accessible from controller and view
+ # This will make the wrapped hash accessible from controller and view.
request.parameters.merge! wrapped_hash
request.request_parameters.merge! wrapped_hash
- # This will display the wrapped hash in the log file
+ # This will display the wrapped hash in the log file.
request.filtered_parameters.merge! wrapped_filtered_hash
end
super
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 1836a07d4e..fdfe82f96b 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -22,7 +22,7 @@ module ActionController
# redirect_to posts_url
# redirect_to proc { edit_post_url(@post) }
#
- # The redirection happens as a "302 Found" header unless otherwise specified using the <tt>:status</tt> option:
+ # The redirection happens as a <tt>302 Found</tt> header unless otherwise specified using the <tt>:status</tt> option:
#
# redirect_to post_url(@post), status: :found
# redirect_to action: 'atom', status: :moved_permanently
@@ -36,7 +36,7 @@ module ActionController
# If you are using XHR requests other than GET or POST and redirecting after the
# request then some browsers will follow the redirect using the original request
# method. This may lead to undesirable behavior such as a double DELETE. To work
- # around this you can return a <tt>303 See Other</tt> status code which will be
+ # around this you can return a <tt>303 See Other</tt> status code which will be
# followed using a GET request.
#
# redirect_to posts_url, status: :see_other
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 6b17719381..67f207afc2 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -36,7 +36,7 @@ module ActionController
super
end
- # Overwrite render_to_string because body can now be set to a rack body.
+ # Overwrite render_to_string because body can now be set to a Rack body.
def render_to_string(*)
result = super
if result.respond_to?(:each)
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index e8965a6561..d9a8b9c12d 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -262,9 +262,9 @@ module ActionController #:nodoc:
# Returns true or false if a request is verified. Checks:
#
- # * Is it a GET or HEAD request? Gets should be safe and idempotent
+ # * Is it a GET or HEAD request? GETs should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the params?
- # * Does the X-CSRF-Token header match the form_authenticity_token
+ # * Does the X-CSRF-Token header match the form_authenticity_token?
def verified_request? # :doc:
!protect_against_forgery? || request.get? || request.head? ||
(valid_request_origin? && any_authenticity_token_valid?)
@@ -327,7 +327,7 @@ module ActionController #:nodoc:
if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
# This is actually an unmasked token. This is expected if
# you have just upgraded to masked tokens, but should stop
- # happening shortly after installing this gem
+ # happening shortly after installing this gem.
compare_with_real_token masked_token, session
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
@@ -336,13 +336,13 @@ module ActionController #:nodoc:
compare_with_real_token(csrf_token, session) ||
valid_per_form_csrf_token?(csrf_token, session)
else
- false # Token is malformed
+ false # Token is malformed.
end
end
def unmask_token(masked_token) # :doc:
# Split the token into the one-time pad and the encrypted
- # value and decrypt it
+ # value and decrypt it.
one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
xor_byte_strings(one_time_pad, encrypted_csrf_token)
diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb
index 2d99e4045b..25757938f5 100644
--- a/actionpack/lib/action_controller/metal/rescue.rb
+++ b/actionpack/lib/action_controller/metal/rescue.rb
@@ -10,7 +10,7 @@ module ActionController #:nodoc:
# exceptions must be shown. This method is only called when
# consider_all_requests_local is false. By default, it returns
# false, but someone may set it to `request.local?` so local
- # requests in production still shows the detailed exception pages.
+ # requests in production still show the detailed exception pages.
def show_detailed_exceptions?
false
end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 877a08b222..58cf60ad2a 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -3,7 +3,7 @@ require "rack/chunked"
module ActionController #:nodoc:
# Allows views to be streamed back to the client as they are rendered.
#
- # The default way Rails renders views is by first rendering the template
+ # By default, Rails renders views by first rendering the template
# and then the layout. The response is sent to the client after the whole
# template is rendered, all queries are made, and the layout is processed.
#
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 0217376ab3..1190e0ed69 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -262,7 +262,7 @@ module ActionController
alias_method :to_unsafe_hash, :to_unsafe_h
# Convert all hashes in values into parameters, then yield each pair in
- # the same way as <tt>Hash#each_pair</tt>
+ # the same way as <tt>Hash#each_pair</tt>.
def each_pair(&block)
@parameters.each_pair do |key, value|
yield key, convert_hashes_to_parameters(key, value)
@@ -410,7 +410,7 @@ module ActionController
#
# params.permit(preferences: {})
#
- # but be careful because this opens the door to arbitrary input. In this
+ # Be careful because this opens the door to arbitrary input. In this
# case, +permit+ ensures values in the returned structure are permitted
# scalars and filters out anything else.
#
@@ -646,15 +646,15 @@ module ActionController
end
# Returns a new <tt>ActionController::Parameters</tt> with all keys from
- # +other_hash+ merges into current hash.
+ # +other_hash+ merged into current hash.
def merge(other_hash)
new_instance_with_inherited_permitted_status(
@parameters.merge(other_hash.to_h)
)
end
- # Returns current <tt>ActionController::Parameters</tt> instance which
- # +other_hash+ merges into current hash.
+ # Returns current <tt>ActionController::Parameters</tt> instance with
+ # +other_hash+ merged into current hash.
def merge!(other_hash)
@parameters.merge!(other_hash.to_h)
self
@@ -715,7 +715,7 @@ module ActionController
undef_method :to_param
- # Returns duplicate of object including all parameters
+ # Returns duplicate of object including all parameters.
def deep_dup
self.class.new(@parameters.deep_dup).tap do |duplicate|
duplicate.permitted = @permitted
@@ -935,7 +935,7 @@ module ActionController
# whitelisted.
#
# In addition, parameters can be marked as required and flow through a
- # predefined raise/rescue flow to end up as a 400 Bad Request with no
+ # predefined raise/rescue flow to end up as a <tt>400 Bad Request</tt> with no
# effort.
#
# class PeopleController < ActionController::Base
@@ -959,7 +959,7 @@ module ActionController
#
# private
# # Using a private method to encapsulate the permissible parameters is
- # # just a good pattern since you'll be able to reuse the same permit
+ # # a good pattern since you'll be able to reuse the same permit
# # list between create and update. Also, you can specialize this method
# # with per-user checking of permissible attributes.
# def person_params
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 9f3cc099d6..21ed5b4ec8 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -3,7 +3,7 @@ module ActionController
# the <tt>_routes</tt> method. Otherwise, an exception will be raised.
#
# In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
- # url options like the +host+. In order to do so, this module requires the host class
+ # URL options like the +host+. In order to do so, this module requires the host class
# to implement +env+ which needs to be Rack-compatible and +request+
# which is either an instance of +ActionDispatch::Request+ or an object
# that responds to the +host+, +optional_port+, +protocol+ and
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index a7cdfe6a98..fadfc8de60 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -42,7 +42,7 @@ module ActionController
options.javascripts_dir ||= paths["public/javascripts"].first
options.stylesheets_dir ||= paths["public/stylesheets"].first
- # Ensure readers methods get compiled
+ # Ensure readers methods get compiled.
options.asset_host ||= app.config.asset_host
options.relative_url_root ||= app.config.relative_url_root
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
index 3d2edb5242..cbb719d8b2 100644
--- a/actionpack/lib/action_controller/renderer.rb
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -5,7 +5,7 @@ module ActionController
# without requirement of being in controller actions.
#
# You get a concrete renderer class by invoking ActionController::Base#renderer.
- # For example,
+ # For example:
#
# ApplicationController.renderer
#
@@ -18,7 +18,7 @@ module ActionController
# ApplicationController.render template: '...'
#
# #render allows you to use the same options that you can use when rendering in a controller.
- # For example,
+ # For example:
#
# FooController.render :action, locals: { ... }, assigns: { ... }
#
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 7229c67f30..72e29c2c9d 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -13,10 +13,10 @@ module ActionController
end
module Live
- # Disable controller / rendering threads in tests. User tests can access
+ # Disable controller / rendering threads in tests. User tests can access
# the database on the main thread, so they could open a txn, then the
# controller thread will open a new connection and try to access data
- # that's only visible to the main thread's txn. This is the problem in #23483
+ # that's only visible to the main thread's txn. This is the problem in #23483.
remove_method :new_controller_thread
def new_controller_thread # :nodoc:
yield
@@ -35,7 +35,7 @@ module ActionController
attr_reader :controller_class
- # Create a new test request with default `env` values
+ # Create a new test request with default `env` values.
def self.create(controller_class)
env = {}
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
@@ -131,7 +131,7 @@ module ActionController
include Rack::Test::Utils
def should_multipart?(params)
- # FIXME: lifted from Rack-Test. We should push this separation upstream
+ # FIXME: lifted from Rack-Test. We should push this separation upstream.
multipart = false
query = lambda { |value|
case value
@@ -300,7 +300,7 @@ module ActionController
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
# assert flash.empty? # makes sure that there's nothing in the flash
#
- # On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
+ # On top of the collections, you have the complete URL that a given action redirected to available in <tt>redirect_to_url</tt>.
#
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
# action call which can then be asserted against.
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index c4719f8a71..118dec2ad4 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -860,8 +860,7 @@ module ActionDispatch
params[key] = URI.parser.unescape(value)
end
end
- old_params = req.path_parameters
- req.path_parameters = old_params.merge params
+ req.path_parameters = params
app = route.app
if app.matches?(req) && app.dispatcher?
begin
diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb
index 9cd4df33ef..f908607e50 100644
--- a/actionpack/lib/action_dispatch/system_test_case.rb
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -1,4 +1,5 @@
require "capybara/dsl"
+require "capybara/minitest"
require "action_controller"
require "action_dispatch/system_testing/driver"
require "action_dispatch/system_testing/server"
@@ -80,6 +81,7 @@ module ActionDispatch
# tests as long as you include the required gems and files.
class SystemTestCase < IntegrationTest
include Capybara::DSL
+ include Capybara::Minitest::Assertions
include SystemTesting::TestHelpers::SetupAndTeardown
include SystemTesting::TestHelpers::ScreenshotHelper
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
index 6de8fb74dc..3078e035a3 100644
--- a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
@@ -8,9 +8,20 @@ module ActionDispatch
# +take_screenshot+ can be used at any point in your system tests to take
# a screenshot of the current state. This can be useful for debugging or
# automating visual testing.
+ #
+ # The screenshot will be displayed in your console, if supported.
+ #
+ # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ environment variable to
+ # control the output. Possible values are:
+ # * [+inline+ (default)] display the screenshot in the terminal using the
+ # iTerm image protocol (http://iterm2.com/documentation-images.html).
+ # * [+simple+] only display the screenshot path.
+ # This is the default value if the +CI+ environment variables
+ # is defined.
+ # * [+artifact+] display the screenshot in the terminal, using the terminal
+ # artifact format (http://buildkite.github.io/terminal/inline-images/).
def take_screenshot
save_image
- puts "[Screenshot]: #{image_path}"
puts display_image
end
@@ -38,14 +49,32 @@ module ActionDispatch
page.save_screenshot(Rails.root.join(image_path))
end
+ def output_type
+ # Environment variables have priority
+ output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] || ENV["CAPYBARA_INLINE_SCREENSHOT"]
+
+ # If running in a CI environment, default to simple
+ output_type ||= "simple" if ENV["CI"]
+
+ # Default
+ output_type ||= "inline"
+
+ output_type
+ end
+
def display_image
- if ENV["CAPYBARA_INLINE_SCREENSHOT"] == "artifact"
- "\e]1338;url=artifact://#{image_path}\a"
- else
+ message = "[Screenshot]: #{image_path}\n"
+
+ case output_type
+ when "artifact"
+ message << "\e]1338;url=artifact://#{image_path}\a\n"
+ when "inline"
name = inline_base64(File.basename(image_path))
image = inline_base64(File.read(image_path))
- "\e]1337;File=name=#{name};height=400px;inline=1:#{image}\a"
+ message << "\e]1337;File=name=#{name};height=400px;inline=1:#{image}\a\n"
end
+
+ message
end
def inline_base64(path)
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 5fa0b727ab..b46a97413a 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -192,11 +192,10 @@ module ActionDispatch
# HTTP methods in integration tests. +#process+ is only required when using a
# request method that doesn't have a method defined in the integration tests.
#
- # This method returns a Response object, which one can use to
- # inspect the details of the response. Furthermore, if this method was
- # called from an ActionDispatch::IntegrationTest object, then that
- # object's <tt>@response</tt> instance variable will point to the same
- # response object.
+ # This method returns the response status, after performing the request.
+ # Furthermore, if this method was called from an ActionDispatch::IntegrationTest object,
+ # then that object's <tt>@response</tt> instance variable will point to a Response object
+ # which one can use to inspect the details of the response.
#
# Example:
# process :get, '/author', params: { since: 201501011400 }
@@ -572,7 +571,7 @@ module ActionDispatch
# end
#
# assert_response :success
- # assert_equal({ id: Arcticle.last.id, title: "Ahoy!" }, response.parsed_body)
+ # assert_equal({ id: Article.last.id, title: "Ahoy!" }, response.parsed_body)
# end
# end
#
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index d563df91df..64818e6ca1 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -4962,3 +4962,64 @@ class FlashRedirectTest < ActionDispatch::IntegrationTest
assert_equal "bar", response.body
end
end
+
+class TestRecognizePath < ActionDispatch::IntegrationTest
+ class PageConstraint
+ attr_reader :key, :pattern
+
+ def initialize(key, pattern)
+ @key = key
+ @pattern = pattern
+ end
+
+ def matches?(request)
+ request.path_parameters[key] =~ pattern
+ end
+ end
+
+ stub_controllers do |routes|
+ Routes = routes
+ routes.draw do
+ get "/hash/:foo", to: "pages#show", constraints: { foo: /foo/ }
+ get "/hash/:bar", to: "pages#show", constraints: { bar: /bar/ }
+
+ get "/proc/:foo", to: "pages#show", constraints: proc { |r| r.path_parameters[:foo] =~ /foo/ }
+ get "/proc/:bar", to: "pages#show", constraints: proc { |r| r.path_parameters[:bar] =~ /bar/ }
+
+ get "/class/:foo", to: "pages#show", constraints: PageConstraint.new(:foo, /foo/)
+ get "/class/:bar", to: "pages#show", constraints: PageConstraint.new(:bar, /bar/)
+ end
+ end
+
+ APP = build_app Routes
+ def app
+ APP
+ end
+
+ def test_hash_constraints_dont_leak_between_routes
+ expected_params = { controller: "pages", action: "show", bar: "bar" }
+ actual_params = recognize_path("/hash/bar")
+
+ assert_equal expected_params, actual_params
+ end
+
+ def test_proc_constraints_dont_leak_between_routes
+ expected_params = { controller: "pages", action: "show", bar: "bar" }
+ actual_params = recognize_path("/proc/bar")
+
+ assert_equal expected_params, actual_params
+ end
+
+ def test_class_constraints_dont_leak_between_routes
+ expected_params = { controller: "pages", action: "show", bar: "bar" }
+ actual_params = recognize_path("/class/bar")
+
+ assert_equal expected_params, actual_params
+ end
+
+ private
+
+ def recognize_path(*args)
+ Routes.recognize_path(*args)
+ end
+end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index f2ae74454a..6e71809385 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,7 +1,9 @@
* Remove the option `encode_special_chars` misnomer from `strip_tags`
- As of rails-html-sanitizer v1.0.3 sanitizer will ignore the
- `encode_special_chars` option. Fixes #28060.
+ As of rails-html-sanitizer v1.0.3, the sanitizer will ignore the
+ `encode_special_chars` option.
+
+ Fixes #28060.
*Andrew Hood*
@@ -97,6 +99,23 @@
*Peter Schilling*, *Matthew Draper*
+* Add `:skip_pipeline` option to several asset tag helpers
+
+ `javascript_include_tag`, `stylesheet_link_tag`, `favicon_link_tag`,
+ `image_tag` and `audio_tag` now accept a `:skip_pipeline` option which can
+ be set to true to bypass the asset pipeline and serve the assets from the
+ public folder.
+
+ *Richard Schneeman*
+
+* Add `:poster_skip_pipeline` option to the `video_tag` helper
+
+ `video_tag` now accepts a `:poster_skip_pipeline` option which can be used
+ in combination with the `:poster` option to bypass the asset pipeline and
+ serve the poster image for the video from the public folder.
+
+ *Richard Schneeman*
+
* Show cache hits and misses when rendering partials.
Partials using the `cache` helper will show whether a render hit or missed
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index d561745611..41505fbf83 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -6,10 +6,6 @@
*Yuji Yaginuma*
-* Push skipped jobs to `enqueued_jobs` when using `perform_enqueued_jobs` with a `only` filter in tests
-
- *Alexander Pauly*
-
* Removed deprecated support to passing the adapter class to `.queue_adapter`.
*Rafael Mendonça França*
diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb
index ec825f12cd..1b633b210e 100644
--- a/activejob/lib/active_job/queue_adapters/test_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb
@@ -24,27 +24,30 @@ module ActiveJob
end
def enqueue(job) #:nodoc:
+ return if filtered?(job)
+
job_data = job_to_hash(job)
enqueue_or_perform(perform_enqueued_jobs, job, job_data)
end
def enqueue_at(job, timestamp) #:nodoc:
+ return if filtered?(job)
+
job_data = job_to_hash(job, at: timestamp)
enqueue_or_perform(perform_enqueued_at_jobs, job, job_data)
end
private
-
def job_to_hash(job, extras = {})
{ job: job.class, args: job.serialize.fetch("arguments"), queue: job.queue_name }.merge!(extras)
end
def enqueue_or_perform(perform, job, job_data)
- if !perform || filtered?(job)
- enqueued_jobs << job_data
- else
+ if perform
performed_jobs << job_data
Base.execute job.serialize
+ else
+ enqueued_jobs << job_data
end
end
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 2e6357f824..81e75b4374 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -56,17 +56,6 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
- def test_assert_enqueued_jobs_when_performing_with_only_option
- assert_nothing_raised do
- assert_enqueued_jobs 1, only: HelloJob do
- perform_enqueued_jobs only: LoggingJob do
- HelloJob.perform_later("sean")
- LoggingJob.perform_later("yves")
- end
- end
- end
- end
-
def test_assert_no_enqueued_jobs_with_no_block
assert_nothing_raised do
assert_no_enqueued_jobs
diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb
index b8e6d2376b..095801d8f0 100644
--- a/activemodel/lib/active_model/type.rb
+++ b/activemodel/lib/active_model/type.rb
@@ -21,16 +21,8 @@ module ActiveModel
class << self
attr_accessor :registry # :nodoc:
- delegate :add_modifier, to: :registry
- # Add a new type to the registry, allowing it to be referenced as a
- # symbol by ActiveRecord::Attributes::ClassMethods#attribute. If your
- # type is only meant to be used with a specific database adapter, you can
- # do so by passing +adapter: :postgresql+. If your type has the same
- # name as a native type for the current adapter, an exception will be
- # raised unless you specify an +:override+ option. +override: true+ will
- # cause your type to be used instead of the native type. +override:
- # false+ will cause the native type to be used over yours if one exists.
+ # Add a new type to the registry, allowing it to be get through ActiveModel::Type#lookup
def register(type_name, klass = nil, **options, &block)
registry.register(type_name, klass, **options, &block)
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 96094a285f..6a095603fa 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,17 @@
+* Add `:default` option to `belongs_to`.
+
+ Use it to specify that an association should be initialized with a particular
+ record before validation. For example:
+
+ # Before
+ belongs_to :account
+ before_validation -> { self.account ||= Current.account }
+
+ # After
+ belongs_to :account, default: -> { Current.account }
+
+ *George Claghorn*
+
* Deprecate `Migrator.schema_migrations_table_name`.
*Ryuta Kamizono*
@@ -495,11 +509,6 @@
*Prathamesh Sonpatki*
-* Optimistic locking: Added ability to update `locking_column` value.
- Ignore optimistic locking if trying to update with new `locking_column` value.
-
- *bogdanvlviv*
-
* Fixed: Optimistic locking does not work well with `null` in the database.
Fixes #26024.
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 4606c91ffd..6efa448d49 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1647,6 +1647,9 @@ module ActiveRecord
# +:inverse_of+ to avoid an extra query during validation.
# NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If
# you don't want to have association presence validated, use <tt>optional: true</tt>.
+ # [:default]
+ # Provide a callable (i.e. proc or lambda) to specify that the association should
+ # be initialized with a particular record before validation.
#
# Option examples:
# belongs_to :firm, foreign_key: "client_of"
@@ -1660,6 +1663,7 @@ module ActiveRecord
# belongs_to :comment, touch: true
# belongs_to :company, touch: :employees_last_updated_at
# belongs_to :user, optional: true
+ # belongs_to :account, default: -> { company.account }
def belongs_to(name, scope = nil, options = {})
reflection = Builder::BelongsTo.build(self, name, scope, options)
Reflection.add_reflection self, name, reflection
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 64b2311911..4a4f88bb94 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -21,6 +21,10 @@ module ActiveRecord
self.target = record
end
+ def default(record)
+ writer(record) if reader.nil?
+ end
+
def reset
super
@updated = false
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index a1609ab0fb..50a1c39ccf 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
def self.valid_options(options)
- super + [:polymorphic, :touch, :counter_cache, :optional]
+ super + [:polymorphic, :touch, :counter_cache, :optional, :default]
end
def self.valid_dependent_options
@@ -16,6 +16,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
super
add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
add_touch_callbacks(model, reflection) if reflection.options[:touch]
+ add_default_callbacks(model, reflection) if reflection.options[:default]
end
def self.define_accessors(mixin, reflection)
@@ -118,6 +119,12 @@ module ActiveRecord::Associations::Builder # :nodoc:
model.after_destroy callback.(:changes_to_save)
end
+ def self.add_default_callbacks(model, reflection)
+ model.before_validation lambda { |o|
+ o.association(reflection.name).default o.instance_exec(&reflection.options[:default])
+ }
+ end
+
def self.add_destroy_callbacks(model, reflection)
model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index a497a354f7..1e826ff5ad 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -31,6 +31,8 @@ module ActiveRecord
# Returns the relation names useable to back Active Record models.
# For most adapters this means all #tables and #views.
def data_sources
+ select_values(data_source_sql, "SCHEMA")
+ rescue NotImplementedError
tables | views
end
@@ -39,12 +41,14 @@ module ActiveRecord
# data_source_exists?(:ebooks)
#
def data_source_exists?(name)
+ select_values(data_source_sql(name), "SCHEMA").any? if name.present?
+ rescue NotImplementedError
data_sources.include?(name.to_s)
end
# Returns an array of table names defined in the database.
def tables
- raise NotImplementedError, "#tables is not implemented"
+ select_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
end
# Checks to see if the table +table_name+ exists on the database.
@@ -52,12 +56,14 @@ module ActiveRecord
# table_exists?(:developers)
#
def table_exists?(table_name)
+ select_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present?
+ rescue NotImplementedError
tables.include?(table_name.to_s)
end
# Returns an array of view names defined in the database.
def views
- raise NotImplementedError, "#views is not implemented"
+ select_values(data_source_sql(type: "VIEW"), "SCHEMA")
end
# Checks to see if the view +view_name+ exists on the database.
@@ -65,6 +71,8 @@ module ActiveRecord
# view_exists?(:ebooks)
#
def view_exists?(view_name)
+ select_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present?
+ rescue NotImplementedError
views.include?(view_name.to_s)
end
@@ -1304,6 +1312,14 @@ module ActiveRecord
def can_remove_index_by_name?(options)
options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
end
+
+ def data_source_sql(name = nil, type: nil)
+ raise NotImplementedError
+ end
+
+ def quoted_scope(name = nil, type: nil)
+ raise NotImplementedError
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index e3b6327dd8..55ec112c17 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -6,6 +6,7 @@ require "active_record/connection_adapters/mysql/quoting"
require "active_record/connection_adapters/mysql/schema_creation"
require "active_record/connection_adapters/mysql/schema_definitions"
require "active_record/connection_adapters/mysql/schema_dumper"
+require "active_record/connection_adapters/mysql/schema_statements"
require "active_record/connection_adapters/mysql/type_metadata"
require "active_support/core_ext/string/strip"
@@ -15,6 +16,7 @@ module ActiveRecord
class AbstractMysqlAdapter < AbstractAdapter
include MySQL::Quoting
include MySQL::ColumnDumper
+ include MySQL::SchemaStatements
def update_table_definition(table_name, base) # :nodoc:
MySQL::Table.new(table_name, base)
@@ -310,57 +312,6 @@ module ActiveRecord
show_variable "collation_database"
end
- def tables # :nodoc:
- sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'"
- sql << " AND table_schema = #{quote(@config[:database])}"
-
- select_values(sql, "SCHEMA")
- end
-
- def views # :nodoc:
- select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", "SCHEMA")
- end
-
- def data_sources # :nodoc:
- sql = "SELECT table_name FROM information_schema.tables "
- sql << "WHERE table_schema = #{quote(@config[:database])}"
-
- select_values(sql, "SCHEMA")
- end
-
- def table_exists?(table_name) # :nodoc:
- return false unless table_name.present?
-
- schema, name = extract_schema_qualified_name(table_name)
-
- sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'"
- sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def data_source_exists?(table_name) # :nodoc:
- return false unless table_name.present?
-
- schema, name = extract_schema_qualified_name(table_name)
-
- sql = "SELECT table_name FROM information_schema.tables "
- sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def view_exists?(view_name) # :nodoc:
- return false unless view_name.present?
-
- schema, name = extract_schema_qualified_name(view_name)
-
- sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
- sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
def truncate(table_name, name = nil)
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
end
@@ -406,13 +357,13 @@ module ActiveRecord
end
def table_comment(table_name) # :nodoc:
- schema, name = extract_schema_qualified_name(table_name)
+ scope = quoted_scope(table_name)
select_value(<<-SQL.strip_heredoc, "SCHEMA")
SELECT table_comment
FROM information_schema.tables
- WHERE table_schema = #{quote(schema)}
- AND table_name = #{quote(name)}
+ WHERE table_schema = #{scope[:schema]}
+ AND table_name = #{scope[:name]}
SQL
end
@@ -512,7 +463,7 @@ module ActiveRecord
def foreign_keys(table_name)
raise ArgumentError unless table_name.present?
- schema, name = extract_schema_qualified_name(table_name)
+ scope = quoted_scope(table_name)
fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA")
SELECT fk.referenced_table_name AS 'to_table',
@@ -525,9 +476,9 @@ module ActiveRecord
JOIN information_schema.referential_constraints rc
USING (constraint_schema, constraint_name)
WHERE fk.referenced_column_name IS NOT NULL
- AND fk.table_schema = #{quote(schema)}
- AND fk.table_name = #{quote(name)}
- AND rc.table_name = #{quote(name)}
+ AND fk.table_schema = #{scope[:schema]}
+ AND fk.table_name = #{scope[:name]}
+ AND rc.table_name = #{scope[:name]}
SQL
fk_info.map do |row|
@@ -599,14 +550,14 @@ module ActiveRecord
def primary_keys(table_name) # :nodoc:
raise ArgumentError unless table_name.present?
- schema, name = extract_schema_qualified_name(table_name)
+ scope = quoted_scope(table_name)
select_values(<<-SQL.strip_heredoc, "SCHEMA")
SELECT column_name
FROM information_schema.key_column_usage
WHERE constraint_name = 'PRIMARY'
- AND table_schema = #{quote(schema)}
- AND table_name = #{quote(name)}
+ AND table_schema = #{scope[:schema]}
+ AND table_name = #{scope[:name]}
ORDER BY ordinal_position
SQL
end
@@ -940,12 +891,6 @@ module ActiveRecord
)
end
- def extract_schema_qualified_name(string) # :nodoc:
- schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
- schema, name = @config[:database], schema unless name
- [schema, name]
- end
-
def integer_to_sql(limit) # :nodoc:
case limit
when 1; "tinyint"
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
new file mode 100644
index 0000000000..10c8bd179a
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -0,0 +1,33 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ module SchemaStatements # :nodoc:
+ private
+ def data_source_sql(name = nil, type: nil)
+ scope = quoted_scope(name, type: type)
+
+ sql = "SELECT table_name FROM information_schema.tables"
+ sql << " WHERE table_schema = #{scope[:schema]}"
+ sql << " AND table_name = #{scope[:name]}" if scope[:name]
+ sql << " AND table_type = #{scope[:type]}" if scope[:type]
+ sql
+ end
+
+ def quoted_scope(name = nil, type: nil)
+ schema, name = extract_schema_qualified_name(name)
+ scope = {}
+ scope[:schema] = schema ? quote(schema) : "database()"
+ scope[:name] = quote(name) if name
+ scope[:type] = quote(type) if type
+ scope
+ end
+
+ def extract_schema_qualified_name(string)
+ schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
+ schema, name = nil, schema unless name
+ [schema, name]
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
index 24dcf852e1..9ad6a6c0d0 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
@@ -2,6 +2,8 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
+ undef to_yaml if method_defined?(:to_yaml)
+
attr_reader :extra
def initialize(type_metadata, extra: "")
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index afef0da5c7..a332375b78 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -54,81 +54,13 @@ module ActiveRecord
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
end
- # Returns the list of all tables in the schema search path.
- def tables
- select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", "SCHEMA")
- end
-
- def data_sources # :nodoc
- select_values(<<-SQL, "SCHEMA")
- SELECT c.relname
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
- AND n.nspname = ANY (current_schemas(false))
- SQL
- end
-
- def views # :nodoc:
- select_values(<<-SQL, "SCHEMA")
- SELECT c.relname
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
- AND n.nspname = ANY (current_schemas(false))
- SQL
- end
-
- # Returns true if table exists.
- # If the schema is not specified as part of +name+ then it will only find tables within
- # the current schema search path (regardless of permissions to access tables in other schemas)
- def table_exists?(name)
- name = Utils.extract_schema_qualified_name(name.to_s)
- return false unless name.identifier
-
- select_values(<<-SQL, "SCHEMA").any?
- SELECT tablename
- FROM pg_tables
- WHERE tablename = #{quote(name.identifier)}
- AND schemaname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
- SQL
- end
-
- def data_source_exists?(name) # :nodoc:
- name = Utils.extract_schema_qualified_name(name.to_s)
- return false unless name.identifier
-
- select_values(<<-SQL, "SCHEMA").any?
- SELECT c.relname
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
- AND c.relname = #{quote(name.identifier)}
- AND n.nspname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
- SQL
- end
-
- def view_exists?(view_name) # :nodoc:
- name = Utils.extract_schema_qualified_name(view_name.to_s)
- return false unless name.identifier
-
- select_values(<<-SQL, "SCHEMA").any?
- SELECT c.relname
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
- AND c.relname = #{quote(name.identifier)}
- AND n.nspname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
- SQL
- end
-
def drop_table(table_name, options = {}) # :nodoc:
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
# Returns true if schema exists.
def schema_exists?(name)
- select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", "SCHEMA").to_i > 0
+ select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
end
# Verifies existence of an index with a given name.
@@ -138,8 +70,8 @@ module ActiveRecord
Passing default to #index_name_exists? is deprecated without replacement.
MSG
end
- table = Utils.extract_schema_qualified_name(table_name.to_s)
- index = Utils.extract_schema_qualified_name(index_name.to_s)
+ table = quoted_scope(table_name)
+ index = quoted_scope(index_name)
select_value(<<-SQL, "SCHEMA").to_i > 0
SELECT COUNT(*)
@@ -148,9 +80,9 @@ module ActiveRecord
INNER JOIN pg_class i ON d.indexrelid = i.oid
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
WHERE i.relkind = 'i'
- AND i.relname = '#{index.identifier}'
- AND t.relname = '#{table.identifier}'
- AND n.nspname = #{index.schema ? "'#{index.schema}'" : 'ANY (current_schemas(false))'}
+ AND i.relname = #{index[:name]}
+ AND t.relname = #{table[:name]}
+ AND n.nspname = #{index[:schema]}
SQL
end
@@ -162,7 +94,7 @@ module ActiveRecord
MSG
end
- table = Utils.extract_schema_qualified_name(table_name.to_s)
+ scope = quoted_scope(table_name)
result = query(<<-SQL, "SCHEMA")
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
@@ -176,8 +108,8 @@ module ActiveRecord
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
WHERE i.relkind = 'i'
AND d.indisprimary = 'f'
- AND t.relname = '#{table.identifier}'
- AND n.nspname = #{table.schema ? "'#{table.schema}'" : 'ANY (current_schemas(false))'}
+ AND t.relname = #{scope[:name]}
+ AND n.nspname = #{scope[:schema]}
ORDER BY i.relname
SQL
@@ -239,22 +171,22 @@ module ActiveRecord
# Returns a comment stored in database for given table
def table_comment(table_name) # :nodoc:
- name = Utils.extract_schema_qualified_name(table_name.to_s)
- if name.identifier
+ scope = quoted_scope(table_name, type: "BASE TABLE")
+ if scope[:name]
select_value(<<-SQL.strip_heredoc, "SCHEMA")
SELECT pg_catalog.obj_description(c.oid, 'pg_class')
FROM pg_catalog.pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relname = #{quote(name.identifier)}
- AND c.relkind IN ('r') -- (r)elation/table
- AND n.nspname = #{name.schema ? quote(name.schema) : 'ANY (current_schemas(false))'}
+ WHERE c.relname = #{scope[:name]}
+ AND c.relkind IN (#{scope[:type]})
+ AND n.nspname = #{scope[:schema]}
SQL
end
end
# Returns the current database name.
def current_database
- select_value("select current_database()", "SCHEMA")
+ select_value("SELECT current_database()", "SCHEMA")
end
# Returns the current schema name.
@@ -430,17 +362,15 @@ module ActiveRecord
end
def primary_keys(table_name) # :nodoc:
- name = Utils.extract_schema_qualified_name(table_name.to_s)
+ scope = quoted_scope(table_name)
select_values(<<-SQL.strip_heredoc, "SCHEMA")
SELECT column_name
FROM information_schema.key_column_usage kcu
JOIN information_schema.table_constraints tc
- ON kcu.table_name = tc.table_name
- AND kcu.table_schema = tc.table_schema
- AND kcu.constraint_name = tc.constraint_name
+ USING (table_schema, table_name, constraint_name)
WHERE constraint_type = 'PRIMARY KEY'
- AND kcu.table_name = #{quote(name.identifier)}
- AND kcu.table_schema = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
+ AND kcu.table_name = #{scope[:name]}
+ AND kcu.table_schema = #{scope[:schema]}
ORDER BY kcu.ordinal_position
SQL
end
@@ -579,6 +509,7 @@ module ActiveRecord
end
def foreign_keys(table_name)
+ scope = quoted_scope(table_name)
fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA")
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
FROM pg_constraint c
@@ -588,8 +519,8 @@ module ActiveRecord
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
JOIN pg_namespace t3 ON c.connamespace = t3.oid
WHERE c.contype = 'f'
- AND t1.relname = #{quote(table_name)}
- AND t3.nspname = ANY (current_schemas(false))
+ AND t1.relname = #{scope[:name]}
+ AND t3.nspname = #{scope[:schema]}
ORDER BY c.conname
SQL
@@ -673,6 +604,39 @@ module ActiveRecord
)
PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
end
+
+ private
+ def data_source_sql(name = nil, type: nil)
+ scope = quoted_scope(name, type: type)
+ scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
+
+ sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
+ sql << " WHERE n.nspname = #{scope[:schema]}"
+ sql << " AND c.relname = #{scope[:name]}" if scope[:name]
+ sql << " AND c.relkind IN (#{scope[:type]})"
+ sql
+ end
+
+ def quoted_scope(name = nil, type: nil)
+ schema, name = extract_schema_qualified_name(name)
+ type = \
+ case type
+ when "BASE TABLE"
+ "'r'"
+ when "VIEW"
+ "'v','m'"
+ end
+ scope = {}
+ scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"
+ scope[:name] = quote(name) if name
+ scope[:type] = type if type
+ scope
+ end
+
+ def extract_schema_qualified_name(string)
+ name = Utils.extract_schema_qualified_name(string.to_s)
+ [name.schema, name.identifier]
+ end
end
end
end
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 311988625f..f57179ae59 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
@@ -1,6 +1,8 @@
module ActiveRecord
module ConnectionAdapters
class PostgreSQLTypeMetadata < DelegateClass(SqlTypeMetadata)
+ undef to_yaml if method_defined?(:to_yaml)
+
attr_reader :oid, :fmod, :array
def initialize(type_metadata, oid: nil, fmod: nil)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index bc04565434..22c37abb78 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -3,6 +3,7 @@ gem "pg", "~> 0.18"
require "pg"
require "active_record/connection_adapters/abstract_adapter"
+require "active_record/connection_adapters/statement_pool"
require "active_record/connection_adapters/postgresql/column"
require "active_record/connection_adapters/postgresql/database_statements"
require "active_record/connection_adapters/postgresql/explain_pretty_printer"
@@ -15,7 +16,6 @@ require "active_record/connection_adapters/postgresql/schema_dumper"
require "active_record/connection_adapters/postgresql/schema_statements"
require "active_record/connection_adapters/postgresql/type_metadata"
require "active_record/connection_adapters/postgresql/utils"
-require "active_record/connection_adapters/statement_pool"
module ActiveRecord
module ConnectionHandling # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
new file mode 100644
index 0000000000..4ba245a0d8
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -0,0 +1,32 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ module SchemaStatements # :nodoc:
+ private
+ def data_source_sql(name = nil, type: nil)
+ scope = quoted_scope(name, type: type)
+ scope[:type] ||= "'table','view'"
+
+ sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
+ sql << " AND name = #{scope[:name]}" if scope[:name]
+ sql << " AND type IN (#{scope[:type]})"
+ sql
+ end
+
+ def quoted_scope(name = nil, type: nil)
+ type = \
+ case type
+ when "BASE TABLE"
+ "'table'"
+ when "VIEW"
+ "'view'"
+ end
+ scope = {}
+ scope[:name] = quote(name) if name
+ scope[:type] = type if type
+ scope
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 8b627a6d4d..d24bfc0c93 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -5,6 +5,7 @@ require "active_record/connection_adapters/sqlite3/quoting"
require "active_record/connection_adapters/sqlite3/schema_creation"
require "active_record/connection_adapters/sqlite3/schema_definitions"
require "active_record/connection_adapters/sqlite3/schema_dumper"
+require "active_record/connection_adapters/sqlite3/schema_statements"
gem "sqlite3", "~> 1.3.6"
require "sqlite3"
@@ -55,6 +56,7 @@ module ActiveRecord
include SQLite3::Quoting
include SQLite3::ColumnDumper
+ include SQLite3::SchemaStatements
NATIVE_DATABASE_TYPES = {
primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL",
@@ -265,45 +267,6 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def tables # :nodoc:
- select_values("SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'", "SCHEMA")
- end
-
- def data_sources # :nodoc:
- select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", "SCHEMA")
- end
-
- def views # :nodoc:
- select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", "SCHEMA")
- end
-
- def table_exists?(table_name) # :nodoc:
- return false unless table_name.present?
-
- sql = "SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'"
- sql << " AND name = #{quote(table_name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def data_source_exists?(table_name) # :nodoc:
- return false unless table_name.present?
-
- sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
- sql << " AND name = #{quote(table_name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def view_exists?(view_name) # :nodoc:
- return false unless view_name.present?
-
- sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'"
- sql << " AND name = #{quote(view_name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
def new_column_from_field(table_name, field) # :nondoc:
case field["dflt_value"]
when /^null$/i
@@ -535,7 +498,7 @@ module ActiveRecord
end
def sqlite_version
- @sqlite_version ||= SQLite3Adapter::Version.new(select_value("select sqlite_version(*)"))
+ @sqlite_version ||= SQLite3Adapter::Version.new(select_value("SELECT sqlite_version(*)"))
end
def translate_exception(exception, message)
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 0028dc0edb..8f78330d4a 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -559,7 +559,6 @@ module ActiveRecord
@marked_for_destruction = false
@destroyed_by_association = nil
@new_record = true
- @txn = nil
@_start_transaction_state = {}
@transaction_state = nil
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 2659c60f1f..78ce9f8291 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -47,8 +47,6 @@ module ActiveRecord
# self.locking_column = :lock_person
# end
#
- # Please note that the optimistic locking will be ignored if you update the
- # locking column's value.
module Optimistic
extend ActiveSupport::Concern
@@ -80,13 +78,11 @@ module ActiveRecord
def _update_record(attribute_names = self.attribute_names)
return super unless locking_enabled?
-
- lock_col = self.class.locking_column
-
- return super if attribute_names.include?(lock_col)
return 0 if attribute_names.empty?
begin
+ lock_col = self.class.locking_column
+
previous_lock_value = read_attribute_before_type_cast(lock_col)
increment_lock
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 6af05c1860..edbd20a6c1 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -1,6 +1,8 @@
module ActiveRecord
module Type
class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc:
+ undef to_yaml if method_defined?(:to_yaml)
+
include ActiveModel::Type::Helpers::Mutable
attr_reader :subtype, :coder
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index fdd7b0157a..2179d1294c 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -260,8 +260,7 @@ module ActiveRecord
def test_tables_logs_name
sql = <<-SQL
- SELECT name FROM sqlite_master
- WHERE type = 'table' AND name <> 'sqlite_sequence'
+ SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence' AND type IN ('table')
SQL
assert_logged [[sql.squish, "SCHEMA", []]] do
@conn.tables
@@ -279,8 +278,7 @@ module ActiveRecord
def test_table_exists_logs_name
with_example_table do
sql = <<-SQL
- SELECT name FROM sqlite_master
- WHERE type = 'table' AND name <> 'sqlite_sequence' AND name = 'ex'
+ SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence' AND name = 'ex' AND type IN ('table')
SQL
assert_logged [[sql.squish, "SCHEMA", []]] do
assert @conn.table_exists?("ex")
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 5875a1871f..5b08ba1358 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -116,6 +116,26 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
ActiveRecord::Base.belongs_to_required_by_default = original_value
end
+ def test_default
+ david = developers(:david)
+ jamis = developers(:jamis)
+
+ model = Class.new(ActiveRecord::Base) do
+ self.table_name = "ships"
+ def self.name; "Temp"; end
+ belongs_to :developer, default: -> { david }
+ end
+
+ ship = model.create!
+ assert_equal david, ship.developer
+
+ ship = model.create!(developer: jamis)
+ assert_equal jamis, ship.developer
+
+ ship.update!(developer: nil)
+ assert_equal david, ship.developer
+ end
+
def test_default_scope_on_relations_is_not_cached
counter = 0
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index c13a962e3e..f9eccfbda1 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -349,13 +349,14 @@ class DirtyTest < ActiveRecord::TestCase
def test_partial_update_with_optimistic_locking
person = Person.new(first_name: "foo")
- old_lock_version = person.lock_version
with_partial_writes Person, false do
assert_queries(2) { 2.times { person.save! } }
Person.where(id: person.id).update_all(first_name: "baz")
end
+ old_lock_version = person.lock_version
+
with_partial_writes Person, true do
assert_queries(0) { 2.times { person.save! } }
assert_equal old_lock_version, person.reload.lock_version
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 23095618a4..3a3b8e51f9 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -247,17 +247,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "new title2", t2.title
end
- def test_lock_without_default_should_update_with_lock_col
- t1 = LockWithoutDefault.create(title: "title1", lock_version: 6)
-
- assert_equal 6, t1.lock_version
-
- t1.update(lock_version: 0)
- t1.reload
-
- assert_equal 0, t1.lock_version
- end
-
def test_lock_without_default_queries_count
t1 = LockWithoutDefault.create(title: "title1")
@@ -270,12 +259,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "title2", t1.title
assert_equal 1, t1.lock_version
- assert_queries(1) { t1.update(title: "title3", lock_version: 6) }
-
- t1.reload
- assert_equal "title3", t1.title
- assert_equal 6, t1.lock_version
-
t2 = LockWithoutDefault.new(title: "title1")
assert_queries(1) { t2.save! }
@@ -321,17 +304,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "new title2", t2.title
end
- def test_lock_with_custom_column_without_default_should_update_with_lock_col
- t1 = LockWithCustomColumnWithoutDefault.create(title: "title1", custom_lock_version: 6)
-
- assert_equal 6, t1.custom_lock_version
-
- t1.update(custom_lock_version: 0)
- t1.reload
-
- assert_equal 0, t1.custom_lock_version
- end
-
def test_lock_with_custom_column_without_default_queries_count
t1 = LockWithCustomColumnWithoutDefault.create(title: "title1")
@@ -344,12 +316,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "title2", t1.title
assert_equal 1, t1.custom_lock_version
- assert_queries(1) { t1.update(title: "title3", custom_lock_version: 6) }
-
- t1.reload
- assert_equal "title3", t1.title
- assert_equal 6, t1.custom_lock_version
-
t2 = LockWithCustomColumnWithoutDefault.new(title: "title1")
assert_queries(1) { t2.save! }
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 14fb2fbbfa..a6c22ac672 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -10,8 +10,6 @@ require "concurrent/atomic/cyclic_barrier"
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
- self.use_transactional_tests = false
-
def test_default_scope
expected = Developer.all.merge!(order: "salary DESC").to_a.collect(&:salary)
received = DeveloperOrderedBySalary.all.collect(&:salary)
@@ -61,17 +59,6 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal "Jamis", DeveloperCalledJamis.create!.name
end
- unless in_memory_db?
- def test_default_scoping_with_threads
- 2.times do
- Thread.new {
- assert_includes DeveloperOrderedBySalary.all.to_sql, "salary DESC"
- DeveloperOrderedBySalary.connection.close
- }.join
- end
- end
- end
-
def test_default_scope_with_inheritance
wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash
assert_equal "Jamis", wheres["name"]
@@ -435,29 +422,6 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal comment, CommentWithDefaultScopeReferencesAssociation.find_by(id: comment.id)
end
- unless in_memory_db?
- def test_default_scope_is_threadsafe
- threads = []
- assert_not_equal 1, ThreadsafeDeveloper.unscoped.count
-
- barrier_1 = Concurrent::CyclicBarrier.new(2)
- barrier_2 = Concurrent::CyclicBarrier.new(2)
-
- threads << Thread.new do
- Thread.current[:default_scope_delay] = -> { barrier_1.wait; barrier_2.wait }
- assert_equal 1, ThreadsafeDeveloper.all.to_a.count
- ThreadsafeDeveloper.connection.close
- end
- threads << Thread.new do
- Thread.current[:default_scope_delay] = -> { barrier_2.wait }
- barrier_1.wait
- assert_equal 1, ThreadsafeDeveloper.all.to_a.count
- ThreadsafeDeveloper.connection.close
- end
- threads.each(&:join)
- end
- end
-
test "additional conditions are ANDed with the default scope" do
scope = DeveloperCalledJamis.where(name: "David")
assert_equal 2, scope.where_clause.ast.children.length
@@ -506,3 +470,37 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_match gender_pattern, Lion.female.to_sql
end
end
+
+class DefaultScopingWithThreadTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ def test_default_scoping_with_threads
+ 2.times do
+ Thread.new {
+ assert_includes DeveloperOrderedBySalary.all.to_sql, "salary DESC"
+ DeveloperOrderedBySalary.connection.close
+ }.join
+ end
+ end
+
+ def test_default_scope_is_threadsafe
+ threads = []
+ assert_not_equal 1, ThreadsafeDeveloper.unscoped.count
+
+ barrier_1 = Concurrent::CyclicBarrier.new(2)
+ barrier_2 = Concurrent::CyclicBarrier.new(2)
+
+ threads << Thread.new do
+ Thread.current[:default_scope_delay] = -> { barrier_1.wait; barrier_2.wait }
+ assert_equal 1, ThreadsafeDeveloper.all.to_a.count
+ ThreadsafeDeveloper.connection.close
+ end
+ threads << Thread.new do
+ Thread.current[:default_scope_delay] = -> { barrier_2.wait }
+ barrier_1.wait
+ assert_equal 1, ThreadsafeDeveloper.all.to_a.count
+ ThreadsafeDeveloper.connection.close
+ end
+ threads.each(&:join)
+ end
+end unless in_memory_db?
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 2cee8375b7..969d36c303 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,7 +1,23 @@
+* Remove implicit coercion deprecation of durations
+
+ In #28204 we deprecated implicit conversion of durations to a numeric which
+ represented the number of seconds in the duration because of unwanted side
+ effects with calculations on durations and dates. This unfortunately had
+ the side effect of forcing a explicit cast when configuring third-party
+ libraries like expiration in Redis, e.g:
+
+ redis.expire("foo", 5.minutes)
+
+ To work around this we've removed the deprecation and added a private class
+ that wraps the numeric and can perform calculation involving durations and
+ ensure that they remain a duration irrespective of the order of operations.
+
+ *Andrew White*
+
* Update `titleize` regex to allow apostrophes
In 4b685aa the regex in `titleize` was updated to not match apostrophes to
- better reflect the nature of the transformation. Unfortunately this had the
+ better reflect the nature of the transformation. Unfortunately, this had the
side effect of breaking capitalization on the first word of a sub-string, e.g:
>> "This was 'fake news'".titleize
@@ -22,7 +38,7 @@
* Add `Time.rfc3339` parsing method
- The `Time.xmlschema` and consequently its alias `iso8601` accepts timestamps
+ `Time.xmlschema` and consequently its alias `iso8601` accepts timestamps
without a offset in contravention of the RFC 3339 standard. This method
enforces that constraint and raises an `ArgumentError` if it doesn't.
@@ -30,7 +46,7 @@
* Add `ActiveSupport::TimeZone.rfc3339` parsing method
- Previously there was no way to get a RFC 3339 timestamp into a specific
+ Previously, there was no way to get a RFC 3339 timestamp into a specific
timezone without either using `parse` or chaining methods. The new method
allows parsing directly into the timezone, e.g:
@@ -39,7 +55,7 @@
>> Time.zone.rfc3339("1999-12-31T14:00:00Z")
=> Fri, 31 Dec 1999 14:00:00 HST -10:00
- This new method has stricter semantics than the current `parse` method
+ This new method has stricter semantics than the current `parse` method,
and will raise an `ArgumentError` instead of returning nil, e.g:
>> Time.zone = "Hawaii"
@@ -63,7 +79,7 @@
* Add `ActiveSupport::TimeZone.iso8601` parsing method
- Previously there was no way to get a ISO 8601 timestamp into a specific
+ Previously, there was no way to get a ISO 8601 timestamp into a specific
timezone without either using `parse` or chaining methods. The new method
allows parsing directly into the timezone, e.g:
@@ -72,7 +88,7 @@
>> Time.zone.iso8601("1999-12-31T14:00:00Z")
=> Fri, 31 Dec 1999 14:00:00 HST -10:00
- If the timestamp is a ISO 8601 date (YYYY-MM-DD) then the time is set
+ If the timestamp is a ISO 8601 date (YYYY-MM-DD), then the time is set
to midnight, e.g:
>> Time.zone = "Hawaii"
@@ -80,7 +96,7 @@
>> Time.zone.iso8601("1999-12-31")
=> Fri, 31 Dec 1999 00:00:00 HST -10:00
- This new method has stricter semantics than the current `parse` method
+ This new method has stricter semantics than the current `parse` method,
and will raise an `ArgumentError` instead of returning nil, e.g:
>> Time.zone = "Hawaii"
@@ -175,7 +191,9 @@
* Soft-deprecated the top-level `HashWithIndifferentAccess` constant.
`ActiveSupport::HashWithIndifferentAccess` should be used instead.
- *Robin Dupret* (#28157)
+ Fixes #28157.
+
+ *Robin Dupret*
* In Core Extensions, make `MarshalWithAutoloading#load` pass through the second, optional
argument for `Marshal#load( source [, proc] )`. This way we don't have to do
@@ -190,6 +208,13 @@
## Rails 5.1.0.beta1 (February 23, 2017) ##
+* Fixed bug in `DateAndTime::Compatibility#to_time` that caused it to
+ raise `RuntimeError: can't modify frozen Time` when called on any frozen `Time`.
+ Properly pass through the frozen `Time` or `ActiveSupport::TimeWithZone` object
+ when calling `#to_time`.
+
+ *Kevin McPhillips* & *Andrew White*
+
* Cache `ActiveSupport::TimeWithZone#to_datetime` before freezing.
*Adam Rice*
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 5eee04a34e..e09cee3335 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -156,7 +156,7 @@ module ActiveSupport
expires_in = options[:expires_in].to_i
if expires_in > 0 && !options[:raw]
# Set the memcache expire a few minutes in the future to support race condition ttls on read
- expires_in += 300
+ expires_in += 5.minutes
end
rescue_error_with false do
@data.send(method, key, value, expires_in, options)
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
index db95ae0db5..ab80392460 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
@@ -10,13 +10,5 @@ module DateAndTime
# this behavior, but new apps will have an initializer that sets
# this to true, because the new behavior is preferred.
mattr_accessor(:preserve_timezone, instance_writer: false) { false }
-
- def to_time
- if preserve_timezone
- @_to_time_with_instance_offset ||= getlocal(utc_offset)
- else
- @_to_time_with_system_offset ||= getlocal
- end
- end
end
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
index 30bb7f4a60..eb8b8b2c65 100644
--- a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
@@ -1,5 +1,15 @@
require "active_support/core_ext/date_and_time/compatibility"
class DateTime
- prepend DateAndTime::Compatibility
+ include DateAndTime::Compatibility
+
+ remove_possible_method :to_time
+
+ # Either return an instance of `Time` with the same UTC offset
+ # as +self+ or an instance of `Time` representing the same time
+ # in the the local system timezone depending on the setting of
+ # on the setting of +ActiveSupport.to_time_preserves_timezone+.
+ def to_time
+ preserve_timezone ? getlocal(utc_offset) : getlocal
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb
index cf39b1d312..3a44e08630 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -62,6 +62,17 @@ class Object
#
# Hence the inherited default for `if` key is ignored.
#
+ # NOTE: You cannot call class methods implicitly inside of with_options.
+ # You can access these methods using the class name instead:
+ #
+ # class Phone < ActiveRecord::Base
+ # enum phone_number_type: [home: 0, office: 1, mobile: 2]
+ #
+ # with_options presence: true do
+ # validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys }
+ # end
+ # end
+ #
def with_options(options, &block)
option_merger = ActiveSupport::OptionMerger.new(self, options)
block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger)
diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb
index ca4b9574d5..45e86b77ce 100644
--- a/activesupport/lib/active_support/core_ext/time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb
@@ -1,5 +1,14 @@
require "active_support/core_ext/date_and_time/compatibility"
+require "active_support/core_ext/module/remove_method"
class Time
- prepend DateAndTime::Compatibility
+ include DateAndTime::Compatibility
+
+ remove_possible_method :to_time
+
+ # Either return +self+ or the time in the local system timezone depending
+ # on the setting of +ActiveSupport.to_time_preserves_timezone+.
+ def to_time
+ preserve_timezone ? self : getlocal
+ end
end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index d26bbac511..99080e34a1 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -1,4 +1,5 @@
require "active_support/core_ext/array/conversions"
+require "active_support/core_ext/module/delegation"
require "active_support/core_ext/object/acts_like"
require "active_support/core_ext/string/filters"
require "active_support/deprecation"
@@ -9,6 +10,66 @@ module ActiveSupport
#
# 1.month.ago # equivalent to Time.now.advance(months: -1)
class Duration
+ class Scalar < Numeric #:nodoc:
+ attr_reader :value
+ delegate :to_i, :to_f, :to_s, to: :value
+
+ def initialize(value)
+ @value = value
+ end
+
+ def coerce(other)
+ [Scalar.new(other), self]
+ end
+
+ def -@
+ Scalar.new(-value)
+ end
+
+ def <=>(other)
+ if Scalar === other || Duration === other
+ value <=> other.value
+ elsif Numeric === other
+ value <=> other
+ else
+ nil
+ end
+ end
+
+ def +(other)
+ calculate(:+, other)
+ end
+
+ def -(other)
+ calculate(:-, other)
+ end
+
+ def *(other)
+ calculate(:*, other)
+ end
+
+ def /(other)
+ calculate(:/, other)
+ end
+
+ private
+ def calculate(op, other)
+ if Scalar === other
+ Scalar.new(value.public_send(op, other.value))
+ elsif Duration === other
+ Duration.seconds(value).public_send(op, other)
+ elsif Numeric === other
+ Scalar.new(value.public_send(op, other))
+ else
+ raise_type_error(other)
+ end
+ end
+
+ def raise_type_error(other)
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
+ end
+ end
+
SECONDS_PER_MINUTE = 60
SECONDS_PER_HOUR = 3600
SECONDS_PER_DAY = 86400
@@ -91,12 +152,11 @@ module ActiveSupport
end
def coerce(other) #:nodoc:
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Implicit coercion of ActiveSupport::Duration to a Numeric
- is deprecated and will raise a TypeError in Rails 5.2.
- MSG
-
- [other, value]
+ if Scalar === other
+ [other, self]
+ else
+ [Scalar.new(other), self]
+ end
end
# Compares one Duration with another or a Numeric to this Duration.
@@ -132,19 +192,23 @@ module ActiveSupport
# Multiplies this Duration by a Numeric and returns a new Duration.
def *(other)
- if Numeric === other
+ if Scalar === other || Duration === other
+ Duration.new(value * other.value, parts.map { |type, number| [type, number * other.value] })
+ elsif Numeric === other
Duration.new(value * other, parts.map { |type, number| [type, number * other] })
else
- value * other
+ raise_type_error(other)
end
end
# Divides this Duration by a Numeric and returns a new Duration.
def /(other)
- if Numeric === other
+ if Scalar === other || Duration === other
+ Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] })
+ elsif Numeric === other
Duration.new(value / other, parts.map { |type, number| [type, number / other] })
else
- value / other
+ raise_type_error(other)
end
end
@@ -274,5 +338,9 @@ module ActiveSupport
def method_missing(method, *args, &block)
value.send(method, *args, &block)
end
+
+ def raise_type_error(other)
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
+ end
end
end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 69109d2005..24053b4fe5 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -50,6 +50,11 @@ module ActiveSupport
# key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
# derivation function.
#
+ # First additional parameter is used as the signature key for +MessageVerifier+.
+ # This allows you to specify keys to encrypt and sign data.
+ #
+ # ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
+ #
# Options:
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
# <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index e31983cf26..b0dd6b7e8c 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -411,6 +411,17 @@ module ActiveSupport
@to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
end
+ # Returns an instance of +Time+, either with the same UTC offset
+ # as +self+ or in the local system timezone depending on the setting
+ # of +ActiveSupport.to_time_preserves_timezone+.
+ def to_time
+ if preserve_timezone
+ @to_time_with_instance_offset ||= getlocal(utc_offset)
+ else
+ @to_time_with_system_offset ||= getlocal
+ end
+ end
+
# So that +self+ <tt>acts_like?(:time)</tt>.
def acts_like_time?
true
@@ -429,7 +440,7 @@ module ActiveSupport
def freeze
# preload instance variables before freezing
- period; utc; time; to_datetime
+ period; utc; time; to_datetime; to_time
super
end
diff --git a/activesupport/test/core_ext/date_and_time_compatibility_test.rb b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
index 4c90460032..6c6205a4d2 100644
--- a/activesupport/test/core_ext/date_and_time_compatibility_test.rb
+++ b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
@@ -16,11 +16,13 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_time_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
+ assert_equal source.object_id, time.object_id
end
end
end
@@ -28,11 +30,43 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_time_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_equal source.object_id, time.object_id
+ end
+ end
+ end
+
+ def test_time_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_equal source.object_id, time.object_id
+ assert_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_time_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ assert_not_equal source.object_id, time.object_id
+ assert_not_predicate time, :frozen?
end
end
end
@@ -40,7 +74,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_datetime_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).to_time
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24))
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
@@ -52,7 +87,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_datetime_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).to_time
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24))
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
@@ -61,17 +97,47 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
end
end
+ def test_datetime_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_datetime_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
def test_twz_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @utc_offset, time.utc_offset
- time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @date_time, time.getutc
@@ -84,19 +150,69 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_twz_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone)
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone)
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @date_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_twz_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @date_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_twz_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
- time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze
+ time = source.to_time
assert_instance_of Time, time
assert_equal @date_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
end
end
end
@@ -104,7 +220,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_string_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = "2016-04-23T15:11:12+01:00".to_time
+ source = "2016-04-23T15:11:12+01:00"
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
@@ -116,11 +233,40 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_string_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = "2016-04-23T15:11:12+01:00".to_time
+ source = "2016-04-23T15:11:12+01:00"
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_string_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = "2016-04-23T15:11:12+01:00".freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_string_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = "2016-04-23T15:11:12+01:00".freeze
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
end
end
end
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 2b1a715b7a..1648a9b270 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -96,24 +96,20 @@ class DurationTest < ActiveSupport::TestCase
assert_instance_of ActiveSupport::Duration, 2.seconds - 1.second
assert_equal 1.second, 2.seconds - 1
assert_instance_of ActiveSupport::Duration, 2.seconds - 1
+ assert_equal 1.second, 2 - 1.second
+ assert_instance_of ActiveSupport::Duration, 2.seconds - 1
end
def test_multiply
assert_equal 7.days, 1.day * 7
assert_instance_of ActiveSupport::Duration, 1.day * 7
-
- assert_deprecated do
- assert_equal 86400, 1.day * 1.second
- end
+ assert_equal 86400, 1.day * 1.second
end
def test_divide
assert_equal 1.day, 7.days / 7
assert_instance_of ActiveSupport::Duration, 7.days / 7
-
- assert_deprecated do
- assert_equal 1, 1.day / 1.day
- end
+ assert_equal 1, 1.day / 1.day
end
def test_date_added_with_multiplied_duration
@@ -121,9 +117,7 @@ class DurationTest < ActiveSupport::TestCase
end
def test_plus_with_time
- assert_deprecated do
- assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration"
- end
+ assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration"
end
def test_time_plus_duration_returns_same_time_datatype
@@ -142,13 +136,6 @@ class DurationTest < ActiveSupport::TestCase
assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb"
end
- def test_implicit_coercion_is_deprecated
- assert_deprecated { 1 + 1.second }
- assert_deprecated { 1 - 1.second }
- assert_deprecated { 1 * 1.second }
- assert_deprecated { 1 / 1.second }
- end
-
def test_fractional_weeks
assert_equal((86400 * 7) * 1.5, 1.5.weeks)
assert_equal((86400 * 7) * 1.7, 1.7.weeks)
@@ -286,20 +273,125 @@ class DurationTest < ActiveSupport::TestCase
def test_comparable
assert_equal(-1, (0.seconds <=> 1.second))
assert_equal(-1, (1.second <=> 1.minute))
-
- assert_deprecated do
- assert_equal(-1, (1 <=> 1.minute))
- end
-
+ assert_equal(-1, (1 <=> 1.minute))
assert_equal(0, (0.seconds <=> 0.seconds))
assert_equal(0, (0.seconds <=> 0.minutes))
assert_equal(0, (1.second <=> 1.second))
assert_equal(1, (1.second <=> 0.second))
assert_equal(1, (1.minute <=> 1.second))
+ assert_equal(1, (61 <=> 1.minute))
+ end
- assert_deprecated do
- assert_equal(1, (61 <=> 1.minute))
+ def test_implicit_coercion
+ assert_equal 2.days, 2 * 1.day
+ assert_instance_of ActiveSupport::Duration, 2 * 1.day
+ assert_equal Time.utc(2017, 1, 3), Time.utc(2017, 1, 1) + 2 * 1.day
+ assert_equal Date.civil(2017, 1, 3), Date.civil(2017, 1, 1) + 2 * 1.day
+ end
+
+ def test_scalar_coerce
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+ assert_instance_of ActiveSupport::Duration::Scalar, 10 + scalar
+ assert_instance_of ActiveSupport::Duration, 10.seconds + scalar
+ end
+
+ def test_scalar_delegations
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+ assert_kind_of Float, scalar.to_f
+ assert_kind_of Integer, scalar.to_i
+ assert_kind_of String, scalar.to_s
+ end
+
+ def test_scalar_unary_minus
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal(-10, -scalar)
+ assert_instance_of ActiveSupport::Duration::Scalar, -scalar
+ end
+
+ def test_scalar_compare
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal(1, scalar <=> 5)
+ assert_equal(0, scalar <=> 10)
+ assert_equal(-1, scalar <=> 15)
+ assert_equal(nil, scalar <=> "foo")
+ end
+
+ def test_scalar_plus
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 20, 10 + scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 10 + scalar
+ assert_equal 20, scalar + 10
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar + 10
+ assert_equal 20, 10.seconds + scalar
+ assert_instance_of ActiveSupport::Duration, 10.seconds + scalar
+ assert_equal 20, scalar + 10.seconds
+ assert_instance_of ActiveSupport::Duration, scalar + 10.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar + "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_minus
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 10, 20 - scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 20 - scalar
+ assert_equal 5, scalar - 5
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar - 5
+ assert_equal 10, 20.seconds - scalar
+ assert_instance_of ActiveSupport::Duration, 20.seconds - scalar
+ assert_equal 5, scalar - 5.seconds
+ assert_instance_of ActiveSupport::Duration, scalar - 5.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar - "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_multiply
+ scalar = ActiveSupport::Duration::Scalar.new(5)
+
+ assert_equal 10, 2 * scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 2 * scalar
+ assert_equal 10, scalar * 2
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar * 2
+ assert_equal 10, 2.seconds * scalar
+ assert_instance_of ActiveSupport::Duration, 2.seconds * scalar
+ assert_equal 10, scalar * 2.seconds
+ assert_instance_of ActiveSupport::Duration, scalar * 2.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar * "foo"
end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_divide
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 10, 100 / scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 100 / scalar
+ assert_equal 5, scalar / 2
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar / 2
+ assert_equal 10, 100.seconds / scalar
+ assert_instance_of ActiveSupport::Duration, 2.seconds * scalar
+ assert_equal 5, scalar / 2.seconds
+ assert_instance_of ActiveSupport::Duration, scalar / 2.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar / "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
end
def test_twelve_months_equals_one_year
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 3cc29ca040..c3afe68378 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -431,11 +431,29 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal time, Time.at(time)
end
- def test_to_time
- with_env_tz "US/Eastern" do
- assert_equal Time, @twz.to_time.class
- assert_equal Time.local(1999, 12, 31, 19), @twz.to_time
- assert_equal Time.local(1999, 12, 31, 19).utc_offset, @twz.to_time.utc_offset
+ def test_to_time_with_preserve_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ time = @twz.to_time
+
+ assert_equal Time, time.class
+ assert_equal time.object_id, @twz.to_time.object_id
+ assert_equal Time.local(1999, 12, 31, 19), time
+ assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_to_time_without_preserve_timezone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ time = @twz.to_time
+
+ assert_equal Time, time.class
+ assert_equal time.object_id, @twz.to_time.object_id
+ assert_equal Time.local(1999, 12, 31, 19), time
+ assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
+ end
end
end
@@ -518,6 +536,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
@twz.period
@twz.time
@twz.to_datetime
+ @twz.to_time
end
end
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 31865ea375..2902c5d677 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -953,9 +953,6 @@ class Client < ApplicationRecord
end
```
-NOTE: Please note that the optimistic locking will be ignored if you update the
-locking column's value.
-
### Pessimistic Locking
Pessimistic locking uses a locking mechanism provided by the underlying database. Using `lock` when building a relation obtains an exclusive lock on the selected rows. Relations using `lock` are usually wrapped inside a transaction for preventing deadlock conditions.
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 32b38cde5e..5313361dfd 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -490,9 +490,6 @@ If you set `:only_integer` to `true`, then it will use the
regular expression to validate the attribute's value. Otherwise, it will try to
convert the value to a number using `Float`.
-WARNING. Note that the regular expression above allows a trailing newline
-character.
-
```ruby
class Player < ApplicationRecord
validates :points, numericality: true
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 68dde4482f..61b7112247 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -335,7 +335,7 @@ an asset has been updated and if so loads it into the page:
<%= javascript_include_tag "application", "data-turbolinks-track" => "reload" %>
```
-In regular views you can access images in the `public/assets/images` directory
+In regular views you can access images in the `app/assets/images` directory
like this:
```erb
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index ba0cdbf3af..33dee6a868 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -606,7 +606,6 @@ You can also inspect for an object method this way:
@new_record = true
@readonly = false
@transaction_state = nil
-@txn = nil
```
You can also use `display` to start watching variables. This is a good way of
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 9b6ceff9ff..58470e2f10 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,7 +1,9 @@
* Avoid running system tests by default with the `bin/rails test`
and `bin/rake test` commands since they may be expensive.
- *Robin Dupret* (#28286)
+ Fixes #28286.
+
+ *Robin Dupret*
* Improve encryption for encrypted secrets.
@@ -124,6 +126,10 @@
*Tsukuru Tanimichi*
+* Add `--skip-coffee` option to `rails new`
+
+ *Seunghwan Oh*
+
* Allow the use of listen's 3.1.x branch
*Esteban Santana Santana*
diff --git a/railties/lib/rails/commands/destroy/destroy_command.rb b/railties/lib/rails/commands/destroy/destroy_command.rb
index c802910b5d..794673851d 100644
--- a/railties/lib/rails/commands/destroy/destroy_command.rb
+++ b/railties/lib/rails/commands/destroy/destroy_command.rb
@@ -14,9 +14,9 @@ module Rails
return help unless generator
require_application_and_environment!
- Rails.application.load_generators
+ load_generators
- Rails::Generators.invoke generator, args, behavior: :revoke, destination_root: Rails.root
+ Rails::Generators.invoke generator, args, behavior: :revoke, destination_root: Rails::Command.root
end
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index b082d70cba..ab4aa04fff 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -34,7 +34,7 @@ group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
<%- unless options.skip_system_test? || options.api? -%>
# Adds support for Capybara system testing and selenium driver
- gem 'capybara', '~> 2.7.0'
+ gem 'capybara', '~> 2.13.0'
gem 'selenium-webdriver'
<%- end -%>
end
diff --git a/railties/test/commands/secrets_test.rb b/railties/test/commands/secrets_test.rb
index 13fcf6c8a4..00b0343397 100644
--- a/railties/test/commands/secrets_test.rb
+++ b/railties/test/commands/secrets_test.rb
@@ -17,8 +17,21 @@ class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase
assert_match "No $EDITOR to open decrypted secrets in", run_edit_command(editor: "")
end
+ test "edit secrets" do
+ run_setup_command
+
+ # Run twice to ensure encrypted secrets can be reread after first edit pass.
+ 2.times do
+ assert_match(/external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289…/, run_edit_command)
+ end
+ end
+
private
def run_edit_command(editor: "cat")
Dir.chdir(app_path) { `EDITOR="#{editor}" bin/rails secrets:edit` }
end
+
+ def run_setup_command
+ Dir.chdir(app_path) { `bin/rails secrets:setup` }
+ end
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 986afb6d2a..79fe4e4eb7 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -133,7 +133,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_rails_update_generates_correct_session_key
+ def test_app_update_generates_correct_session_key
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
@@ -156,7 +156,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_file "config/initializers/cors.rb"
end
- def test_rails_update_keep_the_cookie_serializer_if_it_is_already_configured
+ def test_app_update_keep_the_cookie_serializer_if_it_is_already_configured
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
@@ -168,7 +168,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_rails_update_set_the_cookie_serializer_to_marshal_if_it_is_not_already_configured
+ def test_app_update_set_the_cookie_serializer_to_marshal_if_it_is_not_already_configured
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
@@ -183,7 +183,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_rails_update_dont_set_file_watcher
+ def test_app_update_dont_set_file_watcher
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
@@ -197,7 +197,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_rails_update_does_not_create_new_framework_defaults_by_default
+ def test_app_update_does_not_create_new_framework_defaults_by_default
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
@@ -215,7 +215,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_rails_update_does_not_create_rack_cors
+ def test_app_update_does_not_create_rack_cors
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
@@ -227,7 +227,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_rails_update_does_not_remove_rack_cors_if_already_present
+ def test_app_update_does_not_remove_rack_cors_if_already_present
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 436fbd5d73..b9c2e791da 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -558,4 +558,59 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
end
end
+
+ def test_scaffold_on_invoke_inside_mountable_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable` }
+ engine_path = File.join(destination_root, "bukkits")
+
+ Dir.chdir(engine_path) do
+ quietly { `bin/rails generate scaffold User name:string age:integer` }
+
+ assert File.exist?("app/models/bukkits/user.rb")
+ assert File.exist?("test/models/bukkits/user_test.rb")
+ assert File.exist?("test/fixtures/bukkits/users.yml")
+
+ assert File.exist?("app/controllers/bukkits/users_controller.rb")
+ assert File.exist?("test/controllers/bukkits/users_controller_test.rb")
+
+ assert File.exist?("app/views/bukkits/users/index.html.erb")
+ assert File.exist?("app/views/bukkits/users/edit.html.erb")
+ assert File.exist?("app/views/bukkits/users/show.html.erb")
+ assert File.exist?("app/views/bukkits/users/new.html.erb")
+ assert File.exist?("app/views/bukkits/users/_form.html.erb")
+
+ assert File.exist?("app/helpers/bukkits/users_helper.rb")
+
+ assert File.exist?("app/assets/javascripts/bukkits/users.js")
+ assert File.exist?("app/assets/stylesheets/bukkits/users.css")
+ end
+ end
+
+ def test_scaffold_on_revoke_inside_mountable_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable` }
+ engine_path = File.join(destination_root, "bukkits")
+
+ Dir.chdir(engine_path) do
+ quietly { `bin/rails generate scaffold User name:string age:integer` }
+ quietly { `bin/rails destroy scaffold User` }
+
+ assert_not File.exist?("app/models/bukkits/user.rb")
+ assert_not File.exist?("test/models/bukkits/user_test.rb")
+ assert_not File.exist?("test/fixtures/bukkits/users.yml")
+
+ assert_not File.exist?("app/controllers/bukkits/users_controller.rb")
+ assert_not File.exist?("test/controllers/bukkits/users_controller_test.rb")
+
+ assert_not File.exist?("app/views/bukkits/users/index.html.erb")
+ assert_not File.exist?("app/views/bukkits/users/edit.html.erb")
+ assert_not File.exist?("app/views/bukkits/users/show.html.erb")
+ assert_not File.exist?("app/views/bukkits/users/new.html.erb")
+ assert_not File.exist?("app/views/bukkits/users/_form.html.erb")
+
+ assert_not File.exist?("app/helpers/bukkits/users_helper.rb")
+
+ assert_not File.exist?("app/assets/javascripts/bukkits/users.js")
+ assert_not File.exist?("app/assets/stylesheets/bukkits/users.css")
+ end
+ end
end