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.
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 =&gt; self.x, :y =&gt; self.y, :width =&gt; self.width, :height =&gt; 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.