Class: ActiveRecord::Associations::HasAndBelongsToManyAssociation
- Inherits:
-
Object
- Object
- ActiveRecord::Associations::HasAndBelongsToManyAssociation
- Defined in:
- lib/association_collection_tools.rb
Instance Method Summary collapse
-
#fast_add(ids, attributes = {}) ⇒ Object
fast_add allows you to quickly add objects identified by an array of ids to a HABTM association.
-
#fast_clear ⇒ Object
Removes all records from this association.
-
#fast_copy(other_object, attributes = {}) ⇒ Object
Copies a HABTM association collection from one object to another without instantiating a bunch of ActiveRecord objects.
- #fast_delete_all ⇒ Object
-
#ids ⇒ Object
Return the list of IDs in this association collection without unnecessarily instantiating a bunch of Active Record objects.
Instance Method Details
#fast_add(ids, attributes = {}) ⇒ Object
fast_add allows you to quickly add objects identified by an array of ids to a HABTM association.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/association_collection_tools.rb', line 81 def fast_add(ids,attributes = {}) return [] if ids.empty? column_names = [ @reflection.primary_key_name, @reflection.association_foreign_key ] attribute_values = [] attributes.keys.each{|k| column_names << k attribute_values << attributes[k] } @owner.connection.execute("REPLACE INTO #{@reflection.[:join_table]} (#{column_names.join(",")}) VALUES #{ids.map{|aid| "(#{@owner.quoted_id},#{aid}#{attributes.empty? ? "" : ("," + attribute_values.join(','))})"}.join(",")}") return ids end |
#fast_clear ⇒ Object
Removes all records from this association. Returns self
so method calls may be chained. If this association is not marked as :dependent, then use a faster delete_all method that doesn’t instantiate a bunch of AR objects.
110 111 112 113 114 115 116 117 118 119 |
# File 'lib/association_collection_tools.rb', line 110 def fast_clear if @reflection.[:dependent] && @reflection.[:dependent] == :delete_all return self if length.zero? # forces load_target if hasn't happened already destroy_all else fast_delete_all end self end |
#fast_copy(other_object, attributes = {}) ⇒ Object
Copies a HABTM association collection from one object to another without instantiating a bunch of ActiveRecord objects. This is faster than the standard assignment operation since:
-
Eliminates massive number of SQL calls used in standard HABTM copy by changing it from an O(n) operation to O(1) where n is the number of objects in the association collection.
-
It transfers only object IDs back and forth between the database instead of all object attributes. Resulting in less work for the database, less data transferred and less memory used in ruby.
-
It doesn’t instantiate ActiveRecord objects in memory.
A normal HABTM copy (e.g., person1.items = person2.items) results in the following SQL calls.
SELECT * FROM items INNER JOIN items_people ON items.id = items_people.item_id WHERE (items_people.person_id = 1 ) SELECT * FROM items INNER JOIN items_people ON items.id = items_people.item_id WHERE (items_people.person_id = 2 ) DELETE FROM items_people WHERE person_id = 2 AND item_id IN (4) INSERT INTO items_people (‘item_id`, `person_id`) VALUES (1, 2) INSERT INTO items_people (`item_id`, `person_id`) VALUES (2, 2) INSERT INTO items_people (`item_id`, `person_id`) VALUES (3, 2)
Notice that:
-
items AR objects are instantiated unnecessarily (especially since person2.items are about to be deleted)
-
1 SQL call is issued for each object (item) in the association collection (items_people)
whereas person.items.fast_copy will result in the the following SQL calls greatly reducing the impact on the database and on ruby memory utilization.
DELETE FROM items_people WHERE person_id = 2 SELECT item_id FROM items_people WHERE person_id = 1 REPLACE INTO items_people (person_id,item_id) VALUES (2,3),(2,2),(2,1)
Here are some benchmarks:
when n = 10 and 26 objects in e2.groups:
Benchmark.bm do |x|
x.report { for i in 1..n; e1.groups.clear;e1.groups = e2.groups;end }
x.report { for i in 1..n; e1.groups.clear;e1.groups.fast_copy(e2);end }
end
user system total real
1.140000 0.040000 1.180000 ( 1.832122) 0.020000 0.010000 0.030000 ( 0.125368)
when n = 100 and 26 objects in e2.groups:
user system total real
11.140000 0.360000 11.500000 ( 18.171410)
0.140000 0.010000 0.150000 ( 2.368200)
This method also supports HABTM join tables with additional attributes. Simply pass in an attribute hash as the second argument and it will add the attributes to the records it creates in the join table.
e.g, person1.items.fast_copy(person2, => Time.now)
REALITY CHECK: The HABTM docs refer to collection_singular_ids=ids which implies identical functionality, but I can’t find mention of this method in anything other than the documentation. Maybe this actually already exists and I’m just blind, but from the looks of dev.rubyonrails.org/ticket/2917, it appears that it is a documentation bug.
73 74 75 76 77 |
# File 'lib/association_collection_tools.rb', line 73 def fast_copy(other_object,attributes = {}) self.fast_clear other_object_assocation_ids = other_object.send(@reflection.name).ids self.fast_add(other_object_assocation_ids, attributes) end |
#fast_delete_all ⇒ Object
121 122 123 124 |
# File 'lib/association_collection_tools.rb', line 121 def fast_delete_all @owner.connection.execute("DELETE FROM #{@reflection.[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id}") @target = [] end |
#ids ⇒ Object
Return the list of IDs in this association collection without unnecessarily instantiating a bunch of Active Record objects.
98 99 100 101 102 103 104 |
# File 'lib/association_collection_tools.rb', line 98 def ids if self.loaded? self.map{|x| x.id} else connection.select_all("SELECT #{@reflection.association_foreign_key} FROM #{@reflection.[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id}").map!{|x| x[@reflection.association_foreign_key].to_i} end end |