Finding out updated fields

Hi,

When we update a record via an update form, is there an easy way to find
out the fields that have been changed. If the update is successful, i
want to display:
The following fields have been changed:
field-name = new-value

Thanks,

Lantis.

There is a recipe for doing this called “Keeping Track of Who Did What”
in the book “Rails Recipes”
(http://pragmaticprogrammer.com/titles/fr_rr/).

-Jeremy

It’s not really what i want. That recipe shows how to keep track of
user’s activity. What i want is to find out what was changed in the edit
operation (the fields of the record & the new values).

Thanks anyway,

Lantis.

I am also interested in a solution to this problem since I have a form
with over 100’s text fields and I dont want to load teh DB with updating
all fields every time only one is updated…

My current idea would be to associate each text field with a hiden field
containing the old_value of the field. And when you do the update, you
only update the records for which old_field != new_field.

But still, I dont find this solution very elegant. Since rails know the
value of all fields (that is how it is able to auto-populate them), it
should be aware of changes to any of these fields by comparing the
params hash with the hash in memory.

Anyone can comment on this? I am just guessing…

Hello,

Lantis :

It’s not really what i want. That recipe shows how to keep track of
user’s activity. What i want is to find out what was changed in the edit
operation (the fields of the record & the new .

Alain :

[…] Since rails know the value of all fields (that is how it
is able to auto-populate them), it should be aware of changes
to any of these fields by comparing the
params hash with the hash in memory.

Yes, I think that’s the way we should do it, if Rails doesn’t do it,
let’s do it instead !

Assuming a Person model, here a typical scaffolded code…

def edit
@person = Person.find(params[:id])
end

def update
@person = Person.find(params[:id])
if @person.update_attributes(params[:person])
flash[:notice] = ‘Person was successfully updated.’
redirect_to :action => ‘show’, :id => @person
else
render :action => ‘edit’
end
end

So, the idea is to take the params[:person] hash and remove
the not-changing key/value pairs.

def update
@person = Person.find(params[:id])

attr = @person.attributes
new_attr = params[:person]
new_attr.delete_if { |k,v| attr.has_key?(k) && attr[k] == v }

# => updated fields in new_attr Hash
# if we wanna display it, use a @new_attr variable.

if @person.update_attributes(new_attr)
  flash[:notice] = 'Person was successfully updated.'
  redirect_to :action => 'show', :id => @person
else
  render :action => 'edit'
end

end

No tested though, but it should do the trick.

So if it works, you can add a method to AR::B like that :

def update_only_changed_attributes(attrs)
attrs.delete_if do|key,value|
has_attribute?(key) && self[key] == value
end
update_attributes(attrs)
end

Not tested… again :slight_smile:

In the debugging process of this method, i’m wondering if we
have to check for the primary key, to remove it from the hash or not.
And other issues i haven’t suspected… (do i have to stringify the keys
?)

 -- Jean-François.

Alain wrote:

should be aware of changes to any of these fields by comparing the
params hash with the hash in memory.

Anyone can comment on this? I am just guessing…

Yes, I think it’d be useful for Active Record to track which
attributes in a model have changed, both allowing the user to
identify changed fields, and restricting DB updates to only those
changed fields.

I actually wrote a mod to implement this, but I don’t think I’ve
fully tested it. It adds a @changed_attributes hash to a model.

What held me up was also implementing a @changed_association hash
which allowed AR to not only automatically save the new records
in an object’s descended associates, but also any changed attribute
in those descendants. This would simplify controller code.

Here’s the code to get the flavour of what is required. Changed
lines marked with ‘N’.


module ActiveRecord
class Base
def initialize(attributes = nil)
@attributes = attributes_from_column_definition
N @changed_attributes = {}
@new_record = true
ensure_proper_type
self.attributes = attributes unless attributes.nil?
yield self if block_given?
end

 def write_attribute(attr_name, value)
   attr_name = attr_name.to_s
   if (column = column_for_attribute(attr_name)) && column.number?
     new_value = convert_number_column_value(value)

N if new_value != @attributes[attr_name]
N @changed_attributes[attr_name] = true
N @attributes[attr_name] = new_value
end
else
@attributes[attr_name] = value
end
end

private
N def attributes_with_quotes(include_primary_key = true, only_changed
= false)
attributes.inject({}) do |quoted, (name, value)|
if column = column_for_attribute(name)
N unless !include_primary_key && column.primary || only_changed
&& @changed_attributes[name]
quoted[name] = quote(value, column)
end
end
quoted
end
end

 def update

N quoted_attributes = attributes_with_quotes(false,true)
connection.update(
"UPDATE #{self.class.table_name} " +
N "SET #{quoted_comma_pair_list(connection, quoted_attributes)} "
+
“WHERE #{self.class.primary_key} = #{quote(id)}”,
“#{self.class.name} Update”
N ) unless quoted_attributes.empty?

   return true
 end

end
end


We develop, watch us RoR, in numbers too big to ignore.