【问题标题】:Is it possible to evaluate a Ruby DSL in a non-global context?是否可以在非全局上下文中评估 Ruby DSL?
【发布时间】:2010-02-22 14:34:10
【问题描述】:

我正在使用Blockenspiel 使用 Ruby 创建一个 DSL。它工作得很好 解决了我的很多问题,但是我遇到了以下问题 这与 Blockenspiel 没有严格的关系。

假设我有一个如下所示的 DSL:

dish do
  name = 'Pizza'
  ingredients = ...
  nutrition_facts = ...
end

dish do
  name = 'Doner'
  ingredients = ...
  nutrition_facts = ...
end

现在我有一个菜单编译器,它接收菜肴并将它们编译成 一份菜单。编译器现在应该能够编译多个菜单文件, 所以它已经设置并清除了一个全局上下文。这应该最好 并行发生。

我发现 sinatra 使用类变量,但这有 结果是它只能进行顺序处理并且你 当你想编译一个新的时必须清除类变量 菜单。另一种方法是使用全局变量。

我更愿意在一个范围内评估 DSL 方法 对象,这样就没有全局上下文,我可以编译 菜单并行,但我最后一次尝试这个时,我遇到了一些 在菜单文件中声明(helper-)方法时出现问题。

哪些方法可行?推荐的方法是什么?

【问题讨论】:

    标签: ruby metaprogramming dsl


    【解决方案1】:

    我见过的许多图书馆都是利用instance_eval 来做这类事情的。

    只要性能不是大问题,您可以执行以下操作:

    class Menu
      def initialize file
        instance_eval File.read(file),file,1
      end
    
      def dish &block
        Dish.new &block
      end
      #....
    end
    
    class Dish
      def name(n=nil)
        @name = n if n
        @name
      end
      def ingredients(igrd=nil)
        @ingredients= igrd if igrd
        @ingredients
      end
    end
    #....
    

    Menu.new 'menus/pizza_joint'

    菜单/pizza_joint

    dish do
      name 'Cheese Pizza'
      ingredients ['Cheese','Dough','Sauce']
    end
    

    实际上有一些 DSL 库可以添加像 #name#ingredients 这样的访问器,因此您不必手动构建它们。例如dslify

    【讨论】:

    • 我是否也可以声明新函数并在评估时在菜单中使用它们?
    • 是的。 instance_eval,像块一样让您在其中定义类模块和方法。
    【解决方案2】:

    基本上有两种方法可以归档您想要的内容。

    选项 a:使用 setter-methods 生成一个对象:

    Dish = Struct.new(:name, :ingredients, :nutrition_facts)
    def dish
      d = Dish.new
      yield d
      d
    end
    
    dish do |d|
      d.name = 'Pizza'
      d.ingredients = ...
      d.nutrition_facts = ...
    end
    

    选项 b:您使用实例变量和 instance_eval

    class Dish
      attr_accessor :name, :ingredients, :nutrition_facts
    end
    def dish(&blk)
      d = Dish.new
      d.instance_eval(&blk)
      d
    end
    
    dish do
      @name = 'Doner'
      @ingredients = ...
      @nutrition_facts = ...
    end
    

    在这两种情况下,dish 方法都会返回一个 Dish 实例,您可以在该实例上调用例如name 访问块中设置的名称(多次调用 disc 将返回独立对象)。请注意,使用 instance_eval 用户还可以调用块中 Dish 类的私有方法,并且拼写错误的变量名不会导致错误。

    【讨论】:

    • 如果您将@foo = 替换为self.foo =,则不必担心拼写错误。
    • 但我仍然需要一个全局菜单,我想避免这种情况。
    • 您可以为 DSL 创建一个menu(name='default') do ... end 组件。 dish 方法只能在 Menu 中使用。菜单文件中的帮助方法到底有什么问题?
    • 我希望从我编译的菜单文件中获取菜单名称。我想在没有全局状态或上下文的情况下并行编译所有菜单。上下文应该只对菜单文件是本地的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-27
    • 2020-02-08
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-17
    相关资源
    最近更新 更多