Help me to better understand modules

Z

Zouplaz

Hello, I would like to use ruby modules for the first time. I understand
theirs principle for a basic usage but get quite unsure for the present
situation :

- I want to put some code in a library (railsapp/lib) to
1) lighten the controller that will use it
2) test unit the code on one side, and test unit the controller on
the other side
- So, the lib is like this
1) class BaseModule
2) class SpecificModule < BaseModule
- The controller SomeController
1) creates an instance sm = SpecificModule.new
2) sm.doSomething

I don't like the idea of creating an instance - Because it seems (to me)
too much Java/C++ oriented

Instead I would like

module BaseModule
module SpecificModule
include BaseModule

class SomeController
include SpecificModule

Then, I could call doIt() from the controller


Hence, my question :
- is it 'good design' to use the modules like this ? May I have some
(bad) surprises later ?
- are the modules 'motchable / stubbable' ?
- is it different to unit test modules compared to classes ?

I did few tries with modules (see above) and I have a question more :
why did I need to use the 'self' keyword with self.my_stuff or
self.method_payment ? (without self. the methods are unknow under
SpecificPayment module)
I had a look to the rails code base and it seems that most methods
defined in modules don't use it... In my case it seems that I should use
'self' anywhere...

Any advice ?

Thank for the attention ;-)

module Payment

@@attribut = "payment"

def self.method_payment()
p 'here again'
end

end

module SpecificPayment

include Payment

@@attribut_specific = "foo"

def self.my_stuff()
p 'here'
Payment.method_payment()
end

end

class AClass
include SpecificPayment

def go()
SpecificPayment.my_stuff()
Payment.method_payment()
end

end

a = AClass.new
a.go()
 
B

Brian Candler

Zouplaz said:
Hence, my question :
- is it 'good design' to use the modules like this ? May I have some
(bad) surprises later ?
- are the modules 'motchable / stubbable' ?
- is it different to unit test modules compared to classes ?

In principle it should be fine. Modules are just objects, and I believe
you should be able to mock/stub its methods just fine. Anyway, a 5
minute test will prove it one way or the other :)
why did I need to use the 'self' keyword with self.my_stuff or
self.method_payment ? (without self. the methods are unknow under
SpecificPayment module)

Effectively there are two different ways to use module methods, and you
have attempted to mix them both.

(1) You can have what I would call "pure" module methods. They are not
attached to any object, but only operate on the arguments given.

module Math
def self.double(x)
x*2
end
end

p Math.double(9)

(2) You can have a module which is "mixed in" to a real class or a real
object just like inheritance. It can invoke methods on those objects, or
even access instance variables directly (although I wouldn't recommend
the latter, it's a very low-level coupling)

These are defined without 'self' and become methods on the class into
which they are mixed (using 'include M' within the class) or in an
object's singleton class (using 'obj.extend M')

module Foo
def double
val * 2 # or: @val * 2
end
end

class Bar
include Foo
attr_reader :val
def initialize(v)
@val = v
end
end

a = Bar.new(9)
p a.double

Your code is trying to do both. To change it into the mixin style you
would write:

module Payment
def method_payment
p 'here again'
end
end

module SpecificPayment
def my_stuff
p 'here'
method_payment
end
end

class AClass
include Payment
include SpecificPayment

def go
my_stuff
method_payment
end
end

a = AClass.new
a.go


Note that I have removed your class variables (@@...). These are messy
at the best of times, but if you write mixed-in modules which use class
variables, I really wouldn't have a clue as to where the values were
stored, or which objects or classes would be able to access them. It's a
real recipe for trouble. Much safer to use class instance variables, or
just regular objects, to store such state.
Any advice ?

Modules will probably work just fine for you, but it's a matter of
personal preference as to how to use them, depending on the specifics of
your problem.

I typically find that object composition ("has-a") rather than
subclassing ("is-a" or mixin) is the most flexible way to compose
systems. Obviously the component classes are easy to test in isolation,
and they tend to be easier to re-use.

Looking at your example, obviously without understanding your problem
domain fully I would still be inclined to use concrete classes rather
than modules. That is, something like this:

class PaymentHandler
def method_payment
p 'here again'
end
end

class SpecificPaymentHandler < PaymentHandler
def my_stuff
p 'here'
method_payment
end
end

class AClass
def initialize
@payment = SpecificPaymentHandler.new
end
def go
@payment.my_stuff
@payment.method_payment
end
end

AClass.new.go

Some people's immediate reaction would be: "but that creates a new
object for every controller action invocation!". Ignore them. That is
premature optimisation; object creation is cheap in Ruby. Consider:

10.times { puts "hello" }

This creates 10 separate string objects, but nobody complains (much)
about that.

If the right conceptual model is for a payment-handler class, which
carries its own state, then do that.

Of course, if SpecificPayment.my_stuff() and Payment.method_payment()
are "pure" module methods, depending only on values passed as arguments,
then by all means keep them that way.

Hope that gives you something to think about :)

