ruby-on-railsrubyprocruby-block

Ruby block as an array element for splatting in public_send


I am trying to build a generic method using meta programming where it uses manipulating methods from array and send to the object using splat, following is the working snippet:

ALLOWED_DATA_TYPES = {
  'Integer' => [['to_i']],
  'Csv' => [['split', ',']]
}

ALLOWED_DATA_TYPES.each do |type, methods|
  define_method("#{type.downcase}_ified_value") do
    manipulated_value = value
    methods.each { |method| manipulated_value = manipulated_value.public_send(*method) }
    return manipulated_value
  end
end

It was working great so far, until we decided to add another datatype and it needs to call method on array, for e.g.

"1,2,3".split(',').map(&:to_f)

Now I am stuck, because it's a block. Technically, following code is working alright:

"1,2,3".public_send('split', ',').public_send(:map, &:to_f)
# => [1.0, 2.0, 3.0]

But adding that block to array is throwing error

[['split', ','], ['map', &:to_f]]
# => SyntaxError: syntax error, unexpected &, expecting ']'

I know I can create a proc and call it with amp & but I hope u get it that it is loosing consistency, I need something that will just work with splat operator as defined using #define_method

I am out of ideas now, please help.


Solution

  • You're out of luck, & is not an operator - it is a special syntax that is only allowed in function parameters (both definition and invocation). One way you could do it is to reserve the last element of the array for the block; but then you always have to use it (even if it is just nil).

    methods.each { |*method, proc| manipulated_value = manipulated_value.public_send(*method, &proc) }
    

    This should work with [['split', ',', nil], ['map', :to_f]].

    Off-topic, but note that these three lines can be more succintly rewritten using inject:

    manipulated_value = value
    methods.each { |*method, proc| manipulated_value = manipulated_value.public_send(*method, &proc) }
    return manipulated_value
    

    becomes

    methods.inject(value) { |manipulated_value, (*method, proc)| manipulated_value.public_send(*method, &proc) }