diff options
53 files changed, 478 insertions, 403 deletions
@@ -45,7 +45,7 @@ group :doc do end # Active Support. -gem "dalli", ">= 2.2.1" +gem "dalli", "< 2.7.7" gem "listen", ">= 3.0.5", "< 3.2", require: false gem "libxml-ruby", platforms: :ruby gem "connection_pool", require: false @@ -64,9 +64,6 @@ group :job do gem "sneakers", require: false gem "que", require: false gem "backburner", require: false - # TODO: add qu after it support Rails 5.1 - # gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false - # gem "qu-redis", require: false gem "delayed_job_active_record", require: false gem "sequel", require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 58d751c4c3..4f0c512114 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -285,7 +285,7 @@ GEM logging (2.2.2) little-plugger (~> 1.1) multi_json (~> 1.10) - loofah (2.1.1) + loofah (2.2.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.0) @@ -324,12 +324,12 @@ GEM mysql2 (0.4.10-x86-mingw32) nio4r (2.2.0) nio4r (2.2.0-java) - nokogiri (1.8.1) + nokogiri (1.8.2) mini_portile2 (~> 2.3.0) - nokogiri (1.8.1-java) - nokogiri (1.8.1-x64-mingw32) + nokogiri (1.8.2-java) + nokogiri (1.8.2-x64-mingw32) mini_portile2 (~> 2.3.0) - nokogiri (1.8.1-x86-mingw32) + nokogiri (1.8.2-x86-mingw32) mini_portile2 (~> 2.3.0) os (0.9.6) parallel (1.12.1) @@ -510,7 +510,7 @@ DEPENDENCIES chromedriver-helper coffee-rails connection_pool - dalli (>= 2.2.1) + dalli (< 2.7.7) delayed_job delayed_job_active_record google-cloud-storage (~> 1.8) diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb index ec748236b4..e9ddef3b94 100644 --- a/actionmailer/lib/action_mailer/test_helper.rb +++ b/actionmailer/lib/action_mailer/test_helper.rb @@ -14,7 +14,7 @@ module ActionMailer # assert_emails 0 # ContactMailer.welcome.deliver_now # assert_emails 1 - # ContactMailer.welcome.deliver_later + # ContactMailer.welcome.deliver_now # assert_emails 2 # end # @@ -38,9 +38,7 @@ module ActionMailer new_count = ActionMailer::Base.deliveries.size assert_equal number, new_count - original_count, "#{number} emails expected, but #{new_count - original_count} were sent" else - perform_enqueued_jobs(only: [ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob]) do - assert_equal number, ActionMailer::Base.deliveries.size - end + assert_equal number, ActionMailer::Base.deliveries.size end end diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 6d9f36ad75..240269d1c7 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -15,6 +15,8 @@ module ActionDispatch # # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } } # + # Cookies will not be flagged as secure for excluded requests. + # # 2. <b>Secure cookies</b>: Sets the +secure+ flag on cookies to tell browsers they # must not be sent along with +http://+ requests. Enabled by default. Set # +config.ssl_options+ with <tt>secure_cookies: false</tt> to disable this feature. @@ -71,7 +73,7 @@ module ActionDispatch if request.ssl? @app.call(env).tap do |status, headers, body| set_hsts_header! headers - flag_cookies_as_secure! headers if @secure_cookies + flag_cookies_as_secure! headers if @secure_cookies && !@exclude.call(request) end else return redirect_to_https request unless @exclude.call(request) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index f3970d5445..d9dd24935b 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -664,6 +664,7 @@ module ActionDispatch def define_generate_prefix(app, name) _route = @set.named_routes.get name _routes = @set + _url_helpers = @set.url_helpers script_namer = ->(options) do prefix_options = options.slice(*_route.segment_keys) @@ -675,7 +676,7 @@ module ActionDispatch # We must actually delete prefix segment keys to avoid passing them to next url_for. _route.segment_keys.each { |k| options.delete(k) } - _routes.url_helpers.send("#{name}_path", prefix_options) + _url_helpers.send("#{name}_path", prefix_options) end app.routes.define_mounted_helper(name, script_namer) diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb index ffa85f4e14..e47d5020f4 100644 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb @@ -19,6 +19,7 @@ module ActionDispatch def after_teardown take_failed_screenshot Capybara.reset_sessions! + ensure super end end diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index 90f2ee46ea..baf46e7c7e 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -208,6 +208,14 @@ class SecureCookiesTest < SSLTest assert_cookies(*DEFAULT.split("\n")) end + def test_cookies_as_not_secure_with_exclude + excluding = { exclude: -> request { request.domain =~ /example/ } } + get headers: { "Set-Cookie" => DEFAULT }, ssl_options: { redirect: excluding } + + assert_cookies(*DEFAULT.split("\n")) + assert_response :ok + end + def test_no_cookies get assert_nil response.headers["Set-Cookie"] diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 1cf0bd3016..dbd7a4ee11 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -45,11 +45,9 @@ module ActionView # Create a dependency tree for template named +name+. def tree(name, finder, partial = false, seen = {}) logical_name = name.gsub(%r|/_|, "/") + finder.formats = [finder.rendered_format] if finder.rendered_format - options = {} - options[:formats] = [finder.rendered_format] if finder.rendered_format - - if template = finder.disable_cache { finder.find_all(logical_name, [], partial, [], options).first } + if template = finder.disable_cache { finder.find_all(logical_name, [], partial, []).first } finder.rendered_format ||= template.formats.first if node = seen[template.identifier] # handle cycles in the tree diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index f1eca2268a..eef527d36f 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -109,11 +109,11 @@ module ActionView # a little duplication to construct less strings case when @object_name.empty? - "#{sanitized_method_name}#{"[]" if multiple}" + "#{sanitized_method_name}#{multiple ? "[]" : ""}" when index - "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" + "#{@object_name}[#{index}][#{sanitized_method_name}]#{multiple ? "[]" : ""}" else - "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" + "#{@object_name}[#{sanitized_method_name}]#{multiple ? "[]" : ""}" end end diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index 1860bc4732..80cb73d683 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -122,9 +122,12 @@ module ActionView private def scope_key_by_partial(key) - if key.to_s.first == "." + stringified_key = key.to_s + if stringified_key.first == "." if @virtual_path - @virtual_path.gsub(%r{/_?}, ".") + key.to_s + @_scope_key_by_partial_cache ||= {} + @_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".") + "#{@_scope_key_by_partial_cache[@virtual_path]}#{stringified_key}" else raise "Cannot use t(#{key.inspect}) shortcut because path is not available" end diff --git a/actionview/test/activerecord/multifetch_cache_test.rb b/actionview/test/activerecord/multifetch_cache_test.rb new file mode 100644 index 0000000000..12be069e69 --- /dev/null +++ b/actionview/test/activerecord/multifetch_cache_test.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "active_record_unit" +require "active_record/railties/collection_cache_association_loading" + +ActionView::PartialRenderer.prepend(ActiveRecord::Railties::CollectionCacheAssociationLoading) + +class MultifetchCacheTest < ActiveRecordTestCase + fixtures :topics, :replies + + def setup + view_paths = ActionController::Base.view_paths + + @view = Class.new(ActionView::Base) do + def view_cache_dependencies + [] + end + + def combined_fragment_cache_key(key) + [ :views, key ] + end + end.new(view_paths, {}) + end + + def test_only_preloading_for_records_that_miss_the_cache + @view.render partial: "test/partial", collection: [topics(:rails)], cached: true + + @topics = Topic.preload(:replies) + + @view.render partial: "test/partial", collection: @topics, cached: true + + assert_not @topics.detect { |topic| topic.id == topics(:rails).id }.replies.loaded? + assert @topics.detect { |topic| topic.id != topics(:rails).id }.replies.loaded? + end +end diff --git a/actionview/test/fixtures/digestor/comments/show.js.erb b/actionview/test/fixtures/digestor/comments/show.js.erb new file mode 100644 index 0000000000..38b37dfa2b --- /dev/null +++ b/actionview/test/fixtures/digestor/comments/show.js.erb @@ -0,0 +1 @@ +alert("<%=j render("comments/comment") %>") diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb index 1bfa39a319..ddaa7febb3 100644 --- a/actionview/test/template/digestor_test.rb +++ b/actionview/test/template/digestor_test.rb @@ -160,6 +160,18 @@ class TemplateDigestorTest < ActionView::TestCase assert_equal [:html], tree_template_formats("messages/show").uniq end + def test_template_dependencies_with_fallback_from_js_to_html_format + finder.rendered_format = :js + assert_equal ["comments/comment"], dependencies("comments/show") + end + + def test_template_digest_with_fallback_from_js_to_html_format + finder.rendered_format = :js + assert_digest_difference("comments/show") do + change_template("comments/_comment") + end + end + def test_recursion_in_renders assert digest("level/recursion") # assert recursion is possible assert_not_nil digest("level/recursion") # assert digest is stored diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 4e832eca20..a3d13ad162 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,11 @@ +* Remove support for Qu gem. + + Reasons are that the Qu gem wasn't compatible since Rails 5.1, + gem development was stopped in 2014 and maintainers have + confirmed its demise. See issue #32273 + + *Alberto Almagro* + * Add support for timezones to Active Job. Record what was the current timezone in effect when the job was diff --git a/activejob/README.md b/activejob/README.md index f1ebb76e08..d49fcfe3c2 100644 --- a/activejob/README.md +++ b/activejob/README.md @@ -88,6 +88,12 @@ Active Job has built-in adapters for multiple queueing backends (Sidekiq, Resque, Delayed Job and others). To get an up-to-date list of the adapters see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). +**Please note:** We are not accepting pull requests for new adapters. We +encourage library authors to provide an ActiveJob adapter as part of +their gem, or as a stand-alone gem. For discussion about this see the +following PRs: [23311](https://github.com/rails/rails/issues/23311#issuecomment-176275718), +[21406](https://github.com/rails/rails/pull/21406#issuecomment-138813484), and [#32285](https://github.com/rails/rails/pull/32285). + ## Auxiliary gems * [activejob-stats](https://github.com/seuros/activejob-stats) diff --git a/activejob/Rakefile b/activejob/Rakefile index 77f3e7f8ff..b4da75adab 100644 --- a/activejob/Rakefile +++ b/activejob/Rakefile @@ -2,7 +2,6 @@ require "rake/testtask" -# TODO: add qu back to the list after it support Rails 5.1 ACTIVEJOB_ADAPTERS = %w(async inline delayed_job que queue_classic resque sidekiq sneakers sucker_punch backburner test) ACTIVEJOB_ADAPTERS.delete("queue_classic") if defined?(JRUBY_VERSION) diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb index c1a1d3c510..7854467cc1 100644 --- a/activejob/lib/active_job/queue_adapters.rb +++ b/activejob/lib/active_job/queue_adapters.rb @@ -7,7 +7,6 @@ module ActiveJob # # * {Backburner}[https://github.com/nesquena/backburner] # * {Delayed Job}[https://github.com/collectiveidea/delayed_job] - # * {Qu}[https://github.com/bkeepers/qu] # * {Que}[https://github.com/chanks/que] # * {queue_classic}[https://github.com/QueueClassic/queue_classic] # * {Resque}[https://github.com/resque/resque] @@ -16,6 +15,7 @@ module ActiveJob # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch] # * {Active Job Async Job}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html] # * {Active Job Inline}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html] + # * Please Note: We are not accepting pull requests for new adapters. See the README for more details. # # === Backends Features # @@ -23,7 +23,6 @@ module ActiveJob # |-------------------|-------|--------|------------|------------|---------|---------| # | Backburner | Yes | Yes | Yes | Yes | Job | Global | # | Delayed Job | Yes | Yes | Yes | Job | Global | Global | - # | Qu | Yes | Yes | No | No | No | Global | # | Que | Yes | Yes | Yes | Job | No | Job | # | queue_classic | Yes | Yes | Yes* | No | No | No | # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes | @@ -114,7 +113,6 @@ module ActiveJob autoload :InlineAdapter autoload :BackburnerAdapter autoload :DelayedJobAdapter - autoload :QuAdapter autoload :QueAdapter autoload :QueueClassicAdapter autoload :ResqueAdapter diff --git a/activejob/lib/active_job/queue_adapters/qu_adapter.rb b/activejob/lib/active_job/queue_adapters/qu_adapter.rb deleted file mode 100644 index bd7003e177..0000000000 --- a/activejob/lib/active_job/queue_adapters/qu_adapter.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require "qu" - -module ActiveJob - module QueueAdapters - # == Qu adapter for Active Job - # - # Qu is a Ruby library for queuing and processing background jobs. It is - # heavily inspired by delayed_job and Resque. Qu was created to overcome - # some shortcomings in the existing queuing libraries. - # The advantages of Qu are: Multiple backends (redis, mongo), jobs are - # requeued when worker is killed, resque-like API. - # - # Read more about Qu {here}[https://github.com/bkeepers/qu]. - # - # To use Qu set the queue_adapter config to +:qu+. - # - # Rails.application.config.active_job.queue_adapter = :qu - class QuAdapter - def enqueue(job, *args) #:nodoc: - qu_job = Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload| - payload.instance_variable_set(:@queue, job.queue_name) - end.push - - # qu_job can be nil depending on the configured backend - job.provider_job_id = qu_job.id unless qu_job.nil? - qu_job - end - - def enqueue_at(job, timestamp, *args) #:nodoc: - raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html" - end - - class JobWrapper < Qu::Job #:nodoc: - def initialize(job_data) - @job_data = job_data - end - - def perform - Base.execute @job_data - end - end - end - end -end diff --git a/activejob/test/adapters/qu.rb b/activejob/test/adapters/qu.rb deleted file mode 100644 index 5b471fa347..0000000000 --- a/activejob/test/adapters/qu.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -require "qu-immediate" - -ActiveJob::Base.queue_adapter = :qu diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb index 7a95d3d039..32afb5ca62 100644 --- a/activejob/test/integration/queuing_test.rb +++ b/activejob/test/integration/queuing_test.rb @@ -82,7 +82,7 @@ class QueuingTest < ActiveSupport::TestCase end test "should supply a provider_job_id when available for immediate jobs" do - skip unless adapter_is?(:async, :delayed_job, :sidekiq, :qu, :que, :queue_classic) + skip unless adapter_is?(:async, :delayed_job, :sidekiq, :que, :queue_classic) test_job = TestJob.perform_later @id assert test_job.provider_job_id, "Provider job id should be set by provider" end diff --git a/activejob/test/support/integration/adapters/qu.rb b/activejob/test/support/integration/adapters/qu.rb deleted file mode 100644 index 67db03e279..0000000000 --- a/activejob/test/support/integration/adapters/qu.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module QuJobsManager - def setup - require "qu-rails" - require "qu-redis" - ActiveJob::Base.queue_adapter = :qu - ENV["REDISTOGO_URL"] = "redis://127.0.0.1:6379/12" - backend = Qu::Backend::Redis.new - backend.namespace = "active_jobs_int_test" - Qu.backend = backend - Qu.logger = Rails.logger - Qu.interval = 0.5 - unless can_run? - puts "Cannot run integration tests for qu. To be able to run integration tests for qu you need to install and start redis.\n" - exit - end - end - - def clear_jobs - Qu.clear "integration_tests" - end - - def start_workers - @thread = Thread.new { Qu::Worker.new("integration_tests").start } - end - - def stop_workers - @thread.kill - end - - def can_run? - begin - Qu.backend.connection.client.connect - rescue - return false - end - true - end -end diff --git a/activemodel/lib/active_model/type/time.rb b/activemodel/lib/active_model/type/time.rb index ad7ba0351a..c094ee0013 100644 --- a/activemodel/lib/active_model/type/time.rb +++ b/activemodel/lib/active_model/type/time.rb @@ -28,14 +28,10 @@ module ActiveModel private def cast_value(value) - return value unless value.is_a?(::String) + return apply_seconds_precision(value) unless value.is_a?(::String) return if value.empty? - if value.start_with?("2000-01-01") - dummy_time_value = value - else - dummy_time_value = "2000-01-01 #{value}" - end + dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ") fast_string_to_time(dummy_time_value) || begin time_hash = ::Date._parse(dummy_time_value) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 92e46ccf9f..aec5fa6ba1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -130,7 +130,7 @@ module ActiveRecord end def quoted_time(value) # :nodoc: - quoted_date(value).sub(/\A2000-01-01 /, "") + quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "") end def quoted_binary(value) # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 508132accb..901717ae3d 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -156,7 +156,6 @@ module ActiveRecord env_config = config[env] if config[env].is_a?(Hash) && !(config[env].key?("adapter") || config[env].key?("url")) end - config.reject! { |k, v| v.is_a?(Hash) && !(v.key?("adapter") || v.key?("url")) } config.merge! env_config if env_config config.each do |key, value| diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index bfdc7995f0..4c57bd48ab 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -3,7 +3,7 @@ require "active_record/connection_adapters/abstract_mysql_adapter" require "active_record/connection_adapters/mysql/database_statements" -gem "mysql2", "~> 0.4.4" +gem "mysql2", ">= 0.4.4", "< 0.6.0" require "mysql2" module ActiveRecord 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 45b230f0f9..e20e5f2914 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -107,7 +107,7 @@ module ActiveRecord oid = row[4] comment = row[5] - using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten + using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten orders = {} opclasses = {} diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb index 8042dbfea2..70de96326c 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb @@ -17,7 +17,7 @@ module ActiveRecord end def quoted_time(value) - quoted_date(value) + quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ") end def quoted_binary(value) diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 88d28dc52a..ee0e651912 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -57,6 +57,10 @@ module ActiveRecord spec = resolver.resolve(config).symbolize_keys spec[:name] = spec_name + # use the primary config if a config is not passed in and + # it's a three tier config + spec = spec[spec_name.to_sym] if spec[spec_name.to_sym] + connection_handler.establish_connection(spec) end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index ade5946dd5..6ab80a654d 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -157,6 +157,13 @@ end_warning end end + initializer "active_record.collection_cache_association_loading" do + require "active_record/railties/collection_cache_association_loading" + ActiveSupport.on_load(:action_view) do + ActionView::PartialRenderer.prepend(ActiveRecord::Railties::CollectionCacheAssociationLoading) + end + end + initializer "active_record.set_reloader_hooks" do ActiveSupport.on_load(:active_record) do ActiveSupport::Reloader.before_class_unload do diff --git a/activerecord/lib/active_record/railties/collection_cache_association_loading.rb b/activerecord/lib/active_record/railties/collection_cache_association_loading.rb new file mode 100644 index 0000000000..b5129e4239 --- /dev/null +++ b/activerecord/lib/active_record/railties/collection_cache_association_loading.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ActiveRecord + module Railties # :nodoc: + module CollectionCacheAssociationLoading #:nodoc: + def setup(context, options, block) + @relation = relation_from_options(options) + + super + end + + def relation_from_options(cached: nil, partial: nil, collection: nil, **_) + return unless cached + + relation = partial if partial.is_a?(ActiveRecord::Relation) + relation ||= collection if collection.is_a?(ActiveRecord::Relation) + + if relation && !relation.loaded? + relation.skip_preloading! + end + end + + def collection_without_template + @relation.preload_associations(@collection) if @relation + super + end + + def collection_with_template + @relation.preload_associations(@collection) if @relation + super + end + end + end +end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 736173ae1b..34e643b2de 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -8,7 +8,8 @@ module ActiveRecord :extending, :unscope] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, - :reverse_order, :distinct, :create_with, :skip_query_cache] + :reverse_order, :distinct, :create_with, :skip_query_cache, + :skip_preloading] CLAUSE_METHODS = [:where, :having, :from] INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having] @@ -546,6 +547,16 @@ module ActiveRecord ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins) end + def preload_associations(records) + preload = preload_values + preload += includes_values unless eager_loading? + preloader = nil + preload.each do |associations| + preloader ||= build_preloader + preloader.preload records, associations + end + end + protected def load_records(records) @@ -575,13 +586,7 @@ module ActiveRecord klass.find_by_sql(arel, &block).freeze end - preload = preload_values - preload += includes_values unless eager_loading? - preloader = nil - preload.each do |associations| - preloader ||= build_preloader - preloader.preload @records, associations - end + preload_associations(@records) unless skip_preloading_value @records.each(&:readonly!) if readonly_value diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 3afa368575..4e60863e52 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -899,6 +899,11 @@ module ActiveRecord self end + def skip_preloading! # :nodoc: + self.skip_preloading_value = true + self + end + # Returns the Arel object associated with the relation. def arel(aliases = nil) # :nodoc: @arel ||= build_arel(aliases) diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index 6dbc977f9a..3290675338 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -17,8 +17,8 @@ module ActiveRecord # You can set custom coder to encode/decode your serialized attributes to/from different formats. # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+. # - # NOTE: If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for - # the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store]. + # NOTE: If you are using structured database data types (eg. PostgreSQL +hstore+/+json+, or MySQL 5.7+ + # +json+) there is no need for the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store]. # Simply use {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead to generate # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access # using a symbol. diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 6fdb353368..1c85ff5674 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -55,4 +55,11 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value)) end + + def test_quoted_time_normalizes_date_qualified_time + value = ::Time.utc(2018, 3, 11, 12, 30, 0, 999999) + type = ActiveRecord::Type::Time.new + + assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value)) + end end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index f4cc251fb9..c06a4e2c52 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -71,6 +71,54 @@ module ActiveRecord ENV["RAILS_ENV"] = previous_env end + unless in_memory_db? + def test_establish_connection_using_3_level_config_defaults_to_default_env_primary_db + previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env" + + config = { + "default_env" => { + "primary" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" }, + "readonly" => { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" } + }, + "another_env" => { + "primary" => { "adapter" => "sqlite3", "database" => "db/another-primary.sqlite3" }, + "readonly" => { "adapter" => "sqlite3", "database" => "db/another-readonly.sqlite3" } + } + } + @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config + + ActiveRecord::Base.establish_connection + + assert_equal "db/primary.sqlite3", ActiveRecord::Base.connection.pool.spec.config[:database] + ensure + ActiveRecord::Base.configurations = @prev_configs + ENV["RAILS_ENV"] = previous_env + ActiveRecord::Base.establish_connection(:arunit) + end + + def test_establish_connection_using_2_level_config_defaults_to_default_env_primary_db + previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env" + + config = { + "default_env" => { + "adapter" => "sqlite3", "database" => "db/primary.sqlite3" + }, + "another_env" => { + "adapter" => "sqlite3", "database" => "db/bad-primary.sqlite3" + } + } + @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config + + ActiveRecord::Base.establish_connection + + assert_equal "db/primary.sqlite3", ActiveRecord::Base.connection.pool.spec.config[:database] + ensure + ActiveRecord::Base.configurations = @prev_configs + ENV["RAILS_ENV"] = previous_env + ActiveRecord::Base.establish_connection(:arunit) + end + end + def test_establish_connection_using_two_level_configurations config = { "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" } } @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb index 51f6164138..e64a8372d0 100644 --- a/activerecord/test/cases/date_time_precision_test.rb +++ b/activerecord/test/cases/date_time_precision_test.rb @@ -27,6 +27,24 @@ if subsecond_precision_supported? assert_equal 5, Foo.columns_hash["updated_at"].precision end + def test_datetime_precision_is_truncated_on_assignment + @connection.create_table(:foos, force: true) + @connection.add_column :foos, :created_at, :datetime, precision: 0 + @connection.add_column :foos, :updated_at, :datetime, precision: 6 + + time = ::Time.now.change(nsec: 123456789) + foo = Foo.new(created_at: time, updated_at: time) + + assert_equal 0, foo.created_at.nsec + assert_equal 123456000, foo.updated_at.nsec + + foo.save! + foo.reload + + assert_equal 0, foo.created_at.nsec + assert_equal 123456000, foo.updated_at.nsec + end + def test_timestamps_helper_with_custom_precision @connection.create_table(:foos, force: true) do |t| t.timestamps precision: 4 diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 6534770c57..92eb0c814f 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -46,27 +46,60 @@ module ActiveRecord assert_equal t.to_s(:db), @quoter.quoted_date(t) end - def test_quoted_time_utc + def test_quoted_timestamp_utc with_timezone_config default: :utc do t = Time.now.change(usec: 0) assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t) end end - def test_quoted_time_local + def test_quoted_timestamp_local with_timezone_config default: :local do t = Time.now.change(usec: 0) assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t) end end - def test_quoted_time_crazy + def test_quoted_timestamp_crazy with_timezone_config default: :asdfasdf do t = Time.now.change(usec: 0) assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t) end end + def test_quoted_time_utc + with_timezone_config default: :utc do + t = Time.now.change(usec: 0) + + expected = t.getutc.change(year: 2000, month: 1, day: 1) + expected = expected.to_s(:db).sub("2000-01-01 ", "") + + assert_equal expected, @quoter.quoted_time(t) + end + end + + def test_quoted_time_local + with_timezone_config default: :local do + t = Time.now.change(usec: 0) + + expected = t.change(year: 2000, month: 1, day: 1) + expected = expected.getlocal.to_s(:db).sub("2000-01-01 ", "") + + assert_equal expected, @quoter.quoted_time(t) + end + end + + def test_quoted_time_crazy + with_timezone_config default: :asdfasdf do + t = Time.now.change(usec: 0) + + expected = t.change(year: 2000, month: 1, day: 1) + expected = expected.getlocal.to_s(:db).sub("2000-01-01 ", "") + + assert_equal expected, @quoter.quoted_time(t) + end + end + def test_quoted_datetime_utc with_timezone_config default: :utc do t = Time.now.change(usec: 0).to_datetime diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index 1428b3e132..d6351bfe88 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -59,7 +59,7 @@ module ActiveRecord assert_equal [], relation.extending_values end - (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with, :skip_query_cache]).each do |method| + (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with, :skip_query_cache, :skip_preloading]).each do |method| test "##{method}!" do assert relation.public_send("#{method}!", :foo).equal?(relation) assert_equal :foo, relation.public_send("#{method}_value") @@ -137,6 +137,11 @@ module ActiveRecord assert relation.skip_query_cache_value end + test "skip_preloading!" do + relation.skip_preloading! + assert relation.skip_preloading_value + end + private def relation @relation ||= Relation.new(FakeKlass) diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index a612ce9bb2..50d766a99e 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -298,7 +298,7 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dump_expression_indices index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip - assert_equal 't.index "lower((name)::text)", name: "company_expression_index"', index_definition + assert_match %r{CASE.+lower\(\(name\)::text\)}i, index_definition end def test_schema_dump_interval_type diff --git a/activerecord/test/cases/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb index 41455637bb..086500de38 100644 --- a/activerecord/test/cases/time_precision_test.rb +++ b/activerecord/test/cases/time_precision_test.rb @@ -27,6 +27,24 @@ if subsecond_precision_supported? assert_equal 6, Foo.columns_hash["finish"].precision end + def test_time_precision_is_truncated_on_assignment + @connection.create_table(:foos, force: true) + @connection.add_column :foos, :start, :time, precision: 0 + @connection.add_column :foos, :finish, :time, precision: 6 + + time = ::Time.now.change(nsec: 123456789) + foo = Foo.new(start: time, finish: time) + + assert_equal 0, foo.start.nsec + assert_equal 123456000, foo.finish.nsec + + foo.save! + foo.reload + + assert_equal 0, foo.start.nsec + assert_equal 123456000, foo.finish.nsec + end + def test_passing_precision_to_time_does_not_set_limit @connection.create_table(:foos, force: true) do |t| t.time :start, precision: 3 diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 8b0106dbf0..ca86100bc5 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -210,7 +210,7 @@ ActiveRecord::Schema.define do t.index [:firm_id, :type, :rating], name: "company_index", length: { type: 10 }, order: { rating: :desc } t.index [:firm_id, :type], name: "company_partial_index", where: "(rating > 10)" t.index :name, name: "company_name_index", using: :btree - t.index "lower(name)", name: "company_expression_index" if supports_expression_index? + t.index "(CASE WHEN rating > 0 THEN lower(name) END)", name: "company_expression_index" if supports_expression_index? end create_table :content, force: true do |t| diff --git a/activestorage/lib/active_storage/service/s3_service.rb b/activestorage/lib/active_storage/service/s3_service.rb index 8ab7a44131..5e489f4be1 100644 --- a/activestorage/lib/active_storage/service/s3_service.rb +++ b/activestorage/lib/active_storage/service/s3_service.rb @@ -9,8 +9,8 @@ module ActiveStorage class Service::S3Service < Service attr_reader :client, :bucket, :upload_options - def initialize(access_key_id:, secret_access_key:, region:, bucket:, upload: {}, **options) - @client = Aws::S3::Resource.new(access_key_id: access_key_id, secret_access_key: secret_access_key, region: region, **options) + def initialize(bucket:, upload: {}, **options) + @client = Aws::S3::Resource.new(**options) @bucket = @client.bucket(bucket) @upload_options = upload diff --git a/activestorage/test/service/s3_service_test.rb b/activestorage/test/service/s3_service_test.rb index d6996209d2..7833e51122 100644 --- a/activestorage/test/service/s3_service_test.rb +++ b/activestorage/test/service/s3_service_test.rb @@ -3,7 +3,7 @@ require "service/shared_service_tests" require "net/http" -if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].present? +if SERVICE_CONFIGURATIONS[:s3] class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase SERVICE = ActiveStorage::Service.configure(:s3, SERVICE_CONFIGURATIONS) diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 1ea2d0bbf2..6967c164ab 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -714,11 +714,9 @@ module ActiveSupport # Creates a new cache entry for the specified value. Options supported are # +:compress+, +:compress_threshold+, and +:expires_in+. def initialize(value, options = {}) - if should_compress?(value, options) - @value = compress(value) - @compressed = true - else - @value = value + @value = value + if should_compress?(options) + compress! end @version = options[:version] @@ -783,28 +781,31 @@ module ActiveSupport end private - def should_compress?(value, options) - if value && options.fetch(:compress, true) + def should_compress?(options) + if @value && options.fetch(:compress, true) compress_threshold = options.fetch(:compress_threshold, DEFAULT_COMPRESS_LIMIT) - serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize + serialized_value_size = (@value.is_a?(String) ? @value : marshaled_value).bytesize - return true if serialized_value_size >= compress_threshold + serialized_value_size >= compress_threshold end - - false end def compressed? defined?(@compressed) ? @compressed : false end - def compress(value) - Zlib::Deflate.deflate(Marshal.dump(value)) + def compress! + @value = Zlib::Deflate.deflate(marshaled_value) + @compressed = true end def uncompress(value) Marshal.load(Zlib::Inflate.inflate(value)) end + + def marshaled_value + @marshaled_value ||= Marshal.dump(@value) + end end end end diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index a134bb7095..a1cb6db25d 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -154,7 +154,7 @@ module ActiveSupport # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …]) # # No namespace is set by default. Provide one if the Redis cache - # server is shared with other apps: <tt>namespace: 'myapp-cache'<tt>. + # server is shared with other apps: <tt>namespace: 'myapp-cache'</tt>. # # Compression is enabled by default with a 1kB threshold, so cached # values larger than 1kB are automatically compressed. Disable by diff --git a/guides/source/5_2_release_notes.md b/guides/source/5_2_release_notes.md index 3c36ba5f7a..541c025fac 100644 --- a/guides/source/5_2_release_notes.md +++ b/guides/source/5_2_release_notes.md @@ -85,69 +85,9 @@ Rails 5.2 ships with a new DSL that allows you to configure a for your application. You can configure a global default policy and then override it on a per-resource basis and even use lambdas to inject per-request values into the header such as account subdomains in a multi-tenant application. - -Example global policy: - -```ruby -# config/initializers/content_security_policy.rb -Rails.application.config.content_security_policy do |policy| - policy.default_src :self, :https - policy.font_src :self, :https, :data - policy.img_src :self, :https, :data - policy.object_src :none - policy.script_src :self, :https - policy.style_src :self, :https - - # Specify URI for violation reports - policy.report_uri "/csp-violation-report-endpoint" -end -``` - -Example controller overrides: - -```ruby -# Override policy inline -class PostsController < ApplicationController - content_security_policy do |p| - p.upgrade_insecure_requests true - end -end - -# Using literal values -class PostsController < ApplicationController - content_security_policy do |p| - p.base_uri "https://www.example.com" - end -end - -# Using mixed static and dynamic values -class PostsController < ApplicationController - content_security_policy do |p| - p.base_uri :self, -> { "https://#{current_user.domain}.example.com" } - end -end - -# Disabling the global CSP -class LegacyPagesController < ApplicationController - content_security_policy false, only: :index -end -``` - -To report only content violations for migrating -legacy content using the `content_security_policy_report_only` -configuration attribute: - -```ruby -# config/initializers/content_security_policy.rb -Rails.application.config.content_security_policy_report_only = true -``` - -```ruby -# Controller override -class PostsController < ApplicationController - content_security_policy_report_only only: :index -end -``` +You can read more about this in the +[Securing Rails Applications](security.html#content-security-policy) +guide. Railties -------- @@ -172,26 +112,16 @@ Please refer to the [Changelog][railties] for detailed changes. ### Notable changes -* Namespace error pages' CSS selectors to stop the styles from bleeding - into other pages when using Turbolinks. - ([Pull Request](https://github.com/rails/rails/pull/28814)) - * Added a shared section to `config/database.yml` that will be loaded for all environments. ([Pull Request](https://github.com/rails/rails/pull/28896)) -* Allow irb options to be passed from `rails console` command. - ([Pull Request](https://github.com/rails/rails/pull/29010)) - * Add `railtie.rb` to the plugin generator. ([Pull Request](https://github.com/rails/rails/pull/29576)) * Clear screenshot files in `tmp:clear` task. ([Pull Request](https://github.com/rails/rails/pull/29534)) -* Load environment file in `dbconsole` command. - ([Pull Request](https://github.com/rails/rails/pull/29725)) - * Skip unused components when running `bin/rails app:update`. If the initial app generation skipped Action Cable, Active Record etc., the update task honors those skips too. @@ -246,19 +176,11 @@ Please refer to the [Changelog][railties] for detailed changes. * Add `mini_magick` to default `Gemfile` as comment. ([Pull Request](https://github.com/rails/rails/pull/30633)) -* Gemfile for new apps: upgrade redis-rb from ~> 3.0 to 4.0. - ([Pull Request](https://github.com/rails/rails/pull/30748)) - * `rails new` and `rails plugin new` get `Active Storage` by default. Add ability to skip `Active Storage` with `--skip-active-storage` and do so automatically when `--skip-active-record` is used. ([Pull Request](https://github.com/rails/rails/pull/30101)) -* Fix minitest rails plugin. - The custom reporters are added only if needed. - This will fix conflicts with others plugins. - ([Commit](https://github.com/rails/rails/commit/ac99916fcf7bf27bb1519d4f7387c6b4c5f0463d)) - Action Cable ------------ @@ -277,9 +199,6 @@ Please refer to the [Changelog][action-cable] for detailed changes. * Hash long stream identifiers when using PostgreSQL adapter. ([Pull Request](https://github.com/rails/rails/pull/29297)) -* Add support for compatibility with redis-rb gem for 4.0 version. - ([Pull Request](https://github.com/rails/rails/pull/30748)) - Action Pack ----------- @@ -298,10 +217,6 @@ Please refer to the [Changelog][action-pack] for detailed changes. ### Notable changes -* Add `action_controller_api` and `action_controller_base` load hooks to be - called in `ActiveSupport.on_load`. - ([Pull Request](https://github.com/rails/rails/pull/28402)) - * Add support for recyclable cache keys with fragment caching. ([Pull Request](https://github.com/rails/rails/pull/29092)) @@ -312,18 +227,9 @@ Please refer to the [Changelog][action-pack] for detailed changes. * AEAD encrypted cookies and sessions with GCM. ([Pull Request](https://github.com/rails/rails/pull/28132)) -* `driven_by` now registers poltergeist and capybara-webkit. - ([Pull Request](https://github.com/rails/rails/pull/29315)) - -* Fallback `ActionController::Parameters#to_s` to `Hash#to_s`. - ([Pull Request](https://github.com/rails/rails/pull/29630)) - * Protect from forgery by default. ([Pull Request](https://github.com/rails/rails/pull/29742)) -* Make `take_failed_screenshot` work within engine. - ([Pull Request](https://github.com/rails/rails/pull/30421)) - * Enforce signed/encrypted cookie expiry server side. ([Pull Request](https://github.com/rails/rails/pull/30121)) @@ -353,9 +259,6 @@ Please refer to the [Changelog][action-pack] for detailed changes. [Commit](https://github.com/rails/rails/commit/619b1b6353a65e1635d10b8f8c6630723a5a6f1a), [Commit](https://github.com/rails/rails/commit/4ec8bf68ff92f35e79232fbd605012ce1f4e1e6e)) -* Fix optimized url helpers when using relative url root. - ([Pull Request](https://github.com/rails/rails/pull/31261)) - * Register most popular audio/video/font mime types supported by modern browsers. ([Pull Request](https://github.com/rails/rails/pull/31251)) @@ -409,21 +312,10 @@ Please refer to the [Changelog][action-view] for detailed changes. ### Notable changes -* Update `distance_of_time_in_words` helper to display better error messages - for bad input. - ([Pull Request](https://github.com/rails/rails/pull/20701)) - * Add `:json` type to `auto_discovery_link_tag` to support [JSON Feeds](https://jsonfeed.org/version/1). ([Pull Request](https://github.com/rails/rails/pull/29158)) -* Generate field ids in `collection_check_boxes` and - `collection_radio_buttons`. - ([Pull Request](https://github.com/rails/rails/pull/29412)) - -* Fix issues with scopes and engine on `current_page?` method. - ([Pull Request](https://github.com/rails/rails/pull/29503)) - * Add `srcset` option to `image_tag` helper. ([Pull Request](https://github.com/rails/rails/pull/29349)) @@ -453,10 +345,6 @@ Please refer to the [Changelog][action-mailer] for detailed changes. * Add `assert_enqueued_email_with` test helper. ([Pull Request](https://github.com/rails/rails/pull/30695)) -* Bring back proc with arity of 1 in `ActionMailer::Base.default` proc - since it was supported in Rails 5.0 but not deprecated. - ([Pull Request](https://github.com/rails/rails/pull/30391)) - Active Record ------------- @@ -546,9 +434,6 @@ Please refer to the [Changelog][active-record] for detailed changes. when the current migration does not exist. ([Commit](https://github.com/rails/rails/commit/bb9d6eb094f29bb94ef1f26aa44f145f17b973fe)) -* Add type caster to `RuntimeReflection#alias_name`. - ([Pull Request](https://github.com/rails/rails/pull/28961)) - * Respect `SchemaDumper.ignore_tables` in rake tasks for databases structure dump. ([Pull Request](https://github.com/rails/rails/pull/29077)) @@ -559,39 +444,16 @@ Please refer to the [Changelog][active-record] for detailed changes. does not include a timestamp any more. ([Pull Request](https://github.com/rails/rails/pull/29092)) -* Loading model schema from database is now thread-safe. - ([Pull Request](https://github.com/rails/rails/pull/29216)) - * Prevent creation of bind param if casted value is nil. ([Pull Request](https://github.com/rails/rails/pull/29282)) * Use bulk INSERT to insert fixtures for better performance. ([Pull Request](https://github.com/rails/rails/pull/29504)) -* Fix destroying existing object does not work well when optimistic locking - enabled and `locking_column` is null in the database. - ([Pull Request](https://github.com/rails/rails/pull/28926)) - -* `ActiveRecord::Persistence#touch` does not work well - when optimistic locking enabled and `locking_column`, - without default value, is null in the database. - ([Pull Request](https://github.com/rails/rails/pull/28914)) - * Merging two relations representing nested joins no longer transforms the joins of the merged relation into LEFT OUTER JOIN. ([Pull Request](https://github.com/rails/rails/pull/27063)) -* Previously, when building records using a `has_many :through` association, - if the child records were deleted before the parent was saved, - they would still be persisted. Now, if child records are deleted - before the parent is saved on a `has_many :through` association, - the child records will not be persisted. - ([Pull Request](https://github.com/rails/rails/pull/29593)) - -* Query cache was unavailable when entering the `ActiveRecord::Base.cache` - block without being connected. - ([Pull Request](https://github.com/rails/rails/pull/29609)) - * Fix transactions to apply state to child transactions. Previously, if you had a nested transaction and the outer transaction was rolledback, the record from the inner transaction would still be marked @@ -617,26 +479,10 @@ Please refer to the [Changelog][active-record] for detailed changes. recognize 't' and 'f' as was previously serialized. ([Pull Request](https://github.com/rails/rails/pull/29699)) -* `Relation#joins` is no longer affected by the target model's - `current_scope`, with the exception of `unscoped`. - ([Commit](https://github.com/rails/rails/commit/5c71000d086cc42516934415b79380c2224e1614)) - * Values constructed using multi-parameter assignment will now use the post-type-cast value for rendering in single-field form inputs. ([Commit](https://github.com/rails/rails/commit/1519e976b224871c7f7dd476351930d5d0d7faf6)) -* Fix `unscoped(where: [columns])` removing the wrong bind values. - ([Pull Request](https://github.com/rails/rails/pull/29780)) - -* When a `has_one` association is destroyed by `dependent: destroy`, - `destroyed_by_association` will now be set to the reflection, matching the - behaviour of `has_many` associations. - ([Pull Request](https://github.com/rails/rails/pull/29855)) - -* Fix `COUNT(DISTINCT ...)` with `ORDER BY` and `LIMIT` - to keep the existing select list. - ([Pull Request](https://github.com/rails/rails/pull/29848)) - * `ApplicationRecord` is no longer generated when generating models. If you need to generate it, it can be created with `rails g application_record`. ([Pull Request](https://github.com/rails/rails/pull/29916)) @@ -652,9 +498,6 @@ Please refer to the [Changelog][active-record] for detailed changes. * Add `binary` fixture helper method. ([Pull Request](https://github.com/rails/rails/pull/30073)) -* Ensure `sum` honors `distinct` on `has_many :through` associations. - ([Commit](https://github.com/rails/rails/commit/566f1fd068711dfe557bef63406f8dd6d41d473d)) - * Automatically guess the inverse associations for STI. ([Pull Request](https://github.com/rails/rails/pull/23425)) @@ -676,19 +519,6 @@ Please refer to the [Changelog][active-record] for detailed changes. * PostgreSQL `tsrange` now preserves subsecond precision. ([Pull Request](https://github.com/rails/rails/pull/30725)) -* Fix `COUNT(DISTINCT ...)` for `GROUP BY` with `ORDER BY` and `LIMIT`. - ([Commit](https://github.com/rails/rails/commit/5668dc6b1863ef43be8f8ef0fb1d5db913085fb3)) - -* MySQL: Don't lose `auto_increment: true` in the `db/schema.rb`. - ([Commit](https://github.com/rails/rails/commit/9493d4553569118b2a85da84fd3a8ba2b5b2de76)) - -* Fix longer sequence name detection for serial columns. - ([Pull Request](https://github.com/rails/rails/pull/28339)) - -* Fix `bin/rails db:setup` and `bin/rails db:test:prepare` create wrong - ar_internal_metadata's data for a test database. - ([Pull Request](https://github.com/rails/rails/pull/30579)) - * Raises when calling `lock!` in a dirty record. ([Commit](https://github.com/rails/rails/commit/63cf15877bae859ff7b4ebaf05186f3ca79c1863)) @@ -732,9 +562,6 @@ Please refer to the [Changelog][active-record] for detailed changes. * Add support for PostgreSQL operator classes to `add_index`. ([Pull Request](https://github.com/rails/rails/pull/19090)) -* Fix conflicts `counter_cache` with `touch: true` by optimistic locking. - ([Pull Request](https://github.com/rails/rails/pull/31405)) - * Log database query callers. ([Pull Request](https://github.com/rails/rails/pull/26815), [Pull Request](https://github.com/rails/rails/pull/31519), @@ -746,16 +573,6 @@ Please refer to the [Changelog][active-record] for detailed changes. * Using subselect for `delete_all` with `limit` or `offset`. ([Commit](https://github.com/rails/rails/commit/9e7260da1bdc0770cf4ac547120c85ab93ff3d48)) -* Fix `count(:all)` to correctly work `distinct` with custom SELECT list. - ([Commit](https://github.com/rails/rails/commit/c6cd9a59f200863ccfe8ad1d9c5a8876c39b9c5c)) - -* Fix to invoke callbacks when using `update_attribute`. - ([Commit](https://github.com/rails/rails/commit/732aa34b6e6459ad66a3d3ad107cfff75cc45160)) - -* Use `count(:all)` in `HasManyAssociation#count_records` to prevent invalid - SQL queries for association counting. - ([Pull Request](https://github.com/rails/rails/pull/27561)) - * Fixed inconsistency with `first(n)` when used with `limit()`. The `first(n)` finder now respects the `limit()`, making it consistent with `relation.to_a.first(n)`, and also with the behavior of `last(n)`. @@ -779,17 +596,10 @@ Please refer to the [Changelog][active-record] for detailed changes. * Clear the transaction state when an Active Record object is duped. ([Pull Request](https://github.com/rails/rails/pull/31751)) -* Fix `count(:all)` with eager loading and having an order other than - the driving table. - ([Commit](https://github.com/rails/rails/commit/ebc09ed9ad9a04338138739226a1a92c7a2707ee)) - * Fix not expanded problem when passing an Array object as argument to the where method using `composed_of` column. ([Pull Request](https://github.com/rails/rails/pull/31724)) -* PostgreSQL: Allow pg-1.0 gem to be used with Active Record. - ([Pull Request](https://github.com/rails/rails/pull/31671)) - * Make `reflection.klass` raise if `polymorphic?` not to be misused. ([Commit](https://github.com/rails/rails/commit/63fc1100ce054e3e11c04a547cdb9387cd79571a)) @@ -798,10 +608,6 @@ Please refer to the [Changelog][active-record] for detailed changes. even if `ORDER BY` columns include other table's primary key. ([Commit](https://github.com/rails/rails/commit/851618c15750979a75635530200665b543561a44)) -* Fix that after commit callbacks on update does not triggered - when optimistic locking is enabled. - ([Commit](https://github.com/rails/rails/commit/7f9bd034c485c2425ae0164ff5d6374834e3aa1d)) - * Fix `dependent: :destroy` issue for has_one/belongs_to relationship where the parent class was getting deleted when the child was not. ([Commit](https://github.com/rails/rails/commit/b0fc04aa3af338d5a90608bf37248668d59fc881)) @@ -818,10 +624,6 @@ Please refer to the [Changelog][active-model] for detailed changes. Change `#values` to only return the not empty values. ([Pull Request](https://github.com/rails/rails/pull/28584)) -* Fix regression in numericality validator when comparing Decimal and Float - input values with more scale than the schema. - ([Pull Request](https://github.com/rails/rails/pull/28584)) - * Add method `#merge!` for `ActiveModel::Errors`. ([Pull Request](https://github.com/rails/rails/pull/29714)) @@ -832,9 +634,6 @@ Please refer to the [Changelog][active-model] for detailed changes. is `false`. ([Pull Request](https://github.com/rails/rails/pull/31058)) -* Fix to working before/after validation callbacks on multiple contexts. - ([Pull Request](https://github.com/rails/rails/pull/31483)) - * Models using the attributes API with a proc default can now be marshalled. ([Commit](https://github.com/rails/rails/commit/0af36c62a5710e023402e37b019ad9982e69de4b)) @@ -883,10 +682,6 @@ Please refer to the [Changelog][active-support] for detailed changes. in Active Record and its use in Action Pack's fragment caching. ([Pull Request](https://github.com/rails/rails/pull/29092)) -* Fix implicit coercion calculations with scalars and durations. - ([Pull Request](https://github.com/rails/rails/pull/29163), - [Pull Request](https://github.com/rails/rails/pull/29971)) - * Add `ActiveSupport::CurrentAttributes` to provide a thread-isolated attributes singleton. Primary use case is keeping all the per-request attributes easily available to the whole system. @@ -923,9 +718,6 @@ Please refer to the [Changelog][active-support] for detailed changes. `ActiveSupport::MessageEncryptor`. ([Pull Request](https://github.com/rails/rails/pull/29892)) -* Fix modulo operations involving durations. - ([Commit](https://github.com/rails/rails/commit/a54e13bd2e8fb4d6aa0aebe59271699a2d62567b)) - * Update `String#camelize` to provide feedback when wrong option is passed. ([Pull Request](https://github.com/rails/rails/pull/30039)) @@ -1016,9 +808,6 @@ Please refer to the [Changelog][active-support] for detailed changes. This allows to specify multiple numeric differences in the same assertion. ([Pull Request](https://github.com/rails/rails/pull/31600)) -* Return all mappings for a timezone identifier in `country_zones`. - ([Commit](https://github.com/rails/rails/commit/cdce6a709e1cbc98fff009effc3b1b3ce4c7e8db)) - * Caching: MemCache and Redis `read_multi` and `fetch_multi` speedup. Read from the local in-memory cache before consulting the backend. ([Commit](https://github.com/rails/rails/commit/a2b97e4ffef971607a1be8fc7909f099b6840f36)) @@ -1030,9 +819,6 @@ Please refer to the [Changelog][active-job] for detailed changes. ### Notable changes -* Add support for compatibility with redis-rb gem for 4.0 version. - ([Pull Request](https://github.com/rails/rails/pull/30748)) - * Allow block to be passed to `ActiveJob::Base.discard_on` to allow custom handling of discard jobs. ([Pull Request](https://github.com/rails/rails/pull/30622)) diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md index 9d911d4ee4..6d52ac0a99 100644 --- a/guides/source/active_job_basics.md +++ b/guides/source/active_job_basics.md @@ -147,7 +147,7 @@ class GuestsCleanupJob < ApplicationJob #.... end -# Now your job will use `resque` as it's backend queue adapter overriding what +# Now your job will use `resque` as its backend queue adapter overriding what # was configured in `config.active_job.queue_adapter`. ``` diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md index 831a02a9a1..d67f65e88a 100644 --- a/guides/source/active_storage_overview.md +++ b/guides/source/active_storage_overview.md @@ -114,6 +114,13 @@ gem "aws-sdk-s3", require: false NOTE: The core features of Active Storage require the following permissions: `s3:ListBucket`, `s3:PutObject`, `s3:GetObject`, and `s3:DeleteObject`. If you have additional upload options configured such as setting ACLs then additional permissions may be required. +NOTE: If you want to use environment variables, standard SDK configuration files, profiles, +IAM instance profiles or task roles, you can omit the `access_key_id`, `secret_access_key`, +and `region` keys in the example above. The Amazon S3 Service supports all of the +authentication options described in the [AWS SDK documentation] +(https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html). + + ### Microsoft Azure Storage Service Declare an Azure Storage service in `config/storage.yml`: diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 5dde6f34fa..3f357b532b 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -446,30 +446,28 @@ config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.c ### ActiveSupport::Cache::RedisCacheStore -The Redis cache store takes advantage of Redis support for least-recently-used -and least-frequently-used key eviction when it reaches max memory, allowing it -to behave much like a Memcached cache server. +The Redis cache store takes advantage of Redis support for automatic eviction +when it reaches max memory, allowing it to behave much like a Memcached cache server. Deployment note: Redis doesn't expire keys by default, so take care to use a dedicated Redis cache server. Don't fill up your persistent-Redis server with volatile cache data! Read the [Redis cache server setup guide](https://redis.io/topics/lru-cache) in detail. -For an all-cache Redis server, set `maxmemory-policy` to an `allkeys` policy. -Redis 4+ support least-frequently-used (`allkeys-lfu`) eviction, an excellent -default choice. Redis 3 and earlier should use `allkeys-lru` for -least-recently-used eviction. +For a cache-only Redis server, set `maxmemory-policy` to one of the variants of allkeys. +Redis 4+ supports least-frequently-used eviction (`allkeys-lfu`), an excellent +default choice. Redis 3 and earlier should use least-recently-used eviction (`allkeys-lru`). Set cache read and write timeouts relatively low. Regenerating a cached value is often faster than waiting more than a second to retrieve it. Both read and write timeouts default to 1 second, but may be set lower if your network is -consistently low latency. +consistently low-latency. By default, the cache store will not attempt to reconnect to Redis if the connection fails during a request. If you experience frequent disconnects you may wish to enable reconnect attempts. -Cache reads and writes never raise exceptions. They just return `nil` instead, +Cache reads and writes never raise exceptions; they just return `nil` instead, behaving as if there was nothing in the cache. To gauge whether your cache is hitting exceptions, you may provide an `error_handler` to report to an exception gathering service. It must accept three keyword arguments: `method`, @@ -477,12 +475,33 @@ the cache store method that was originally called; `returning`, the value that was returned to the user, typically `nil`; and `exception`, the exception that was rescued. -Putting it all together, a production Redis cache store may look something -like this: +To get started, add the redis gem to your Gemfile: ```ruby -cache_servers = %w[ "redis://cache-01:6379/0", "redis://cache-02:6379/0", … ], -config.cache_store = :redis_cache_store, url: cache_servers, +gem 'redis' +``` + +You can enable support for the faster [hiredis](https://github.com/redis/hiredis) +connection library by additionally adding its ruby wrapper to your Gemfile: + +```ruby +gem 'hiredis' +``` + +Redis cache store will automatically require & use hiredis if available. No further +configuration is needed. + +Finally, add the configuration in the relevant `config/environments/*.rb` file: + +```ruby +config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] } +``` + +A more complex, production Redis cache store may look something like this: + +```ruby +cache_servers = %w(redis://cache-01:6379/0 redis://cache-02:6379/0) +config.cache_store = :redis_cache_store, { url: cache_servers, connect_timeout: 30, # Defaults to 20 seconds read_timeout: 0.2, # Defaults to 1 second @@ -491,9 +510,10 @@ config.cache_store = :redis_cache_store, url: cache_servers, error_handler: -> (method:, returning:, exception:) { # Report errors to Sentry as warnings - Raven.capture_exception exception, level: 'warning", + Raven.capture_exception exception, level: 'warning', tags: { method: method, returning: returning } } +} ``` ### ActiveSupport::Cache::NullStore diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index f545b90103..5b6cfe6659 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -1125,7 +1125,7 @@ TIP: Rails automatically wraps fields that contain an error with a div with class `field_with_errors`. You can define a CSS rule to make them standout. -Now you'll get a nice error message when saving an article without title when +Now you'll get a nice error message when saving an article without a title when you attempt to do just that on the new article form <http://localhost:3000/articles/new>: @@ -1522,7 +1522,7 @@ comments on articles. ### Generating a Model We're going to see the same generator that we used before when creating -the `Article` model. This time we'll create a `Comment` model to hold +the `Article` model. This time we'll create a `Comment` model to hold a reference to an article. Run this command in your terminal: ```bash @@ -1857,7 +1857,7 @@ This will now render the partial in `app/views/comments/_comment.html.erb` once for each comment that is in the `@article.comments` collection. As the `render` method iterates over the `@article.comments` collection, it assigns each comment to a local variable named the same as the partial, in this case -`comment` which is then available in the partial for us to show. +`comment`, which is then available in the partial for us to show. ### Rendering a Partial Form @@ -2060,7 +2060,7 @@ What's Next? Now that you've seen your first Rails application, you should feel free to update it and experiment on your own. -Remember you don't have to do everything without help. As you need assistance +Remember, you don't have to do everything without help. As you need assistance getting up and running with Rails, feel free to consult these support resources: diff --git a/guides/source/security.md b/guides/source/security.md index 4cf6c06f2d..b419f7b48d 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -1089,6 +1089,112 @@ Here is a list of common headers: * **Access-Control-Allow-Origin:** Used to control which sites are allowed to bypass same origin policies and send cross-origin requests. * **Strict-Transport-Security:** [Used to control if the browser is allowed to only access a site over a secure connection](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) +### Content Security Policy + +Rails provides a DSL that allows you to configure a +[Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) +for your application. You can configure a global default policy and then +override it on a per-resource basis and even use lambdas to inject per-request +values into the header such as account subdomains in a multi-tenant application. + +Example global policy: + +```ruby +# config/initializers/content_security_policy.rb +Rails.application.config.content_security_policy do |policy| + policy.default_src :self, :https + policy.font_src :self, :https, :data + policy.img_src :self, :https, :data + policy.object_src :none + policy.script_src :self, :https + policy.style_src :self, :https + + # Specify URI for violation reports + policy.report_uri "/csp-violation-report-endpoint" +end +``` + +Example controller overrides: + +```ruby +# Override policy inline +class PostsController < ApplicationController + content_security_policy do |p| + p.upgrade_insecure_requests true + end +end + +# Using literal values +class PostsController < ApplicationController + content_security_policy do |p| + p.base_uri "https://www.example.com" + end +end + +# Using mixed static and dynamic values +class PostsController < ApplicationController + content_security_policy do |p| + p.base_uri :self, -> { "https://#{current_user.domain}.example.com" } + end +end + +# Disabling the global CSP +class LegacyPagesController < ApplicationController + content_security_policy false, only: :index +end +``` + +Use the `content_security_policy_report_only` +configuration attribute to set +[Content-Security-Policy-Report-Only](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only) +in order to report only content violations for migrating +legacy content + +```ruby +# config/initializers/content_security_policy.rb +Rails.application.config.content_security_policy_report_only = true +``` + +```ruby +# Controller override +class PostsController < ApplicationController + content_security_policy_report_only only: :index +end +``` + +You can enable automatic nonce generation: + +```ruby +# config/initializers/content_security_policy.rb +Rails.application.config.content_security_policy do |policy| + policy.script_src :self, :https +end + +Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } +``` + +Then you can add an automatic nonce value by passing `nonce: true` +as part of `html_options`. Example: + +```html+erb +<%= javascript_tag nonce: true do -%> + alert('Hello, World!'); +<% end -%> +``` + +Use [`csp_meta_tag`](http://api.rubyonrails.org/classes/ActionView/Helpers/CspHelper.html#method-i-csp_meta_tag) +helper to create a meta tag "csp-nonce" with the per-session nonce value +for allowing inline `<script>` tags. + +```html+erb +<head> + <%= csp_meta_tag %> +</head> +``` + +This is used by the Rails UJS helper to create dynamically +loaded inline `<script>` elements. + Environmental Security ---------------------- diff --git a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb index 8df548b5de..806b7de6d6 100644 --- a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb +++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb @@ -97,7 +97,7 @@ module Rails elsif configurations[environment].blank? && configurations[connection].blank? raise ActiveRecord::AdapterNotSpecified, "'#{environment}' database is not configured. Available configuration: #{configurations.inspect}" else - configurations[environment].presence || configurations[connection] + configurations[connection] || configurations[environment].presence end end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index e1889979d7..8c5d872573 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -299,7 +299,7 @@ module Rails def gem_for_database # %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql ) case options[:database] - when "mysql" then ["mysql2", ["~> 0.4.4"]] + when "mysql" then ["mysql2", [">= 0.4.4", "< 0.6.0"]] when "postgresql" then ["pg", [">= 0.18", "< 2.0"]] when "oracle" then ["activerecord-oracle_enhanced-adapter", nil] when "frontbase" then ["ruby-frontbase", nil] |