Reputation: 1071
I have a DSL - like piece of Ruby code that looks like this:
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
Every time the log() method is called, a Log object is instantiated (behind the scenes) with the block passed to the method (the block is passed on to the Log object’s constructor). My question is, is there a way to collect all the returns from the log() method? In the example above, the return value of the outermost block is the last call to log(). But I want to get all the calls’ results in an array, not just the last one.
Thanks!
Upvotes: 3
Views: 79
Reputation: 67860
Your internal DSL is still too Ruby-like, you may refactor it to look something like this:
activity '20-06-2012' do
log :eat do
duration 0.30
priority 5
end
log :work do
duration 2
priority 3
end
end
Now capture the calls in your instance_eval
and accumulate the values in an internal array, the usual DSL stuff.
Upvotes: 3
Reputation: 4623
Create a new object, ActivityLog
or whatever, and pass it to the block from the Activity
initializer.
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
If you only have a handful of log types, just add them to ActivityLog
(ActivityLog#eat
, ActivityLog#work
, etc.). Initialize the corresponding object (Eat
, Work
) and call its log method with the received block.
As a next step, you could add them to an array and iterate through it in ActivityLog#initialize
and dynamically create each method.
The lazy way is for ActivityLog
to have a method_missing
that accepts any method, converts it to a class constant, and then does the above steps. This is bad because it typically makes it harder to debug, you can get misleading exceptions if you call a method on the object that doesn't correspond to a logger, and you can't call methods
against the object and see the methods listed.
Hope that helps.
Upvotes: 1
Reputation: 138042
If (and only if) your log structure is simple and consistent, you could get away with something like this:
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
You'll need to make your own mind up about whether this fits your structure and whether it's readable enough
Upvotes: 0
Reputation: 9177
The last statement of the block is always the return value. If you don't want to force your users to write l
as the last statement in the block, you should use the modifications done to the l
instance (whatever it is), instead of the blocks return value. Another option is to change the setter-behavior of the l
object to return an array of all values on assignment - but I would go with the first option, depending on your code, it might look like this:
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>
Upvotes: 1
Reputation: 138042
André's approach will work, but it's not very readable. If you go back to it in a month or so, it's tricky to spot that comma hiding in there.
Personally I'd make the array collection more explicit:
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
This way, it's very hard to look at the code and misread what it does
Upvotes: 0
Reputation: 810
use return
, separating each of the blocks with a comma.
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
when you return multiple values separated by a comma, Ruby joins them into one array.
Upvotes: 0