0

I've got a little DSL that looks like this:

ActivityLogger.log do
  activity('27-06-2012') do
    eat do |act|
     act.duration = 15
     act.priority = 5
    end
  end
end

I want to refactor it so it loses the block params in the innermost block, so it looks like this:

ActivityLogger.log do
  activity('27-06-2012') do
    eat do
     duration = 15
     priority = 5
    end
  end
end

The #eat method instantiates a Log object:

def eat(&block)
  @logs << Log.new(Eat, &block)
end

Log's constructor yields self in the last line:

def initialize(activity, &block)
  @activity = activity
  yield self
end

To my mind, that is where the problem is. I've tried both using instance_eval in the #eat method (see link#2 below) and removing the yield statement from the Log's constructor entirely (link#3), but none of these approaches work (the Log object gets created, but doesn't get its #duration and #priority methods set).

Here are the links:

1) A working DSL with block parameters

2) Non-working DSL, first refactoring attempt

3) Non-working DSL, second refactoring attempt

Thanks!

fullstackplus
  • 1,061
  • 3
  • 17
  • 31
  • 1
    read http://stackoverflow.com/questions/5851127/change-the-context-binding-inside-a-block-in-ruby and article http://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation – Mike Jun 28 '12 at 11:45

1 Answers1

4

duration = 15 doesn't call the setter method as you expect but defines a local variable duration. You have to either call the setter explicitly via self.duration = 15 or implement your setter like

def duration(value)
  @duration = value
end

and call duration 15.

Stefan
  • 109,145
  • 14
  • 143
  • 218
  • When I use `duration = 15` with block params it does set the value as expected. These methods are declared as `attr_accessor` in the Log class, and the values can be seen by inspecting them as I do in the `ActivityLogger#report` method. – fullstackplus Jun 28 '12 at 12:21
  • 1
    You call `act.duration = 15`, not just `duration = 15` – Stefan Jun 28 '12 at 12:23
  • Yes. When I call `duration = 15` nothing happens - which is the point of this thread. – fullstackplus Jun 28 '12 at 12:27
  • This is because `duration = 15` defines a variable, just like `i = 1` – Stefan Jun 28 '12 at 12:31
  • 1
    @user906230: Stefan is right, when writing DSLs you either use `something.xyz = 1` or `xyz 1`. `xyz = 1` will *always* create a local variables, that's how scoping in Ruby works. – tokland Jun 28 '12 at 12:33
  • I understand but this isn't the point of the question. I am calling `something.xyz = 1` now, so no local variables are created. – fullstackplus Jun 28 '12 at 12:56
  • Solved: got rid of `yield self` in the Log constructor, am using `xyz = 1` in the calls with explicit setters as Stefan suggested. Thanks for the comments. – fullstackplus Jun 28 '12 at 14:12