Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 504

Show
Ignore:
Timestamp:
01/25/05 12:45:01 (4 years ago)
Author:
david
Message:

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). Added the option of supplying an array of attributes to Base#create, so that multiple records can be created at once. Added that Base#delete and Base#destroy both can take an array of ids to delete/destroy #336. 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.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activerecord/CHANGELOG

    r498 r504  
    11*SVN* 
    22 
    3 * 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] 
     3* 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. 
     4 
     5* Added that Base#delete and Base#destroy both can take an array of ids to delete/destroy #336 
     6 
     7* Added the option of supplying an array of attributes to Base#create, so that multiple records can be created at once. 
     8 
     9* 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 
     10 
     11    people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} } 
     12    Person.update(people.keys, people.values) 
    413 
    514* 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] 
  • trunk/activerecord/lib/active_record/associations/association_collection.rb

    r456 r504  
    5454      def create(attributes = {}) 
    5555        # Can't use Base.create since the foreign key may be a protected attribute. 
    56         record = build(attributes) 
    57         record.save unless @owner.new_record? 
    58         record 
     56        if attributes.is_a?(Array) 
     57          attributes.collect { |attr| create(attr) } 
     58        else 
     59          record = build(attributes) 
     60          record.save unless @owner.new_record? 
     61          record 
     62        end 
    5963      end 
    6064 
  • trunk/activerecord/lib/active_record/associations/has_many_association.rb

    r464 r504  
    1010 
    1111      def build(attributes = {}) 
    12         load_target 
    13         record = @association_class.new(attributes) 
    14         record[@association_class_primary_key_name] = @owner.id unless @owner.new_record? 
    15         @target << record 
    16         record 
     12        if attributes.is_a?(Array) 
     13          attributes.collect { |attr| create(attr) } 
     14        else 
     15          load_target 
     16          record = @association_class.new(attributes) 
     17          record[@association_class_primary_key_name] = @owner.id unless @owner.new_record? 
     18          @target << record 
     19          record 
     20        end 
    1721      end 
    1822 
  • trunk/activerecord/lib/active_record/base.rb

    r496 r504  
    341341      # fail under validations, the unsaved object is still returned. 
    342342      def create(attributes = nil) 
    343         object = new(attributes) 
    344         object.save 
    345         object 
     343        if attributes.is_a?(Array) 
     344          attributes.collect { |attr| create(attr) } 
     345        else 
     346          object = new(attributes) 
     347          object.save 
     348          object 
     349        end 
    346350      end 
    347351 
     
    349353      # and returns it. If the save fail under validations, the unsaved object is still returned. 
    350354      def update(id, attributes) 
    351         object = find(id) 
    352         object.attributes = attributes 
    353         object.save 
    354         object 
    355       end 
    356  
    357       # Deletes the record with the given +id+ without instantiating an object first. 
     355        if id.is_a?(Array) 
     356          idx = -1 
     357          id.collect { |id| idx += 1; update(id, attributes[idx]) } 
     358        else 
     359          object = find(id) 
     360          object.update_attributes(attributes) 
     361          object 
     362        end 
     363      end 
     364 
     365      # Deletes the record with the given +id+ without instantiating an object first. If an array of ids is provided, all of them 
     366      # are deleted. 
    358367      def delete(id) 
    359         delete_all([ "#{primary_key} = ?", id ]) 
     368        delete_all([ "#{primary_key} IN (?)", id ]) 
    360369      end 
    361370       
    362371      # Destroys the record with the given +id+ by instantiating the object and calling #destroy (all the callbacks are the triggered). 
     372      # If an array of ids is provided, all of them are destroyed. 
    363373      def destroy(id) 
    364         find(id).destroy 
     374        id.is_a?(Array) ? id.each { |id| destroy(id) } : find(id).destroy 
    365375      end 
    366376 
     
    374384      end 
    375385 
    376       # Updates several records at a time using the pattern of a hash that contains id => {attributes} pairs as contained in +id_and_attributes_pairs+. 
    377       # If there are certain conditions that must be met in order for the update to occur, an optional block 
    378       # containing a conditional statement may be used.  Example: 
    379       #   Person.update_collection { 23 => { "first_name" => "John", "last_name" => "Peterson" }, 
    380       #                        25 => { "first_name" => "Duane", "last_name" => "Johnson" } } 
    381       # 
    382       #   # Updates only those records whose first name begins with 'duane' or 'Duane' 
    383       #   Person.update_collection @params['people'] do |activerecord_object, new_attributes| 
    384       #     activerecord_object.first_name =~ /^[dD]uane.*/ 
    385       #   end 
    386       # 
    387       # The conditional block may also be of use when you have more than one kind of key in the +id_and_attributes_pairs+ hash. 
    388       # For example, if you have a view that contains form elements of both existing and new records, you might end up with 
    389       # a hash that looks like this: 
    390       #   @params['people'] = { "1" => { "first_name" => "Bob", "last_name" => "Schilling" }, 
    391       #                         "2" => { "first_name" => "Joe", "last_name" => "Tycoon" }, 
    392       #                         "new_person" => { "first_name" => "Mary", "last_name" => "Smith" } } 
    393       # In such a case, you could discriminate against 'updating' the new_person record with the following code: 
    394       #   Person.update_collection(@params['people']) { |ar, attrs| ar.id.to_i > 0 } 
    395       # 
    396       # This works because the to_i method converts all non-integer strings to 0. 
    397       def update_collection(id_and_attributes_pairs) 
    398         updated_records = Array.new 
    399  
    400         transaction do 
    401           records = find(id_and_attributes_pairs.keys)           
    402           id_and_attributes_pairs.each do |id, attrs| 
    403             record = records.select { |r| r.id.to_s == id }.first 
    404  
    405             # Update each record unless the closure is false 
    406             if (!block_given? || (block_given? && yield(record, attrs))) 
    407               record.update_attributes(attrs) 
    408               updated_records << record 
    409             end 
    410           end 
    411         end 
    412  
    413         return updated_records 
    414       end 
    415        
    416            
    417386      # Destroys the objects for all the records that matches the +condition+ by instantiating each object and calling 
    418387      # the destroy method. Example: 
  • trunk/activerecord/test/associations_test.rb

    r484 r504  
    387387  end 
    388388 
     389  def test_build_many 
     390    new_clients = @signals37.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) 
     391    assert_equal 2, new_clients.size 
     392 
     393    assert @signals37.save 
     394    assert_equal 3, @signals37.clients_of_firm(true).size 
     395  end 
     396 
    389397  def test_invalid_build 
    390398    new_client = @signals37.clients_of_firm.build 
     
    403411    assert_equal new_client, @signals37.clients_of_firm.last 
    404412    assert_equal new_client, @signals37.clients_of_firm(true).last 
     413  end 
     414   
     415  def test_create_many 
     416    @signals37.clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}]) 
     417    assert_equal 3, @signals37.clients_of_firm(true).size 
    405418  end 
    406419 
  • trunk/activerecord/test/base_test.rb

    r496 r504  
    115115    assert_equal("New Topic", topicReloaded.title) 
    116116  end 
     117   
     118  def test_create_many 
     119    topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) 
     120    assert_equal 2, topics.size 
     121    assert_equal "first", topics.first.title 
     122  end 
    117123 
    118124  def test_create_columns_not_equal_attributes 
     
    257263   
    258264  def test_destroy_all 
    259     assert_equal(2, Topic.find_all.size) 
     265    assert_equal 2, Topic.find_all.size 
    260266 
    261267    Topic.destroy_all "author_name = 'Mary'" 
    262     assert_equal(1, Topic.find_all.size) 
    263   end 
    264    
     268    assert_equal 1, Topic.find_all.size 
     269  end 
     270 
     271  def test_destroy_many 
     272    Client.destroy([2, 3]) 
     273    assert_equal 0, Client.count 
     274  end 
     275 
     276  def test_delete_many 
     277    Topic.delete([1, 2]) 
     278    assert_equal 0, Topic.count 
     279  end 
     280 
    265281  def test_boolean_attributes 
    266282    assert ! Topic.find(1).approved? 
     
    293309  end 
    294310 
    295   def test_update_collection 
    296     ids_and_attributes = { "1" => { "content" => "1 updated" }, "2" => { "content" => "2 updated" } } 
    297     updated = Topic.update_collection(ids_and_attributes) 
     311  def test_update_many 
     312    topic_data = { "1" => { "content" => "1 updated" }, "2" => { "content" => "2 updated" } } 
     313    updated = Topic.update(topic_data.keys, topic_data.values) 
    298314 
    299315    assert_equal 2, updated.size 
    300316    assert_equal "1 updated", Topic.find(1).content 
    301     assert_equal "2 updated", Topic.find(2).content 
    302  
    303     ids_and_attributes["1"]["content"] = "one updated" 
    304     ids_and_attributes["2"]["content"] = "two updated" 
    305     updated = Topic.update_collection(ids_and_attributes) { |ar, attrs| ar.id == 1 } 
    306  
    307     assert_equal 1, updated.size 
    308     assert_equal "one updated", Topic.find(1).content 
    309317    assert_equal "2 updated", Topic.find(2).content 
    310318  end