Skip to main content

Advice on implementing a template system in Rails

Rails • Asked by Jamie Carr
F2315e182a11037217585a30f2aae11e

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


7a515c88e77c933cfc59ddc8ca139964

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.

F2315e182a11037217585a30f2aae11e

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


7a515c88e77c933cfc59ddc8ca139964

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


F2315e182a11037217585a30f2aae11e

You are a star! Thanks a lot Josh.


7a515c88e77c933cfc59ddc8ca139964

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.


Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 18,000+ developers who get early access to new screencasts, articles, guides, updates, and more.

By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

More of a social being? We're also on Twitter and YouTube.