Advice on implementing a template system in Rails
I am currently building a Rails app that allows users to create and manage projects. Within these projects are different groups and tasks which are of course associated models belonging to the project.
What I want to achieve is a way for users to save the current configuration of groups and tasks as a template that can then be used for projects they might make in the future. This is my first call to the GoRails community and I am keen to see how you guys would approach this as I have become a bit stuck! I have looked at self referencing models and even the deep clonable gem to clone entire projects as a template but both of these felt clunky and rely on the original project still existing.
Thanks in advance,
Jamie
Let me strongly challenge one notion. Each new project should have no dependencies on its ancestor. Forget self-referencing models and don't worry if the origin goes away. A deep cloning method is what most end-users will expect. Most humans say to themselves "I want a new one that is just like that old one, please just copy that". Very few humans say "I want to be the curator of an abstract master data structure" or "I want this one to referentially inherit the attributes of that one".
Now, speaking personally, I love curating and managing master data schemes and abstract structures and so on. But I've come to accept that my users do not. If I force them to work that way, they either don't bother, or they do it very badly and then become unhappy when results differ from expectations. Neither outcome is good for my business. So a deep copy is what I offer.
When I really do need templates, I build a simple container for them. A container that's just another model, with a single JSON field serializing the template data, and some metadata fields concerning the template ownership & lifecycle. Then I write #as_template
and ::from_template
methods in the origin models. The serializable_hash
method may help. Incidentally, once I've built those methods, then piping #as_template
into ::from_template
gives me a domain-specific duplicator for free.
You might be tempted to serialize template data ready for passing to ::new
or ::create
. For example with association keys suffixed by _attributes
in expectation of accepts_nested_attributes_for
. I avoid this, I think it's overspecialising the data structure.
Whatever you do, I advise you to steer clear of two things:
- Creating a parallel hierarchy of template models. It becomes a maintenance hassle.
- Using a template for anything other than building new objects. You should be able to delete the template after it has been used with no consequences.
Thank you so much for such an in depth answer, forgive my ignorance as I try to understand certain parts of your answer. As I understand it you would essentially make a new create
action (from_template
) in the origin model that takes the serialized data from the template model to create a new instance? And another action as_template
that would be called to save the current instance into a serializable_hash stored in the template model?
It sounds like this is an implementation you have tackled before, if this is the case, is there a git repo that you are able/willing to share?
Thanks again for such a detailed response
Hi Jamie
Yep I think you've got the idea. I've done this a couple of times. Here's a simple demo as a single file. https://gist.github.com/inopinatus/11bf7deedb5a813d3e75e0cf63db863a
Hopefully that's enough to give you ideas. Production code might use a PostgreSQL JSON column rather than a sqlite text column, and I'd refactor generation of the #as_template
and ::from_template
methods into a library module.
Cheers
Josh
You're welcome. Small update, I decided to extract it properly and upgrade the idea into a gem. https://github.com/inopinatus/hokusai
Aside from a slight rejig of the API, there's a change to using YAML rather than JSON to (potentially) support more complex structures.