Reputation: 1083
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
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
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
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