Best way to create or save a record (with associations) as a template
I'm curious as to what people think is the best to create a new record, or save an existing record... as a template. The template would be used as a starting point to duplicate that record easily and quickly in the future (with associated records).
For example, let's say I have a survey application with surveys, which have many questions, which have many answers. Each user creates their own surveys, but each user might have a few different survey templates (pre-populated questions and answers) they'd like to start from and then edit... rather than creating an entirely new survey each time from scratch.
I see two different approaches for this...
The first is to save existing surveys as templates (still not sure how though), and then create survey's from those.
The second is to create the templates first, then create surveys from those templates.
I think both approaches are useful and I'd like to get advice on how to implement both of them.
Thanks for any help.
Templates are a definitely interesting topic. I spent some time making templates in the past and I believe I ended up trying a couple different things. One approach I had a templates table, but it felt like there was a lot of duplication going on (two tables, same columns for example). The other was to mark records as templates and we'd just copy a few fields over to the new record. That ended up working better for what we were doing because it was pretty simple.
I personally like just operating off an existing record because you can just say "Okay, let's dup this, including it's children, but only keep these certain fields, strip out these others" and you don't have to deal with creating multiple tables or anything.
If you go that approach, you can use the .dup
method on the instance, so you'd load up the original and then call .dup
on it which should generate a copy in memory without an ID attached. You might also need to loop through associated records and dup those as well for the new model. Then you can clean any of the fields you want by setting them to nil or whatever.
The other approach for this same thing is to take the attributes from the original, use except
on the hash to strip out those fields, and then create a new record like you normally would in the controller. Either way is effectively the same, this one might be a lot more familiar to most people. .dup
wins if you don't need to strip out any fields though because everything's already set automatically.
Chris, I appreciate the answers. I'll try the .dup
method. I agree that operating off existing records is the better way to go... much less duplication in the background.
I'm using this the deep_cloneable gem (https://github.com/moiristo/deep_cloneable) and it seems to work pretty well.
I am trying to clone a model which has many has_and_belongs_to_many associations. Is there an easy way to copy those associations?
Throwing in some thoughts on this as it's something I've dealt with in the past and am currently working on a project right now!
I agree with Chris's thoughts, that a single table is much easier to maintain, especially when you start dealing with associations and changing column names, etc...
One of the cleanest ways to separate them in your Rails code is to use different classes/models. For instance, let's say you have a workflows
table and some of the records are templates based on a is_a_template
boolean column.
I would break that up into a few different classes.
class BaseWorkflow < ApplicationRecord
# code shared between "live" and "template" records
scope :templates, -> { where(is_a_template: true) }
scope :not_templates, -> { where(is_a_template: false) }
has_many :steps
end
class WorkflowTemplate < BaseWorkflow
before_save { self.is_a_template = true }
default_scope { templates }
end
# "live" workflow
class Workflow < BaseWorkflow
before_save { self.is_a_template = false }
default_scope { not_templates }
end
The interesting part is when you have several layers of associations, such as workflow has_many :steps
, and step has_many :task
,etc... I personally found that using live/template classes for all the children became a burden and it was rather redundant, because you already know they are templates because their parent is a template.
This allows you to put all your core relationships in the BaseWorkflow
class and not duplicate them in each of your live/template classes.
I also played with duplicating all my tables and I also tried separating all of the live/template versions into 2 sets of models, and it because overwhelming very quickly and I just had a few core models I was dealing with. Anytime I had some logic change or needed a new method, it was a pain to duplicate it in 2 locations.
My current setup is to only break up the parent record into live/template, and then place any unique code in those classes instead of having conditional logic in the base record. This allows you to breakup the logic only when you need to, which is deciding whether this parent record and it's associations belongs in the live or template category.
@deniel will love to see a demo app on this approach as I can clean my code a lot with this trick. your using a Single Table Inheritance
Daniel,
I agree. I did try having separate models for templates and non-templates (and all of the associated records), and it was a terrible approach. As you mentioned, it was a lot of unnecessary duplication. Now I just use the same model, with a template boolean. I hadn't thought of breaking it up into different classes, but I may look into that if the need arises.
I still use the deep_cloneable gem, but for a record with a lot of children and grandchildren... it can be slow. I've been using another gem (active record import) for those large records... and it's much faster... although it a bit more setup.