【问题标题】:ActiveRecord: How can I clone nested associations?ActiveRecord:如何克隆嵌套关联?
【发布时间】:2011-10-06 10:03:03
【问题描述】:

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

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_manyAnswers。所以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 停止正确匹配。

【问题讨论】:

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


    【解决方案1】:

    使用deep_clonable gem

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

    【讨论】:

    • 克隆方法已更改为 dup。见the repo readme
    • 现在改为:new_survey = original_survey.deep_clone :include => [:questions => :answers]
    【解决方案2】:

    您可能还喜欢 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
    

    您还可以通过多种方式控制复制哪些字段,但例如,如果您想防止 cmets 被复制但又想保持相同的标签,您可以执行以下操作:

    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
    

    关联的递归复制很容易,只需在子模型上启用变形虫

    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 有更多选项,因此请务必查看文档。

    享受吧! :)

    【讨论】:

    • 我实际上尝试过这个宝石。这是非常有前途的,但还没有准备好。我有两个问题,我都发回了 github 项目,但最终放弃并使用 dup 手动编写了我的复制功能。希望几个月后再试一次,运气更好!
    【解决方案3】:

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

    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)
    

    这会将上次调查中的所有问题复制到新调查中并将它们绑定。

    【讨论】:

    • +1 作为您的答案,因为我被困在某个地方,这对我帮助很大。谢谢!但我的问题是,当我们已经在做new_question.save 时,为什么我们需要做questions &lt;&lt; new_question。你能解释一下我的理解吗?就像我们保存对象一样,但为什么又要像 &lt;&lt; 这样的 questions 数组?
    • 另外save指的是你上面的答案?我没有使用它,它仍然有效。我的代码:def duplicate_records new_course = self.dup new_course.image = File.open(self.image.file.file) if self.image.present? new_course.save new_course.chapters = self.chapters new_course.chapters.each do |chapter| new_chapter = chapter.dup new_chapter.image = File.open(chapter.image.file.file) if chapter.image.present? new_chapter.save chapters &lt;&lt; new_chapter end new_course end
    • @LearningROR 所以,questions &lt;&lt; new_question 就是这样我们将该问题保存为新调查对象上的问题。因此,copy_from 是您从对象访问的一种方法,就像您将执行survey.copy_from(other_survey) 一样,它将复制来自other_survey 的每个问题,然后将它们分配给survey.questions。我们在对象范围内,所以questions 就是那个对象. 问题。
    • @LearningROR 至于save,这有点像编程的恶习。因为我没有测试我的答案,所以我添加了它以确保在您调用 .copy_from(other_survey) 的对象中调用 save 。这可能没有必要,因为&lt;&lt; 应该已经完成​​了持久化new_question 的工作,就像您调用的对象的questions 关系.copy_from(…)
    • @LearningROR 如果您想更好地理解为什么savequestionsobj.saveobj.questions,请阅读更多关于“类方法与实例方法”的信息。这是我在谷歌上搜索到的一篇文章medium.com/@lauren.kroner/…
    【解决方案4】:

    不应该吗..

      new_question.answers << new_answer
    end
    
    new_template.questions << new_question
    

    【讨论】:

      【解决方案5】:

      你也可以给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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-06-26
        • 1970-01-01
        • 1970-01-01
        • 2016-07-26
        • 1970-01-01
        • 2021-12-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多