2012-06-20 4 views
3

У меня есть DSL - как кусок рубина кода, который выглядит следующим образом:Возвращается из серии вызовов в пределах блока в Руби

Activity.new('20-06-2012') do 
    Eat.log do |l| 
    l.duration = 0.30 
    l.priority = 5 
    end 
    Work.log do |l| 
    l.duration = 2 
    l.priority = 3 
    end 
end 

Каждый раз, когда метод журнала() вызывается, объект журнала является (за кадром) с блоком, переданным методу (блок передается конструктору объекта журнала). Мой вопрос в том, есть ли способ собрать все результаты из метода log()? В приведенном выше примере возвращаемое значение самого внешнего блока является последним вызовом log(). Но я хочу получить результаты всех вызовов в массиве, а не только в последнем.

Спасибо!

ответ

3

Ваш в тренний DSL еще слишком Ruby, как вы можете реорганизовать его выглядеть примерно так:

activity '20-06-2012' do 
    log :eat do 
    duration 0.30 
    priority 5 
    end 
    log :work do 
    duration 2 
    priority 3 
    end 
end 

Теперь захватить звонки в вашем instance_eval и накапливают значения во внутреннем массиве, обычный DSL вещи.

+0

Таким образом, мне пришлось бы превратить методы класса в методы экземпляра и использовать 'instance_eval' - я собирался сделать это как более поздний шаг, но теперь могу это сделать. Благодаря! – abbottjam

+1

@ пользователь906230: точно. Если целью были Ruby-программисты, было бы неплохо иметь блоки с аргументами, 'new' и т. Д., Но не программистам будет сложно это понять. – tokland

+0

+1 для точно модификации DSL, которую я собирался предложить :) – Phrogz

0

return, разделяя каждый из блоков запятой.

Activity.new('20-06-2012') do 
    return_value = Eat.log do |l| 
    l.duration = 0.30 
    l.priority = 5 
    end, Work.log do |l| 
    l.duration = 2 
    l.priority = 3 
    end 
return return_value #redundant 
end 

Когда вы возвращаете несколько значений, разделенных запятой, Ruby объединяет их в один массив.

0

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

Лично я хотел бы сделать коллекцию массив более явным:

Activity.new('20-06-2012') do 
    logs = [] 
    logs << Eat.log do |l| 
    l.duration = 0.30 
    l.priority = 5 
    end 
    logs << Work.log do |l| 
    l.duration = 2 
    l.priority = 3 
    end 
    logs 
end 

Таким образом, это очень трудно смотреть на код и неправильно, что он делает

1

Последнее утверждение блока всегда возвращаемое значение. Если вы не хотите, чтобы ваши пользователи записывали l в качестве последнего оператора в блоке, вы должны использовать модификации, выполненные для экземпляра l (независимо от того, что это такое), вместо значения возвращаемых блоков. Другим вариантом является изменение сеттер-поведение l объекта возвращает массив всех значений на уступку, - но я бы с первым вариантом, в зависимости от вашего кода, это может выглядеть следующим образом:

class Log 
    attr_accessor :duration, :priority 
    def initialize(block) 
    block.call(self) 
    end 
end 

Log.new proc{ |l| l.duration = 0.3; l.priority = 5 } 
# => #<Log:0x000000029d8030 @duration=0.3, @priority=5> 
+0

Это выглядит хорошо, я попробую. – abbottjam

0

Если (и только если) ваша структура журнала проста и последовательна, вы могли бы уйти с чем-то вроде этого:

Activity.new('20-06-2012') do 
    logs = { 
    Eat => {:duration => 0.30, :priority => 5}, 
    Work => {:duration => 2, :priority => 3} 
    } 

    logs.map do |log_type, log_data| 
    log_type.log do |l| 
     l.duration = log_data[:duration] 
     l.priority = log_data[:priority] 
    end 
    end 
end 

вы должны сделать свой собственный ум об этом, соответствует ли вашей структуре и будет ли это читаемое достаточно

1

Создайте новый объект, ActivityLog или что-нибудь еще, и передайте его в блок от инициализатора Activity.

Activity.new('20-06-2012') do |log| 
    log.eat do |l| 
    l.duration = 0.30 
    l.priority = 5 
    end 
    log.work do |l| 
    l.duration = 2 
    l.priority = 3 
    end 
end 

Если у вас есть только несколько типов журналов, просто добавьте их в ActivityLog (ActivityLog#eat, ActivityLog#work и т.д.). Инициализируйте соответствующий объект (Eat, Work) и вызовите его метод журнала с принятым блоком.

В качестве следующего шага вы можете добавить их в массив и перебрать через него в ActivityLog#initialize и динамически создать каждый метод.

Ленивый способ для ActivityLog иметь method_missing, который принимает любой метод, преобразует его в константу класса, а затем выполняет указанные выше шаги. Это плохо, потому что обычно это затрудняет отладку, вы можете получить вводящие в заблуждение исключения, если вы вызываете метод объекта, который не соответствует регистратору, и вы не можете вызвать methods против объекта и просмотреть перечисленные методы.

Надеюсь, что это поможет.

+0

Он делает, спасибо! – abbottjam

Смежные вопросы