Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a mixin method inside an instance method

Tags:

ruby

gem

Why does this not work:

class Myclass
include HTTParty

   def dosomething
     base_uri("some_url")
   end


end

The base_uri method is a class method of HTTParty. It works fine if I call it from my class, outside of any instance methods, or from a class method, but when try to call it from an instance method I get "NoMethodError: undefined method `base_uri' for #"

Why? Shouldn't there be some way to refer to the HTTParty class from within my instance method so I can call that HTTParty class method?

I could change it to a class method, but then every instance of my class would have the same value for base_uri.

like image 702
wadesworld Avatar asked Jan 17 '23 01:01

wadesworld


1 Answers

Why doesn't it work? Because that's not how Ruby works. Similarly, this doesn't work:

class Foo
  def self.utility_method; ...; end
  def inst_method
    utility_method  # Error! This instance has no method named "utility_method"
  end
end

You could work around this by just doing:

class MyClass
  include HTTParty
  def dosomething
    HTTParty.base_uri("some_url")
  end
end

Let's look deeper at how method lookup works with modules. First, some code:

module M
  def self.m1; end
  def m2; end
end

class Foo
  include M
end
p Foo.methods     - Object.methods #=> []
p Foo.new.methods - Object.methods #=> [:m2]

class Bar
  extend M
end
p Bar.methods     - Object.methods #=> [:m2]
p Bar.new.methods - Object.methods #=> []

class Jim; end
j = Jim.new
j.extend M
p j.methods       - Object.methods #=> [:m2]

As we see, you can use extend to cause an object (a class or instance) to use the 'instance' methods of a module for the object itself (instead of instances), but you cannot cause 'class methods' of the module to be inherited by anything. The closest you can get is this idiom:

module M2
  module ClassMethods
    def m1; end             # Define as an instance method of this sub-module!
  end
  extend ClassMethods       # Make all methods on the submodule also my own
  def self.included(k)
    k.extend(ClassMethods)  # When included in a class, extend that class with
  end                       # my special class methods

  def m2; end
end

class Foo
  include M2
end
p Foo.methods     - Object.methods #=> [:m1]
p Foo.new.methods - Object.methods #=> [:m2]

If the HTTParty module used the above pattern, and so made the base_uri method available on your MyClass, then you could do this:

class MyClass
  include HTTParty
  def dosomething
    self.class.base_uri("some_url")
  end
end

...but that's more work than just directly referencing the module owning the method.

Finally, because this might help you, here's a diagram I made some years ago. (It's missing some core objects from Ruby 1.9, like BasicObject, but is otherwise still applicable. Click for a PDF version. Note #3 from the diagram is particularly applicable.)

Ruby Method Lookup Flow
(source: phrogz.net)

like image 176
Phrogz Avatar answered Jan 25 '23 11:01

Phrogz