accessing method in module of module ruby - ruby

I'm trying to modify existing ruby code, and ruby is not my first languange. Part of the code is like below:
#someFile1.rb
module A
module B
def somefunction()
end
end
end
class X::Y
include A::B
end
#someFile2.rb
module A
module C
def anotherfunction()
#somefunction() <-- error
end
end
end
class X::Y
include A::C
end
Somehow I can't access method somefunction() in anotherfunction.
How to access method defined in module B in method inside module C? Why it's not working?

Instance methods in modules aren't generally accessible until you mix them into a class and create an object of that class.
module A
module B
def some_method
"foo"
end
end
end
module A
module C
def another_method
some_method
end
end
end
class X
include A::B
include A::C
end
X.new.another_method
# => "foo"
But I'd say it isn't very elegant to have a module that depends upon the fact that some other module has also been mixed into the same object
Class methods in modules, on the other hand, are accessible like this:
module A
module B
def self.somefunction
"foo"
end
end
end
module A
module C
def self.another_function
A::B.somefunction
end
end
end
A::C.another_function
# => "foo"

Assuming you want to call the module functions themselves, you would first need to make them module functions (think static in Java or namespace in C++). Then you can use the :: (namespace resolution) operator. See foo and bar.
If you want to import them into classes, just import both, and both will be visible. See baz and qux.
module A
module B
def self.foo
puts "foo"
end
def baz
puts "baz"
end
end
end
module A
module C
def self.bar
puts "bar"
A::B::foo
end
def qux
puts "qux"
baz
end
end
end
class X
include A::B
include A::C
end
A::C::bar
x = X.new
x.qux
Output:
bar
foo
baz
qux

Related

How is it I can't access a module function from within a class from the same module in ruby?

I'm having some issues with the access rules of ruby. At one point I thought that something like this worked:
module Foo
def bar(x)
puts "#{x}"
end
class Baz
def initialize(x)
bar(x)
end
end
end
Foo::Baz.new(3)
but somehow it no longer does. I've tried to declare bar using self.bar and Foo.bar, but neither work. What am I missing?
The inclusion of the module methods isn't "automatic", I think because it can lead to unexpected behavior.
You can try making them a class method as you mentioned, or including the module and getting access to the methods there:
module Foo
class << self
def bar(x)
puts "#{s}" % x
end
def s
"s1"
end
end
def bar(x)
puts "#{s}" % x
end
def s
"s2"
end
class Baz
include Foo
def initialize(x)
bar(x)
Foo.bar(x)
end
end
end
Foo::Baz.new(3)
# s2
# s1

How to work through name collisions in ruby

Two modules Foo and Baa respectively define a method with the same name name, and I did include Foo and include Baa in a particular context.
When I call name, how can I disambiguate whether to call the name method of Foo or Baa?
Only the order of modules inclusion decides which one will get called. Can't have both with the same name - the latter will override the former.
Of course, you can do any tricks, just from the top of my head:
module A
def foo
:foo_from_A
end
end
module B
def foo
:foo_from_B
end
end
class C
def initialize(from)
#from = from
end
def foo
from.instance_method(__method__).bind(self).call
end
private
attr_reader :from
end
C.new(A).foo #=> :a_from_A
C.new(B).foo #=> :a_from_B
But that's no good for real life use cases :)
Technically, there is no name collision because the method foo is redefined.
In the following exemple, A.foo is redefined and is never called
module A
def foo
raise "I'm never called"
end
end
module B
def foo
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_B
If you write A and B module, you can use super to call previous definition of foo. As if it where an inherited method.
module A
def foo
puts :foo_from_A
end
end
module B
def foo
super
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_A
# foo_from_B
There are side effects and I would not use this but this is doing the trick :
module A
def foo
puts :foo_from_A
end
end
module B
def foo
puts :foo_from_B
end
end
class C
def self.include_with_suffix(m, suffix)
m.instance_methods.each do |method_name|
define_method("#{method_name}#{suffix}", m.instance_method(method_name))
end
end
include_with_suffix A, "_from_A"
include_with_suffix B, "_from_B"
end
c= C.new
c.foo_from_A
c.foo_from_B
begin
c.foo
rescue NoMethodError
puts "foo is not defined"
end
# =>
# foo_from_A
# foo_from_B
# foo is not defined
Provided none of the methods of Foo or Baa call name (which seems a reasonable assumption), one can simply create aliases.
module Foo
def name; "Foo#name"; end
end
module Baa
def name; "Baa#name"; end
end
class C
include Foo
alias :foo_name :name
include Baa
alias :baa_name :name
undef_method :name
end
c = C.new
c.foo_name
#=> "Foo#name"
c.baa_name
#=> "Baa#name"
C.instance_methods & [:foo_name, :baa_name, :name]
#=> [:foo_name, :baa_name]
The keyword alias is documented here. One may alternatively use the method #alias_method. See this blog for a comparison of the two.
Module#undef_method is not strictly necessary. It's just to ensure that an exception is raised if name is called.
You should definetely read about method lookups.
Anyway, I would do it this way:
module Foo
def name
:foo
end
end
module Bar
def name
:bar
end
end
class MyClass
include Foo
include Bar
def foo_name
Foo.instance_method(:name).bind(self).call
end
def bar_name
Bar.instance_method(:name).bind(self).call
end
#
# or even like this: obj.name(Foo)
#
def name(mod)
mod.instance_method(:name).bind(self).call
end
end
BTW if you are using Module#instance_method and UnboundMethod#bind you don't really need to include specific module. This code works:
Foo.instance_method(:name).bind('any object (e.g. string)').call

Using a module inside another module with the same name

I have a module like this
module A
module ClassMethods
def a
"a"
end
def b
a
end
end
def self.included(klass)
klass.extend ClassMethods
end
end
I want to factor b into it's own module because Bs are clearly not As. The b method depends on A, and I would like the methods defined in A to be available to B.
Doing this:
module B
module ClassMethods
extend A
def b
a
end
end
def self.included(klass)
klass.extend(ClassMethods)
end
end
It does not work. Nor does include. I tried moving include or extend as well as the how constants are references. What am I missing here?
I hope I understood what you wanted. I split up module A and module B. When I include those in a new class Klass, I have access to both method a and method b, whereas method b from module B depends on method a from module A:
module A
module ClassMethods
def a
"a"
end
end
def self.included(klass)
klass.extend(ClassMethods)
end
end
module B
module ClassMethods
# include A::ClassMethods here and you can do only 'include B' inside Klass
def b
"Called from B: %s" % a
end
end
def self.included(klass)
klass.extend(ClassMethods)
end
end
class Klass
include A, B
end
p Klass.a # => "a"
p Klass.b # => "Called from B: a"
As I understand you can define the same Module in different places, no need to say include, extend or anything. As you see neither Module A nor Module B uses those keywords, they both just define ClassMethods.
Of course, you will have to include module A wherever you use method b of module B or else the method call to a will fail. You can also do include A::ClassMethods inside B::ClassMethods, that way you only need to include B inside Klass.
Add the extend A action in the included callback of the module B instead of adding it in the ClassMethod definition, like the following code does:
module A
module ClassMethods
def a
"a"
end
end
def self.included(klass)
klass.extend ClassMethods
end
end
module B
module ClassMethods
def b
a
end
end
def self.included(klass)
klass.extend(ClassMethods)
klass.extend(A)
end
end
class C
include B
end
C.b
# => "a"

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.

Ruby: Is it possible to define a class method in a module?

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)

Resources