2016年5月5日 星期四

writing-a-domain-specific-language-in-ruby


how to write domain-specific language in ruby

example:
Smokestack.define do
  factory User do
    name "Gabe BW"
    pet_name "Toto"
  end
end

user = Smokestack.build(User)
puts user.name == 'Gabe BW'  # true
puts user.pet_name == 'Toto' # true

other_user = Smokestack.build(User, name: "Bob")
puts other_user.name == 'Bob'      # true
puts other_user.pet_name == 'Toto' # true

how it works?

module Smokestack
  @registry = {}

  def self.registry
    @registry
  end

  def self.define(&block)
    definition_proxy = DefinitionProxy.new
    definition_proxy.instance_eval(&block)
  end

  def self.build(factory_class, overrides = {})
    instance = factory_class.new
    factory = registry[factory_class]
    attributes = factory.attributes.merge(overrides)
    attributes.each do |attribute_name, value|
      instance.send("#{attribute_name}=", value)
    end
    instance
  end
end

class DefinitionProxy
  def factory(factory_class, &block)
    factory = Factory.new
    factory.instance_eval(&block)
    Smokestack.registry[factory_class] = factory
  end
end

class Factory < BasicObject
  def initialize
    @attributes = {}
  end

  attr_reader :attributes

  def method_missing(name, *args, &block)
    @attributes[name] = args[0]
  end
end


三個主要的物件
module Smokestack => 記錄所有的, 用@registry 來記錄目前所有的定義(definition_proxy), build就是實際產生一個需要的instance
class DefinitionProxy => 定義 factory方法, 用instance_eval打開每一個factory, 再傳入需要做的block, 之後透過smokestack來registry factory

class Factory < BasicObject => 實際的每一個factory要做什麼, 用method_missing來實現 

沒有留言:

張貼留言