Reputation: 167
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
Reputation: 12578
You might want to take a look at this SO question and this code by Niklas B.
Upvotes: 0
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
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