f0i
f0i

Reputation: 167

block like parameter for Ruby DSL

I have a ruby code that currently look like this:

grades.sum{|g| g.grade * g.weight}

For a user maintained DSL, I would like to implement something like this:

grades.sum('grade' * 'weight')

Is this possible?

Upvotes: 1

Views: 157

Answers (3)

Boris Stitnicky
Boris Stitnicky

Reputation: 12578

You might want to take a look at this SO question and this code by Niklas B.

Upvotes: 0

tokland
tokland

Reputation: 67900

No, it's not possible unless you monkeypatch the class String to add your application logic (please don't do that). But this -which even looks beter- is possible if you play a little bit with instance_eval:

grades.sum { grade * weight }

For example, let's assume grades is an array:

module Enumerable
  def sum(&block)
    inject(0) { |acc, x| acc + x.instance_eval(&block) }
  end
end

Upvotes: 6

Chris Heald
Chris Heald

Reputation: 62668

Strictly speaking, no, because the Ruby interpreter will attempt to multiply the strings "grade" and "weight" together before passing them as a parameter to "sum". You could achieve it by monkeypatching String to override all the operations you want to permit, but that's an awful idea and will be a maintenance nightmare.

Now, that said, you have a couple of ways that you could achieve this. If you require a full string as the parameter:

grades.sum('grade * weight')

Then, you can use instance_eval to eval that code in the context of the value you're passing to #sum. The implementation would look something like:

class Grade
  def weight
    0.5
  end

  def grade
    75
  end
end

class Array
  def sum(code = nil)
    if block_given?
      inject(0) {|s, v| s + yield(v) }
    else
      sum {|v| v.instance_eval code }
    end
  end
end

grades = Array.new(5, Grade.new)
puts grades.sum("weight * grade")

# Expected output: 5 * 75 * 0.5 = 187.5
#
# Actual output:
# % ruby grades.rb
# 187.5

This also preserves your original block form:

puts grades.sum {|g| g.weight * g.grade } # => 187.5

Upvotes: 1

Related Questions