C.Johnson
C.Johnson

Reputation: 3

Rails - how to save existing record and apply update to a copy?

I have a webpage that tracks budgets containing a LOT of variables, stored in 40+ columns. Over time, adjustments are made to these budgets, but I need to be able to track changes over time and year to year. I tried adding a private method to my model that should create a duplicate of the existing record triggered by a :before_update callback. However, it's not working. The update changes the existing record, and the original is not preserved at all.

Model:

class Budget < ActiveRecord::Base

  before_update :copy_budget

  private

  def copy_budget
    @budget = Budget.find(params[:id])
    @budget.dup
    @budget.save
  end
end

I'm still learning rails, (this is in Rails 4) and I think this would have been the best way to do this. If not, is there a better way to set the form to ALWAYS post a new record instead of routing to update if a record already exists?

Currently the form_for line looks like this:

<%= form_for(@budget) do |f| %>

Everything works as it should, with the exception of the duplication not happening. What am I missing? Is it possible the .dup function is also duplicating the :id? This is assigned by auto-increment in the MySQL db I an using, so if .dup is copying EVERYTHING, is there a way to copy all of the data except the :id into a new record?

Thanks in advance for any suggestions.

Upvotes: 0

Views: 1946

Answers (2)

Ruchira Bomiriya
Ruchira Bomiriya

Reputation: 1

While this question is a bit dated, the same requirement came up for me recently. In the before_update method, you need to duplicate the existing record. But, you don't really have it because self contains the updated information. You will need to pull the original record and duplicate it.

You are saving the old record with a new id. It will be wise to remember the original primary record's id. So, you add a new column named p_id. Then, the following works:

before_update :archive_record

def archive_record
  prev = Record.find(self.id)
  arch = prev.dup
  arch.p_id = self.id          ## arch.p_id = arch.id works just as well
  arch.save
end

Your primary records will have p_id as nil. You can define a matching scope for this. Primary records will always be current and their id remains fixed. Each duplicate (history/archive record) will remember the id of its primary record.

There are all sorts of other trade-offs in this approach. But this will keep track of all the versions of record in your app.

Upvotes: 0

Simple Lime
Simple Lime

Reputation: 11035

the dup method returns the new object without an id, it doesn't update it in place. Since your copy_budget method is already an instance method on Budget, you also would not need to (and you wouldn't even be able to, since params aren't accessible in models) look up the budget by id and instead could just use the current instance (self). So the following changed would fix the copy_budget method for you, but you are still copying an already modified object, just before it gets saved to the database

def copy_budget
  copy_of_budget = self.dup
  copy_of_budget.save
end

it would work the way you're expecting it to work. However, you aren't linking the copy in anyway to the current version of the Budget (no way to tell Budget id = 1 is an older version of Budget id = 2). I'd recommend taking a look at a gem such as PaperTrail (I'm sure there are lots of others if that one doesn't suit your needs) which has already thought through a lot of the problems and features with keeping a history of record changed.

Upvotes: 1

Related Questions