aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/CHANGELOG11
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb14
-rwxr-xr-xactiverecord/lib/active_record/base.rb71
-rwxr-xr-xactiverecord/test/associations_test.rb13
-rwxr-xr-xactiverecord/test/base_test.rb36
6 files changed, 81 insertions, 74 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index edd278e42b..3b9e273c91 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,6 +1,15 @@
*SVN*
-* Added Base.update_collection that can update an array of id/attribute pairs, such as the ones produced by the recent added support for automatic id-based indexing for lists of items #526 [Duane Johnson]
+* Added that has_many association build and create methods can take arrays of record data like Base#create and Base#build to build/create multiple records at once.
+
+* Added that Base#delete and Base#destroy both can take an array of ids to delete/destroy #336
+
+* Added the option of supplying an array of attributes to Base#create, so that multiple records can be created at once.
+
+* Added the option of supplying an array of ids and attributes to Base#update, so that multiple records can be updated at once (inspired by #526/Duane Johnson). Example
+
+ people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
+ Person.update(people.keys, people.values)
* Added ActiveRecord::Base.timestamps_gmt that can be set to true to make the automated timestamping use GMT instead of local time #520 [Scott Baron]
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 9507822f40..cf1d0ecefc 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -53,9 +53,13 @@ module ActiveRecord
def create(attributes = {})
# Can't use Base.create since the foreign key may be a protected attribute.
- record = build(attributes)
- record.save unless @owner.new_record?
- record
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr) }
+ else
+ record = build(attributes)
+ record.save unless @owner.new_record?
+ record
+ end
end
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index f3ab6bcdab..0d72b9b0c5 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -9,11 +9,15 @@ module ActiveRecord
end
def build(attributes = {})
- load_target
- record = @association_class.new(attributes)
- record[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
- @target << record
- record
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr) }
+ else
+ load_target
+ record = @association_class.new(attributes)
+ record[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
+ @target << record
+ record
+ end
end
def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index cb13f78f7d..b0ad90b10b 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -340,28 +340,38 @@ module ActiveRecord #:nodoc:
# Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
# fail under validations, the unsaved object is still returned.
def create(attributes = nil)
- object = new(attributes)
- object.save
- object
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr) }
+ else
+ object = new(attributes)
+ object.save
+ object
+ end
end
# Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
# and returns it. If the save fail under validations, the unsaved object is still returned.
def update(id, attributes)
- object = find(id)
- object.attributes = attributes
- object.save
- object
+ if id.is_a?(Array)
+ idx = -1
+ id.collect { |id| idx += 1; update(id, attributes[idx]) }
+ else
+ object = find(id)
+ object.update_attributes(attributes)
+ object
+ end
end
- # Deletes the record with the given +id+ without instantiating an object first.
+ # Deletes the record with the given +id+ without instantiating an object first. If an array of ids is provided, all of them
+ # are deleted.
def delete(id)
- delete_all([ "#{primary_key} = ?", id ])
+ delete_all([ "#{primary_key} IN (?)", id ])
end
# Destroys the record with the given +id+ by instantiating the object and calling #destroy (all the callbacks are the triggered).
+ # If an array of ids is provided, all of them are destroyed.
def destroy(id)
- find(id).destroy
+ id.is_a?(Array) ? id.each { |id| destroy(id) } : find(id).destroy
end
# Updates all records with the SET-part of an SQL update statement in +updates+ and returns an integer with the number of rows updates.
@@ -373,47 +383,6 @@ module ActiveRecord #:nodoc:
return connection.update(sql, "#{name} Update")
end
- # Updates several records at a time using the pattern of a hash that contains id => {attributes} pairs as contained in +id_and_attributes_pairs+.
- # If there are certain conditions that must be met in order for the update to occur, an optional block
- # containing a conditional statement may be used. Example:
- # Person.update_collection { 23 => { "first_name" => "John", "last_name" => "Peterson" },
- # 25 => { "first_name" => "Duane", "last_name" => "Johnson" } }
- #
- # # Updates only those records whose first name begins with 'duane' or 'Duane'
- # Person.update_collection @params['people'] do |activerecord_object, new_attributes|
- # activerecord_object.first_name =~ /^[dD]uane.*/
- # end
- #
- # The conditional block may also be of use when you have more than one kind of key in the +id_and_attributes_pairs+ hash.
- # For example, if you have a view that contains form elements of both existing and new records, you might end up with
- # a hash that looks like this:
- # @params['people'] = { "1" => { "first_name" => "Bob", "last_name" => "Schilling" },
- # "2" => { "first_name" => "Joe", "last_name" => "Tycoon" },
- # "new_person" => { "first_name" => "Mary", "last_name" => "Smith" } }
- # In such a case, you could discriminate against 'updating' the new_person record with the following code:
- # Person.update_collection(@params['people']) { |ar, attrs| ar.id.to_i > 0 }
- #
- # This works because the to_i method converts all non-integer strings to 0.
- def update_collection(id_and_attributes_pairs)
- updated_records = Array.new
-
- transaction do
- records = find(id_and_attributes_pairs.keys)
- id_and_attributes_pairs.each do |id, attrs|
- record = records.select { |r| r.id.to_s == id }.first
-
- # Update each record unless the closure is false
- if (!block_given? || (block_given? && yield(record, attrs)))
- record.update_attributes(attrs)
- updated_records << record
- end
- end
- end
-
- return updated_records
- end
-
-
# Destroys the objects for all the records that matches the +condition+ by instantiating each object and calling
# the destroy method. Example:
# Person.destroy_all "last_login < '2004-04-04'"
diff --git a/activerecord/test/associations_test.rb b/activerecord/test/associations_test.rb
index 7ede042879..2095f70cd3 100755
--- a/activerecord/test/associations_test.rb
+++ b/activerecord/test/associations_test.rb
@@ -386,6 +386,14 @@ class HasManyAssociationsTest < Test::Unit::TestCase
assert_equal 2, @signals37.clients_of_firm(true).size
end
+ def test_build_many
+ new_clients = @signals37.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}])
+ assert_equal 2, new_clients.size
+
+ assert @signals37.save
+ assert_equal 3, @signals37.clients_of_firm(true).size
+ end
+
def test_invalid_build
new_client = @signals37.clients_of_firm.build
assert new_client.new_record?
@@ -403,6 +411,11 @@ class HasManyAssociationsTest < Test::Unit::TestCase
assert_equal new_client, @signals37.clients_of_firm.last
assert_equal new_client, @signals37.clients_of_firm(true).last
end
+
+ def test_create_many
+ @signals37.clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}])
+ assert_equal 3, @signals37.clients_of_firm(true).size
+ end
def test_deleting
force_signal37_to_load_all_clients_of_firm
diff --git a/activerecord/test/base_test.rb b/activerecord/test/base_test.rb
index 2119785caf..0f93f2ffbc 100755
--- a/activerecord/test/base_test.rb
+++ b/activerecord/test/base_test.rb
@@ -114,6 +114,12 @@ class BasicsTest < Test::Unit::TestCase
topicReloaded = Topic.find(id)
assert_equal("New Topic", topicReloaded.title)
end
+
+ def test_create_many
+ topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
+ assert_equal 2, topics.size
+ assert_equal "first", topics.first.title
+ end
def test_create_columns_not_equal_attributes
topic = Topic.new
@@ -256,12 +262,22 @@ class BasicsTest < Test::Unit::TestCase
end
def test_destroy_all
- assert_equal(2, Topic.find_all.size)
+ assert_equal 2, Topic.find_all.size
Topic.destroy_all "author_name = 'Mary'"
- assert_equal(1, Topic.find_all.size)
+ assert_equal 1, Topic.find_all.size
end
-
+
+ def test_destroy_many
+ Client.destroy([2, 3])
+ assert_equal 0, Client.count
+ end
+
+ def test_delete_many
+ Topic.delete([1, 2])
+ assert_equal 0, Topic.count
+ end
+
def test_boolean_attributes
assert ! Topic.find(1).approved?
assert Topic.find(2).approved?
@@ -292,21 +308,13 @@ class BasicsTest < Test::Unit::TestCase
assert_equal "bulk updated again!", Topic.find(2).content
end
- def test_update_collection
- ids_and_attributes = { "1" => { "content" => "1 updated" }, "2" => { "content" => "2 updated" } }
- updated = Topic.update_collection(ids_and_attributes)
+ def test_update_many
+ topic_data = { "1" => { "content" => "1 updated" }, "2" => { "content" => "2 updated" } }
+ updated = Topic.update(topic_data.keys, topic_data.values)
assert_equal 2, updated.size
assert_equal "1 updated", Topic.find(1).content
assert_equal "2 updated", Topic.find(2).content
-
- ids_and_attributes["1"]["content"] = "one updated"
- ids_and_attributes["2"]["content"] = "two updated"
- updated = Topic.update_collection(ids_and_attributes) { |ar, attrs| ar.id == 1 }
-
- assert_equal 1, updated.size
- assert_equal "one updated", Topic.find(1).content
- assert_equal "2 updated", Topic.find(2).content
end
def test_delete_all