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.
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.
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.
Update: Chris investigate this during a Slack discussion and found a fix. Use
loader/vue.js from webpacker gem's master head, then set
const extractStyles = true. https://github.com/rails/we...
How do I use SCSS? I tried setting <style lang="scss" scoped> in app.vue but then webpack stopped emitting the .css files entirely.
I've fallen into using this pattern several times, but I'd very down on it now. It's usually because I've been inappropriately afraid of column or migration proliferation, but that's actually a false alarm. PostgreSQL can handle hundreds of columns, and migrations aren't that scary.
Each time, I've ended up with an awful lot of internal infrastructure and un-rails-y configuration-over-convention (like those typed stores) to reinvent something that PostgreSQL, in conjunction with ActiveRecord, does exceptionally well by design: storing typed scalars. The code smell is compounded by heaps of nil-whacking and existence-checking and validations which using a simple DB column would make wholly unnecessary.
The most shameful case is boolean attributes, which no longer have two states to deal with, but five: true, false, nil, nonexistent, and oops a string. Yes, that last cropped up for me because some other app used a string during an import. When you see code handling a five-state boolean, you a) have a little cry, and then b) deal with the stench.
For my major app, I recently threw away the JSON preferences store and made them all regular columns. The diff is mostly lines removed.
I still use JSON stores, but reserved for three cases:
1. In which there's an application-level semantic difference between attribute having the null value, and attribute not present. In this case I find using the JSON stores are less hassle than implementing the EAV pattern.
2. In which there is a deep JSON structure to store & query e.g. when we have user-supplied attribute names (in which case we're not using store_accessors anyway)
3. When I have more columns than PostgreSQL can handle. (This has never happened)
I will never willingly use them as a first choice for settings/preferences again.
Posted in Running multiple Rails versions
Here's why that works, as I understand it: Bundler will take care of ensuring that only the gem versions specified in your Gemfile.lock are in Ruby's library load path.
Hey Chris, thanks for this guided tour, very useful!
Rails.ajax(...) will be super helpful.
I found some other changes in UJS behaviour since the rewrite, particularly in
handleRemote's function call signature and in the parameters to
ajax:success events. I've written my notes up here: https://inopinatus.org/2017...
I've had similar issues, there are several undocumented changes in UJS's behaviour. Had to listen for
rails:attachBindings and directly modify the
Rails.buttonClickSelector.selector in a 5.1 upgrade I have in progress. Also note that
remote: true by default, that one bit me as well, along with the changes to the call signature for
I particularly like avoiding the extra complexity of a full-blown router-based SPA, especially when enhancing an existing Rails app. Thanks for the clear and concise walkthrough! It'll help me avoid several pitfalls that I suspect I'd have otherwise fallen into.
It worries me that the HTTP paths & verbs are hard-coded into the JS here. Would the next improvement be pulling those into data attributes, with values from the usual Rails URL helper methods? This might get us started down the road of reusability and decoupling, with the end goal perhaps being a gem that (like cocoon, or even the standard form builder) does some of the boilerplate for us.
<%= vue_form_with(model: @team) do %> ...
Why not hook into the new non-jQuery rails UJS driver for the submit handling?
Re. the talking head. For me, it's an epic distraction from the main content. It's nice to put a human face to the voice, but overlaying the entire video seems wrong to me. Don't know if others feel the same, but I'd suggest just popping up at the start & at moments when body language/gestures actually add to the pedagogical value.