ActiveRecord: как я могу клонировать вложенные ассоциации?

В настоящее время я клонирую одноуровневую ассоциацию:

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 перестает соответствовать правильно.

5 ответов

Решение

Использовать deep_clonable gem

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

Без использования драгоценных камней вы можете сделать следующее:

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)

Это дублирует все вопросы от предыдущего опроса до нового опроса и связывает их.

Вам также может понравиться драгоценный камень Amoeba для ActiveRecord 3.2.

В вашем случае, вы, вероятно, хотите использовать nullify, regex или же prefix варианты доступны в конфигурации DSL.

Он поддерживает простое и автоматическое рекурсивное дублирование has_one, has_many а также has_and_belongs_to_many ассоциации, предварительная обработка в полевых условиях и очень гибкая и мощная конфигурация DSL, которая может применяться как к модели, так и на лету.

Обязательно ознакомьтесь с документацией Amoeba, но ее использование довольно просто...

просто

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

Рекурсивное копирование ассоциаций легко, просто включите амебу на дочерних моделях

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 есть еще больше опций, поэтому обязательно ознакомьтесь с документацией.

Наслаждайтесь!:)

Не должно ли это быть..

  new_question.answers << new_answer
end

new_template.questions << new_question

Вы также можете использовать псевдоним 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
Другие вопросы по тегам