objects' method lookup path (modules & method override) - ruby

I cannot figure out how come the object is so smart to disregard the following block of code even if performed for the first time.
The method it is searching for is on each module
class A
include B,C,D,B
end
Does ruby keeps an array of module names on the side (as it obviously calls D)?

I'm not 100% I understand your question but I try my best...
Ruby actually remembers which modules are included into a class and merges these modules into the lookup path for methods. You can ask a class for its included methods my using A.included_modules
Methods from included modules are put on top of the modules defined in the current class. Please see this example:
class X
def foo
puts 'Hi from X'
end
end
module AnotherFoo
def foo
puts "Hi from AnotherFoo"
super
end
end
class Y < X
include AnotherFoo
end
y = Y.new
y.foo
# Hi from another foo
# Hi from X
class Y < X
include AnotherFoo
def foo
puts "Hi from Y"
super
end
end
y.foo
# Hi from Y
# Hi from another foo
# Hi from X
You can see the method resolution order: the child class -> the included modules -> the parent class. You can also see that modules are always included only once. So if a class already includes a module, it will not be re-included again.

Related

In Ruby, is it possible to have both Parent and child class in different Module

Is it possible to inherit a class which is in a separate module like Python allows? is there any other alternative that could be done without changing the structure drastically
File 1->
Module X
Class Y
end
end
File 2->
require relative 'path/to/File 1'
Module A
Class B
end
end
I want to inherit Y into B
It's possible like this,Parent class Hello is in module One and child class Hi is in module Two.
module X
class Y
def call_me
puts 'Hello Class'
end
end
end
module A
class B < X::Y
def call_me
puts 'Hi Class'
end
end
end
A::B.new.call_me
output
Hi Class
Is it possible to inherit a class which is in a separate module like Python allows?
Classes aren't "in modules", so the "module" part of your question is actually irrelevant. The rest of your question essentially boils down to "Is it possible to inherit a class", which is possible.
Constants can be namespaces within modules, but that does not create any relationship whatsoever between the class and the module. In your example, the class referenced by the constant Y is not in the module referenced by the constant X and the class referenced by the constant B is not in the module referenced by the constant A.
The constant Y is namespaced inside the module referenced by the constant X, but that only creates a relationship between the constant and the module, not between the class referenced by the constant and the module.
You can prove the fact that there is no relationship easily by simply assigning the class to a different variable:
foo = X::Y
class A::B < foo; end
Note: please don't let the default value of Class#name fool you. The default value of Class#name is indeed the full path of the first constant that the class is assigned to, even if you remove that constant again later:
module M
class C; end
end
module A
B = M::C
end
M::C = nil
M.send(:remove_const, :C)
M = nil
self.class.send(:remove_const, :M)
foo = A::B
A::B = nil
A.send(:remove_const, :B)
A = nil
self.class.send(:remove_const, :A)
# *None* of the constants exist anymore.
foo.name
#=> 'M::C'
# The `Class#name` property still stays the path of the very first constant

Ruby - gain class and instance methods from a module which includes another module?

