Say we have transport search app to which I had to add user data snapshots.

The idea is that when a user searches for transport a Search model gets created, and a new Passenger models get associated with it. The Passenger model is associated with a User model which is associated with a Preference model.

In case you wondering why we even need Passenger model - it’ll act as an intermediary object later on that we can use to get to snapshot data.

The code it looks like this:

class Search < ApplicationRecord
  has_many :users
end

class User < ApplicationRecord
  belongs_to :search
end

class Preference
  belongs_to :user
end

class Passenger < ApplicationRecord
  belongs_to :search
  belongs_to :user
end

I need to preserve the state of the User model, and its associations like Preference as they were at the time when Search was created.

After some quick brainstorming, it was decided to give paper_trail gem a try as it provides a mechanism for versioning. To be honest, I didn’t need to do any diffing or even go to previous versions for this particular case, but it’ll come in handy later for auditing. For the current task, all I needed was a way to get a snapshot of the data at a specific time.

The result was something like this:

class Search < ApplicationRecord
  has_many :users
end

class User < ApplicationRecord
  belongs_to :search

  has_paper_trail
end

class Preference
  belongs_to :user

  has_paper_trail
end

class Passenger < ApplicationRecord
  belongs_to :search
  belongs_to :user

  delegate :name, :email, to: :user_snapshot
  delegate :no_spam, to: :user_preference_snapshot

  private

  def data_snapshot
    @user_snapshot ||= user.paper_trail.version_at(search.created_at)
  end

  def user_preference_snapshot
    @user_preference_snapshot ||= user.preference.paper_trail.version_at(search.created_at)
  end
end

Now I can get the snapshot of the data as it was when Search model was created:

search = Search.last
passenger = search.passengers.first
passenger.name
passenger.email
passenger.no_spam

I’ve never used the paper_trail gem like this before, but the implementation was quick and gave the exact results I needed. We’ll see how it works out in the long run.