Topic: Understanding Modules

I understand the way these things are working but I don't really
understand what's going on and why.

module Foo
  def bar
    1
  end
  def Foo.zim
    2
  end
end


>> Foo.zim # works, OK, fair enough
2
>> Foo::zim # works, but why have two syntaxes that do the same?
2
>> Foo.bar # NoMethodError. Shouldn't this work though? What if I had a procedural API that I wanted to namespace: -

module SomeNamespace
  require 'procedural_api'
end

SomeNamespace.function_from_procedural_api # this ain't gonna work!
SomeNamespace::function_from_procedural_api # nor is this

class SomeClass
  include Foo
end

>> SomeClass.new.bar # OK, it's been mixed in
1
>> SomeClass.zim # NoMethodError, huh? Why doesn't this work?
>> SomeClass.new.zim # NoMethodError. So where has the zim definition actually gone? Doesn't it get included at all?

I'm very confused by all this. Please help unravel the mess in my brain.

Re: Understanding Modules

Okay. Allow me to explain this to you.

Modules behave somewhat like classes that cannot be instantiated. You cannot access instance methods on them.

When you use either the Foo.zim or Foo::zim syntax to access that method, you are calling a "class method" on Foo, which you defined as "Foo.zim" (you could also have called it "self.zim"). However, the magic of modules is that you can bring its instance methods in as either instance methods (via "include") or class methods (via "extend") on a class or another module.

Now, if you have a "procedural API" in Ruby, it should be wrapped in a module in the source file itself, but the simplest way of achieving what you want that I can think of is this:

module SomeNamespace
  module ProceduralAPI
    require 'procedural_api'
  end
 
  extend self::ProceduralAPI
end

Now, on to your next question. smile

When you include a module, its instance methods are imported as instance methods. To my knowledge, nothing happens with its class methods

Re: Understanding Modules

So class methods in modules typically play no role in mixing in. Would you use this for administrative tasks involving the module then. Or perhaps a push based inclusion.

<< 40 minutes later >>

OK, so I wrote this. This worked better than I expected. Apparently you don't even need to reinstantiate Bonsai and Brian when you mix functionality into their classes.

class Animal
  def sound
    puts 'A non-descript sound is emitted from the animal'
  end
end

class Frog < Animal
  def sound
    puts 'Ribbit!'
  end
end

class Cat < Animal
  def sound
    puts 'Meow!'
  end
end

class Person
  attr_accessor :name
  def initialize(name)
    @injured = false
    @name = name
  end
  def injure!(by = nil)
    print "#{name}: Help I'm being attacked"
    print " by a #{by.class}" if by
    print '!'
    puts
    @injured = true
  end
  def injured?
    @injured
  end
end

module Aggression
  def self.apply_to_all_animals
    animal_classes = Module.constants.map { |x| Module.const_get(x) }.select { |x| x.respond_to?(:superclass) && x.superclass == Animal }
    animal_classes.each do |animal_class|
      animal_class.module_eval do
        include Aggression
      end
    end
  end
  def attack(person)
    sound
    puts "*Munch*"
    person.injure!(self)
    sound
    puts
  end
end

bonsai = Cat.new
brian = Frog.new
people = [Person.new('Ollie'), Person.new('manitoba98')]

begin
  bonsai.attack(people[0])
rescue => e
  puts 'Aww bonsai isn\'t capable of agressition. How cute'
end

begin
  brian.attack(people[0])
rescue => e
  puts 'Nor is brian awwww'
end

puts

# and then god said:
Aggression.apply_to_all_animals

bonsai.attack(people[0])
brian.attack(people[1])


Output:
Aww bonsai isn't capable of agressition. How cute
Nor is brian awwww

Meow!
*Munch*
Ollie: Help I'm being attacked by a Cat!
Meow!

Ribbit!
*Munch*
manitoba98: Help I'm being attacked by a Frog!
Ribbit!

Re: Understanding Modules

Yup. Now just a few points:

For design reasons, you generally don't want the Module to contain logic about what it may be included in. Ideally, the module should be defined before the class, at which point you can simply include it like this:

class Animal
  include Aggression
  # (...)
end

This is by far the preferred means of including modules. If, for some reason, you need to include a module dynamically, do it like this, rather than defining another method:

Animal.send(:include, Aggression)

Then, the Animal class (and therefore all subclasses) will include the Aggression functionality. No need to define anything extra. It's just a tad simpler than your approach. smile

Edit: Not totally sure what you meant by "would you use this for administrative

Last edited by manitoba98 (2008-07-12 20:15:13)