'How to show in a view page a list of updates made to a record in ActiveRecord
I am making a simple contacts list app in Ruby. On the show view for each contact, I want to display the basic information (first_name, last_name, email and phone) but also if there have been any changes or updates to that contact. For example, if they changed their email address from [email protected] to [email protected] and this change was made on 27/03/2022 I want a list of this information to also be displayed on the show page.
I guess it is something to do with including ActiveModel::Dirty or a callback but not too sure how to implement any of this!
This is my Contact.rb model file
# frozen_string_literal: true
class Contact < ApplicationRecord
before_validation :normalize_first_name, on: :create
before_validation :normalize_last_name, on: :create
# after_save :print_changes
# before_update :check_for_changes
# after_update :check_for_previous_changes
validates_presence_of :first_name, :last_name, :email, :phone
validates :phone, numericality: { only_integer: true }
validates :email, uniqueness: true
validates :email,
format: { with: /\A(.+)@(.+)\z/, message: 'Email invalid' },
uniqueness: { case_sensitive: false },
length: { minimum: 4, maximum: 95 }
private
def normalize_first_name
self.first_name = first_name.downcase.titleize
end
def normalize_last_name
self.last_name = last_name.downcase.titleize
end
# def check_for_changes
# puts changes # => {"name"=>["Nimish Gupta", "Nimish Mahajan"], "updated_at"=>[Tue, 20 Nov 2018 00:02:14 PST -08:00, Tue, 20 Nov 2018 00:06:15 PST -08:00]}
# puts previous_changes # => {} At this point this will be empty beacuse changes are not made to DB yet
# end
# def check_for_previous_changes
# # but you can make use of previous_changes method to know what change has occurred.
# puts previous_changes # => {"name"=>["Nimish Gupta", "Nimish Mahajan"], "updated_at"=>[Tue, 20 Nov 2018 00:06:15 PST -08:00, Tue, 20 Nov 2018 00:08:07 PST -08:00]}
# end
end
This is the Contact controller
class ContactsController < ApplicationController
before_action :set_contact, only: %i[show edit update destroy]
# GET /contacts or /contacts.json
def index
@contacts = Contact.all
@contact = Contact.new
end
# GET /contacts/1 or /contacts/1.json
def show
end
# GET /contacts/new
def new
@contact = Contact.new
end
# GET /contacts/1/edit
def edit; end
# POST /contacts or /contacts.json
def create
@contact = Contact.new(contact_params)
respond_to do |format|
if @contact.save
format.html { redirect_to contact_url(@contact), notice: 'Contact was successfully created.' }
format.json { render :show, status: :created, location: @contact }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @contact.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /contacts/1 or /contacts/1.json
def update
respond_to do |format|
if @contact.update(contact_params)
format.html { redirect_to contact_url(@contact), notice: 'Contact was successfully updated.' }
format.json { render :show, status: :ok, location: @contact }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @contact.errors, status: :unprocessable_entity }
end
end
end
# DELETE /contacts/1 or /contacts/1.json
def destroy
@contact.destroy
respond_to do |format|
format.html { redirect_to contacts_url, notice: 'Contact was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_contact
@contact = Contact.find(params[:id])
end
# Only allow a list of trusted parameters through.
def contact_params
params.require(:contact).permit(:first_name, :last_name, :email, :phone)
end
end
And the Show page where I want the changes to be displayed
<p id="notice"><%= notice %></p>
<h1>This is the show page for each contact</h1>
<p>
<strong>First name:</strong>
<%= @contact.first_name %>
</p>
<p>
<strong>Last name:</strong>
<%= @contact.last_name %>
</p>
<p>
<strong>Email:</strong>
<%= @contact.email %>
</p>
<p>
<strong>Phone:</strong>
<%= @contact.phone %>
</p>
<%= link_to 'Edit', edit_contact_path(@contact) %> |
<%= link_to 'Back', contacts_path %>
<h1>Contact history</h1>
<%# <%= check_for_changes(@contact) %>
<table>
<thead>
<tr>
<th>Field</th>
<th>Updated</th>
<th>Previous entry</th>
</tr>
</thead>
<tbody>
<%# <% @contacts.each do |contact| > %>
<tr>
<td>Email</td>
<td>27/03/2022</td>
<td>[email protected]</td>
<%# <td><%= contact.first_name ></td> %>
<%# <td><%= contact.last_name ></td> %>
<%# <td><%= contact.email ></td> %>
</tr>
<%# <% end > %>
</tbody>
</table>
Solution 1:[1]
ActiveModel::Dirty only allows you to track changes within the same request the changes are made. Once the contact is stored to the database with the new changes and the request ends, there is no built in method to get the old values anymore.
You will have to build a solution yourself. You will need a model that can store all the changes that were made, which belong to your contact. Let's call that ContactChange from now on.
In order to create the contact change any time the contact is changed, you could use the ActiveModel::Dirty methods and put it in an after_save callback, or you could create a service object to both update the contact and create a contact change.
Or if you just want an out of the box solution, there are a few gems. For example paper trail.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Siim Liiser |
