How do I extend this Ruby module? - ruby

I'm trying to utilise the Gmail gem in a gem that I'm building. From the source, you can see that the gem defines the Gmail module/class like so (simplified):
module Gmail
class << self
def connect; end
end
end
What I would like to do is to extend the Gmail module/class into a class of my own. Essentially, this is a generic example of what I'm trying to do:
module Foo
class << self
def example
puts :this_is_foo
end
end
end
class Bar
extend Foo
end
Then I should be able to call:
Bar.example
But I get the following exception:
NoMethodError: undefined method `example' for Bar:Class
How do I make methods that are available in Foo available in Bar in the above example?

You can use included for your goal:
module Foo
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def example
puts :this_is_foo
end
end
end
class Bar
include Foo
end
Bar.example
this_is_foo
#=> nil
Or if you want to include only class methods, you can make your example method instance and extend Bar module:
module Foo
def example
puts :this_is_foo
end
end
class Bar
extend Foo
end
Bar.example
this_is_foo
#=> nil

Related

How to access class method from the included hook of a Ruby module

I'd like my module to define new instance methods based on its including class' instance methods. But in the included hook, the class methods are not defined yet (as the module is included at the top of the class, before the class methods are defined):
module MyModule
def self.included(includer)
puts includer.instance_methods.include? :my_class_method # false <- Problem
end
end
class MyClass
include MyModule
def my_class_method
end
end
I want the users of the module to be free to include it at the top of their class.
Is there a way to make a module define additional methods to a class?
Note: I don't have to use the included hook if there is another way to achieve this.
There'a a method_added callback you could use:
module MyModule
def self.included(includer)
def includer.method_added(name)
puts "Method added #{name.inspect}"
end
end
end
class MyClass
include MyModule
def foo ; end
end
Output:
Method added :foo
If you want to track both, existing and future methods, you might need something like this:
module MyModule
def self.on_method(name)
puts "Method #{name.inspect}"
end
def self.included(includer)
includer.instance_methods(false).each do |name|
on_method(name)
end
def includer.method_added(name)
MyModule.on_method(name)
end
end
end
Example:
class MyClass
def foo ; end
include MyModule
def bar; end
end
# Method :foo
# Method :bar

Can I overwrite instance method from module?

I know, I can overwrite class method from module this way
class Foo
class << self
def some_static_method
puts 'some_static_method'
end
end
end
module BAR
class << Foo
def some_static_method
puts 'another_static_method'
end
end
end
class Foo
include BAR
end
Foo.some_static_method # => 'another_static_method'
Is it possible for an instance method?
You can do the following:
class Foo
def self.some_static_method; puts "Hello from Foo" end
end
module Bar
def self.included(base)
base.instance_eval do
def some_static_method; puts "Hello from Bar" end
end
end
end
class Foo
include Bar
end
Foo.some_static_method
This should work
UPDATE
To override instance method use:
class Foo
def some_instance_method; puts "Hello from Foo" end
end
module Bar
def self.included(base)
base.class_eval do
def some_instance_method; puts "Hello from Bar" end
end
end
end
class Foo
include Bar
end
Foo.new.some_instance_method
Your question is actually not about method overriding. It is about what class is referred to within a class ... construction in a module body.
When you do
module Bar
class << Foo
p self
end
end
# => #<Class:Foo>
the << Foo points to the singleton class of the Foo in the main environment because class << Foo cannot define the singleton class directly of a class Foo that has not been defined in advance. So it looks up for Foo that is already defined, and such class is found in the main environment.
When you do
module Bar
class Foo
p self
end
end
# => Bar::Foo
a new class Bar::Foo is created; the Foo points to this Bar::Foo that is newly created, and it does not point to the Foo in the main environment. In order to point to it, you have to explicitly specify that with ::.
module Bar
class ::Foo
p self
end
end
# => Foo
If you are using Ruby > 2.0.0 then what you can use is Module#prepend. Instead of include you can prepend an module and that way all of the module's methods are overriding any existing class instance methods with the same name. You can see a quick example here.
Prior to Ruby 2, Rails had introduced a similar hack: #alias_method_chain
Here is a nice comparison of the two approaches.

Can I "retroactively" add class methods from a module (after it's already been included)?

In Ruby, I can do this:
module Foo
end
class Bar
include Foo
end
module Foo
def do_something_instancey
puts "I'm an instance!"
end
end
Then, if I instantiate a Bar object, I can call do_something_instancey on it:
b = Bar.new
b.do_something_instancey
However, if I do this...
module Foo
def self.included(base)
def base.do_something_classy do
puts "I'm a class!"
end
end
end
My understanding is that because I included Foo in Bar before defining that class method, I cannot call Bar.do_something_classy because it never got "attached" to Bar.
I realize that might be slightly inaccurate/not really the right terminology. Regardless, is there a way, in the above example, to attach a class method to Bar from Foo after the module has already been included?
Here's an example for both, class and instance methods:
module Foo
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
end
end
class Bar
include Foo
end
module Foo
def do_something_instancey
puts "I'm an instance!"
end
module ClassMethods
def do_something_classy
puts "I'm a class!"
end
end
end
b = Bar.new
b.do_something_instancey
# => I'm an instance!
Bar.do_something_classy
# => I'm a class!
To add class methods to each class that has (already) included a specific module, you could traverse Ruby's ObjectSpace:
ObjectSpace.each_object(Class) do |klass|
if klass.include? Foo
klass.define_singleton_method(:do_something_classy) do
puts "I'm a class!"
end
end
end
Description of retroactive_module_inclusion gem:
This gem circumvents the "dynamic module include" (aka "double
inclusion") problem, which is the fact that M.module_eval { include N
} does not make the methods of module N available to modules and
classes which had included module M beforehand, only to the ones that
include it thereafter. This behaviour hurts the least surprise
principle, specially because if K is a class, then K.class_eval {
include M } does make all methods of M available to all classes
which had previously inherited it.

adding class specific functionality in ruby modules

possibly I'm not explaining the concept very well, but I'm looking to add class methods to a series of ruby classes to enable them to hold class specific information which will then be called by individual instance methods of the classes.
I can make it work, but it is a bit ugly. Can anyone as it requires 2 modules, one included and the other extended (see example code below).
Can anyone think of a more elegant way of implementing this functionality ?
Thanks
Steve
This module is extended to give class methods but adding an instance member to each class it is included in
module My1
def my_methods (*sym_array)
#my_methods=sym_array
end
def method_list
#my_methods
end
end
This module is included to give instance methods
module My2
def foo
self.class.method_list.each { |m| self.send m }
end
end
Now use the modules - the ugliness is having to use an include and extend statement to allow me to pass a set of symbols to a class method which will then be implemented in an
instance
class Foo
extend My1
include My2
my_methods :baz
def baz
puts "Baz!"
end
end
class Bar
extend My1
include My2
my_methods :frodo
def frodo
puts "Frodo!"
end
end
class Wibble < Bar
extend My1
include My2
my_methods :wobble
def wobble
puts "Wobble!"
end
end
Here is the required output - note that each class has its own instance #my_methods so the behaviour is different for the derived class Wibble < Bar
f=Foo.new
b=Bar.new
w=Wibble.new
f.foo #=> "Bar!"
b.foo #=> "Frodo!"
w.foo #=> "Wobble!"
When a module is included, a hook is called on it. You can use that to do the extend you want.
module M1
def self.included(base)
base.extend(M2)
end
end
People often call that second module M1::ClassMethods. If you're using rails, ActiveSupport::Concern encapsulates this pattern
I would suggest to use a hook from module instead:
module MyModule
def self.included(klass)
klass.extend ClassMethods
end
def foo
self.class.method_list.each{ |m| self.send m }
end
module ClassMethods
attr_reader :method_list
def my_methods(*sym_array)
#method_list = sym_array
end
end
end
So it simplifies to call include only a module whenever you want the functionality to given classes.
class Foo
include MyModule
my_methods :baz
def baz
puts "Baz!"
end
end

ruby - extend module inside another module

I'm trying to define a couple of modules to easily add in some instance and class methods to other classes, here's what I'm doing:
module Foo
module Bar
def speak
puts "hey there"
end
end
module Baz
extend Foo::Bar
def welcome
puts "welcome, this is an instance method"
end
end
end
class Talker
include Foo::Baz
end
Talker.new.welcome
Talker.speak
The output of this is:
welcome, this is an instance method
undefined method 'speak' for Talker.class (NoMethodError)
I was expecting Talker to have the 'speak' method since it includes Foo::Baz which itself extends Foo::Bar.
What am I missing?
You can try this:
module Baz
extend Foo::Bar
def self.included(base)
base.send :extend, Foo::Bar
end
def welcome
puts "welcome, this is an instance method"
end
end
This will auto-extend all classes in wich Baz is included.
PS:
extend Foo::Bar in module Baz was in original snippet, this code do not influence on method def self.included(base).
try this:
class Talker
extend Foo::Baz
end
since you want to call Talker.speak as a class method and not as an instance method (like Talker.new.speak) you have to include the Foo:Baz in a way that the class will take the methods itself.
One possibility is to use 'extend' (as above) the other is modifying it's eigenclass:
class Talker
class << self
include Foo::Baz
end
end

Resources