diff options
Diffstat (limited to 'activerecord/lib')
12 files changed, 63 insertions, 74 deletions
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index c6941a2e76..b0c0beac0e 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -109,9 +109,8 @@ module ActiveRecord end end - # Add +records+ to this association. Returns +self+ so method calls may - # be chained. Since << flattens its argument list and inserts each record, - # +push+ and +concat+ behave identically. + # Add +records+ to this association. Since +<<+ flattens its argument list + # and inserts each record, +push+ and +concat+ behave identically. def concat(*records) records = records.flatten if owner.new_record? diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 4fbbc713e4..78993cee8a 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -366,34 +366,6 @@ module ActiveRecord @association.create!(attributes, &block) end - # Add one or more records to the collection by setting their foreign keys - # to the association's primary key. Since #<< flattens its argument list and - # inserts each record, +push+ and #concat behave identically. Returns +self+ - # so method calls may be chained. - # - # class Person < ActiveRecord::Base - # has_many :pets - # end - # - # person.pets.size # => 0 - # person.pets.concat(Pet.new(name: 'Fancy-Fancy')) - # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')) - # person.pets.size # => 3 - # - # person.id # => 1 - # person.pets - # # => [ - # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, - # # #<Pet id: 2, name: "Spook", person_id: 1>, - # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> - # # ] - # - # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')]) - # person.pets.size # => 5 - def concat(*records) - @association.concat(*records) - end - # Replaces this collection with +other_array+. This will perform a diff # and delete/add only records that have changed. # @@ -1033,8 +1005,9 @@ module ActiveRecord end # Adds one or more +records+ to the collection by setting their foreign keys - # to the association's primary key. Returns +self+, so several appends may be - # chained together. + # to the association's primary key. Since +<<+ flattens its argument list and + # inserts each record, +push+ and +concat+ behave identically. Returns +self+ + # so several appends may be chained together. # # class Person < ActiveRecord::Base # has_many :pets @@ -1057,6 +1030,7 @@ module ActiveRecord end alias_method :push, :<< alias_method :append, :<< + alias_method :concat, :<< def prepend(*args) raise NoMethodError, "prepend on association is not defined. Please use <<, push or append" diff --git a/activerecord/lib/active_record/middleware/database_selector.rb b/activerecord/lib/active_record/middleware/database_selector.rb index 3ab50f5f6b..aad263695b 100644 --- a/activerecord/lib/active_record/middleware/database_selector.rb +++ b/activerecord/lib/active_record/middleware/database_selector.rb @@ -12,8 +12,8 @@ module ActiveRecord # # The resolver class defines when the application should switch (i.e. read # from the primary if a write occurred less than 2 seconds ago) and an - # operations class that sets a value that helps the resolver class decide - # when to switch. + # resolver context class that sets a value that helps the resolver class + # decide when to switch. # # Rails default middleware uses the request's session to set a timestamp # that informs the application when to read from a primary or read from a @@ -24,7 +24,7 @@ module ActiveRecord # # config.active_record.database_selector = { delay: 2.seconds } # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver - # config.active_record.database_operations = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session + # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session # # New applications will include these lines commented out in the production.rb. # @@ -33,16 +33,16 @@ module ActiveRecord # # config.active_record.database_selector = { delay: 2.seconds } # config.active_record.database_resolver = MyResolver - # config.active_record.database_operations = MyResolver::MySession + # config.active_record.database_resolver_context = MyResolver::MySession class DatabaseSelector - def initialize(app, resolver_klass = Resolver, operations_klass = Resolver::Session, options = {}) + def initialize(app, resolver_klass = Resolver, context_klass = Resolver::Session, options = {}) @app = app @resolver_klass = resolver_klass - @operations_klass = operations_klass + @context_klass = context_klass @options = options end - attr_reader :resolver_klass, :operations_klass, :options + attr_reader :resolver_klass, :context_klass, :options # Middleware that determines which database connection to use in a multiple # database application. @@ -57,13 +57,13 @@ module ActiveRecord private def select_database(request, &blk) - operations = operations_klass.build(request) - database_resolver = resolver_klass.call(operations, options) + context = context_klass.call(request) + resolver = resolver_klass.call(context, options) if reading_request?(request) - database_resolver.read(&blk) + resolver.read(&blk) else - database_resolver.write(&blk) + resolver.write(&blk) end end diff --git a/activerecord/lib/active_record/middleware/database_selector/resolver.rb b/activerecord/lib/active_record/middleware/database_selector/resolver.rb index a84c292714..80b8cd7cae 100644 --- a/activerecord/lib/active_record/middleware/database_selector/resolver.rb +++ b/activerecord/lib/active_record/middleware/database_selector/resolver.rb @@ -18,18 +18,18 @@ module ActiveRecord class Resolver # :nodoc: SEND_TO_REPLICA_DELAY = 2.seconds - def self.call(resolver, options = {}) - new(resolver, options) + def self.call(context, options = {}) + new(context, options) end - def initialize(resolver, options = {}) - @resolver = resolver + def initialize(context, options = {}) + @context = context @options = options @delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY @instrumenter = ActiveSupport::Notifications.instrumenter end - attr_reader :resolver, :delay, :instrumenter + attr_reader :context, :delay, :instrumenter def read(&blk) if read_from_primary? @@ -68,7 +68,7 @@ module ActiveRecord instrumenter.instrument("database_selector.active_record.wrote_to_primary") do yield ensure - resolver.update_last_write_timestamp + context.update_last_write_timestamp end end end @@ -82,7 +82,7 @@ module ActiveRecord end def time_since_last_write_ok? - Time.now - resolver.last_write_timestamp >= send_to_replica_delay + Time.now - context.last_write_timestamp >= send_to_replica_delay end end end diff --git a/activerecord/lib/active_record/middleware/database_selector/resolver/session.rb b/activerecord/lib/active_record/middleware/database_selector/resolver/session.rb index 33e0af5ee4..df7af054b7 100644 --- a/activerecord/lib/active_record/middleware/database_selector/resolver/session.rb +++ b/activerecord/lib/active_record/middleware/database_selector/resolver/session.rb @@ -10,7 +10,7 @@ module ActiveRecord # The last_write is used to determine whether it's safe to read # from the replica or the request needs to be sent to the primary. class Session # :nodoc: - def self.build(request) + def self.call(request) new(request.session) end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index aac49a92b4..a1d7c893bf 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -91,7 +91,7 @@ module ActiveRecord initializer "active_record.database_selector" do if options = config.active_record.delete(:database_selector) resolver = config.active_record.delete(:database_resolver) - operations = config.active_record.delete(:database_operations) + operations = config.active_record.delete(:database_resolver_context) config.app_middleware.use ActiveRecord::Middleware::DatabaseSelector, resolver, operations, options end end diff --git a/activerecord/lib/active_record/railties/collection_cache_association_loading.rb b/activerecord/lib/active_record/railties/collection_cache_association_loading.rb index dfaac4eefb..d57680aaaa 100644 --- a/activerecord/lib/active_record/railties/collection_cache_association_loading.rb +++ b/activerecord/lib/active_record/railties/collection_cache_association_loading.rb @@ -3,7 +3,7 @@ module ActiveRecord module Railties # :nodoc: module CollectionCacheAssociationLoading #:nodoc: - def setup(context, options, block) + def setup(context, options, as, block) @relation = relation_from_options(options) super diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index a863227276..347d745d19 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -67,6 +67,7 @@ module ActiveRecord # user = users.new { |user| user.name = 'Oscar' } # user.name # => Oscar def new(attributes = nil, &block) + block = klass.current_scope_restoring_block(&block) scoping { klass.new(attributes, &block) } end @@ -92,7 +93,11 @@ module ActiveRecord # users.create(name: nil) # validation on name # # => #<User id: nil, name: nil, ...> def create(attributes = nil, &block) - scoping { klass.create(attributes, &block) } + if attributes.is_a?(Array) + attributes.collect { |attr| create(attr, &block) } + else + new(attributes, &block).tap(&:save) + end end # Similar to #create, but calls @@ -102,7 +107,11 @@ module ActiveRecord # Expects arguments in the same format as # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]. def create!(attributes = nil, &block) - scoping { klass.create!(attributes, &block) } + if attributes.is_a?(Array) + attributes.collect { |attr| create!(attr, &block) } + else + new(attributes, &block).tap(&:save!) + end end def first_or_create(attributes = nil, &block) # :nodoc: @@ -312,12 +321,12 @@ module ActiveRecord # Please check unscoped if you want to remove all previous scopes (including # the default_scope) during the execution of a block. def scoping - @delegate_to_klass ? yield : klass._scoping(self) { yield } + @delegate_to_klass && klass.current_scope(true) ? yield : _scoping(self) { yield } end def _exec_scope(*args, &block) # :nodoc: @delegate_to_klass = true - instance_exec(*args, &block) || self + _scoping(nil) { instance_exec(*args, &block) || self } ensure @delegate_to_klass = false end @@ -632,6 +641,13 @@ module ActiveRecord end private + def _scoping(scope) + previous, klass.current_scope = klass.current_scope(true), scope + yield + ensure + klass.current_scope = previous + end + def _substitute_values(values) values.map do |name, value| attr = arel_attribute(name) diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 6e8a1fcad4..f7c3b3783f 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -132,7 +132,7 @@ module ActiveRecord private def respond_to_missing?(method, _) - super || @klass.respond_to?(method) || arel.respond_to?(method) + super || @klass.respond_to?(method) end end end diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 7874c4c35a..8cf4fc469f 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -8,7 +8,7 @@ module ActiveRecord module SpawnMethods # This is overridden by Associations::CollectionProxy def spawn #:nodoc: - @delegate_to_klass ? klass.all : clone + @delegate_to_klass && klass.current_scope(true) ? klass.all : clone end # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation. diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb index 9eba1254a4..1142a87d25 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -23,14 +23,21 @@ module ActiveRecord current_scope end - private - def current_scope(skip_inherited_scope = false) - ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope) - end + def current_scope(skip_inherited_scope = false) + ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope) + end - def current_scope=(scope) - ScopeRegistry.set_value_for(:current_scope, self, scope) + def current_scope=(scope) + ScopeRegistry.set_value_for(:current_scope, self, scope) + end + + def current_scope_restoring_block(&block) + current_scope = self.current_scope(true) + -> *args do + self.current_scope = current_scope + yield(*args) if block_given? end + end end def populate_with_current_scope_attributes # :nodoc: diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 6caf9b3251..8c612df27a 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -31,14 +31,7 @@ module ActiveRecord # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10" # } def unscoped - block_given? ? _scoping(relation) { yield } : relation - end - - def _scoping(relation) # :nodoc: - previous, self.current_scope = current_scope(true), relation - yield - ensure - self.current_scope = previous + block_given? ? relation.scoping { yield } : relation end # Are there attributes associated with this scope? |