Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to keep track of model history with mapping table in Ruby on Rails?

dream

I'd like to keep record of when a user changes their address.

This way, when an order is placed, it will always be able to reference the user address that was used at the time of order placement.

possible schema

users (
  id
  username
  email
  ...
)

user_addresses (
  id
  label
  line_1
  line_2
  city
  state
  zip
  ...
)

user_addresses_map (
  user_id
  user_address_id
  start_time
  end_time
)

orders (
  id
  user_id
  user_address_id
  order_status_id
  ...
  created_at
  updated_at
)

in sql, this might look something like: [sql]

select ua.*

from  orders    o

join  users     u
  on  u.id = o.user_id
  
join  user_addressses_map   uam
  on  uam.user_id = u.id
  and uam.user_address_id = o.user_address_id
  
join  user_addresses        ua
  on  ua.id = uam.user_address_id
  and uam.start_time < o.created_at
  and (uam.end_time >= o.created_at or uam.end_time is null)
;

edit: The Solution

@KandadaBoggu posted a great solution. The Vestal Versions plugin is a great solution.

snippet below taken from http://github.com/laserlemon/vestal_versions

Finally, DRY ActiveRecord versioning!

acts_as_versioned by technoweenie was a great start, but it failed to keep up with ActiveRecord’s introduction of dirty objects in version 2.1. Additionally, each versioned model needs its own versions table that duplicates most of the original table’s columns. The versions table is then populated with records that often duplicate most of the original record’s attributes. All in all, not very DRY.

vestal_versions requires only one versions table (polymorphically associated with its parent models) and no changes whatsoever to existing tables. But it goes one step DRYer by storing a serialized hash of only the models’ changes. Think modern version control systems. By traversing the record of changes, the models can be reverted to any point in time.

And that’s just what vestal_versions does. Not only can a model be reverted to a previous version number but also to a date or time!

like image 667
maček Avatar asked Feb 24 '10 03:02

maček


People also ask

What is ORM in ROR?

1.2 Object Relational Mapping Object Relational Mapping, commonly referred to as its abbreviation ORM, is a technique that connects the rich objects of an application to tables in a relational database management system.

What is ActiveRecord in Ruby on Rails?

What is ActiveRecord? ActiveRecord is an ORM. It's a layer of Ruby code that runs between your database and your logic code. When you need to make changes to the database, you'll write Ruby code, and then run "migrations" which makes the actual changes to the database.

Is ActiveRecord an ORM?

Object Relational Mapping is a way to manage database data by "mapping" database tables to classes and instances of classes to rows in those tables. Active Record is just one of such ORMs, others include: Sequel.

How do you save multiple records in Rails?

Use ActiveRecord::Persistence#create , which can accept an array of hashes as a parameter. Save this answer. Show activity on this post. Save this answer.


2 Answers

Thought I'd add an updated answer. Seems the paper_trail gem has become the most popular one for versioning in Rails. It supports Rails 4 as well.

https://github.com/airblade/paper_trail

From their readme:

To setup and install:

gem 'paper_trail', '~> 3.0.6'
bundle exec rails generate paper_trail:install
bundle exec rake db:migrate

Basic Usage:

class Widget < ActiveRecord::Base
  has_paper_trail
end

For a specific instance of the Widget class:

v = widget.versions.last
v.event                     # 'update' (or 'create' or 'destroy')
v.whodunnit                 # '153'  (if the update was via a controller and
                               #         the controller has a current_user method,
                               #         here returning the id of the current user)
v.created_at                # when the update occurred
widget = v.reify            # the widget as it was before the update;
                               # would be nil for a create event

I've only played with it but I'm about to start a pretty ambitious site which will require good versioning of certain classes and I've decided to use paper_trail.

===EDIT====

I have implemented the paper_trail gem in production at www.muusical.com and it has worked well using the above. The only change is that I am using gem 'paper_trail', '~> 4.0.0.rc' in my Gemfile.

like image 144
wuliwong Avatar answered Nov 15 '22 19:11

wuliwong


You're looking for the acts_as_audited plugin. It provides an audits table and model to be used in place of your map.

To set it up run the migration and add the following to your user address model.

class UserAddress < ActiveRecord::Base
  belongs_to :user
  acts_as_audited
end

Once you've set it up, all you need to do is define an address method on order. Something like this:

class Order < ActiveRecord::Base
   belongs_to :user
   attr_reader :address

   def address
     @address ||= user.user_address.revision_at(updated_at)
   end
end

And you can access the users' address at the time of order completion with @order.address

revision_at is a method added to an audited model by acts_as_audited. It takes a timestamp and reconstructs the model as it was in that point of time. I believe it pieces the revision together from the audits up on that specific model before the given time. So it doesn't matter if updated_at on the order matches a time exactly.

like image 40
EmFi Avatar answered Nov 15 '22 18:11

EmFi