Apologies for the title, suggestions to make it clearer are welcome.
I have created a module (we'll denote this by M) which, when included inside a class, will cause it to obtain new class methods and instance methods (Apologies if my terminology is incorrect). This is achieved by = class F including the A::ModuleInstanceMethods module in the code below.
Now that I've done that, I am trying to create a new module (we'll call this new module M') which includes the module M, such that when M' is included in a class, the class should gain the appropriate class and instance methods as per class F. This is where I get stuck. Examples of such a class is G in the code below.
I'd also like classes which include module M'' (module M'' will includes M') to have the same functionality. An example will be class H in the code below. The same should go for classes which include M''' (which itself includes M''), classes which M'''' (which itself includes M'''), and so on. It's pretty similar to an inheritance hierarchy.
If my textual explanation is confusing, do read the code below. In particular, I'd like to resolve the failures caused by calling G.class_method_one and H.class_method_one, but I lack the knowledge to do so.
I know it is possible to just extend the A::ModuleClassMethods module in the classes that I'm interested in, but I wish to avoid doing this. The same could also be achieved by manually adding the portion of the self.included function in A::ModuleInstanceMethods with the base.instance_of? Class, but if possible I'd like to do it programatically instead of copying and pasting the same code in many different sites.
module A
module ModuleClassMethods
def class_method_one
2
end
end
module ModuleInstanceMethods
def instance_method_one
3
end
def self.included(base)
if base.instance_of? Class
base.extend(A::ModuleClassMethods)
elsif base.instance_of? Module
# Intended functionality:
# When modules that `include` A::ModuleInstanceMethods are themselves
# included in a class (such as module `A::D` included in class `F`),
# class `F` will get the functions defined in the A::ModuleClassMethods
# module as class level methods
end
end
end
module D
include A::ModuleInstanceMethods
end
module E
include D
end
end
class F
include A::ModuleInstanceMethods
end
class G
include A::D
end
class H
include A::E
end
F.class_method_one # 2
F.new.instance_method_one # 3
G.new.instance_method_one # 3
# below statement fails
# G.class_method_one
H.new.instance_method_one # 3
# below statement fails
# H.class_method_one
Thank you.
I've seem to have figured it out. This solution makes use of module_eval. For modules, it adds a self.included function which calls A::ModuleInstanceMethods.included. I wouldn't mind learning about more elegant solutions.
module A
module ModuleClassMethods
def class_method_one
2
end
end
module ModuleInstanceMethods
def instance_method_one
3
end
def self.included(base)
if base.instance_of? Class
base.extend(A::ModuleClassMethods)
elsif base.instance_of? Module
base.module_eval {
def self.included(base)
A::ModuleInstanceMethods.included(base)
end
}
end
end
end
end

Refer to a class without explicitly mentioning its namespace

I have a class within several modules: This::Is::A::Long::ClassName. Is there any way, within one script or method, to make ClassName available without having to reference the namespace? Instead of writing:
This::Is::A::Long::ClassName.do_something
This::Is::A::Long::ClassName.do_something_else
This::Is::A::Long::ClassName.do_something_different
is anything as below possible?
include This::Is::A::Long
ClassName.do_something
ClassName.do_something_else
ClassName.do_something_different
If you are using modules for namespacing, the code you posted should work, see this example:
module Long
module Name
class ClassName
end
end
end
ClassName
# => ... uninitialized constant ClassName (NameError)
include Long::Name
ClassName
# => Long::Name::ClassName
Ruby has no equivalent to C++ using namespace, and you can not reference a class without being in the right namespace, but you can always make it a variable since a class is also an object
long_class = This::Is::A::Long::ClassName
long_class.do_something
long_class.do_something_else
# and so on
EDIT
An include does not put you in the right namespace, it includes the methods & classes in the module you are including (that is, it puts the module in the classes ancestors) and is therefore most certainly not suitable for your needs: Consider the following:
module This
module Is
module A
def foo
puts 'A#foo'
end
def bar
puts 'A#bar'
end
class ClassName
end
end
end
end
Now, you may not want to write This::Is::A::ClassName in another class, let's say:
class C
def foo
puts 'C#foo'
end
end
class B < C
include This::Is::A
end
Now, B.new.foo still puts out C#foo, right? Wrong. Since you included the module, the method has been overwritten.

Inherit from **another** class other than actual parent class

