For example I have a module and a class:
module SimpleModule
def self.class_hello
puts "hello from #{@@name}"
end
end
class SimpleClass
@@name = 'StackOverFlow'
def self.test
SimpleModule.class_hello
end
end
Then I test by calling module method from class:
SimpleClass.test
I meet exception:
uninitialized class variable @@name in SimpleModule (NameError)
I know here because scope of module is not same as class scope. So my question is: How can I share SimpleClass scope for SimpleModule scope?
I put metaprogramming because here is just simple example, after that I will advanced by calling dynamic module from dynamic class. (that is the reason why I don't want to use some keyword such as include or extend)
@Edit In fact I want to implement Ruby extends on my own. Here is my already developed version:
# implementation
class Class
def custom_extend(module_name)
module_name.methods(false).each do |method|
define_singleton_method(method) do |*args, &block|
module_name.send(method, *args, &block)
end
end
end
end
And here is my custom module and class for testing:
# -------------------------------------------------------------
# Demonstration
module SimpleModule
def self.class_hello_world
puts 'i am a simple module boss'
end
def self.class_hello_name
puts "hello from #{@@name}"
end
end
class SimpleClass
custom_extend SimpleModule
@@name = 'StackOverFlow'
end
Here is my two test:
SimpleClass.class_hello_world # work
SimpleClass.class_hello_name # not work
As with class methods, you call a module method by preceding its name with the module's name and a period, and you reference a constant using the module name and two colons.
The module methods & instance methods can be accessed by both extend & include keyword. Regardless of which keyword you use extend or include : the only way to access the module methods is by Module_name::method_name.
define_method is a method defined in Module class which you can use to create methods dynamically. To use define_method , you call it with the name of the new method and a block where the parameters of the block become the parameters of the new method.
MetaProgramming gives Ruby the ability to open and modify classes, create methods on the fly and much more. A few examples of metaprogramming in Ruby are: Adding a new method to Ruby's native classes or to classes that have been declared beforehand. Using send to invoke a method by name programmatically.
Here's a slightly modified version of your code. No include
, extend
, append_features
or module_function
are needed. It wouldn't be hard to add custom_include
with the same structure.
UPDATE: Make sure to read @7stud's answer, with a similar structure and very good explanation.
class Class
def custom_extend(module_name)
module_name.instance_methods(false).each do |method|
define_singleton_method(method) do |*args, &block|
module_name.instance_method(method).bind(self).call(*args, &block)
end
end
end
end
module SimpleModule
def class_hello
puts "hello from #{@name}"
end
end
class SimpleClass
@name = 'class'
custom_extend SimpleModule
def self.test
class_hello
end
end
SimpleClass.test
#=> hello from class
The usual way would be :
module SimpleModule
def class_hello
puts "hello from #{@name}"
end
end
class SimpleClass
@name = 'StackOverFlow'
extend SimpleModule
def self.test
class_hello
end
end
SimpleClass.class_hello
but you don't want it. (why?)
In your code, SimpleClass
and SimpleModule
are totally independent from one another. It's clear that you get a NameError
. You need to somehow pass the name
information.
module SimpleModule
def self.class_hello(name='')
puts "hello from #{name}"
end
end
class SimpleClass
@@name = 'StackOverFlow'
def self.test
SimpleModule.class_hello(@@name)
end
end
module SimpleModule
def self.class_hello(calling_class=self)
calling_class.class_eval{
puts "hello from #{@name}"
}
end
end
class SimpleClass
@name = 'StackOverFlow'
def self.test
SimpleModule.class_hello(self)
end
end
SimpleClass.test
binding
parameter :module SimpleModule
def self.class_hello(b)
puts "hello from #{b.eval('@@name')}"
end
end
class SimpleClass
@@name = 'StackOverFlow'
def self.test
SimpleModule.class_hello(binding)
end
end
SimpleClass.test
my_ruby_extend SimpleModule
It surely can be done with a custom my_ruby_extend
. You'd need to show the desired syntax though, and what you already implemented.
This way, you could tell Ruby that SimpleClass
and SimpleModule
are linked. When a method or a variable isn't found in SimpleModule
, it could be sought in SimpleClass
.
I just try to re-implement extend as an exercise.
ruby's extend()
doesn't work like this:
module SimpleModule
def self.class_hello_world
puts 'i am a simple module boss'
end
def self.class_hello_name
puts "hello from #{@@name}"
end
end
class SimpleClass
custom_extend SimpleModule
For example, the following doesn't work:
module Dog
def self.greet
puts "hello"
end
end
class Cat
extend Dog
end
Cat.greet
--output:--
`<main>': undefined method `greet' for Cat:Class (NoMethodError)
extend()
works like this:
module Dog
def greet
puts "hello"
end
end
class Cat
extend Dog
end
Cat.greet
--output:--
hello
In other words, extend()
inserts the module instance methods--not the module methods(e.g. method names preceded by self
)--into Cat's singleton class (which is where Cat's class methods live). In ruby, include()
and extend()
have nothing to do with module methods (again, method names preceded by self
). Modules have two uses in ruby:
def self.method_name
def some_method
include() and extend() deal with #2.
The following solution doesn't work with @@variables
, but trying to figure out all the twist and turns that @@variables
exhibit in ruby is not worth the effort--just don't use them. Use class instance variables instead, i.e @variables
specified outside of any def's:
def my_extend(some_module)
singleton_class.include some_module
end
module Dog
def greet
puts @greeting
end
private
def sayhi
puts "hi"
end
end
class Cat
@greeting = "hello"
my_extend Dog
end
Cat.greet
#Cat.sayhi #=>`<main>': private method `sayhi' called for Cat:Class (NoMethodError)
Cat.class_eval {sayhi} #Change self to the Cat class so the implicit
#self variable that calls sayhi is equal to Cat
--output:--
hello
hi
Now, you just need to implement my_include
and substitute it inplace for include
. :)
Here's a shot at my_include()
:
class Class
def my_include(module_)
#For public and protected methods:
module_.instance_methods(include_super=false).each do |meth_name|
meth = module_.instance_method(meth_name)
define_method(meth_name) do
meth.bind(self).call
end
end
#For private methods:
module_.private_instance_methods(include_super=false).each do |meth_name|
meth = module_.instance_method(meth_name)
define_method(meth_name) do
meth.bind(self).call
end
private :"#{meth_name}"
end
end
end
module Dog
def greet
puts "hello"
end
def go
puts "run, run run"
end
private
def sayhi
puts "hi"
end
end
class Cat
my_include Dog
end
c = Cat.new
c.greet
c.go
c.sayhi
--output:--
hello
run, run run
#=>`<main>': private method `sayhi' called for #<Cat:0x007fc014136f60> (NoMethodError)
With my_extend()
:
class Class
def my_include(module_)
#For public and protected methods:
module_.instance_methods(include_super=false).each do |meth_name|
meth = module_.instance_method(meth_name)
define_method(meth_name) do
meth.bind(self).call
end
end
#For private methods:
module_.private_instance_methods(include_super=false).each do |meth_name|
meth = module_.instance_method(meth_name)
define_method(meth_name) do
meth.bind(self).call
end
private :"#{meth_name}"
end
end
def my_extend(module_)
singleton_class.my_include module_
end
end
module Dog
def greet
puts @greeting
end
private
def sayhi
puts "hi"
end
end
class Cat
@greeting = "hello"
my_extend Dog
end
Cat.greet
#Cat.sayhi #=>private method `sayhi' called for Cat:Class (NoMethodError)
Cat.class_eval {sayhi}
--output:--
hello
hi
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With