5

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
Trần Kim Dự
  • 5,872
  • 12
  • 55
  • 107
  • Why not add the class to the module? – user000001 Feb 04 '17 at 11:29
  • @user000001 as I said, this is a simplified code. the real thing is I implement again ruby-liked **include** and **extend** keyword. Because of this, I need to import all functions programmatically from module to class. I can do this now. The only thing I cannot now is sharing scope between two. – Trần Kim Dự Feb 04 '17 at 11:32

3 Answers3

6

Updated answer

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

Original answer

Usual way

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?)

Your way

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.

As name parameter :

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

With klass parameter :

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

With 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

With 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.

Community
  • 1
  • 1
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
  • thanks for nice answer. My purpose is trying to re-implementing **extend** keyword, that why I don't want to use: 1. extends keyword (obvious). 2. add parameters to module. (because the implementation of module doesn't need to know who should really call this). – Trần Kim Dự Feb 04 '17 at 14:27
  • thanks. I just try to re-implement **extend** as an exercise. I want to test if anything in Ruby can be done by using its core language (such as attr_accessor, alias ...) can be implemented by using Ruby metaprogramming. So maybe as you said, **extend** and **include** in Ruby is a part of Ruby core language and must be done at C++ layer. – Trần Kim Dự Feb 04 '17 at 14:46
  • `C`, not `C++` ;). I'd say that you can implement `my_extend` with plain Ruby. You need to define it and use it somehow, in order to tell Ruby that `SimpleClass` has been "my_extended" with `SimpleModule`. This information was missing from your question. BTW, it sounds that Rubinius would interest you. – Eric Duminil Feb 04 '17 at 14:53
  • thanks. I have edited my question for including what have I done. Please take a look. thanks. – Trần Kim Dự Feb 04 '17 at 15:04
  • thanks so much :D nice answer. I could I can 1000 upvotes for both of you ^^^ – Trần Kim Dự Feb 05 '17 at 05:53
4

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:

  1. As a namespace, e.g. containing def self.method_name
  2. As a mixin, e.g. containing 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
7stud
  • 46,922
  • 14
  • 101
  • 127
  • 1
    Nice code and good explanation. We came up with a very similar solution at almost the same time. I wasn't happy with `module_name` or `modul`, `module_` is a good compromise. I didn't consider private methods, and your methods don't accept arguments or blocks ;) – Eric Duminil Feb 05 '17 at 00:31
  • @EricDuminil, *your methods don't accept arguments or blocks*--Child's play! Too much of a pain to type out when hacking. *We came up with a very similar solution at almost the same time*--I posted my solution an hour ago. Then I went to work on the private methods, which were a little trickier, and I added them to my example. – 7stud Feb 05 '17 at 00:33
  • I didn't mean to say it's an insurmountable problem. Just that it could be added. – Eric Duminil Feb 05 '17 at 00:35
  • Wow. so great :D thanks both of you so much. The nicest part here is `meth.bind(self).call`. I don't know this before, so I am headache for thinking how to share scope from class to module. I have asked a similar question here: [custom include ruby](http://stackoverflow.com/questions/42036886/ruby-metaprogramming-cannot-send-a-method-to-a-module). That people here said that we can't not implemented **custom-include** on my own :D (because I cannot call a module instance method directly). – Trần Kim Dự Feb 05 '17 at 05:48
  • **Note** I have seen again. I don't know why original author have removed some comments. But because of that, it makes me give up on implement **custom-include** and jump to **custom-extend** :D – Trần Kim Dự Feb 05 '17 at 05:51
  • @TrầnKimDự, *The nicest part here is meth.bind(self).call. I don't know this before*--Neither did I. It's just the path I was led down. The first breakthrough was discovering `instance_method()` in class Module. Then after several failures trying `bind()`, I was able to create the proper context to get ruby to assign the right value to self to make bind() work. *I don't know why original author have removed some comments.*--I'm the original author, but I didn't remove any comments. *But because of that, it makes me give up on implement custom-include*--custom-include is in my answer. – 7stud Feb 05 '17 at 07:37
  • @7stud wow. short line of code with amazing background insight :D I don't say about your post but my question from link in my comment: in that topic, Cary Swoveland said that he cannot implement **include** using only-ruby :D (now he deleted that comment). Your answer is fully understandable for **custom extend** and **custom-include**. thanks. – Trần Kim Dự Feb 05 '17 at 07:55
  • @TrầnKimDự, *Cary said...*--Ah, I see. Here's some more background: `instance_method()` returns an UnboundMethod, but the UnboundMethod docs say that you have to bind to an instance of a subclass of a module. No subclass involved here, so I concluded that bind wouldn't work. Later, having run out of ideas, I thought I would try bind() anyway, but I didn't get the error specified in the docs: *'bind': bind argument must be an instance of B`*. So, I kept playing around with bind fully expecting to get that error once I got rid of the error I was getting. Never happened. :) – 7stud Feb 05 '17 at 08:38
  • Wow. nice work. looks like if we put `self` Ruby won't throw exception. Because someone on StackOverFlow meet same error as you said. In this link: [Is it possible to force bind to instances of other classes?](http://stackoverflow.com/questions/13234284/ruby-unbound-methods-is-it-possible-to-force-bind-to-instances-of-other-classes) So maybe there is some "magical thing" here. – Trần Kim Dự Feb 05 '17 at 09:24
  • Very good answer. I modified my answer to implement a custom `extend` (subsequently purged). Not only did I do it the same way you did, but it even involved a "cat" and a "dog". The only explanation I can think of is that we had a subconscious mindlink. Anyway, I obviously think that approach is the way to go. – Cary Swoveland Feb 05 '17 at 20:57
0

Simplify question

You can simply your question by modifying your code as follows.

module SimpleModule
  def self.class_hello
    puts "hello from #{@@name}"
  end
end

class SimpleClass
  @@name = 'StackOverFlow'
end

SimpleModule.class_hello
  # NameError: uninitialized class variable @@name in SimpleModule

Which class?

Clearly, the module's module method class_hello must be informed of the class whose class variable &&name is desired. We therefore must give that class as an argument of class_hello. We then use [Module#class_variable_get](https://ruby-doc.org/core-2.2.1/Module.html#method-i-class_variable_get ) to extract the class variable's value.

module SimpleModule
  def self.class_hello(klass)
    puts "hello from #{ klass.class_variable_get(:@@name) }"
  end
end

SimpleModule.class_hello(SimpleClass)
  # hello from StackOverFlow
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100