I am trying to modularize some Ruby code by organizing methods into separate modules. Originally I had something like this:
class Joe
attr_accessor :name
def self.arms
2
end
def self.legs
2
end
end
I tried doing 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 is not working is the attr_accessor
. I've tried all different combinations of include
/extend
, def self.included(base); base.extend
and I can't seem to find the right combination to make everything work together. How can I do this?
Update: I think the part that I left out was that each of the modules could potentially have both instance methods and class methods. So I currently 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 feels messy. It also feels like the Person
module knows too much about the instance methods and the class methods. If I were to modify the Name
module to remove the empty/unused ClassMethods
module, I would also have to modify the Person
class.
In Ruby, the attr_* methods are in charge of the member access control. The attr method creates an instance variable and a getter method for each attribute name passed as argument. An argument can be a Symbol or a String that will be converted to Symbol
That’s where attr_accessor comes in. You can tell Ruby to create these methods for you with attr_accessor. This is a Ruby method that creates other methods for you. What methods? These are the same methods we created before…
@Angelfirenze, git has nothing to do with attr_accessor. Git is a version control software, whereas attr_accessor is a method in Ruby. Let's say you have a class Person. class Person end person = Person.new person.name # => no method error Obviously we never defined method name. Let's do that.
In many programming languages, this concept is implemented by using getters and setters for each member. In Ruby, the attr_* methods are in charge of the member access control. The attr method creates an instance variable and a getter method for each attribute name passed as argument.
include
is defined in Module
, and therefore can only be called on modules and classes (which are modules). It adds the constants, (instance) methods and (module) variables from the given module(s) to the receiver by calling append_features
.
extend
on the other hand is defined in Object
, i.e. it is not restricted to modules and classes. It adds the instance methods from the given module(s) to the receiver or, more precisely, to the receiver's singleton class.
Here's an example module with an instance method hello
:
module Mod
def hello
"Hello from #{self.class} '#{self}'"
end
end
If we extend
an 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 a class, then hello
becomes a class method:
String.extend(Mod)
String.hello
#=> "Hello from Class 'String'"
This is because class methods are really just instance methods defined in the singleton class of a class.
That said, there are several options to define both, class and instance methods, by calling extend
and / or include
:
extend
and include
This is the most basic one, you could move include Name
from Person into Joe
:
module Person
include Arms, Legs
end
class Joe
extend Person
include Name
end
extend
and include
in superclassOr you could make Person
a class that extend
s and include
s the other modules and use it as Joe
's superclass:
class Person
extend Arms, Legs
include Name
end
class Joe < Person
end
The next options involve some Ruby magic - they use a callback to invoke include
upon extend
or vice versa:
include
from within extended
You could use the extended
callback to include Name
from within Person
:
module Person
include Arms, Legs
def self.extended(mod)
mod.include(Name)
end
end
class Joe
extend Person
end
extend
from within included
Or you could include Person
from within 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 from within Joe
but it might not be obvious (maybe even confusing?) that including or extending Person
also defines class or instance methods.
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