Blankman
Blankman

Reputation: 267320

Saving and updating a child association object

I have a model like:

class Content < ActiveRecord::Base
  has_many :revisions
end

class Revision < ActiveRecord::Base
  belongs_to :Content
end

In my controller I am doing:

def create
  @content = Content.new(content_params)

  if @content.save
     # ...
  end
end

Content has:

- id
- category_id
- title
- body
- ..

Revision has:

- id
- category_id
- content_id
- body

So whenever I save/update/delete a Content the same thing should happened with the Revision.

Should I create a before_save to handle both situations using the same method? How can I easily pass the category_id, content_id and body to the Revision?

Update

During an update, if I am updating a particular revision_id, I have to have that revision_id but not sure how to pass that when I am updating a Content instance. Does that make sense?

Upvotes: 1

Views: 1245

Answers (2)

Richard Peck
Richard Peck

Reputation: 76784

The direct answer to your question is to use a before_update callback, perhaps with inverse_of (so the associated objects are available in memory):

#app/models/content.rb
class Content < ActiveRecord::Base
  has_many :revisions
  accepts_nested_attributes_for :revisions

  before_update :set_revision
  before_create :set_revision

  private

  def set_revision
     self.revisions.build body: self.body
  end
end

#app/models/revision.rb
class Revision < ActiveRecord::Base
  belongs_to :content
end

The above will create a new revision each time you update your content model. Both will save as you're using accepts_nested_attributes_for


As a recommendation, you may wish to look at the Paper Trail gem.

This does exactly what you're looking for (in terms of revisions) -- it saves any updates / edits you perform on a model.

#Gemfile
gem 'paper_trail'

$ rails generate paper_trail:install
$ rake db:migrate

#app/models/content.rb
class Content < ActiveRecord::Base
  has_paper_trail
end

This will automatically record the changes to the model and who performed it.

There is a good RailsCast about it here.

Upvotes: 0

Nick Aschenbach
Nick Aschenbach

Reputation: 191

I might take a look a ActiveRecord::Callbacks. Here is an example for how to use them:

 class Content < ActiveRecord::Base
   has_many :revisions, dependent: :destroy

   after_commit :update_revision

   private

   def update_revision
     # Create or update a revision object
   end
 end

In general, I don't like the idea of creating another model using callbacks. It feels like a violation of the single responsibility principle. You might use a service object to handle the create and update operations you mentioned.

Note the dependent: destroy syntax on the has_many relationship as in the code block above. This will cascade deletes of a Content record to its related Revisions.

Update

If you just want to update a specific revision inside your controller, you could write:

 def create
   @content = Content.new(content_params)

   if @content.save
     Revision.find(revision_id).update_attributes!(...)
   end
 end

Upvotes: 2

Related Questions