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 extends and includes 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