Можно ли оценить Ruby DSL в неглобальном контексте? - PullRequest
3 голосов
/ 22 февраля 2010

Я использую Blockenspiel для создания DSL с Ruby. Это прекрасно работает и решает много моих проблем, но я столкнулся со следующей проблемой это не строго связано с Blockenspiel.

Предположим, у меня есть DSL, который выглядит следующим образом:

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

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

Теперь у меня есть компилятор меню, который берет блюда и компилирует их в меню. Теперь компилятор должен иметь возможность компилировать несколько файлов меню, поэтому он настроил и очистил глобальный контекст. Это должно предпочтительно происходить параллельно.

Я обнаружил, что sinatra использует переменные класса, но это имеет следствие того, что он может выполнять только последовательную обработку и что вы должны очистить переменные класса, когда вы хотите скомпилировать новый меню. Альтернативой может быть использование глобальных переменных.

Я бы предпочел оценить методы DSL в рамках объект, так что нет глобального контекста, и я мог бы скомпилировать меню параллельно, но в последний раз, когда я попробовал это, я столкнулся с некоторыми проблемы при объявлении (helper-) методов в файле меню.

Какие методы возможны? Каков рекомендуемый способ сделать это?

Ответы [ 2 ]

2 голосов
/ 23 февраля 2010

То, что многие библиотеки, которые я видел, использует 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

2 голосов
/ 22 февраля 2010

Существует два основных способа архивации того, что вы хотите.

Вариант а: Вы получаете объект с помощью методов установки:

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 для доступа к имени, установленному в блоке (и множественные вызовы dish вернут независимые объекты). Обратите внимание, что с instance_eval пользователь также сможет вызывать частные методы класса Dish в блоке, и что имена переменных с ошибками будут не вызывать ошибку.

...