Ruby: how to extend module URI into my own derivated module - ruby

I'm used to extending classes to provide my own encapsulated functionnalities on top of a standard class.
As I'm building a more complete object than a basic URI, I have the option to just reopen the module and add my own class or instance method.
Though it could work, I don't think it is the most elegant way for that.
Let's say I'd like to call it Location, and behave like URI but with some extra class/instance methods. Let's take a basic example, a name method:
require 'uri'
PATH = 'http://example.com/dir1/dir2/file.txt'
module Location
extend URI
class Generic
attr_reader :dummy
def name
File.basename(#path)
end
end
end
e = URI.parse PATH
puts e.path
f = Location.parse PATH
puts f.path
puts f.name
puts f.dummy = "doh"
Here, the problem is that URI module's methods like parse are not available on my now Location module:
$ undefined method `parse' for Location:Module (NoMethodError)
Same error using include URI or extend URI
What is the correct way to extend URI module the way I described above, and to add instance variables (like dummy, here) along existing ones to add functionality ?
Thanks for any tips and advice.

URI.parse is defined as a class method on a module. when you include/extend a module, only its instance methods are copied over. If you use include, they're copied over as instance methods and with extend, as class methods.
That being said, with some metaprogramming it's possible to write a method that copies class methods from one module to another:
# This returns a list of symbols (all the added methods)
def copy_class_methods(from_klass, to_klass)
from_klass.methods(false).each do |method_name|
method_obj = from_klass.method(method_name)
to_klass.define_singleton_method(method_name, &method_obj)
end
end
Then, for example:
module Location
copy_class_methods(URI, self)
end
Location.parse("http://example.com/dir1/dir2/file.txt")
# => #<URI::HTTP http://example.com/dir1/dir2/file.txt>

Related

NoMethodError in ruby module

module Add
def addition
sum=1+2
puts sum
end
a=Add.addition
Can anyone tell me what I'm missing and why I am getting this error->
undefined method `addition' for Add:Module (NoMethodError)
You are confusing class methods and instance methods. Your definition:
module Add
def addition
...
end
end
defines methods on instances of Add whereas you called a method on the module Add. If you want to define a class/module method, you need to define like:
module Add
def self.addition
...
end
end
If you want to be able to call it directly, define it as a directly accessible method:
def self.addition
# ...
end
Or you can always rework this using:
module Add
# ...(methods)...
extend self
end
Where that will automatically promote all mixin-type methods as being directly accessible.
You can also tag them more selectively like this:
module Add
def addition
# ...
end
module_method :addition
end
That method is then available either as Add.addition or if some other module or class calls include Add.

Ruby metaprogramming: cannot send a method to a module

For example I have following custom class and module:
module SimpleModule
def hello_world
puts 'i am a SimpleModule method'
end
def self.class_hello_world
puts 'i am a SimpleModule class method'
end
end
class SimpleClass
def hello_world
puts 'i am SimpleClass method'
end
def self.class_hello_world
puts 'i am a SimpleClass class method'
end
end
I tried to called those methods inside class and module by using method send
SimpleClass.send(class_hello_world) # work
SimpleClass.new.send(hello_world) # work
SimpleModule.send(class_hello_world) # work
SimpleModule.new.send(hello_world) # not work
SimpleModule.send(hello_world) # not work
In other word, I don't know how to invoke hello_world from SimpleModule. It is possible if that method is defined with self before.
I need to do this because I want to implement a "custom-include": that include all methods from module to another class.
Please tell me how to do this.
The five statements
Let's consider those five statements one at a time (but in a different order than as presented). Note that send's argument must be the name of the method expressed as a string or symbol.
SimpleModule.send("class_hello_world")
# i am a SimpleModule class method
This is normal, though such methods are normally called module methods. Some common built-in modules, such as Math, contain module methods only.
SimpleClass.send(:class_hello_world)
# i am a SimpleClass class method
Since a class is a module, the behaviour is the same as above. class_hello_world is usually referred to as a class method.
SimpleClass.new.send(:hello_world)
# i am SimpleClass method
This is the normal invocation of an instance method.
SimpleModule.send("hello_world")
#=> NoMethodError: undefined method `hello_world' for SimpleModule:Module
There is no module method hello_world.
SimpleModule.new.send(hello_world)
#=> NoMethodError: undefined method `new' for SimpleModule:Module
One cannot create an instance of a module.
include vs prepend
Suppose one wrote
SimpleClass.include SimpleModule
#=> SimpleClass
SimpleClass.new.hello_world
# i am SimpleClass method
so SimpleClass' original method hello_world is not overwritten by the module's method by the same name. Consider SimpleClass' ancestors.
SimpleClass.ancestors
#=> [SimpleClass, SimpleModule, Object, Kernel, BasicObject]
Ruby will look for hello_world in SimpleClass--and find it--before considering SimpleModule.
One can, however, use Module#prepend to put SimpleModule#hello_world before SimpleClass#hello_world.
SimpleClass.prepend SimpleModule
#=> SimpleClass
SimpleClass.new.hello_world
# i am a SimpleModule method
SimpleClass.ancestors
#=> [SimpleModule, SimpleClass, Object, Kernel, BasicObject]
Binding unbound methods
There is one other thing you do. SimpleModule's instance methods (here just one) are unbound. You could use UnboundMethod#bind to bind each to an instance of SimpleClass and then execute it using call or send.
sc = SimpleClass.new
#=> #<SimpleClass:0x007fcbc2046010>
um = SimpleModule.instance_method(:hello_world)
#=> #<UnboundMethod: SimpleModule#hello_world>
bm = um.bind(sc)
#=> #<Method: SimpleModule#hello_world>
bm.call
#=> i am a SimpleModule method
sc.send(:hello_world)
#=> i am a SimpleModule method
Module's can't have instance methods because they aren't classes. If you define an instance method in a module it is called a mixin. These "mixins" can be included in another class and are then available for use.
The full explanation is here in the docs
Edit:
For example, you could do something like this:
module SimpleModule
def hello_world
p 'hello world'
end
end
class Example
include SimpleModule
end
Example.new.send(:hello_world)
This is one way to call the mixin of a module.
Since you seem to want to create your own version of include.
Take a look at Module.append_features documentation
When this module is included in another, Ruby calls append_features in
this module, passing it the receiving module in mod. Ruby's default
implementation is to add the constants, methods, and module variables
of this module to mod if this module has not already been added to mod
or one of its ancestors. See also Module#include.
So this is what you have to do if you want to rewrite include yourself.
And since you're adventurous, maybe you will enjoy reading Ruby's source code to see how exactly this is implemented internally. See https://github.com/ruby/ruby...class.c#L853:L934
You also can use constantize:
def call_method(method)
"SimpleModule::#{method}".constantize
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.

calling module method into another module in Ruby

FIle module.rb
module CardExpiry
def check_expiry value
return true
end
end
file include.rb
#raise File.dirname(__FILE__).inspect
require "#{File.dirname(__FILE__)}/module.rb"
module Include
include CardExpiry
def self.function
raise (check_expiry 1203).inspect
end
end
calling
Include::function
is this possible ?
Error trigger when calling :
`function': undefined method `check_expiry' for Include:Module (NoMethodError)
You stumbled over the difference of include and extend.
include makes the method in the included module available to instances of your class
extend makes the methods in the included module available in the class
When defining a method with self.method_name and you access self within that method, self is bound to the current class.
check_expiry, however, is included and thus only available on the instance side.
To fix the problem either extend CardExpiry, or make check_expiry a class method.
I've looked at your problem in a bit more detail, and the issue is your module.rb file:
module CardExpiry
def self.check_expiry value
return true
end
end
First, there was an end missing from the file - both def and module need to be closed.
Second, the magical self. in the def line turns the method into a pseudo-global function - this answer explains it better than I can.
Furthermore, to call the method, you need to use:
raise (CardExpiry::check_expiry 1203).inspect

How do i require and include a module in the initialize method

Im trying to dynamically require and then include modules inside a initialize method.
# file: object.rb
class Object
attr_accessor :array
def initialize array
#array = array
#array.each do |f|
require_relative "#{f}"
include f.capitalize # the method name is the same as the filename
puts #variable # property from included method
end
end
end
object = Object.new ['a','b','c']
with these module files
# file: a.rb
module A
#variable = 'string A'
end
and so on for b and c
i keep getting an error :
`block in initialize': undefined method `include'
What am i doing wrong here and is there a better way to achieve what i'm trying to do?
The reason that you can't call include in initialize like that is that include is a method that's only defined on classes and modules, but inside of an instance method like initialize the implicit receiver is an object of your class, not the class itself.
Since you only need the methods to be available on the newly created object, you can just use extend instead of include. extend is like a per-object version of include in that it adds the methods of the given module as singleton methods to an object rather than adding them as instance methods to a module or class.
require_relative "#{f}"
Note the quotation marks. '#{f}' is not interpolated.

Resources