Ruby - 使用实例变量的DSL

时间:2014-03-14 18:23:48

标签: ruby metaprogramming dsl

这里的代码是对更大解决方案的简化。我试图找出方法 做一个红宝石DSL,#34;读取"很好。第一个代码块;作品(现在有效) 我想知道如何编写DSL

问题的核心是,在处理类时,它没有一个实例变量供我使用。 IE:@match_code

有谁知道更简单更优雅的解决方案?整个代码必须保留在一个类中。

我希望它看起来像是:

class MatchStuff 
  include ProcessBase

  match 'account' do | event |
    pp event
  end
end

matcher = MatchStuff.new
matcher.accept 'account'

当前工作(不太好)代码

class ProcessBase
  def initialize
    @match_code = []
  end

  def match(string_match, &block)
    @match_code.push([string_match, block])
  end

  def accept(test_str)
    @match_code.each do | test_block |
      if test_str == test_block[0])
        test_block[1].call test
      end
    end
  end
end

class MatchStuff < ProcessBase
  def initialize 
    super

    match 'account' do | event |
      pp event
    end
  end
end

test = MatchStuff.new
test.accept 'account'

3 个答案:

答案 0 :(得分:1)

为了使用ProcessBase正文中class MatchStuff的方法,除了extend或继承它之外,您还可以include

class MatchStuff
  include ProcessBase
  extend ProcessBase

  match 'account' do |event|
    pp event
  end
end

但是你遇到的问题是@match_code引用match中的类实例变量,而accept中的常规实例变量。我将一个阅读器方法添加到ProcessBase并在accept中的类上使用它,因此您始终使用类实例变量。然后,您可以使用||=来避免使用initialize方法(extend时不会为该类调用。)

module ProcessBase
  # reader method, autovivifying to empty Array
  def match_code
    @match_code ||= []
  end

  def match(string_match, &block)
    # using the reader here, on self, which is the class
    match_code.push([string_match, block])
  end

  def accept(test_str)
    # here, self is the instance, so we have to explicitly get the class
    # and access match_code from there
    self.class.match_code.each do | test_block |
      if test_str == test_block[0])
        test_block[1].call test
      end
    end
  end
end

或者,您可以将match(使用match_code读者)和accept放在单独的模块中,extend一个,include另一个。这样做的好处是不会为您的实例提供一个不起作用的match方法(因为它使用了错误的match_code)。你甚至可以定义一个includedextended方法,当你做另一个时,它会做一个。

module ProcessClassBase
  def match_code
    # as above
  end

  def match
    # as above
  end
end

module ProcessInstanceBase
  def accept
    # as above
  end

  def included(other_mod)
    other_mod.extend(ProcessClassBase)
  end
end

class MatchStuff
  include ProcessInstanceBase # now this also extends ProcessClassBase

  match 'account' do |event|
    pp event
  end
end

值得注意的是,如果你继承MatchStuff,这一切都会中断。调用MatchStuffSub时,子类的实例(称之为accept)将尝试访问MatchStuffSub.match_code而不是MatchStuff.match_code(其中match放置内容)。

答案 1 :(得分:0)

MatchStuff定义为

class MatchStuff < ProcessBase
   def set_match
      match 'account' do |event|
        pp event
      end
   end
end

并从set_match构造函数中调用ProcessBase

class ProcessBase
  def initialize
    @match_code = []
    set_match # Must be defined in derived class
  end
  ...
end

由于您没有为MatchStuff指定构造函数,它将自动调用父类构造函数。

这实现了我对您的目标的理解,即尽可能以最小的方式在'account'中指定MatchStuff字符串和关联的块。

答案 2 :(得分:0)

您可以尝试不使用实例变量。目前尚不清楚匹配字符串的可能值是什么样的,所以我给出了一个例子,说明如何使它们对这种方法“安全”。

module ProcessBase
  def self.included(c)
    c.extend(ClassMethods)
  end

  module ClassMethods
    def match(string_match, &block)
      method = string_match.downcase.gsub(/\s+/,'_').to_sym
      define_method(method, &block)
    end
  end

  def accept(string_match)
    method = string_match.downcase.gsub(/\s+/,'_').to_sym
    self.send(method, 'test')
  end
end

class MatchStuff
  include ProcessBase

  match 'account' do | event |
    pp event
  end
end

test = MatchStuff.new
test.accept 'account'
相关问题