In Ruby, how can I apply attr_accessor from a module that is expanding?

I am trying to modulate some Ruby code by organizing methods into separate modules. Initially, I had something like this:

class Joe attr_accessor :name def self.arms 2 end def self.legs 2 end end 

I tried to do something like this:

 class Joe extend Person end module Person include Name include Arms include Legs end module Name attr_accessor :name end module Arms def arms 2 end end module Legs def legs 2 end end 

However, the part that does not work is attr_accessor . I tried all the various combinations include / extend , def self.included(base); base.extend def self.included(base); base.extend , and I can't find the right combination for everything to work together. How can i do this?


Update: I think the part that I left was that each of the modules could have both instance methods and class methods. So currently I have something like this:

 class Joe include Person end module Person include Name::InstanceMethods include Arms::InstanceMethods include Legs::InstanceMethods def self.included(base) base.extend Name::ClassMethods base.extend Arms::ClassMethods base.extend Legs::ClassMethods end end module Name module ClassMethods; end module InstanceMethods attr_accessor :name end end module Arms module ClassMethods def arms 2 end end module InstanceMethods; end end module Legs module ClassMethods def legs 2 end end module InstanceMethods; end end 

While this works, it seems messy. It also seems that the Person module knows too much about instance methods and class methods. If I were to modify the Name module to remove the empty / unused ClassMethods module, I would also have to change the Person class.

+7
ruby
source share
2 answers

include defined in Module and therefore can only be called on modules and classes (which are modules). It adds constants, (instances) of methods, and (modular) variables from the given module (s) to the receiver, calling append_features .

extend , on the other hand, is defined in Object , that is, it is not limited to modules and classes. It adds instance methods from the given module (s) to the receiver or, more precisely, to the monophonic receiver class.

Here's an example module with the hello instance method:

 module Mod def hello "Hello from #{self.class} '#{self}'" end end 

If we extend instance (as opposed to a class), then hello becomes an instance method:

 str = 'abc' str.extend(Mod) str.hello #=> "Hello from String 'abc'" 

If we extend the class, then hello will become a class method:

 String.extend(Mod) String.hello #=> "Hello from Class 'String'" 

This is because class methods are really just instance methods defined in a singleton class of the class.

However, there are several options for defining class and instance methods by calling extend and / or include :

1. extend and include

This is the easiest, you can move the include Name from Person to Joe :

 module Person include Arms, Legs end class Joe extend Person include Name end 

2. extend and include in the superclass

Or you can make a Person class that extend and include other modules, and use it as a Joe superclass:

 class Person extend Arms, Legs include Name end class Joe < Person end 

The following options include some Ruby magic โ€” they use a callback to call include after extend or vice versa:

3. include from extended

You can use the extended include Name callback from Person :

 module Person include Arms, Legs def self.extended(mod) mod.include(Name) end end class Joe extend Person end 

4. extend from included

Or you could include Person from Joe and use the included callback to call extend :

 module Person include Name def self.included(mod) mod.extend Arms, Legs end end class Joe include Person end 

3 and 4 look clean within Joe , but it may not be obvious (maybe confusing?) That including or extending Person also defines class or instance methods.

+9
source share

To get the required functionality correctly, you will need to change extend to include and provide the included block in your module. Thus, you can have both instances and class methods defined in one module, just include module and arrange your methods accordingly.

It may take a bit of code reorganization, but to help explain more, you can read this article on how modules work in Ruby.

If still unclear, you can look at class_eval .

0
source share

All Articles