ActiveRecord: ¿Cómo puedo clonar asociaciones anidadas?

Actualmente estoy clonando una asociación de un solo nivel como esta:

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

Para que la Survey se clone, se clonan las Questions asociadas con esa encuesta. Multa. Eso funciona bastante bien.

Pero con lo que estoy teniendo problemas es que cada pregunta tiene has_many Answers . Así que la Survey has_many Questions which has_many Answers .

No puedo imaginar cómo clonar las respuestas correctamente. He intentado esto:

 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 

Pero eso hace algunas cosas raras con la sustitución de las respuestas originales y la creación de nuevas, por lo que las identificaciones dejan de coincidir correctamente.

Usa la gem deep_clonable

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

También te puede gustar la gem Amoeba para ActiveRecord 3.2.

En su caso, probablemente desee hacer uso de las opciones de regex , regex o prefix disponibles en la configuración DSL.

Admite la duplicación recursiva fácil y automática de las has_many has_one , has_many y has_and_belongs_to_many , preprocesamiento de campos y una DSL de configuración altamente flexible y potente que se puede aplicar tanto al modelo como sobre la marcha.

Asegúrese de revisar la documentación de la ameba, pero su uso es bastante fácil …

sólo

 gem install amoeba 

o agregar

 gem 'amoeba' 

a tu Gemfile

A continuación, agregue el bloque de ameba a su modelo y ejecute el método dup como de costumbre.

 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 

También puede controlar qué campos se copian de varias maneras, pero por ejemplo, si desea evitar que se dupliquen los comentarios pero desea mantener las mismas tags, puede hacer algo como esto:

 class Post < ActiveRecord::Base has_many :comments has_and_belongs_to_many :tags amoeba do exclude_field :comments end end 

También puede preprocesar los campos para ayudar a indicar la singularidad con prefijos y sufijos, así como expresiones regulares. Además, también hay numerosas opciones para que pueda escribir en el estilo más legible para su propósito:

 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 

La copia recursiva de asociaciones es fácil, solo habilite ameba en modelos infantiles también

 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 

La configuración DSL tiene aún más opciones, así que asegúrese de revisar la documentación.

¡Disfrutar! 🙂

Sin usar gems, puedes hacer lo siguiente:

 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 

Entonces puedes llamar:

 new_survey = Survey.create new_survey.copy_from(past_survey) 

Eso duplicará todas las preguntas de la última encuesta a la nueva encuesta y las vinculará.

¿No debería ser …

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

También puede alias el método de duplicación de Rails, de la siguiente manera:

 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 

y luego puedes hacer esto

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