Question

Have a problem regarding saving ActiveRecord associations and need your help :)

I need to add articles merging functionality to legacy code.

It's expected to work the following way:

  1. Merge "source" article's text to the "target" article.
  2. Check for "source"'s comments and, if any, re-associate them to the "target".
  3. Destroy the "source" article. Comments should be preserved and associated with "target".

Here's my Article model code (reduced for readability).

class Article < Content

  before_destroy :reload_associated_comments

  has_many :comments,   :dependent => :destroy, :order => "created_at ASC" do

  def reload_associated_comments
    unless self.comments.empty?
      article = Article.find(@merge_with)
      self.comments.each do |comment|
        comment.article = article
        article.save!
      end
    end
  end

  def merge_with(id)
    @merge_with = id
    article = Article.find(@merge_with)
    if !article.nil?
      text = article.body + body
      article.body = text
      article.save!
      self.destroy
      return article
    end
    nil
  end
end

Here's Comment model (also reduced):

class Comment < Feedback
  belongs_to :article
end

The problem is when I return from before_destroy hook nothing has been saved to the database. I check it via the following:

eval Article.find(target_article_id).comments

Save raises no exceptions. What I'm missing here?

Thanks in advance!

This worked for me

  def merge_with(id)
    @merge_with = id
    article = Article.find(@merge_with)
    unless article.nil?
      text = article.body + body
      article.body = text
      article.save!
      reload_associated_comments
      self.reload
      self.destroy
      return article
    end
    nil
  end
Was it helpful?

Solution

Actually Rails destroy every comment before calling the before_destroy callback for the article. That's sadly how rails work. Changing this behavior would imply breaking legacy apps. You can find more information on this issue here: https://github.com/rails/rails/issues/670

In my opinion the best solution is to override the destroy method and get rid of the callback:

class Article < Content

  def destroy
    reload_associated_comments
    super
  end

  has_many :comments,   :dependent => :destroy, :order => "created_at ASC" do

  def reload_associated_comments
    unless self.comments.empty?
      article = Article.find(@merge_with)
      self.comments.each do |comment|
        comment.article = article
        comment.save!
      end
    end
  end

  def merge_with(id)
    @merge_with = id
    article = Article.find(@merge_with)
    if !article.nil?
      text = article.body + body
      article.body = text
      article.save!
      self.destroy
      return article
    end
    nil
  end
end

Also, as @Zippie mentioned, you should call comment.save! instead of article.save!

Hope it helps!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top