Using helper methods in Ruby modules - ruby

I'm having some trouble understanding how to incorporate my own helper methods into a Ruby module.
My code:
module MyModule
def self.foo
bar
end
def bar
# helper for MyModule.foo
end
end
MyModule.foo
#=> NameError: undefined local variable or method `bar' for MyModule:Module
I'm not sure why MyModule cannot recognize the bar method. What aspect of scope in Ruby am I being oblivious to?

Modules can be integrated into classes as mixins. So, you need to include it in a class so it can be used with instance of that class.
As of now, you can make bar as your module method so it can be accessed as is.
module MyModule
def self.foo
bar
end
def self.bar
puts "Now it works"
end
end
MyModule.foo #=> Now it works
Ruby Docs
A Module is a collection of methods and constants. The methods in a
module may be instance methods or module methods. Instance methods
appear as methods in a class when the module is included, module
methods do not.

You are trying to call an instance method from a class method. You would have to write
module MyModule
def MyModule.foo
MyModule.bar
end
# Or you can have it this way
def MyModule::bar
# helper for MyModule.foo
end
end
MyModule.foo
to get what you want.

You're missing the scope of a method (module as well as instance) and basically it's lifecycle..
Module method way: -
Following is how you define the module with module methods.
module MyModule
def self.foo
puts "called self.foo"
bar
end
def self.bar
puts "self.bar got called"
# helper for MyModule.foo
end
end
Now, This way, you do not have to instantiate any object to call those methods.. Here is how you would call the methods (one inside the other)
MyModule.foo
Using a class to instantiate the Module and calling methods will not work as they are not instance methods.
Output -
called foo
bar got called
Instance method way: - Following is how you'll define the module with instance methods so that they will work between classes and objects..
module MyModule
def foo
puts "called foo"
bar
end
def bar
puts "bar got called"
# helper for MyModule.foo
end
end
class TestModule
include MyModule
end
Choosing to use the module methods this way you have to call the methods inside module as per below -
#instantiating module MyModule via class
myinstance = TestModule.new
myinstance.foo
Output -
called foo
bar got called

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

Creating class level methods in a module in Ruby

Why doesn't this work?
module Greeter
def self.greet
puts "anyang"
end
end
Greeter.greet # => anyang
class KoreanKid
include Greeter
greet
end
# !> NameError : undefined local variable or method `greet' for KoreanKid:Class
KoreanKid.greet
# !> NoMethodError : undefined method `greet' for KoreanKid:Class
When I call greet right inside the KoreanKid class, that's just simply calling a class method right? That's the same thing as KoreanKid.greet right? Why doesn't the above work?
In my module, I'll have a mix of class methods and instance methods... how do I get both to work cleanly?
Kernel#include adds the existing methods in the module as instance methods of the class. To add class methods, you have to use Kernel#extend:
module Foo
def bar
42
end
end
class Baz
extend Foo
end
Baz.bar # => 42
Note that the methods that we extended were instance methods in the original module.
A popular way to do both is to use the Module#included hook to also extend:
module Foo
def bar
:wut
end
def self.included(target)
target.extend ClassMethods
end
module ClassMethods
def baz
:indeed
end
end
end
class Test
include Foo
end
Test.new.bar # => :wut
Test.baz # => :indeed

How to mixin some class methods and some instance methods from a module in Ruby?

I have a module and would like to mixin some methods as class methods and some as instance methods.
For example:
module Foo
def self.class_method
end
def instance_method
end
end
class Bar
include Foo
end
Usage:
Bar.class_method
Bar.new.instance_method
Is it possible to do this in Ruby?
If not, is it possible to define which methods are class methods and which are instance methods within the Bar class?
I don't want the same method defined as both a class and instance method.
This pattern is very common in Ruby. So common, in fact, that ActiveSupport::Concern abstracts it a bit.
Your typical implementation looks like this:
module Foo
def self.included(other_mod)
other_mod.extend ClassMethods
end
def instance_method
end
module ClassMethods
def class_method
end
end
end
class Bar
include Foo
end
You can't accomplish this easily as you describe without somehow splitting the included module into multiple pieces, though, unfortunately.
You can, but not quite like that. This is a common pattern for including both instance and class methods in one module.
module Foo
def self.included(base)
base.extend ClassMethods
end
def instance_method
puts 'instance'
end
module ClassMethods
def class_method
puts 'class'
end
end
end
class Bar
include Foo
end
bar = Bar.new
Bar.class_method #=> 'class'
bar.instance_method #=> 'instance'
You are close. You probably noticed that the instance method works fine. The problem with the class method is that self => Foo when it's defined, so it does not respond to Bar. If you add the line puts "I'm a module method" in self.class_method, you will find
Foo.class_method => "I'm a module method"
Here's an easy way to accomplish what you want to do:
module Foo_class
attr_accessor :cat
def class_method
puts "I'm a class method"
end
end
module Foo_instance
def instance_method
puts "I'm an instance method"
end
end
class Bar
extend Foo_class
include Foo_instance
end
Bar.class_method #=> I'm a class method
Bar.cat = "meow"
Bar.cat #=> "meow"
Bar.new.instance_method #=> I'm an instance method
I added a class instance variable, #cat, and an accessor for it, just to show how easy that is to do.
Object#extend is great, because you can just add instance variables and methods to a module, just as you would do with Object#include to mixin instance variables and methods, and extend mixes them in as class instance variables and class methods. You can also do this:
bar = Bar.new
bar.extend Foo_class
to have the instance variables and methods in Foo_class apply to the instance bar.

Why 'self' method of module cannot become a singleton method of class?

module Test
def self.model_method
puts "this is a module method"
end
end
class A
include Test
end
A.model_method
this will be error with:
undefined method `model_method' for A:Class (NoMethodError)
But when I use metaclass of A. it works:
module Test
def model_method
puts "this is a module method"
end
end
class A
class << self
include Test
end
end
A.model_method
Can someone explain this?
If you want to have both class methods and instance methods mixed into a class when including a module, you may follow the pattern:
module YourModule
module ClassMethods
def a_class_method
puts "I'm a class method"
end
end
def an_instance_method
puts "I'm an instance method"
end
def self.included(base)
base.extend ClassMethods
end
end
class Whatever
include YourModule
end
Whatever.a_class_method
# => I'm a class method
Whatever.new.an_instance_method
# => I'm an instance method
Basically to over-simplify it, you extend to add class methods and you include to add instance methods. When a module is included, it's #included method is invoked, with the actual class it was included in. From here you can extend the class with some class methods from another module. This is quite a common pattern.
See also: http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
Including a module is analogous to copying its instance methods over.
In your example, there are no instance methods to copy to A. model_method is actually an instance method of Test's singleton class.
Given:
module A
def method
end
end
This:
module B
include A
end
Is analogous to this:
module B
def method
end
end
When you think of it this way, this makes perfect sense:
module B
class << self
include A
end
end
B.method
Here, the methods are being copied to the B module's singleton class, which makes them the "class methods" of B.
Note that this is exactly the same thing as:
module B
extend A
end
In reality, the methods are not being copied; there is no duplication. The module is simply included in the method lookup list.

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