Skip to content

Instantly share code, notes, and snippets.

@okuramasafumi
Last active February 16, 2021 12:55
Show Gist options
  • Save okuramasafumi/4ff586c5ee45d907f02984418d3c91ec to your computer and use it in GitHub Desktop.
Save okuramasafumi/4ff586c5ee45d907f02984418d3c91ec to your computer and use it in GitHub Desktop.
MyDSL
# ライブラリコード
module MyDSL
class ClassMacro < Module
class StoredItem
attr_reader :name
attr_accessor :context
def initialize(name, block_args: [], &block)
@name = name
@block_args = block_args
@block = block
@context = self
end
def value
if @block
instance_exec(@context, &@block)
else
@context.public_send(@name)
end
end
end
class Store
include Enumerable
attr_writer :context
def initialize(items = [], context:)
@items = items
@context = context
end
def <<(item)
@items << item
end
def each(&block)
@items.each do |item|
item.context = @context if item.context == item
block.call(item)
end
end
end
def initialize(&block)
@block = block
end
def included(base)
base.instance_eval do
def define_store_dsl(name, context: nil)
context = context.nil? ? self : context
instance_variable_set("@#{name}", Store.new(context: context)) unless instance_variable_defined?("@#{name}")
define_singleton_method(name) do |*args|
store_var = instance_variable_get("@#{name}")
args.each do |arg|
store_var << StoredItem.new(arg.to_sym)
end
end
define_method(name) do
store = self.class.instance_variable_get("@#{name}")
store.context = context.respond_to?(:call) ? context.call(self) : context
store
end
end
def define_store_dsl_with_block(hash)
method_name = hash.keys.first
store_target = hash.values.first
define_singleton_method(method_name) do |name, &block|
store_var = instance_variable_get("@#{store_target}")
store_var << StoredItem.new(name, &block)
end
end
def define_instance_method(name, &block)
define_method(name, &block)
end
end
# Evaluate DSL that users wrote
base.instance_exec(&@block)
end
end
end
# デベロッパコード
Resource = MyDSL::ClassMacro.new do
define_store_dsl :attributes, context: proc {|resource| resource.user }
define_store_dsl_with_block attribute: :attributes
define_instance_method :to_hash do
attributes.each_with_object({}) do |attribute, hash|
hash[attribute.name] = attribute.value
end
end
end
# エンドユーザーコード
class User
attr_accessor :id, :name, :email
def initialize(id, name, email)
@id = id
@name = name
@email = email
end
end
class UserResource
include Resource
attr_reader :user
def initialize(user)
@user = user
end
attributes :id, :name
attribute :name_with_email do |user|
"#{user.name}: #{user.email}"
end
end
user = User.new(1, 'John', '[email protected]')
p UserResource.new(user).to_hash # => {'id' => 1, 'name' => 'John', 'name_with_email' => 'John: [email protected]'}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment