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,
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
::from_template methods in the origin models. The
serializable_hash method may help. Incidentally, once I've built those methods, then piping
::from_template gives me a domain-specific duplicator for free.
You might be tempted to serialize template data ready for passing to
::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
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
::from_template methods into a library module.
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.