jj008
jj008

Reputation: 1083

Rails Model - Creating Using Template

In my app, I have the ability to create "lessons", and each lesson contains several "components". Right now, I'm trying to implement the ability to create a lesson from a template so it would duplicate/recreate the components from the template to the new lesson.

The data structure of my component is something like this:

{
  id: 123,
  type: Section,
  content: "abc",
  section_id: null
},
{
  id: 124,
  type: Question,
  content: "abc?",
  section_id: 123
},
{
  id: 125,
  type: Section,
  content: "defg",
  section_id: null
},
{
  id: 126,
  type: Outcome,
  content: "defg?",
  section_id: 125
},

Desired outcome:

{
  id: 993,
  type: Section,
  content: "abc",
  section_id: null
},
{
  id: 994,
  type: Question,
  content: "abc?",
  section_id: 993
},
{
  id: 995,
  type: Section,
  content: "defg",
  section_id: null
},
{
  id: 996,
  type: Outcome,
  content: "defg?",
  section_id: 995
},

You can see that there's an association between the Question/Outcome and the Section through section_id.

In my lesson model, I'm looping through the components of a template and taking their attributes for the newly created lesson components.

class Lesson

  attr_accessor :lesson_template_id

  # == Callbacks ==============================
  after_create :create_template_components, if: :lesson_template_id_present?

  def lesson_template
    if @lesson_template_id != ''
      LessonTemplate.find(@lesson_template_id)
    else
      nil
    end
  end


private

  def lesson_template_id_present?
    !!@lesson_template_id
  end


  def create_template_components
    if lesson_template
      lesson_template.components.each do |c|
        self.components.create!({
          type: c.type,
          content: c.content,
          section_id: c.section_id
        })
      end
    end
  end

But the problem is that the section_id is incorrect because the newly create sections would have a new/different id. How can I revise my model to make sure the components are created properly?

Upvotes: 0

Views: 147

Answers (2)

John Skiles Skinner
John Skiles Skinner

Reputation: 2028

My idea is an outer loop that duplicates sections. The new section id is used when recreating other components (outcomes and questions) in an inner loop, which will keep the associations updated and correct.

lesson_template.sections.each do |old_section|
  # outer loop: recreate section
   new_section = self.sections.create!({
    content: old_section.content
  })
  old_section.components.each do |old_non_section_component|
    # inner loop: recreate all components belonging to section
    self.component.create!({
      type: old_non_section_component.type,
      content: old_non_section_component.content,
      # use id from newly-created section, keeping correct association
      section_id: new_section.id # <-- IMPORTANT
    })
  end
end

It's probably worth updating your question to mention that you are using self-referrential single table inheritance, or whatever this technique is called. It's not something I've seen very often.

Upvotes: 1

Arup Rakshit
Arup Rakshit

Reputation: 118261

Create a shallow copy of Section object, and then create it and attach it to the new components.

def create_template_components
  if lesson_template
    lesson_template.components.each do |c|
      newSection = if c.type == 'Question'
        c.section && c.section.dup
      else
        nil
      end
      newSection.save if newSection
      self.components.create!({
        type: c.type,
        content: c.content,
        section_id: newSection.try(:id)
      })
    end
  end
end

#dup

Duped objects have no id assigned and are treated as new records. Note that this is a “shallow” copy as it copies the object's attributes only, not its associations. The extent of a “deep” copy is application specific and is therefore left to the application to implement according to its need. The dup method does not preserve the timestamps (created|updated)_(at|on).

Upvotes: 1

Related Questions