Подтвердить что ты не робот

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_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

Но это делает некоторые странные вещи, фактически заменяя исходные ответы, а затем создавая новые, так что идентификатор останавливается правильно.

4b9b3361

Ответ 1

Используйте deep_clonable gem

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

Ответ 2

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

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

Он поддерживает простое и автоматическое рекурсивное дублирование ассоциаций has_one, has_many и has_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

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

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

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

Ответ 3

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

  new_question.answers << new_answer
end

new_template.questions << new_question

Ответ 4

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