diff options
-rw-r--r-- | Gemfile.lock | 2 | ||||
-rw-r--r-- | actionpack/test/dispatch/session/cookie_store_test.rb | 4 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/abstract_adapter.rb | 3 | ||||
-rw-r--r-- | activerecord/lib/active_record/core.rb | 3 | ||||
-rw-r--r-- | activerecord/lib/active_record/scoping/default.rb | 4 | ||||
-rw-r--r-- | activestorage/lib/active_storage/attached/many.rb | 6 | ||||
-rw-r--r-- | activestorage/lib/active_storage/attached/one.rb | 8 | ||||
-rw-r--r-- | activestorage/test/models/attachments_test.rb | 46 | ||||
-rw-r--r-- | activesupport/lib/active_support/cache/redis_cache_store.rb | 6 | ||||
-rw-r--r-- | activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb | 17 | ||||
-rw-r--r-- | activesupport/test/concurrency/load_interlock_aware_monitor_test.rb | 55 | ||||
-rw-r--r-- | railties/CHANGELOG.md | 4 | ||||
-rw-r--r-- | railties/lib/rails/application.rb | 16 | ||||
-rw-r--r-- | railties/lib/rails/generators/rails/plugin/plugin_generator.rb | 4 | ||||
-rw-r--r-- | railties/test/generators/plugin_generator_test.rb | 32 |
15 files changed, 186 insertions, 24 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index 8c678fa660..f02dfa8f60 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -371,7 +371,7 @@ GEM rack (>= 0.4) rack-protection (2.0.0) rack - rack-test (0.7.0) + rack-test (0.7.1) rack (>= 1.0, < 3) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index cf51c47068..2c43a8c29f 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -283,8 +283,6 @@ class CookieStoreTest < ActionDispatch::IntegrationTest with_test_route_set(expire_after: 5.hours) do # First request accesses the session time = Time.local(2008, 4, 24) - cookie_body = nil - Time.stub :now, time do expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000") @@ -303,6 +301,8 @@ class CookieStoreTest < ActionDispatch::IntegrationTest Time.stub :now, time do expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000") + cookies[SessionKey] = SignedBar + get "/no_session_access" assert_response :success diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 345983a655..7e6db860dd 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -5,6 +5,7 @@ require "active_record/connection_adapters/schema_cache" require "active_record/connection_adapters/sql_type_metadata" require "active_record/connection_adapters/abstract/schema_dumper" require "active_record/connection_adapters/abstract/schema_creation" +require "active_support/concurrency/load_interlock_aware_monitor" require "arel/collectors/bind" require "arel/collectors/composite" require "arel/collectors/sql_string" @@ -107,7 +108,7 @@ module ActiveRecord @schema_cache = SchemaCache.new self @quoted_column_names, @quoted_table_names = {}, {} @visitor = arel_visitor - @lock = Monitor.new + @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @prepared_statements = true diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index b97b14644e..481159e501 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -277,7 +277,8 @@ module ActiveRecord relation = Relation.create(self, arel_table, predicate_builder) if finder_needs_type_condition? && !ignore_default_scope? - relation.where(type_condition).create_with(inheritance_column.to_s => sti_name) + relation.where!(type_condition) + relation.create_with!(inheritance_column.to_s => sti_name) else relation end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 86ae374318..8c612df27a 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -111,7 +111,7 @@ module ActiveRecord # The user has defined their own default scope method, so call that evaluate_default_scope do if scope = default_scope - (base_rel ||= relation).merge(scope) + (base_rel ||= relation).merge!(scope) end end elsif default_scopes.any? @@ -119,7 +119,7 @@ module ActiveRecord evaluate_default_scope do default_scopes.inject(base_rel) do |default_scope, scope| scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call) - default_scope.merge(base_rel.instance_exec(&scope)) + default_scope.merge!(base_rel.instance_exec(&scope)) end end end diff --git a/activestorage/lib/active_storage/attached/many.rb b/activestorage/lib/active_storage/attached/many.rb index 0b3400bccf..6eace65b79 100644 --- a/activestorage/lib/active_storage/attached/many.rb +++ b/activestorage/lib/active_storage/attached/many.rb @@ -20,7 +20,11 @@ module ActiveStorage # document.images.attach([ first_blob, second_blob ]) def attach(*attachables) attachables.flatten.collect do |attachable| - attachments.create!(name: name, blob: create_blob_from(attachable)) + if record.new_record? + attachments.build(record: record, blob: create_blob_from(attachable)) + else + attachments.create!(record: record, blob: create_blob_from(attachable)) + end end end diff --git a/activestorage/lib/active_storage/attached/one.rb b/activestorage/lib/active_storage/attached/one.rb index 7092f6b109..0244232b2c 100644 --- a/activestorage/lib/active_storage/attached/one.rb +++ b/activestorage/lib/active_storage/attached/one.rb @@ -23,7 +23,7 @@ module ActiveStorage if attached? && dependent == :purge_later replace attachable else - write_attachment create_attachment_from(attachable) + write_attachment build_attachment_from(attachable) end end @@ -67,13 +67,13 @@ module ActiveStorage blob.tap do transaction do detach - write_attachment create_attachment_from(attachable) + write_attachment build_attachment_from(attachable) end end.purge_later end - def create_attachment_from(attachable) - ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable)) + def build_attachment_from(attachable) + ActiveStorage::Attachment.new(record: record, name: name, blob: create_blob_from(attachable)) end def write_attachment(attachment) diff --git a/activestorage/test/models/attachments_test.rb b/activestorage/test/models/attachments_test.rb index e645d868ce..edd68d3051 100644 --- a/activestorage/test/models/attachments_test.rb +++ b/activestorage/test/models/attachments_test.rb @@ -27,7 +27,7 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase test "attach new blob from an UploadedFile" do file = file_fixture "racecar.jpg" - @user.avatar.attach Rack::Test::UploadedFile.new file + @user.avatar.attach Rack::Test::UploadedFile.new file.to_s assert_equal "racecar.jpg", @user.avatar.filename.to_s end @@ -56,6 +56,26 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase assert ActiveStorage::Blob.service.exist?(@user.avatar.key) end + test "attach blob to new record" do + user = User.new(name: "Jason") + + assert_no_changes -> { user.new_record? } do + assert_no_difference -> { ActiveStorage::Attachment.count } do + user.avatar.attach create_blob(filename: "funky.jpg") + end + end + + assert user.avatar.attached? + assert_equal "funky.jpg", user.avatar.filename.to_s + + assert_difference -> { ActiveStorage::Attachment.count }, +1 do + user.save! + end + + assert user.reload.avatar.attached? + assert_equal "funky.jpg", user.avatar.filename.to_s + end + test "access underlying associations of new blob" do @user.avatar.attach create_blob(filename: "funky.jpg") assert_equal @user, @user.avatar_attachment.record @@ -160,6 +180,30 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase assert_equal "country.jpg", @user.highlights.second.filename.to_s end + test "attach blobs to new record" do + user = User.new(name: "Jason") + + assert_no_changes -> { user.new_record? } do + assert_no_difference -> { ActiveStorage::Attachment.count } do + user.highlights.attach( + { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, + { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }) + end + end + + assert user.highlights.attached? + assert_equal "town.jpg", user.highlights.first.filename.to_s + assert_equal "country.jpg", user.highlights.second.filename.to_s + + assert_difference -> { ActiveStorage::Attachment.count }, +2 do + user.save! + end + + assert user.reload.highlights.attached? + assert_equal "town.jpg", user.highlights.first.filename.to_s + assert_equal "country.jpg", user.highlights.second.filename.to_s + end + test "find attached blobs" do @user.highlights.attach( { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index 358de9203d..08200a556f 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -300,10 +300,10 @@ module ActiveSupport options = names.extract_options! options = merged_options(options) - keys_to_names = names.map { |name| [ normalize_key(name, options), name ] }.to_h - values = redis.mget(*keys_to_names.keys) + keys = names.map { |name| normalize_key(name, options) } + values = redis.mget(*keys) - keys_to_names.zip(values).each_with_object({}) do |((key, name), value), results| + names.zip(values).each_with_object({}) do |(name, value), results| if value entry = deserialize_entry(value) unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options)) diff --git a/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb b/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb new file mode 100644 index 0000000000..a8455c0048 --- /dev/null +++ b/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Concurrency + # A monitor that will permit dependency loading while blocked waiting for + # the lock. + class LoadInterlockAwareMonitor < Monitor + # Enters an exclusive section, but allows dependency loading while blocked + def mon_enter + mon_try_enter || + ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super } + end + end + end +end diff --git a/activesupport/test/concurrency/load_interlock_aware_monitor_test.rb b/activesupport/test/concurrency/load_interlock_aware_monitor_test.rb new file mode 100644 index 0000000000..2d0f45ec5f --- /dev/null +++ b/activesupport/test/concurrency/load_interlock_aware_monitor_test.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "concurrent/atomic/count_down_latch" +require "active_support/concurrency/load_interlock_aware_monitor" + +module ActiveSupport + module Concurrency + class LoadInterlockAwareMonitorTest < ActiveSupport::TestCase + def setup + @monitor = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new + end + + def test_entering_with_no_blocking + assert @monitor.mon_enter + end + + def test_entering_with_blocking + load_interlock_latch = Concurrent::CountDownLatch.new + monitor_latch = Concurrent::CountDownLatch.new + + able_to_use_monitor = false + able_to_load = false + + thread_with_load_interlock = Thread.new do + ActiveSupport::Dependencies.interlock.running do + load_interlock_latch.count_down + monitor_latch.wait + + @monitor.synchronize do + able_to_use_monitor = true + end + end + end + + thread_with_monitor_lock = Thread.new do + @monitor.synchronize do + monitor_latch.count_down + load_interlock_latch.wait + + ActiveSupport::Dependencies.interlock.loading do + able_to_load = true + end + end + end + + thread_with_load_interlock.join + thread_with_monitor_lock.join + + assert able_to_use_monitor + assert able_to_load + end + end + end +end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index e69a1231b1..d086248278 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Deprecate `after_bundle` callback in Rails plugin templates. + + *Yuji Yaginuma* + * `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. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index df266fbfce..b1429df18b 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -429,23 +429,23 @@ module Rails end end - # Decrypts the credentials hash as kept in `config/credentials.yml.enc`. This file is encrypted with - # the Rails master key, which is either taken from ENV["RAILS_MASTER_KEY"] or from loading - # `config/master.key`. + # Decrypts the credentials hash as kept in +config/credentials.yml.enc+. This file is encrypted with + # the Rails master key, which is either taken from <tt>ENV["RAILS_MASTER_KEY"]</tt> or from loading + # +config/master.key+. def credentials @credentials ||= encrypted("config/credentials.yml.enc") end # Shorthand to decrypt any encrypted configurations or files. # - # For any file added with `bin/rails encrypted:edit` call `read` to decrypt + # For any file added with <tt>bin/rails encrypted:edit</tt> call +read+ to decrypt # the file with the master key. - # The master key is either stored in `config/master.key` or `ENV["RAILS_MASTER_KEY"]`. + # The master key is either stored in +config/master.key+ or <tt>ENV["RAILS_MASTER_KEY"]</tt>. # # Rails.application.encrypted("config/mystery_man.txt.enc").read # # => "We've met before, haven't we?" # - # It's also possible to interpret encrypted YAML files with `config`. + # It's also possible to interpret encrypted YAML files with +config+. # # Rails.application.encrypted("config/credentials.yml.enc").config # # => { next_guys_line: "I don't think so. Where was it you think we met?" } @@ -456,11 +456,11 @@ module Rails # # => "I don't think so. Where was it you think we met?" # # The files or configs can also be encrypted with a custom key. To decrypt with - # a key in the `ENV`, use: + # a key in the +ENV+, use: # # Rails.application.encrypted("config/special_tokens.yml.enc", env_key: "SPECIAL_TOKENS") # - # Or to decrypt with a file, that should be version control ignored, relative to `Rails.root`: + # Or to decrypt with a file, that should be version control ignored, relative to +Rails.root+: # # Rails.application.encrypted("config/special_tokens.yml.enc", key_path: "config/special_tokens.key") def encrypted(path, key_path: "config/master.key", env_key: "RAILS_MASTER_KEY") diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 786aea503c..a83c911806 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -263,6 +263,10 @@ task default: :test public_task :apply_rails_template def run_after_bundle_callbacks + unless @after_bundle_callbacks.empty? + ActiveSupport::Deprecation.warn("`after_bundle` is deprecated and will be removed in the next version of Rails. ") + end + @after_bundle_callbacks.each do |callback| callback.call end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 06f59ee33d..fc7584c175 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -721,6 +721,38 @@ class PluginGeneratorTest < Rails::Generators::TestCase Object.send(:remove_const, "ENGINE_ROOT") end + def test_after_bundle_callback + path = "http://example.org/rails_template" + template = %{ after_bundle { run "echo ran after_bundle" } }.dup + template.instance_eval "def read; self; end" # Make the string respond to read + + check_open = -> *args do + assert_equal [ path, "Accept" => "application/x-thor-template" ], args + template + end + + sequence = ["echo ran after_bundle"] + @sequence_step ||= 0 + ensure_bundler_first = -> command do + assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}" + @sequence_step += 1 + end + + content = nil + generator([destination_root], template: path).stub(:open, check_open, template) do + generator.stub(:bundle_command, ensure_bundler_first) do + generator.stub(:run, ensure_bundler_first) do + silence_stream($stdout) do + content = capture(:stderr) { generator.invoke_all } + end + end + end + end + + assert_equal 1, @sequence_step + assert_match(/DEPRECATION WARNING: `after_bundle` is deprecated/, content) + end + private def action(*args, &block) |