Hey all. So I managed to solve this fluid calculator problem but I’m not especially happy with the solution. I saw other solutions using method_missing (e.g. the Calc2 class I include below) but I’m uneasy about method_missing. Any suggestions for improvements here? Thanks in advance…
class Calc
attr_accessor :str
def initialize
self.str = ''
self
end
def plus
self.str << '+'
self
end
def minus
self.str << '-'
self
end
def times
self.str << '*'
self
end
def divided_by
self.str << '/'
self
end
%w(zero one two three four five six seven eight nine).each_with_index do |num, index|
define_method "#{num}" do
if self.str == ''
self.str << index.to_s
self
else
eval(self.str << index.to_s)
end
end
end
end
# nice n compact, but method_missing blech
class Calc2
def method_missing(method_name, *args)
@numbers ||= []
@numbers << { one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9, plus: "+", minus: "-", times: "*", divided_by: "/" }[method_name]
@numbers.size == 3 ? eval(@numbers.join) : self
end
end
require 'minitest/autorun'
class TestCalc < MiniTest::Unit::TestCase
def setup
@calc1 = Calc.new.four.plus.five
@calc2 = Calc.new.five.plus.four
@calc3 = Calc.new.six.divided_by.two
end
def test_1
assert_equal(9, @calc1)
end
def test_2
assert_equal(9, @calc2)
end
def test_3
assert_equal(3, @calc3)
end
end
Thanks for the feedback, I appreciate it. What do you suggest would be a good name for the variable here? I could’t think of anything particularly descriptive.
I 100% agree that the method_missing version is not the way. I prefer the define_method version if for anything, because you don’t have to remember to add in a respond_to?(method).
Please accept this slight revision, bringing the code at least to a more idiomatic ruby form, and while I am at it, I’ll name your state variable something. It IS a string, but what does it represent? A running ticker for a calculation, thus it’s new name: :calculation_ticker
class Calc
PRIMARY_INTEGERS = %w(zero one two three four five six seven eight nine)
attr_reader :calculation_ticker
def initialize
@calculation_ticker = String.new
# you cannot override the return of initialize
end
def plus
@calculation_ticker << '+'
self
end
def minus
@calculation_ticker << '-'
self
end
def times
@calculation_ticker << '*'
self
end
def divided_by
@calculation_ticker << '/'
self
end
PRIMARY_INTEGERS.each_with_index do |num, index|
define_method "#{num}" do
if @calculation_ticker == ''
@calculation_ticker << index.to_s
self
else
eval(@calculation_ticker << index.to_s)
end
end
end
end
require 'minitest/autorun'
class TestCalc < MiniTest::Unit::TestCase
def setup
@calc1 = Calc.new.four.plus.five
@calc2 = Calc.new.five.plus.four
@calc3 = Calc.new.six.divided_by.two
end
def test_1
assert_equal(9, @calc1)
end
def test_2
assert_equal(9, @calc2)
end
def test_3
assert_equal(3, @calc3)
end
end
Now that the code is cleaned up a bit I would like to address what I see as a fundamental problem here. Why are you chaining these number string as methods in the first place?
If your goal is to play, great, but this is the kind of dsl code I despise. What about this interface?
@zamith, that is better, thanks. @Dreamr, this was just a problem on Code Wars. I hadn’t heard of fluid interfaces, just thought it might be of general interest here.