Brian.
 
Z

Zouplaz

le 10/10/2008 21:56, Brian Candler nous a dit:
Your code is trying to do both. To change it into the mixin style you
would write:

module Payment
def method_payment
p 'here again'
end
end

module SpecificPayment
def my_stuff
p 'here'
method_payment
end
end

class AClass
include Payment
include SpecificPayment

def go
my_stuff
method_payment
end
end

a = AClass.new
a.go

Thank you Brian, for this very valuable explanation - It's very clear to
me now. I've noticed that, with the self. version I need to prefix them
with the module's name, so having namespacing which tends to make the
code more readable.

module Payment
def self.method_payment
end
end

class AClass
include Payment

def go
Payment.method_payment()
end
end
I typically find that object composition ("has-a") rather than
subclassing ("is-a" or mixin) is the most flexible way to compose
systems. Obviously the component classes are easy to test inisolation,
and they tend to be easier to re-use.

Yes, I agree - May be very conventional too but easy to understand, to
read and furthermore to isolate as you point out
Some people's immediate reaction would be: "but that creates a new
object for every controller action invocation!". Ignore them. That is
premature optimisation; object creation is cheap in Ruby. Consider:

10.times { puts "hello" }

This creates 10 separate string objects, but nobody complains (much)
about that.

You win one dollar ;-) This is the first reason that pushed me to have a
look to modules for my current project, instead of using composite
objects (composite controller class in that case)
If the right conceptual model is for a payment-handler class, which
carries its own state, then do that.

Of course, if SpecificPayment.my_stuff() and Payment.method_payment()
are "pure" module methods, depending only on values passed as arguments,
then by all means keep them that way.

I've switched from Class to Module - was quite interesting to compare
the two solutions but I prefer the class method and will switch back to
that because I feel more confortable with it

Now I see situations where modules will be very usefull but for the
current case it give nothing more compared to the class approach.

Btw, the modules methods proved to be stubbable very easily...
Hope that gives you something to think about :)

I learned a lot today ;-) Thank you
 
B

Brian Candler

Zouplaz said:
I've noticed that, with the self. version I need to prefix them
with the module's name, so having namespacing which tends to make the
code more readable.

That's right - you are explicitly calling a method which belongs to the
module itself, not to any object which mixes in the module.
module Payment
def self.method_payment
end
end

class AClass
include Payment

def go
Payment.method_payment()
end
end

You do not need "include Payment" in class AClass. You can call
Payment.method_payment from anywhere. The module "Payment" itself is the
receiver of the method.

You only need "include Payment" if you want to mix in *instance methods*
(those which are defined in the module without "self") - they then
become instance methods of class AClass.

Regards,

Brian.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,994
Messages
2,570,223
Members
46,810
Latest member
Kassie0918

Latest Threads

Top