Can I overwrite instance method from module? - ruby

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.

Related

Inheritance on different namespace

I have self-writed gem
module GemNamespace
class Foo; end
class Bar
def foo
#foo ||= Foo.new
end
end
end
Also I have application
module ApplicationNamespace
class Foo < GemNamespace::Foo; end
class Bar < GemNamespace::Bar; end
end
When I call foo method at my application it returned me instanceof GemNamespace object:
bar = ApplicationNamespace::Bar.new
puts bar.foo
=> #<GemNamespace::Foo:0x007f849d8169f0>
But I want get object of ApplicationNamespace how I can do this without redefine foo method
Your Problem is not, that you have several Namespaces, but that GemNamespace::Bar is tightly coupled to GemNamespace::Foo.
You could use something like this:
class Bar
def initialize(klass)
#klass = klass
end
def foo
#foo ||= #klass.new
end
end
So instead of only ever using GemNamespace::Foo within Bar, you could pass any class.
Your current version of the foo method will allways refer to GemNamespace::Foo because its context is set at definition (not at execution). Instead you could get the module of the current executing class dynamically. I don't think there is a build-in method that does this so you have to get it manually:
def foo
#foo ||= self.class.name.split("::")[0..-2].inject(Kernel) { |s, c| s.const_get c }.const_get("Foo").new
end
This will work for any number of nested modules.

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.

Get included method names

How I can get all instance method names in the baz method call, which are only present in the Bar module (without other instance methods of this class) ?
class Foo
include Bar
def a
end
def b
end
def baz
#HERE
end
end
class Foo
include Bar
def a
end
def b()
end
def baz
Bar.instance_methods(false)
end
end
puts Foo.new.baz

How do I extend a Ruby module in class context?

I have the following Ruby script, in which class Foo includes module Baz, module Baz has a included hook to make Bar extended by the including class (Foo). I am just wondering why:
class << klass
extend Bar #this doesn't work. Why?
end
does not work while:
klass.extend(Bar) works.
Here is my code:
#! /usr/bin/env ruby
module Baz
def inst_method
puts "dude"
end
module Bar
def cls_method
puts "Yo"
end
end
class << self
def included klass
# klass.extend(Bar) this works, but why the other approach below doesn't?
class << klass
extend Bar #this doesn't work. Why?
def hello
puts "hello"
end
end
end
end
end
class Foo
include Baz
end
foo = Foo.new
foo.inst_method
Foo.hello
Foo.cls_method
Within the body of class << klass, self refers to the singleton class of klass, not klass itself, whereas in klass.extend(Bar), the receiver is klass itself. The difference comes from there.
class A
end
class << A
p self # => #<Class:A> # This is the singleton class of A, not A itself.
end
p A # => A # This is A itself.
And since you want to apply extend to klass, doing it within the body of class << klass does not work.
What you want is invoke the extend method on the class object (klass) not the singleton class (class << klass).
Therefore the following code doesn't work because you are invoking the extend method on the singleton class:
class << klass
extend Bar # doesn't work because self refers to the the singleton class of klass
def hello
puts "hello"
end
end

Ruby Module Inclusion in Methods

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

Resources