Appearance
Living generators
WARNING: Experimental feature
This tutorial shows how to build a generator that uses morph to extend an existing serializer with new attributes - safely and idempotently across multiple runs.
The Problem
The problem with generators is that you usually run them once, and if you want to modify something later, the only option is to overwrite them completely, often losing all manual changes made since the first generation. With the Morph tool, you can extend your generators by applying them multiple times without losing what you've already written.
Running these two commands in sequence:
bash
loci g serializer user name email
loci g serializer user avatar_url created_atwill produce a single file with all four attributes.
The Generator
ruby
module Serializer
class SerializerGenerator < Loci::BaseGenerator
arg :model
args :attributes
step "create serializer" do
skip "already exists" if File.exist?(file_path)
# thanks to `up` we can run --reverse to remove entries from
# serializer instead of deleting serializer itself
up do
create_file file_path, <<~RUBY
class #{class_name}
end
RUBY
end
end
step "first" do
attrs = params.attributes.map { " attribute :#{it}" }
morph file_path, <<~RUBY
class #{class_name}
#{attrs.join("\n")}
end
RUBY
end
private
def file_path
params.model.singularize.suffix(:_serializer).ext(:rb).to_path
end
def class_name
params.model.singularize.suffix(:_serializer).to_class
end
end
endThe generator has two steps. The first creates the file skeleton if it does not already exist. The second uses morph to merge attributes into the class. This separation matters: the Morph tool works better when it has something to diff against.
First Run
bash
loci g serializer user name emailThe file does not exist. The first step creates the skeleton, then the second step morphs the attributes in:
ruby
class UserSerializer
attribute :name
attribute :email
endSecond Run
bash
loci g serializer user avatar_url created_atThe file exists, so the first step is skipped. Morph compares the patch against the current file, skips attribute :name and attribute :email (already present), and inserts the new ones:
ruby
class UserSerializer
attribute :name
attribute :email
attribute :avatar_url
attribute :created_at
endRunning the same command again produces no changes - the generator is fully idempotent.
Reverse Mode
Running with --reverse removes the specified attributes:
bash
loci g serializer user email avatar_url --reverseruby
class UserSerializer
attribute :name
attribute :created_at
end