diff options
author | José Valim and Mikel Lindsaar <raasdnil@gmail.com> | 2010-01-21 11:42:22 +1100 |
---|---|---|
committer | José Valim and Mikel Lindsaar <raasdnil@gmail.com> | 2010-01-21 11:42:22 +1100 |
commit | fbdbac2b88218e5e3e6087c67dacf7e755aa4106 (patch) | |
tree | 6fdec21b56ab90fc2ec83cbc38033439a9c84e74 /activerecord/lib | |
parent | d3da87ce771845f99bbdc04d6d6587b22655b063 (diff) | |
parent | fa9f000246c2f6010f18bf40237d105b782873e2 (diff) | |
download | rails-fbdbac2b88218e5e3e6087c67dacf7e755aa4106.tar.gz rails-fbdbac2b88218e5e3e6087c67dacf7e755aa4106.tar.bz2 rails-fbdbac2b88218e5e3e6087c67dacf7e755aa4106.zip |
Merge branch 'master' of git://github.com/rails/rails
Diffstat (limited to 'activerecord/lib')
-rwxr-xr-x | activerecord/lib/active_record/base.rb | 232 | ||||
-rw-r--r-- | activerecord/lib/active_record/locking/optimistic.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/named_scope.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation.rb | 172 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation/finder_methods.rb | 44 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation/query_methods.rb | 12 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation/spawn_methods.rb | 14 |
7 files changed, 220 insertions, 258 deletions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 8f2ea10206..bc1b0bde31 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -556,7 +556,7 @@ module ActiveRecord #:nodoc: end alias :colorize_logging= :colorize_logging - delegate :find, :first, :last, :all, :to => :scoped + delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped @@ -586,40 +586,6 @@ module ActiveRecord #:nodoc: connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) } end - # Returns true if a record exists in the table that matches the +id+ or - # conditions given, or false otherwise. The argument can take five forms: - # - # * Integer - Finds the record with this primary key. - # * String - Finds the record with a primary key corresponding to this - # string (such as <tt>'5'</tt>). - # * Array - Finds the record that matches these +find+-style conditions - # (such as <tt>['color = ?', 'red']</tt>). - # * Hash - Finds the record that matches these +find+-style conditions - # (such as <tt>{:color => 'red'}</tt>). - # * No args - Returns false if the table is empty, true otherwise. - # - # For more information about specifying conditions as a Hash or Array, - # see the Conditions section in the introduction to ActiveRecord::Base. - # - # Note: You can't pass in a condition as a string (like <tt>name = - # 'Jamie'</tt>), since it would be sanitized and then queried against - # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>. - # - # ==== Examples - # Person.exists?(5) - # Person.exists?('5') - # Person.exists?(:name => "David") - # Person.exists?(['name LIKE ?', "%#{query}%"]) - # Person.exists? - def exists?(id_or_conditions = nil) - case id_or_conditions - when Array, Hash - where(id_or_conditions).exists? - else - scoped.exists?(id_or_conditions) - end - end - # Creates an object (or multiple objects) and saves it to the database, if validations pass. # The resulting object is returned whether the object was saved successfully to the database or not. # @@ -653,177 +619,6 @@ module ActiveRecord #:nodoc: end end - # Updates an object (or multiple objects) and saves it to the database, if validations pass. - # The resulting object is returned whether the object was saved successfully to the database or not. - # - # ==== Parameters - # - # * +id+ - This should be the id or an array of ids to be updated. - # * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes. - # - # ==== Examples - # - # # Updating one record: - # Person.update(15, :user_name => 'Samuel', :group => 'expert') - # - # # Updating multiple records: - # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } - # Person.update(people.keys, people.values) - def update(id, attributes) - if id.is_a?(Array) - idx = -1 - id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) } - else - object = find(id) - object.update_attributes(attributes) - object - end - end - - # Deletes the row with a primary key matching the +id+ argument, using a - # SQL +DELETE+ statement, and returns the number of rows deleted. Active - # Record objects are not instantiated, so the object's callbacks are not - # executed, including any <tt>:dependent</tt> association options or - # Observer methods. - # - # You can delete multiple rows at once by passing an Array of <tt>id</tt>s. - # - # Note: Although it is often much faster than the alternative, - # <tt>#destroy</tt>, skipping callbacks might bypass business logic in - # your application that ensures referential integrity or performs other - # essential jobs. - # - # ==== Examples - # - # # Delete a single row - # Todo.delete(1) - # - # # Delete multiple rows - # Todo.delete([2,3,4]) - def delete(id_or_array) - scoped.delete(id_or_array) - end - - # Destroy an object (or multiple objects) that has the given id, the object is instantiated first, - # therefore all callbacks and filters are fired off before the object is deleted. This method is - # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run. - # - # This essentially finds the object (or multiple objects) with the given id, creates a new object - # from the attributes, and then calls destroy on it. - # - # ==== Parameters - # - # * +id+ - Can be either an Integer or an Array of Integers. - # - # ==== Examples - # - # # Destroy a single object - # Todo.destroy(1) - # - # # Destroy multiple objects - # todos = [1,2,3] - # Todo.destroy(todos) - def destroy(id) - if id.is_a?(Array) - id.map { |one_id| destroy(one_id) } - else - find(id).destroy - end - end - - # Updates all records with details given if they match a set of conditions supplied, limits and order can - # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the - # database. It does not instantiate the involved models and it does not trigger Active Record callbacks - # or validations. - # - # ==== Parameters - # - # * +updates+ - A string, array, or hash representing the SET part of an SQL statement. - # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement. See conditions in the intro. - # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage. - # - # ==== Examples - # - # # Update all customers with the given attributes - # Customer.update_all :wants_email => true - # - # # Update all books with 'Rails' in their title - # Book.update_all "author = 'David'", "title LIKE '%Rails%'" - # - # # Update all avatars migrated more than a week ago - # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago] - # - # # Update all books that match our conditions, but limit it to 5 ordered by date - # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5 - def update_all(updates, conditions = nil, options = {}) - relation = unscoped - - relation = relation.where(conditions) if conditions - relation = relation.limit(options[:limit]) if options[:limit].present? - relation = relation.order(options[:order]) if options[:order].present? - - if current_scoped_methods && current_scoped_methods.limit_value.present? && current_scoped_methods.order_values.present? - # Only take order from scope if limit is also provided by scope, this - # is useful for updating a has_many association with a limit. - relation = current_scoped_methods.merge(relation) if current_scoped_methods - else - relation = current_scoped_methods.except(:limit, :order).merge(relation) if current_scoped_methods - end - - relation.update(sanitize_sql_for_assignment(updates)) - end - - # Destroys the records matching +conditions+ by instantiating each - # record and calling its +destroy+ method. Each object's callbacks are - # executed (including <tt>:dependent</tt> association options and - # +before_destroy+/+after_destroy+ Observer methods). Returns the - # collection of objects that were destroyed; each will be frozen, to - # reflect that no changes should be made (since they can't be - # persisted). - # - # Note: Instantiation, callback execution, and deletion of each - # record can be time consuming when you're removing many records at - # once. It generates at least one SQL +DELETE+ query per record (or - # possibly more, to enforce your callbacks). If you want to delete many - # rows quickly, without concern for their associations or callbacks, use - # +delete_all+ instead. - # - # ==== Parameters - # - # * +conditions+ - A string, array, or hash that specifies which records - # to destroy. If omitted, all records are destroyed. See the - # Conditions section in the introduction to ActiveRecord::Base for - # more information. - # - # ==== Examples - # - # Person.destroy_all("last_login < '2004-04-04'") - # Person.destroy_all(:status => "inactive") - def destroy_all(conditions = nil) - where(conditions).destroy_all - end - - # Deletes the records matching +conditions+ without instantiating the records first, and hence not - # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that - # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations - # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns - # the number of rows affected. - # - # ==== Parameters - # - # * +conditions+ - Conditions are specified the same way as with +find+ method. - # - # ==== Example - # - # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')") - # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else']) - # - # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent - # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead. - def delete_all(conditions = nil) - where(conditions).delete_all - end - # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. # The use of this method should be restricted to complicated SQL queries that can't be executed # using the ActiveRecord::Calculations class methods. Look into those before using this. @@ -1111,6 +906,10 @@ module ActiveRecord #:nodoc: reset_table_name end + def quoted_table_name + @quoted_table_name ||= connection.quote_table_name(table_name) + end + def reset_table_name #:nodoc: base = base_class @@ -1128,6 +927,7 @@ module ActiveRecord #:nodoc: name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}" end + @quoted_table_name = nil set_table_name(name) name end @@ -1374,20 +1174,6 @@ module ActiveRecord #:nodoc: store_full_sti_class ? name : name.demodulize end - # Merges conditions so that the result is a valid +condition+ - def merge_conditions(*conditions) - segments = [] - - conditions.each do |condition| - unless condition.blank? - sql = sanitize_sql(condition) - segments << sql unless sql.blank? - end - end - - "(#{segments.join(') AND (')})" unless segments.empty? - end - def unscoped @unscoped ||= Relation.new(self, arel_table) finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped @@ -2324,7 +2110,7 @@ module ActiveRecord #:nodoc: def update(attribute_names = @attributes.keys) attributes_with_values = arel_attributes_values(false, false, attribute_names) return 0 if attributes_with_values.empty? - self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values) + self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values) end # Creates a record with values matching those of the instance attributes @@ -2548,10 +2334,6 @@ module ActiveRecord #:nodoc: hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ") end - def self.quoted_table_name - self.connection.quote_table_name(self.table_name) - end - def quote_columns(quoter, hash) hash.inject({}) do |quoted, (name, value)| quoted[quoter.quote_column_name(name)] = value diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index bf0683eb8f..9044ca418b 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -84,7 +84,7 @@ module ActiveRecord relation.table[self.class.primary_key].eq(quoted_id).and( relation.table[self.class.locking_column].eq(quote_value(previous_value)) ) - ).update(arel_attributes_values(false, false, attribute_names)) + ).arel.update(arel_attributes_values(false, false, attribute_names)) unless affected_rows == 1 diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index d606934dce..ff6c041ef4 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -165,7 +165,7 @@ module ActiveRecord end def ==(other) - other.respond_to?(:to_a) ? to_a == other.to_a : false + other.respond_to?(:to_ary) ? to_a == other.to_a : false end private diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index decde50427..c9fff15199 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -8,7 +8,7 @@ module ActiveRecord include FinderMethods, Calculations, SpawnMethods, QueryMethods delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a - delegate :insert, :update, :to => :arel + delegate :insert, :to => :arel attr_reader :table, :klass @@ -80,19 +80,177 @@ module ActiveRecord if block_given? to_a.many? { |*block_args| yield(*block_args) } else - arel.send(:taken).present? ? to_a.many? : size > 1 + @limit_value.present? ? to_a.many? : size > 1 end end - def destroy_all - to_a.each {|object| object.destroy} - reset + # Updates all records with details given if they match a set of conditions supplied, limits and order can + # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the + # database. It does not instantiate the involved models and it does not trigger Active Record callbacks + # or validations. + # + # ==== Parameters + # + # * +updates+ - A string, array, or hash representing the SET part of an SQL statement. + # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement. See conditions in the intro. + # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage. + # + # ==== Examples + # + # # Update all customers with the given attributes + # Customer.update_all :wants_email => true + # + # # Update all books with 'Rails' in their title + # Book.update_all "author = 'David'", "title LIKE '%Rails%'" + # + # # Update all avatars migrated more than a week ago + # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago] + # + # # Update all books that match our conditions, but limit it to 5 ordered by date + # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5 + def update_all(updates, conditions = nil, options = {}) + if conditions || options.present? + where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates) + else + # Apply limit and order only if they're both present + if @limit_value.present? == @order_values.present? + arel.update(@klass.send(:sanitize_sql_for_assignment, updates)) + else + except(:limit, :order).update_all(updates) + end + end + end + + # Updates an object (or multiple objects) and saves it to the database, if validations pass. + # The resulting object is returned whether the object was saved successfully to the database or not. + # + # ==== Parameters + # + # * +id+ - This should be the id or an array of ids to be updated. + # * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes. + # + # ==== Examples + # + # # Updating one record: + # Person.update(15, :user_name => 'Samuel', :group => 'expert') + # + # # Updating multiple records: + # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } + # Person.update(people.keys, people.values) + def update(id, attributes) + if id.is_a?(Array) + idx = -1 + id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) } + else + object = find(id) + object.update_attributes(attributes) + object + end + end + + # Destroys the records matching +conditions+ by instantiating each + # record and calling its +destroy+ method. Each object's callbacks are + # executed (including <tt>:dependent</tt> association options and + # +before_destroy+/+after_destroy+ Observer methods). Returns the + # collection of objects that were destroyed; each will be frozen, to + # reflect that no changes should be made (since they can't be + # persisted). + # + # Note: Instantiation, callback execution, and deletion of each + # record can be time consuming when you're removing many records at + # once. It generates at least one SQL +DELETE+ query per record (or + # possibly more, to enforce your callbacks). If you want to delete many + # rows quickly, without concern for their associations or callbacks, use + # +delete_all+ instead. + # + # ==== Parameters + # + # * +conditions+ - A string, array, or hash that specifies which records + # to destroy. If omitted, all records are destroyed. See the + # Conditions section in the introduction to ActiveRecord::Base for + # more information. + # + # ==== Examples + # + # Person.destroy_all("last_login < '2004-04-04'") + # Person.destroy_all(:status => "inactive") + def destroy_all(conditions = nil) + if conditions + where(conditions).destroy_all + else + to_a.each {|object| object.destroy} + reset + end + end + + # Destroy an object (or multiple objects) that has the given id, the object is instantiated first, + # therefore all callbacks and filters are fired off before the object is deleted. This method is + # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run. + # + # This essentially finds the object (or multiple objects) with the given id, creates a new object + # from the attributes, and then calls destroy on it. + # + # ==== Parameters + # + # * +id+ - Can be either an Integer or an Array of Integers. + # + # ==== Examples + # + # # Destroy a single object + # Todo.destroy(1) + # + # # Destroy multiple objects + # todos = [1,2,3] + # Todo.destroy(todos) + def destroy(id) + if id.is_a?(Array) + id.map { |one_id| destroy(one_id) } + else + find(id).destroy + end end - def delete_all - arel.delete.tap { reset } + # Deletes the records matching +conditions+ without instantiating the records first, and hence not + # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that + # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations + # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns + # the number of rows affected. + # + # ==== Parameters + # + # * +conditions+ - Conditions are specified the same way as with +find+ method. + # + # ==== Example + # + # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')") + # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else']) + # + # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent + # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead. + def delete_all(conditions = nil) + conditions ? where(conditions).delete_all : arel.delete.tap { reset } end + # Deletes the row with a primary key matching the +id+ argument, using a + # SQL +DELETE+ statement, and returns the number of rows deleted. Active + # Record objects are not instantiated, so the object's callbacks are not + # executed, including any <tt>:dependent</tt> association options or + # Observer methods. + # + # You can delete multiple rows at once by passing an Array of <tt>id</tt>s. + # + # Note: Although it is often much faster than the alternative, + # <tt>#destroy</tt>, skipping callbacks might bypass business logic in + # your application that ensures referential integrity or performs other + # essential jobs. + # + # ==== Examples + # + # # Delete a single row + # Todo.delete(1) + # + # # Delete multiple rows + # Todo.delete([2,3,4]) def delete(id_or_array) where(@klass.primary_key => id_or_array).delete_all end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 999309d2bd..d6d3d66642 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -119,10 +119,40 @@ module ActiveRecord args.any? ? apply_finder_options(args.first).to_a : to_a end + # Returns true if a record exists in the table that matches the +id+ or + # conditions given, or false otherwise. The argument can take five forms: + # + # * Integer - Finds the record with this primary key. + # * String - Finds the record with a primary key corresponding to this + # string (such as <tt>'5'</tt>). + # * Array - Finds the record that matches these +find+-style conditions + # (such as <tt>['color = ?', 'red']</tt>). + # * Hash - Finds the record that matches these +find+-style conditions + # (such as <tt>{:color => 'red'}</tt>). + # * No args - Returns false if the table is empty, true otherwise. + # + # For more information about specifying conditions as a Hash or Array, + # see the Conditions section in the introduction to ActiveRecord::Base. + # + # Note: You can't pass in a condition as a string (like <tt>name = + # 'Jamie'</tt>), since it would be sanitized and then queried against + # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>. + # + # ==== Examples + # Person.exists?(5) + # Person.exists?('5') + # Person.exists?(:name => "David") + # Person.exists?(['name LIKE ?', "%#{query}%"]) + # Person.exists? def exists?(id = nil) - relation = select(primary_key).limit(1) - relation = relation.where(primary_key.eq(id)) if id - relation.first ? true : false + case id + when Array, Hash + where(id).exists? + else + relation = select(primary_key).limit(1) + relation = relation.where(primary_key.eq(id)) if id + relation.first ? true : false + end end protected @@ -242,15 +272,15 @@ module ActiveRecord result = where(primary_key.in(ids)).all expected_size = - if arel.taken && ids.size > arel.taken - arel.taken + if @limit_value && ids.size > @limit_value + @limit_value else ids.size end # 11 ids with limit 3, offset 9 should give 2 results. - if arel.skipped && (ids.size - arel.skipped < expected_size) - expected_size = ids.size - arel.skipped + if @offset_value && (ids.size - @offset_value < expected_size) + expected_size = ids.size - @offset_value end if result.size == expected_size diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 163d698b5c..d0689cd93e 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -140,16 +140,18 @@ module ActiveRecord selects = @select_values.uniq + quoted_table_name = @klass.quoted_table_name + if selects.present? selects.each do |s| @implicit_readonly = false arel = arel.project(s) if s.present? end - elsif joins.present? - arel = arel.project(@klass.quoted_table_name + '.*') + else + arel = arel.project(quoted_table_name + '.*') end - arel = arel.from(@from_value) if @from_value.present? + arel = @from_value.present? ? arel.from(@from_value) : arel.from(quoted_table_name) case @lock_value when TrueClass @@ -167,8 +169,8 @@ module ActiveRecord builder = PredicateBuilder.new(table.engine) conditions = if [String, Array].include?(args.first.class) - merged = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) - Arel::SqlLiteral.new(merged) if merged + sql = @klass.send(:sanitize_sql, args.size > 1 ? args : args.first) + Arel::SqlLiteral.new("(#{sql})") if sql.present? elsif args.first.is_a?(Hash) attributes = @klass.send(:expand_hash_conditions_for_aggregates, args.first) builder.build_from_hash(attributes, table) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 1577a9b116..cccf413e67 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -1,17 +1,7 @@ module ActiveRecord module SpawnMethods - def spawn(arel_table = self.table) - relation = self.class.new(@klass, arel_table) - - (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |query_method| - relation.send(:"#{query_method}_values=", send(:"#{query_method}_values")) - end - - Relation::SINGLE_VALUE_METHODS.each do |query_method| - relation.send(:"#{query_method}_value=", send(:"#{query_method}_value")) - end - - relation + def spawn + clone.reset end def merge(r) |