Appearance
Morph
WARNING: Experimental feature
Morph is an AST-based helper for idempotent code merging. It understands Ruby code and merges applied changes intelligently, grouping similar macros together, placing code in the most appropriate location, and enabling simple templating without the need to define complex matchers. It matches code by its structure rather than by string comparison.
It is not designed for complex code transformations, but rather for extending the existing structure. It works best when adding macros, method definitions, and extending DSLs.
Because morph is idempotent and places code intelligently, generators can be run multiple times with different parameters against the same file - even after the user has modified it. Each run adds only what is missing, leaving existing code untouched.
Basic Usage
before
ruby
class User
has_one :address
def full_name
"#{first_name} #{last_name}"
end
endpatch
ruby
morph "app/models/user.rb", <<~RUBY
# frozen_string_literal: true
class User
has_many :posts
private
def foo = puts "bar"
end
RUBYafter
ruby
# frozen_string_literal: true
class User
has_one :address
has_many :posts
def full_name
"#{first_name} #{last_name}"
end
private
def foo = puts "bar"
endThe __add__ Marker
Mark container lines with # __add__ to indicate they were introduced by the generator. In reverse mode, morph removes the container itself once it becomes empty.
ruby
morph "app/models/user.rb", <<~RUBY
class User < ApplicationRecord
module Searchable # __add__
def self.search(query)
where("name LIKE ?", "%#{query}%")
end
end
end
RUBYRunning --reverse removes the search method. Since Searchable was marked with __add__ and is now empty, it is also removed.
Without __add__, empty containers are left in place.
The __merge__ Marker
Tag a call node with # __merge__ to merge its arguments.
before
ruby
class UsersController < ApplicationController
def user_params
params.require(:user).permit(:email, :password)
end
endpatch
ruby
morph "app/controllers/users_controller.rb", <<~RUBY
class UsersController < ApplicationController
def user_params
params.require(:user).permit(:name, :avatar) # __merge__
end
end
RUBYafter
ruby
class UsersController < ApplicationController
def user_params
params.require(:user).permit(:email, :password, :name, :avatar)
end
endWithout __merge__, Morph would detect the user_params method as already defined and skip modification.