ActiveRecord:如何克隆嵌套关联?

时间:2011-07-15 19:03:10

标签: ruby-on-rails ruby-on-rails-3 activerecord associations clone

我目前正在克隆这样的单级关联:

class Survey < ActiveRecord::Base
  def duplicate
    new_template = self.clone
    new_template.questions << self.questions.collect { |question| question.clone } 
    new_template.save   
  end
end

因此克隆Survey然后克隆与该调查相关联的Questions。精细。这很有效。

但我遇到的问题是每个问题has_many Answers。所以Survey has_many Questions which has_many Answers

我无法弄清楚如何正确克隆答案。我试过这个:

def duplicate
  new_template = self.clone

  self.questions.each do |question|
    new_question = question.clone
    new_question.save

    question.answers.each do |answer|
      new_answer = answer.clone
      new_answer.save
      new_question.answers << answer
    end

    new_template.questions << question
  end

  new_template.save   
end

但实际上替换原始答案然后创建新答案会产生一些奇怪的东西,因此ID会正确停止匹配。

5 个答案:

答案 0 :(得分:45)

使用deep_clonable gem

new_survey = original_survey.clone :include => [:questions => :answers]

答案 1 :(得分:3)

您可能也喜欢ActiveRecord 3.2的Amoeba gem

在您的情况下,您可能希望使用配置DSL中提供的nullifyregexprefix选项。

它支持has_onehas_manyhas_and_belongs_to_many关联的简单和自动递归复制,字段预处理以及高度灵活且功能强大的配置DSL,可以应用于模型和苍蝇。

请务必查看Amoeba Documentation,但使用非常简单......

gem install amoeba

或添加

gem 'amoeba'

到您的Gemfile

然后将变形虫块添加到模型中并照常运行dup方法

class Post < ActiveRecord::Base
  has_many :comments
  has_and_belongs_to_many :tags

  amoeba do
    enable
  end
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

class Tag < ActiveRecord::Base
  has_and_belongs_to_many :posts
end

class PostsController < ActionController
  def some_method
    my_post = Post.find(params[:id])
    new_post = my_post.dup
    new_post.save
  end
end

您还可以控制以多种方式复制哪些字段,但是,例如,如果您想要防止评论被复制但是您想要保留相同的标记,则可以执行以下操作:

class Post < ActiveRecord::Base
  has_many :comments
  has_and_belongs_to_many :tags

  amoeba do
    exclude_field :comments
  end
end

您还可以预处理字段,以帮助指示前缀和后缀以及正则表达式的唯一性。此外,还有许多选项,因此您可以为您的目的以最易读的方式编写:

class Post < ActiveRecord::Base
  has_many :comments
  has_and_belongs_to_many :tags

  amoeba do
    include_field :tags
    prepend :title => "Copy of "
    append :contents => " (copied version)"
    regex :contents => {:replace => /dog/, :with => "cat"}
  end
end

递归复制关联很容易,只需在子模型上启用amoeba

class Post < ActiveRecord::Base
  has_many :comments

  amoeba do
    enable
  end
end

class Comment < ActiveRecord::Base
  belongs_to :post
  has_many :ratings

  amoeba do
    enable
  end
end

class Rating < ActiveRecord::Base
  belongs_to :comment
end

配置DSL还有更多选项,因此请务必查看文档。

享受! :)

答案 2 :(得分:1)

不使用宝石,您可以执行以下操作:

class Survey < ApplicationRecord
  has_and_belongs_to_many :questions

  def copy_from(last_survey)
    last_survery.questions.each do |question|
      new_question = question.dup
      new_question.save

      questions << new_question
    end

    save
  end
  …
end

然后您可以致电:

new_survey = Survey.create
new_survey.copy_from(past_survey)

这将把上次调查中的所有问题复制到新调查中,并将它们联系起来。

答案 3 :(得分:0)

不应该......

  new_question.answers << new_answer
end

new_template.questions << new_question

答案 4 :(得分:-1)

您也可以使用rails dup方法对其进行别名,如下所示:

class Survey
   has_many :questions, :inverse_of=>:survey, :autosave=>true
   alias orig_dup dup
   def dup
       copy=orig_dup
       copy.questions=questions
       copy
   end
end

class Questions
   belongs_to :survey, :inverse_of=>:questions
   has_many :answers, :inverse_of=>:question, :autosave=>true
   alias orig_dup dup
   def dup
       copy=orig_dup
       copy.answers=answers
       copy
   end
end

class Answer
    belongs_to :question
end

然后你可以这样做

aaa = Survey.find(123).dup
aaa.save