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/


Regards

Andrew

On 28 ÆÅ×, 22:13, “Oliver P.” [email protected] wrote:

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

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
.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?

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

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?

2007/3/2, Oliver P. [email protected]:

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

2007/3/2, Oliver P. [email protected]:

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.

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…

2007/3/2, Oliver P. [email protected]:

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?

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 F.'s description.

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