0

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?

2
  • You mean section_id: c.section_id is wrong always? Sorry I am not getting the problem. Could you tell me the more? Commented Sep 12, 2018 at 19:35
  • Yes, using create_template_components, the section_id will always be 123 (for example), even though the section_id will be new. Commented Sep 12, 2018 at 19:42

2 Answers 2

1

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).

Sign up to request clarification or add additional context in comments.

7 Comments

What if I have multiple sections (123 and 125), and separate components associated with those sections? For example, component 124 associated with section 123, and component 126 associated with section 125.
@jj008 Do you want always a new section to be created when copying from template components? If not, when you want a new one?
Yes, I want to create a new section when I'm creating a new lesson using a template. So if I create a new lesson, I want to have component 994 associated with section 993, and component 996 associated with section 995 (for example)
Ok, then the code I gave, always create new section.. newSection = c.section && c.section.dup.create
for c.section, did you mean c.type = 'Section'? Also, I'm getting an error saying undefined method create
|
1

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.

10 Comments

I think this is close to what I'm looking for, but the main question is, when I'm creating a new component, how can I use the correct and new section id (when I have different sections and different components associated to those sections?
Well, if the new component you are creating is a section, you don't have to worry about setting section id. If the new component is a question, this solution should use the new section id. That's why I've named the var new_section and we're using its id. (Now, if there are more types of components than just those two, or if there are some "orphan" questions that don't belong to a section, then that would complicate things.)
Section is a type of component and there are multiple types of components. I'm trying to figure out how to "duplicate" the sections and components from a template with each component having the correct corresponding section_id, according to the template.
Yeah, I think I get it. Does this solution not work? What behavior do you see?
I have revised the data in my question, I'm trying to create/duplicate what's on the template but keep the "association" (through the section_id)
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.