line_to, fill_rect, and clear_rect functions

def line_to(x: 0, y: 0, rect: nil)
unless rect.nil?
x, y, width, height = rect.x, rect.y, rect.width, rect.height
end

#{@context}.lineTo(#{x}, #{y})
end

def fill_rect(x: 0, y: 0, width: 0, height: 0, rect: nil)
unless rect.nil?
x, y, width, height = rect.x, rect.y, rect.width, rect.height
end

#{@context}.fillRect(#{x}, #{y}, #{width}, #{height})
end

def clear_rect(x: 0, y: 0, width: 0, height: 0, rect: nil)
unless rect.nil?
x, y, width, height = rect.x, rect.y, rect.width, rect.height
end

#{@context}.clearRect(#{x}, #{y}, #{width}, #{height})
end


I am looking for a way to refactor the above code to take out the first three lines in the methods. I don't want to use Metaprogramming in this specific case; is there any other way?

In case full code will help, see the repo.

• Do these functions appear in a class? If so, could you show us the whole class? Also, could you explain why there are backticks? – 200_success Feb 15 '15 at 9:46
• Yes they are in a class and its Opal, backticks are to evaluate JS. Have just updated the post with the link to the full code. – Jikku Jose Feb 15 '15 at 11:30

If I understand this correctly, you want to be able to call these methods with two different signatures:

method(x: .., y: .., height: .., width: ..)
method(rect: ..)


There has to be some logic in the methods to decide which signature to use. I like your current solution for that. Using metaprogramming would make things too complicated.

Since the signatures are different, you could argue they are actually two different methods: #line_to and #line_to_rectangle for example. You could simply define two different methods:

def line_to(x, y)
#{@context}.lineTo(#{x}, #{y})
end

def line_to_rectangle(rectangle)
line_to(rectangle.x, rectangle.y)
end


The best solution would be to only expect a single signature in the first place.

One way to do that is to use ** to pass a rectangle object as keyword arguments. For this to work you have to define a #to_hash.

class Rectangle
def to_hash
{ :x => self.x, :y => self.y, :width => self.width, :height => self.height }
end
end

...

line_to(**rectangle)


You'll have to make sure that #line_to takes all keys returned by Rectangle#to_hash. That means it should also a width and height keyword argument. You can do this by taking them explicitly or implicitly (def line_to(x: 0, y: 0, **discard)). If you don't take those unnecessary arguments it will raise an exception. Hardly ideal.

Another way to do that would be to create a second type of object to replace the keyword arguments (x, y, width and height). It should have the same interface as rectangle. I'd recommend not complicating things this much, though, and pick a single signature.

• Exactly! I am trying to do method overloading; I know it isn't there in Ruby. But still trying for a good way for this. I am thinking of using something like a lambda; but won't the variables die of when it quits! I can do an eval! But that to me is dirty. – Jikku Jose Feb 15 '15 at 14:28
• I added some extra, non-ideal, solutions. I hope the main message is clear: pick a single method signature :P – britishtea Feb 15 '15 at 16:24

Does factoring out the common logic help?

def line_to(x: 0, y: 0, rect: nil)
x, y, width, height = try_rect_values(x, y, width, height, rect)
#{@context}.lineTo(#{x}, #{y})
end

def fill_rect(x: 0, y: 0, width: 0, height: 0, rect: nil)
x, y, width, height = try_rect_values(x, y, width, height, rect)
#{@context}.fillRect(#{x}, #{y}, #{width}, #{height})
end

def clear_rect(x: 0, y: 0, width: 0, height: 0, rect: nil)
x, y, width, height = try_rect_values(x, y, width, height, rect)
#{@context}.clearRect(#{x}, #{y}, #{width}, #{height})
end

def try_rect_values(x, y, width, height, rect)
return x, y, width, height if rect.nil?

return rect.x, rect.y, rect.width, rect.height
end

• Well, not something I am looking for, but thanks for your time. Anyways, I think you can avoid width & height if rect is nil. – Jikku Jose Feb 16 '15 at 1:48