From b9599502c9e738a5a1513e75d08f8d40ed408265 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 19 Jan 2010 15:22:09 +0530 Subject: Add Relation#construct_relation_for_association_calculations for calculations with includes --- activerecord/lib/active_record/relation.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index e37e692a97..19f91f4278 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -45,12 +45,10 @@ module ActiveRecord def to_a return @records if loaded? - eager_loading = @eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?) - - @records = eager_loading ? find_with_associations : @klass.find_by_sql(arel.to_sql) + @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql) preload = @preload_values - preload += @includes_values unless eager_loading + preload += @includes_values unless eager_loading? preload.each {|associations| @klass.send(:preload_associations, @records, associations) } # @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT. @@ -112,6 +110,7 @@ module ActiveRecord def reset @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil + @should_eager_load = @join_dependency = nil @records = [] self end @@ -133,6 +132,10 @@ module ActiveRecord end end + def eager_loading? + @should_eager_load ||= (@eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?)) + end + protected def method_missing(method, *args, &block) -- cgit v1.2.3 From 9465b84b543ae1ada29c0e39e34f919c724eab6d Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 19 Jan 2010 22:15:35 +0530 Subject: Rename CalculationMethods to Calculations and get rid of the old Calculations module --- activerecord/lib/active_record/relation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 19f91f4278..6ac7137976 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -5,7 +5,7 @@ module ActiveRecord MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from] - include FinderMethods, CalculationMethods, SpawnMethods, QueryMethods + include FinderMethods, Calculations, SpawnMethods, QueryMethods delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a -- cgit v1.2.3 From dbce07b81d24a991ddd29b91f9c358b1f236bda2 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 19 Jan 2010 22:52:08 +0530 Subject: Give preference to to_a over arel from Relation#method_missing --- activerecord/lib/active_record/relation.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 6ac7137976..8bb019f147 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -8,6 +8,7 @@ module ActiveRecord include FinderMethods, Calculations, SpawnMethods, QueryMethods delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a + delegate :insert, :update, :where_clause, :to => :arel attr_reader :table, :klass @@ -139,10 +140,10 @@ module ActiveRecord protected def method_missing(method, *args, &block) - if arel.respond_to?(method) - arel.send(method, *args, &block) - elsif Array.method_defined?(method) + if Array.method_defined?(method) to_a.send(method, *args, &block) + elsif arel.respond_to?(method) + arel.send(method, *args, &block) elsif match = DynamicFinderMatch.match(method) attributes = match.attribute_names super unless @klass.send(:all_attributes_exists?, attributes) -- cgit v1.2.3 From 9acf0af544f2f5dcaf257bdc25047017c972ffce Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 19 Jan 2010 23:11:54 +0530 Subject: Remove Relation#where_clause --- activerecord/lib/active_record/relation.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 8bb019f147..f86677aa51 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, :where_clause, :to => :arel + delegate :insert, :update, :to => :arel attr_reader :table, :klass @@ -164,10 +164,6 @@ module ActiveRecord @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield } end - def where_clause(join_string = " AND ") - arel.send(:where_clauses).join(join_string) - end - def references_eager_loaded_tables? joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.uniq (tables_in_string(to_sql) - joined_tables).any? -- cgit v1.2.3 From 52ec4311f5bf8b596612f297da0b3be8e284b038 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 20 Jan 2010 03:35:25 +0530 Subject: Delegate all finders to Relation --- activerecord/lib/active_record/relation.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index f86677aa51..decde50427 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -60,8 +60,6 @@ module ActiveRecord @records end - alias all to_a - def size loaded? ? @records.length : count end -- cgit v1.2.3 From 8f0f02a1667d6a1c948d6c60adf9581ec47376b9 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 20 Jan 2010 18:12:50 +0530 Subject: Make Relation#destroy_all handle all the cases --- activerecord/lib/active_record/relation.rb | 36 +++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index decde50427..8942690b49 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -84,9 +84,39 @@ module ActiveRecord end end - def destroy_all - to_a.each {|object| object.destroy} - reset + # Destroys the records matching +conditions+ by instantiating each + # record and calling its +destroy+ method. Each object's callbacks are + # executed (including :dependent 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 def delete_all -- cgit v1.2.3 From 223e2a2709cbd0013d51b024bb4e0f950586c125 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 20 Jan 2010 18:24:36 +0530 Subject: Remove Base.delete as it's same as Relation#delete --- activerecord/lib/active_record/relation.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 8942690b49..217fbb2ff4 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -123,6 +123,26 @@ module ActiveRecord 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 :dependent association options or + # Observer methods. + # + # You can delete multiple rows at once by passing an Array of ids. + # + # Note: Although it is often much faster than the alternative, + # #destroy, 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 -- cgit v1.2.3 From 97568056763dc3bca7804a1426fae32a2031cfec Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 20 Jan 2010 18:28:45 +0530 Subject: Move destroy to Relation --- activerecord/lib/active_record/relation.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 217fbb2ff4..a253dbd19d 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -119,6 +119,33 @@ module ActiveRecord 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 } end -- cgit v1.2.3 From f216fadc0e4a54d1807fe5a9462f7bd34e9024b0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 20 Jan 2010 18:33:14 +0530 Subject: Delegate delete_all to Relation --- activerecord/lib/active_record/relation.rb | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index a253dbd19d..a6c283e03c 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -146,8 +146,25 @@ module ActiveRecord 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 :dependent 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 before_* 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 -- cgit v1.2.3 From 8b9bfbe225a59ccefa46f1e8bf301bc483bef0e0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 20 Jan 2010 18:40:19 +0530 Subject: Dont delegate Relation#update to arel --- activerecord/lib/active_record/relation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index a6c283e03c..a7750f60b4 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 -- cgit v1.2.3 From 8d31c9f3a0c69ce7e8f905a4e75177037bbbcad5 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 20 Jan 2010 22:20:08 +0530 Subject: Move update and update_all to Relation --- activerecord/lib/active_record/relation.rb | 64 ++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index a7750f60b4..0e91959db5 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -84,6 +84,70 @@ module ActiveRecord 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 :limit and :order, 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 :dependent association options and -- cgit v1.2.3 From 459e9b29d4b9922d5ce3f87dd62fd43e752ac3da Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 20 Jan 2010 22:20:33 +0530 Subject: Use @limit_value and @offset_value instead of calling arel --- activerecord/lib/active_record/relation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 0e91959db5..c9fff15199 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -80,7 +80,7 @@ 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 -- cgit v1.2.3 From da142cd86584d56299470ee4dd6f90be7127b477 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 21 Jan 2010 17:48:53 +0530 Subject: Supplying Arel::SqlLiteral is much faster --- activerecord/lib/active_record/relation.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index c9fff15199..04b85119cb 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -282,8 +282,11 @@ module ActiveRecord def scope_for_create @scope_for_create ||= begin - @create_with_value || wheres.inject({}) do |hash, where| - hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality) + @create_with_value || @where_values.inject({}) do |hash, where| + if where.is_a?(Arel::Predicates::Equality) + hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2 + end + hash end end -- cgit v1.2.3 From ee8c006dac7d76f79fb43df97a4ab8a2ae87b8b2 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 22 Jan 2010 20:10:29 +0530 Subject: Allow calling class methods on a Relation --- activerecord/lib/active_record/relation.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 04b85119cb..fa99e3f891 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -301,6 +301,8 @@ module ActiveRecord def method_missing(method, *args, &block) if Array.method_defined?(method) to_a.send(method, *args, &block) + elsif @klass.respond_to?(method) + @klass.send(:with_scope, self) { @klass.send(method, *args, &block) } elsif arel.respond_to?(method) arel.send(method, *args, &block) elsif match = DynamicFinderMatch.match(method) -- cgit v1.2.3 From 4afd9702fe111a5cbaa0d9572e7661c90b188d49 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 22 Jan 2010 20:14:37 +0530 Subject: Relation should respond to class methods --- activerecord/lib/active_record/relation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/relation.rb') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index fa99e3f891..1a96cdad17 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -32,7 +32,7 @@ module ActiveRecord end def respond_to?(method, include_private = false) - return true if arel.respond_to?(method, include_private) || Array.method_defined?(method) + return true if arel.respond_to?(method, include_private) || Array.method_defined?(method) || @klass.respond_to?(method, include_private) if match = DynamicFinderMatch.match(method) return true if @klass.send(:all_attributes_exists?, match.attribute_names) -- cgit v1.2.3