Builder throwing “wrong number of arguments” error when passed a block in Ruby 1.9
-
25-10-2019 - |
Question
I'm trying to upgrade a Ruby 1.8 app to 1.9 and hit a bit of a roadblock here. In Ruby 1.8.7, I can pass on a block to Builder 3.0.0 and it gets called as expected:
1.8.7 :003 > @builder = Builder::XmlMarkup.new
1.8.7 :004 > block = lambda { puts "foo" }
1.8.7 :005 > @builder.tag(&block)
foo
But in 1.9, I get this error:
1.9.3p0 :002 > @builder = Builder::XmlMarkup.new
1.9.3p0 :003 > block = lambda { puts "foo" }
1.9.3p0 :004 > @builder.content(&block)
ArgumentError: wrong number of arguments (1 for 0)
from (irb):3:in `block in irb_binding'
from /Users/dev/.bundle/ruby/1.9.1/gems/builder-3.0.0/lib/builder/xmlbase.rb:155:in `call'
...
And rewriting that as a stabby lambda (which is just syntactic sugar, right?) doesn't help:
1.9.3p0 :006 > block = -> { puts "foo" }
1.9.3p0 :007 > @builder.content(&block)
ArgumentError: wrong number of arguments (1 for 0)
Passing an actual block instead of a reference to one does work:
1.9.3p0 :008 > @builder.content { puts "foo" }
foo
Help?
Solution
That is actually because in ruby 1.9, lambda and proc behave subtly differently. Think of lambda, being mathematically precise, requiring the exact number of arguments specified, while proc exhibits the more permissive behavior of ruby 1.8. For example,
a = lambda {|v| p v }
a.call() # ArgumentError: wrong number of arguments (0 for 1)
a.call(1) # prints "1"
a.call(1, 2) # ArgumentError: wrong number of arguments (2 for 1)
b = proc {|v| p v }
b.call() # prints "nil"
b.call(1) # prints "1"
b.call(1, 2) # prints "1"
Note that both objects are of type Proc, but can be distinguished from each other by the .lambda? method.
a.class # => Proc
a.lambda? # => true
a.arity # => 1 (number of parameters)
b.class # => Proc
b.lambda? # => false
b.arity # => 1 (number of parameters)
OTHER TIPS
Ooh, figured it out. The line causing the problem in Builder is this:
block.call(self)
In other words, it passes itself as an argument to the block. In Ruby 1.8, the block is free to ignore this, but in 1.9, it must declare all arguments. Thus:
1.9.3p0 :023 > block = lambda { |dummy| puts "foo" }
1.9.3p0 :024 > @builder.content(&block)
foo
Yay!