Forum: Ruby on Rails Finding out updated fields

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Lantis S. (Guest)
on 2006-04-17 22:19
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.
Jeremy M. (Guest)
on 2006-04-18 08:34
(Received via mailing list)
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
Lantis S. (Guest)
on 2006-04-18 19:02
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.
Alain (Guest)
on 2006-04-18 22:43
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...
Jean-François (Guest)
on 2006-04-19 00:05
(Received via mailing list)
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 :)

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.
Mark Reginald J. (Guest)
on 2006-04-19 00:33
(Received via mailing list)
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.
This topic is locked and can not be replied to.