I've been doing some 'monkey-patching' (ahem excuse me superman-patching), like so, adding the below code and more to file(s) in my "#{Rails.root}/initializers/" folder:
module RGeo
module Geographic
class ProjectedPointImpl
def to_s
coords = self.as_text.split("(").last.split(")").first.split(" ")
"#{coords.last}, #{coords.first}"
end# of to_s
def google_link
url = "https://maps.google.com/maps?hl=en&q=#{self.to_s}"
end
end# of ProjectedPointImpl class
end# of Geographic module
end
I ended up realizing that there were two different _Point_ instances which I wanted to utilize these methods on (both were strings with the same formatting, i.e. Well-Known Text (WKT)), and added an exact copy of the above two methods to a certain RGeo::Geos::CAPIPointImpl class space.
I then, in my youthful, unexperienced way, after thinking about DRY (don't repeat yourself) principles, proceeded to create an ad hoc class which I presumed I might be able to inherit from for both
class Arghhh
def to_s
coords = self.as_text.split("(").last.split(")").first.split(" ")
"#{coords.last}, #{coords.first}"
end# of to_s
def google_link
url = "https://maps.google.com/maps?hl=en&q=#{self.to_s}"
end
end
and told my classes to inherit from it i.e.: ProjectedPointImpl < Arghhh
I was promptly responded to by ruby with this error when I stopped and then tried to reload my rails console:
`<module:Geos>': superclass mismatch for class CAPIPointImpl (TypeError)
...
I think my naivete in trying to get CAPIPointImpl (in this case) to inherit from another class than its parent also highlights my knowledge gap on this subject very explicitly
What methods might I be able to use to practically graft extra shared methods onto two classes coming from otherwise separate parents? Does ruby allow for these types of abstract exceptions?
What you need to do is to define the new methods in a module which you then "mix into" the existing classes. Here's a rough sketch:
# Existing definition of X
class X
def test
puts 'X.test'
end
end
# Existing definition of Y
class Y
def test
puts 'Y.test'
end
end
module Mixin
def foo
puts "#{self.class.name}.foo"
end
def bar
puts "#{self.class.name}.bar"
end
end
# Reopen X and include Mixin module
class X
include Mixin
end
# Reopen Y and include Mixin module
class Y
include Mixin
end
x = X.new
x.test # => 'X.test'
x.foo # => 'X.foo'
x.bar # => 'X.bar'
y = Y.new
y.test # => 'Y.test'
y.foo # => 'Y.foo'
y.bar # => 'Y.bar'
In this example we have two existing classes X and Y. We define the methods we would like to add to both X and Y in a module which I've called Mixin. We can then reopen both X and Y and include the module Mixin into them. Once that's done, both X and Y have their original methods plus the methods from Mixin.

Accessing module methods with ::

Documentation I've read tells me to use Module.method to access methods in a module. However, I can use Module::method as well. Is this syntactic sugar, or am I confused?
module Cat
FURRY_LEVEL = 4
def self.sound
%w{meow purr hiss zzzz}.sample
end
end
puts Cat.sound # This works.
puts Cat::sound # This also works. Why?!
puts Cat.FURRY_LEVEL # Expected error occurs here.
puts Cat::FURRY_LEVEL # This works.
Constant resolution always requires that you use ::.
Method invocation is idiomatically and usually a period (.), but :: is also legal. This is not just true for so-called module methods, but for invoking any method on any object:
class Foo
def bar
puts "hi"
end
end
Foo.new::bar
#=> hi
It's not so much "syntax sugar" as it is simply alternative syntax, such as the ability to write if or case statements with either a newline, then and newline, or just then.
It is specifically allowed because Ruby allows methods with the same name as a constant, and sometimes it makes sense to think that they are the same item:
class Foo
class Bar
attr_accessor :x
def initialize( x )
self.x = x
end
end
def self.Bar( size )
Foo::Bar.new( size )
end
end
p Foo::Bar #=> Foo::Bar (the class)
p Foo::Bar(42) #=> #<Foo::Bar:0x2d54fc0 #x=42> (instance result from method call)
You see this commonly in Ruby in the Nokogiri library, which has (for example) the Nokogiri::XML module as well as the Nokogiri.XML method. When creating an XML document, many people choose to write
#doc = Nokogiri::XML( my_xml )
You see this also in the Sequel library, where you can write either:
class User < Sequel::Model # Simple class inheritance
class User < Sequel::Model(DB[:regular_users]) # Set which table to use
Again, we have a method (Sequel.Model) named the same as a constant (Sequel::Model). The second line could also be written as
class User < Sequel.Model(DB[:regular_users])
…but it doesn't look quite as nice.
The :: is called scope resolution operator, which is used to find out under what scope the method, class or constant is defined.
In the following example, we use :: to access class Base which is defined under module ActiveRecord
ActiveRecord::Base.connection_config
# => {:pool=>5, :timeout=>5000, :database=>"db/development.sqlite3", :adapter=>"sqlite3"}
We use :: to access constants defined in module
> Cat::FURRY_LEVEL
=> 4
> Cat.FURRY_LEVEL
=> undefined method `FURRY_LEVEL' for Cat:Module (NoMethodError)
The . operator is used to call a module method(defined with self.) of a module.
Summary: Even though both :: and . does the same job here, it is used for different purpose. You can read more from here.

Resources