Reputation: 3
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
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
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