Hokusai
Stamp out domain-specific clones of a model object, even after the original has departed, with these lightweight ActiveRecord concerns.
Quick demo
If you want to write code like this, snapshotting an ActiveRecord object (and perhaps some associations) into a template:
@template = Template.create!(origin: @project, name: "New template from project")
hoping to use it like this, at some later time (perhaps long after the origin has been deleted):
template = Template.find(params[:template_id])
@new_project = template.stamp do |project|
project.name = "New project from template"
end
then you have (maybe) come to the right place.
Installation
Add this line to your application's Gemfile:
gem 'hokusai'
And then execute:
$ bundle
Or install it yourself as:
$ gem install hokusai
Usage
Include Hokusai::Container
in an ActiveRecord model so that we can save templates in it. The columns hokusai_class
and hokusai_template
are required. You can add whatever data or metadata you wish, from a simple name to complex lifecycle or ownership attributes. In this example we just require a name:
class Template < ApplicationRecord
include Hokusai::Container
validates :name, presence: true
before_validation :ensure_name
private
def ensure_name
self.name = "New #{hokusai_class.constantize.model_name.singular} template"
end
end
Now create and run a suitable migration:
class CreateTemplates < ActiveRecord::Migration
def change
create_table :templates do |t|
t.string :name, null: false
t.string :hokusai_class, null: false
t.text :hokusai_template, null: false
t.timestamps
end
end
end
Then include Hokusai::Templatable
in the models you want to make templates from. Declare a list of columns to persist, and which associations to include:
class Device < ApplicationRecord
include Hokusai::Templatable
template :name, :model, :location, :year, include: [:interfaces]
has_many :interfaces
end
class Interface < ApplicationRecord
include Hokusai::Templatable
belongs_to :device
template :name, :address, :enabled
end
You can then create a new template, and stamp out a copy:
device = Device.create!(name: "router-1", model: "CX-6790", location: "SFO", year: 2017)
device.interfaces.create!([
{name: "de0", address: "10.0.0.1", enabled: false},
{name: "lo0", address: "127.0.0.1", enabled: true}
])
template = Template.create!(origin: device, name: 'SFO router template')
new_device = template.stamp do |device|
device.name = "router-2"
end
new_device.save!
This example ends with a deep, domain-specific clone of the origin object. You can delete the origin and the template is still useful.
What can Hokusai stamp out?
Broadly speaking, this is intended for any ActiveRecord object that can be serialized to & from YAML.
The Hokusai::Templatable
concern is provided. This handles ordinary has_many
, has_one
, and belongs_to
associations via the include:
option, in which case it will call as_template
on the associated records. If you want a belongs_to
reference id to carry across, serialize the _id column rather than including the association in the template.
Recursive serialization is not currently detected and will cause a "stack level too deep" error. For those you may need to implement as_template
by hand.
Aggregates types may also need special treatment. You can override read_attribute_for_template
in the model for these.
Advanced configuration
You're not constrained to using Hokusai::Templatable
. Any model that implements #as_template
and ::from_template
will do, and the container doesn't impose constraints on their behaviour. The only expectation is that #as_template
returns a data structure ready for serialization as YAML, and that ::from_template
accepts the same structure as the first parameter. Beyond that you can do as you please with the data.
Todo
- Generator for the container migration.
- Support for serializing self-referential data structures.
- Clearer tests.
- Comprehensive tests for a wide range of complicated associations.
- Remove dependency on ActiveRecord.
- Support configurable column names and template assignment method name.
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/inopinatus/hokusai.
License
The gem is available as open source under the terms of the MIT License.