Изменить контекст / привязку внутри блока в ruby - PullRequest
35 голосов
/ 02 мая 2011

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

desc 'list all todos'
command :list do |c|
  c.desc 'show todos in long form'
  c.switch :l
  c.action do |global,option,args|
    # some code that's not relevant to this question
  end
end

desc 'make a new todo'
command :new do |c|
  # etc.
end

Один из разработчиков предложил мне улучшить мой DSL, чтобы он не требовал передачи c в блок command и, следовательно, не требовалc. для всех методов внутри;предположительно, он подразумевал, что я мог заставить следующий код работать так же:

desc 'list all todos'
command :list do
  desc 'show todos in long form'
  switch :l
  action do |global,option,args|
    # some code that's not relevant to this question
  end
end

desc 'make a new todo'
command :new do
  # etc.
end

Код для command выглядит примерно так:

def command(*names)
  command = make_command_object(..)
  yield command                                                                                                                      
end

Я попробовал несколько вещей и не смог получитьэто работать;Я не мог понять, как изменить контекст / привязку кода внутри блока command, чтобы он отличался от значения по умолчанию.

Есть какие-нибудь идеи относительно того, возможно ли это, и как я могу это сделать?

Ответы [ 4 ]

32 голосов
/ 02 мая 2011

Вставьте этот код:

  def evaluate(&block)
    @self_before_instance_eval = eval "self", block.binding
    instance_eval &block
  end

  def method_missing(method, *args, &block)
    @self_before_instance_eval.send method, *args, &block
  end

Для получения дополнительной информации обратитесь к этой действительно хорошей статье здесь

10 голосов
/ 05 мая 2011

Может

def command(*names, &blk)
  command = make_command_object(..)
  command.instance_eval(&blk)
end

может оценить блок в контексте объекта команды.

4 голосов
/ 02 мая 2011
class CommandDSL
  def self.call(&blk)
    # Create a new CommandDSL instance, and instance_eval the block to it
    instance = new
    instance.instance_eval(&blk)
    # Now return all of the set instance variables as a Hash
    instance.instance_variables.inject({}) { |result_hash, instance_variable|
      result_hash[instance_variable] = instance.instance_variable_get(instance_variable)
      result_hash # Gotta have the block return the result_hash
    }
  end

  def desc(str); @desc = str; end
  def switch(sym); @switch = sym; end
  def action(&blk); @action = blk; end
end

def command(name, &blk)
  values_set_within_dsl = CommandDSL.call(&blk)

  # INSERT CODE HERE
  p name
  p values_set_within_dsl 
end

command :list do
  desc 'show todos in long form'
  switch :l
  action do |global,option,args|
    # some code that's not relevant to this question
  end
end

Напечатает:

:list
{:@desc=>"show todos in long form", :@switch=>:l, :@action=>#<Proc:0x2392830@C:/Users/Ryguy/Desktop/tesdt.rb:38>}
2 голосов
/ 28 августа 2012

Я написал класс, который занимается именно этой проблемой и занимается такими вещами, как @instance_variable access, nesting и так далее. Вот описание другого вопроса:

Блочный вызов в Ruby on Rails

...