Active Record: dynamic updates possible?

Hello,

I have seen that Active Record is making a full update (with all columns) if I change the position of an item in a has_many, acts_as_list configuration. e.g.:

UPDATE pages SET "name" = 'asdasdasd', "content" = NULL, "position" = 1, "article_id" = 1 WHERE id = 10

I know Hibernate O/R mapper. There is an option called "dynamic- update". The O/R mapper keeps track of changed properties in the entity objects and generates the following update statement:

UPDATE pages SET "position" = 1 WHERE id = 10

The mapper does not change the complete row. Only the columns changed. Is something like this possible in Rails? Is something like this planned for Active Record?

Hi Oliver, I myself am an Hibernate user and am currently looking into migrating an existing project over to ruby/rails. Now I know nothing about the ruby/rails framework yet but the following document depicts an hibernate map/class with dynamic update and its rails equivalent. Also shows a number of class methods as well as Instance methods associated with the code def at runtime!:

  • update(id, attributes)
  • update_all(updates, conditions

may be of interest?!

http://www-128.ibm.com/developerworks/linux/library/wa-rubyonrails/

I have made some tests with update_all. It worked but it would be nice if ActiveRecord can handle this internally a little bit more intelligent. I had a problem with a blob column (image upload). This entities had the acts_as_list option with the position. ActiveRecord automatically updates the complete row columns on <object>.move_higher, etc. calls. This is not needed - for my point of view. Another problem with update_all is that it generates a SQL query - but does not recognize the options for the dependent objects - e.g. :dependent => :destroy or :dependent => :delete_all. An option like "dynamic-update" in Hibernate will make the development easier, the programs faster and the concurrent usage of the program better. e.g. User A can change property A (=column A) and User B property B (=column B) of the same entity at the same time - with dynamic update option both changes are in database afterwards.

Another problem I had was with update_attribute. I thought ActiveRecord will generate a simple UPDATE SQL statement for this column only. I have seen in the log that update_attribute generates an UPDATE statement with all columns.

What do you think?

Another problem with update_all is that it generates a SQL query - but does not recognize the options for the dependent objects - e.g. :dependent => :destroy or :dependent => :delete_all.

Do you have use case where UPDATE requires DELETE of dependent records ?

Another problem I had was with update_attribute. I thought ActiveRecord will generate a simple UPDATE SQL statement for this column only. I have seen in the log that update_attribute generates an UPDATE statement with all columns.

You can make a plugin with smth like : module ActiveRecord   class Base     # page = Page.find(10)     # page.update_only( 'position' => 5)     # will result in SQL:     # UPDATE pages SET position = 5 WHERE id = 10     # notes:     # 1.created/updated_on/at fields aren't handled     # 2.returns true if update succeed, false otherwise     def update_only( new_attrs )       return true if new_attrs.empty?       pkey = quote_value(id) # to allow changing primary key altogether :slight_smile:       new_attrs.each { |column, value| write_attribute(column, value) }       if valid?         update_list = new_attrs.keys.map do |name|           self.class.connection.quote_column_name(name) + "=" +

quote_value(read_attribute(name),column_for_attribute(name) )         end.join(' , ')         connection.update(           "UPDATE #{self.class.table_name} SET #{update_list} WHERE #{self.class.primary_key} = #{pkey}",           "#{self.class.name} Update" )         true       else         false       end     end   end end

Do you have use case where UPDATE requires DELETE of dependent records ?

Not yet.

You can make a plugin with smth like : module ActiveRecord   class Base     [...]

Ok. Something like that can fix the update_attribute issue. But if I use acts_as_list with move_higher I have to change the move_higher functionality (override?)...

Thank you for your post - very interesting :slight_smile:

This is the problem I currently have with ActiveRecord - you want to implement your program the "easy" Rails way - and then you get some pitfalls - and your code getting more and ugly. I think it would be nice to get ActiveRecord more flexible/powerful for such cases.

I have another post for this topic in "Ruby on Rails: Core" list: http://groups.google.com/group/rubyonrails-core/browse_thread/thread/dc2d301dd9c8e5c9

Ok. Something like that can fix the update_attribute issue. But if I use acts_as_list with move_higher I have to change the move_higher functionality (override?)...

see attached diff for possible changes

This is the problem I currently have with ActiveRecord - you want to implement your program the "easy" Rails way - and then you get some pitfalls - and your code getting more and ugly. I think it would be nice to get ActiveRecord more flexible/powerful for such cases.

Rails is flexible, providing basic functionality that is good enough for majority of cases, and easy way to extend it to your needs, http://agilewebdevelopment.com/plugins/category/2

acts_as_list.diff (3.3 KB)

see attached diff for possible changes

Thank you. I will take a look. I´ll try to write a plugin.

Rails is flexible, providing basic functionality that is good enough for majority of cases, and easy way to extend it to your needs,http://agilewebdevelopment.com/plugins/category/2

I think my case is such a major case and ActiveRecord can be more flexible. I know that I can extend it with a plugin, etc. but I think it makes no sense to update all columns all the time. What do you think about this default behaviour?

Update all columns all the time is not a problem, unless there are blobs which aren't touched, so occasionally people ask why update statement doesn't include only changed attributes, and have to resort to update_all.

Besides, model can have callbacks (timestamps, locking, versioning etc) on update, so this aspect also should be considered.

active_record_update_only_and_acts_as_list_patch.diff (5.17 KB)

Update all columns all the time is not a problem, [...]

Ok, but why should the persistence layer update more columns than necessary? I think there is a reason for this functionality in the "enterprise" (no I don´t want to start a flamewar "enterprise" discussion) O/R Mapper Hibernate. If the persistence layer keeps track of the changes I can call the save method in my code (=easy code) - but if there are no changes - no UPDATE statement will be generated and executed. For me this is better than executing a complete UPDATE statement. What do you think?

Besides, model can have callbacks (timestamps, locking, versioning etc) on update, so this aspect also should be considered.

Ok. I see...

At present time there is no solution to track model changes in Rails, probably, due to low demand for most users, see: http://dev.rubyonrails.org/ticket/1423 http://dev.rubyonrails.org/ticket/3238 http://dev.rubyonrails.org/ticket/5961

Hi Oliver, I was able to verify that the following methods does an update of all the fields of a record:

update_attribute update_attributes update_attributes!

Also, I was able to find ticket, #5961, that had patch in regards to this issue:

http://dev.rubyonrails.org/ticket/5961

Thus, I would recommend opening/creating a ticket so that this issue gets resolved in future releases:

http://dev.rubyonrails.org

Good luck,

-Conrad

I think apples and oranges are compared here. Active Record is not a full "O/R mapper". It is more like a handy SQL helper. A 'real' O/R mapper, by my definition, has a managed persistance layer: for each row in the database, there is one (1) object in the objects space. The application modifies this objects graph, which is stored back to the db. A 'real' O/R mapper has provisions like a notifiction system, such that as one part or instance of the application modifies a specific object, this gets notified to the other intstances of the app. Complex stuff (I asume Hibernate is such a system, as is Enterprise Objects Framework (EOF) of Apple's WebObjects). Active Record is more like a helper layer, imho: You can create as many in-memory objects representing rows in the database as you want. You are responsible yourself for keeping track of the changes and getting them back to the store. There is no formal pool of objects shared between multiple instances of the app, and so on. So your assumption "If the persistence layer keeps track of the changes" is false in the case of ActiveRecord: this responsibility lies with your custom controller and/or model, provisions for automaticaly keeping track of changes are just not there.

~h

Very interesting thread.

I've had the same thoughts myself. Which is why I started the DataMapper project (source available in the repo on RubyForge, gem release sometime next week: http://rubyforge.org/projects/datamapper). To put it simply, I'm striving for a better ActiveRecord. To keep compatibility with ActiveRecord for a majority of interfaces and use- cases, though the underlying implementation is much closer to more traditional O/R Mappers such as Hibernate, the Scott Ambler examples, and Martin Fowler's description.

This specific case for example is handled easily since the DataMapper employs a UnitOfWork pattern to track changes.