I have this module
module MyModule
def self.foo()
puts "A"
end
end
And this mixin class
class ParentClass
include MyModule
...other code
end
Can I overwrite the foo method in a child class like this or is there a better way?
class ChildClass < ParentClass
def self.foo()
puts "B"
end
... other code
end
You cannot include class methods directly:
module MyModule
def self.foo() puts "A" end
end
class ParentClass
include MyModule
end
ParentClass.methods.include?(:foo)
#=> false
ParentClass.instance_methods.include?(:foo)
#=> false
Instead, use Object#extend, which converts instance methods in MyModule to class methods in ParentClass:
module MyModule
def foo() puts "A" end
end
class ParentClass
extend MyModule
end
ParentClass.methods.include?(:foo)
#=> true
ParentClass.foo
A
class ChildClass < ParentClass
def self.foo() puts "B" end
end
ChildClass.foo
B
So, you may ask, what's the point of having class methods in a module? They can be present when the module is not used as a mixin, or has a combination of instance and class methods. The module's class methods are simply helper functions:
module A
def self.say
puts "It's a cat."
end
def say
puts "It's a dog."
end
end
class B
include A
end
A.say
It's a cat
B.new.say
It's a dog.
It is my impression that most modules that contain class methods tend to contain no instance methods; that is, they are not used as mix-ins. Readers are encouraged to correct me if I am wrong about that.
A common way to include some instance methods and extend others is to use the callback Module#included:
module A
def a; end
def self.included(klass)
klass.extend B
end
module B
def b; end
end
end
class C
include A
end
C.instance_methods.include?(:a)
#=> true
C.methods.include?(:b)
#=> true
Related
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
Is it okay to instantiate a class A and assign it to a class variable in class B to call A's methods in B?
class A
...
end
class B
##a = A.new
def method_B
##a.method_A
end
end
You can wrap methods in a module and include them in your class(For instance methods only)
module Foo
def method_A
p 'hello'
end
end
class B
include Foo
def method_B
method_A
end
end
B.new.method_B
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
Say there are three classes: A, B & C. I want each class to have a class method, say self.foo, that has exactly the same code for A, B & C.
Is it possible to define self.foo in a module and include this module in A, B & C? I tried to do so and got an error message saying that foo is not recognized.
Yep
module Foo
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def some_method
# stuff
end
end
end
One possible note I should add - if the module is going to be ALL class methods - better off just using extend ModuleName in the Model and defining the methods directly in the module instead - rather than having a ClassMethods module inside the Module, a la
module ModuleName
def foo
# stuff
end
end
module Common
def foo
puts 'foo'
end
end
class A
extend Common
end
class B
extend Common
end
class C
extend Common
end
A.foo
Or, you can extend the classes afterwards:
class A
end
class B
end
class C
end
[A, B, C].each do |klass|
klass.extend Common
end
Rails 3 introduced a module named ActiveSupport::Concern which has the goal of simplifying the syntax of modules.
module Foo
extend ActiveSupport::Concern
module ClassMethods
def some_method
# stuff
end
end
end
It allowed us to save a few lines of "boilerplate" code in the module.
This is basic ruby mixin functionality that makes ruby so special.
While extend turns module methods into class methods, include turns module methods into instance methods in the including/extending class or module.
module SomeClassMethods
def a_class_method
'I´m a class method'
end
end
module SomeInstanceMethods
def an_instance_method
'I´m an instance method!'
end
end
class SomeClass
include SomeInstanceMethods
extend SomeClassMethods
end
instance = SomeClass.new
instance.an_instance_method => 'I´m an instance method!'
SomeClass.a_class_method => 'I´m a class method'
Just wanted to extend Oliver's answer
Define Class methods and instance methods together in a module.
module Foo
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def a_class_method
puts "ClassMethod Inside Module"
end
end
def not_a_class_method
puts "Instance method of foo module"
end
end
class FooBar
include Foo
end
FooBar.a_class_method
FooBar.methods.include?(:a_class_method)
FooBar.methods.include?(:not_a_class_method)
fb = FooBar.new
fb.not_a_class_method
fb.methods.include?(:not_a_class_method)
fb.methods.include?(:a_class_method)
In class Foo I'd like to include method Bar under certain conditions:
module Bar
def some_method
"orly"
end
end
class Foo
def initialize(some_condition)
if !some_condition
"bar"
else
class << self; include Bar; end
end
end
end
Is there any cleaner (and clearer) way to achieve the include in the method without having to do it inside the singleton class?
extend is the equivalent of include in a singleton class:
module Bar
def some_method
puts "orly"
end
end
class Foo
def initialize(some_condition)
extend(Bar) if some_condition
end
end
Foo.new(true).some_method # => "orly"
Foo.new(false).some_method # raises NoMethodError