Back

Rails add a update change log for Active Record model object

added on :: 8 January 2015

This may come in handy if you want to keep a track on what happened to your database records over time. As you know database is where the Active Record objects go to sleep. Why would you not want to know the activities that when on on each object.


Say you have a bicycle record in the bicycles. And a you painted one bike from blue to bright yellow. So you updated the bicycles colour as yellow. You may never know that it was once blue. If you have a change log, it would say back in the day this bikes colour was changed to yellow from blue. Also you can keep a track on who updated the records in your database as the object changes its state.

It is best to keep the changes in a separate table. So lets call that table "happenings" or "activities"

Here is happening migration file and 'description' is where the changes hash goes in.

class CreateHappenings < ActiveRecord::Migration
  def change
    create_table :happenings do |t|
      t.text :description
      t.integer :user_id
      t.string :happenable_type
      t.integer :happenable_id
t.timestamps
    end
  end
end


This is that 'happenings.rb' table

class Happening < ActiveRecord::Base


belongs_to :happenable, :polymorphic => true
belongs_to :user

serialize :description, Hash


def self.create_from_action(info )
happening = Happening.new
happening.user_id = info[:current_user]
happening.happenable_type = info[:happenable_type]
happening.happenable_id = info[:happenable_id]
happening.description = info[:updatings]
happening.save
end


def parse_the_hash
itm = self.description
unless itm.empty?
hello = []
itm.each do |k, v|
unless k == "updated_at" || k == "created_at" || v.all?(&:blank?)
if v.first.present? && v.first.length == 0
hello << "#{k} updated to #{v.last}"
else
hello << "#{k} changed from #{v.first} to #{v.last}"
end
end
end
hello.join(", ")
end
end


end


To get the objects changes. This is a personal preference, I find it is more descriptive to keep an eye on database after update entries right in the controller rather than after update callbacks. That way when you read the controller method you know exactly what happens to that object.

So here is that 'get the changes' list in a hash. As this method is used by multiple controllers lets put it in the application_controller.rb file.

  def obj_before_after_difference(before, after)

((before.keys - ["updated_at"]) + (after.keys - ["updated_at"])).uniq.inject({}) do |memo, key|
unless before.key?(key) && after.key?(key) && before[key] == after[key]
memo[key] = [before.key?(key) ? before[key] : :_no_key, after.key?(key) ? after[key] : :_no_key]
end
memo
end
end

So now you can put this method in your update method.

  def update
    @pricing = Pricing.find(params[:id])
    before =   @pricing.attributes
    respond_to do |format|
      if @pricing.update(pricing_params)
        format.html { redirect_to :back  }
        format.json { render :show, status: :ok, location: @pricing }
        after =   @pricing.attributes
       Happening.create_from_action(:current_user  => current_user.id, :happenable_type => "Pricing",:happenable_id => @pricing.id, :updatings => obj_before_after_difference(before, after)  )

      else
        format.html { render :edit }
        format.json { render json: @pricing.errors, status: :unprocessable_entity }
      end
    end
  end


Now to see all the changes. In your "show.html.erb" file you could add a activity list.


Activity list





<%the_obj.happenings.order("created_at DESC").each do |happ|%>
<% unless happ.description.empty? %>

-<%=happ.parse_the_hash %> (on: <%=happ.created_at.strftime("%d-%m-%Y - %l:%M %p")%> by <%= happ.user.name if happ.user %>)

<%end%>
<%end%>










Back