Use of extend <module> from within a Class

M

Mike Papper

I would like to include a module into a 'static' class - such that all
the modules methods are class methods. The problem is that code in the
module that uses "self.class.XYZ' breaks and when this code is included,
ruby cannot find the XYZ method.

Here is an example:

module Basic
def methoda
puts "AAAAAAAAAAA"
end

def mb
self.class.cee
end

module ClassMethods
attr_writer :cee

def cee
puts "CCCCCCCCCCCCC"
end
end
end

class Use
extend Basic

def self.z
mb
end
end

If I do the following:
Use.z

I get this error:
NoMethodError: undefined method `cee' for Class:Class
from (irb):100:in `mb'
from (irb):116:in `z'
from (irb):119

--------------
The context for this is in Rails, I want to use
ActionController::UrlWriter.url_for outside of the controller. And
within a static class. However, it complains with this error:

NoMethodError: undefined method `default_url_options' for Class:Class

Heres the url_for method in ActionController::UrlWriter
def url_for(options)
options = self.class.default_url_options.merge(options) # ERROR
OCCURS HERE

url = ''
...
...
end
--------------

I think my understanding of modules and using extend (or include) is
faulty or at least doesnt cover the subtleties of when a modle uses
"self.class" and its included in another class, then what becomes of
self.class in this case??

Any input into how this stuff works in Ruby is appreciated,

Mike
 
J

Jesús Gabriel y Galán

I would like to include a module into a 'static' class - such that all
the modules methods are class methods.

One basic way to do this is to have the class extend the module:

irb(main):017:0> module Z
irb(main):018:1> def test; "testZ"; end
irb(main):019:1> end
=3D> nil
irb(main):020:0> class C
irb(main):021:1> extend Z
irb(main):022:1> end
=3D> C
irb(main):023:0> C.test
=3D> "testZ"

The problem is that code in the
module that uses "self.class.XYZ' breaks and when this code is included,
ruby cannot find the XYZ method.

Here is an example:

module Basic
=A0def methoda
=A0 =A0puts "AAAAAAAAAAA"
=A0end

=A0def mb
=A0 =A0self.class.cee

This is wrong. self.class is Class, and the Class class doesn't have a
method "cee".
=A0end

=A0module ClassMethods
=A0 =A0attr_writer :cee

=A0 =A0def cee
=A0 =A0 =A0puts "CCCCCCCCCCCCC"
=A0 =A0end
=A0end
end

I think you are confusing this usage. The name ClassMethods is a
convention used to denote a set of methods that will be added to the
class as class methods when you include this module. This is usually
done as:

module Test
module ClassMethods
def test; "test"; end
end

def self.included mod
mod.extend ClassMethods
end
end

If you simply want a method defined in the singleton class of your module d=
o:

module Basic
def self.cee
"CCCCCCC"
end
end

If you want to call it from a method of the module:

module Basic
def mb
Basic.cee
end
def self.cee
"CCCCC"
end
end

If you then want mb to be a class method of the classes you choose,
you can use extend instead of include:

irb(main):045:0> module Basic
irb(main):046:1> def mb
irb(main):047:2> Basic.cee
irb(main):048:2> end
irb(main):049:1> def self.cee
irb(main):050:2> "CCCCCCCCCCCCC"
irb(main):051:2> end
irb(main):052:1> end
=3D> nil
irb(main):053:0> class YY
irb(main):054:1> extend Basic
irb(main):055:1> end
=3D> YY
irb(main):056:0> YY.mb
=3D> "CCCCCCCCCCCCC"

I think my understanding of modules and using extend (or include) is
faulty or at least doesnt cover the subtleties of when a modle uses
"self.class" and its included in another class, then what becomes of
self.class in this case??

You can ask Ruby :)

irb(main):036:0> module Basic
irb(main):037:1> def mb
irb(main):038:2> puts self
irb(main):039:2> end
irb(main):040:1> end
=3D> nil
irb(main):041:0> class XX
irb(main):042:1> extend Basic
irb(main):043:1> end
=3D> XX
irb(main):044:0> XX.mb
XX

So, how it works is that when a class extends a module, the methods
get added to the singleton class of the class. This means that the
method is now in the lookup path of the class methods. But (and this
is the same with the normal inheritance) self inside those methods is
still the original object that received the method call, so in this
case the class XX. Another example:

irb(main):058:0> class Class
irb(main):059:1> def test_class; puts self; end
irb(main):060:1> end
=3D> nil
irb(main):061:0> XX.test_class
XX

This is with simple inheritance (XX inherits from Class). With
extended modules it's the same, as shown above.
Basically:

1.- extend includes the methods in that module to the singleton class
of the receiving object.
2.- classes are objects
3.- so when a class extends a module, the module instance methods (is
that the correct way to refer to those?) get added to the singleton
class of the class (commonly known as class methods).

This stuff is tricky, and I don't know if I explained myself very
well, so please ask if there's something not clear enough.

Hope this helps,

Jesus.
 
M

Mike Papper

Hi and thanks, please note that I am extending the module from within my
class. the problem is exactly with the call of the form self.class...

concerning what you said about self.class (inside the module):

def mb
self.class.cee

I used this in my example because the rails module that I am extending
does this. It is included using the method named 'helper' and that must
do special things to make it work in the context of rails+controller.

What I really want to do is use url_for of the rails'
ActionController::UrlWriter. as it happens, that module does have
ClassMethods and within there a method that calls self.class (which
fails when I extend the module).

Mike
----
 
R

Rick DeNatale

I would like to include a module into a 'static' class - such that all
the modules methods are class methods. The problem is that code in the
module that uses "self.class.XYZ' breaks and when this code is included,
ruby cannot find the XYZ method.

Here is an example:

module Basic
=A0def methoda
=A0 =A0puts "AAAAAAAAAAA"
=A0end

=A0def mb
=A0 =A0self.class.cee
=A0end

=A0module ClassMethods
=A0 =A0attr_writer :cee

=A0 =A0def cee
=A0 =A0 =A0puts "CCCCCCCCCCCCC"
=A0 =A0end
=A0end
end
class Use
=A0extend Basic

Okay, at this point in the class definition self =3D=3D Use

and extend Basic

mixes the Basic module into the singleton class of Use, so all of the
methods of Basic (i.e. mb and methoda) are now CLASS methods of Use

There is no actual usage of Basic::ClassMethods
=A0def self.z
This is a class method of Use (because of def self.z) inside this
method self is also the class object Use
=A0 =A0mb
=A0end
end

If I do the following:
=A0Use.z

I get this error:
NoMethodError: undefined method `cee' for Class:Class
=A0 =A0 =A0 =A0from (irb):100:in `mb'
=A0 =A0 =A0 =A0from (irb):116:in `z'
=A0 =A0 =A0 =A0from (irb):119

so what happened.

in Basic#mb we have

def mb
self.class.cee
end

and self is still the class object Use, but Use.class is class, which
is one issue. Even if the mb method had self.cee, Use doesn't have cee
as a class method because although Basic::ClassMethods was defined, it
was never referenced.

As Jesus pointed out, you appeared to be confused with the way Rails,
and lots of other ruby code causes modules to add both instance and
class methods to classes which include them.

The normal pattern is

module SomeModule
# define any instance methods here

module ClassMethods
# define any class methods here
end

# and here is the crucial step this gets called when some class
# includes the module
def self.included(some_class)
some_class.extend(ClassMethods)
end
end

So if you want to make a module which adds only class methods either

1) define the module 'normally' and in the class which wants the
methods as class methods

class Foo
extend MyGreatModuleOfClassMethods
end

or

2) Use the pattern above, leave the instance methods out, and put all
of the methods in the inner ClassMethods module (actually the name of
this module is irrelevant except for clarity. then in the using class

class Foo
include MyGreatModuleWhichOnlyHasClassMethodsRightNow
end

HTH




--=20
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale
 
M

Mike Papper

Thanks for the clarification and sample.

So, I cannot simply extend the ActionController::UrlWriter module in my
class because that module does this:

self.class.default_url_options.merge(options)

However, within a rails controller, this module can be included as a
"helper". Does anyone know how the magic rails helper function makes
this call to self.class.default_url work? I've looked at the code for
helper but cannot really make any sense of it.

Mike

...
 
R

Rick DeNatale

Thanks for the clarification and sample.

So, I cannot simply extend the ActionController::UrlWriter module in my
class because that module does this:

self.class.default_url_options.merge(options)

However, within a rails controller, this module can be included as a
"helper". Does anyone know how the magic rails helper function makes
this call to self.class.default_url work? I've looked at the code for
helper but cannot really make any sense of it.

MyController < ActionController::Base
include UrlWriter # include not extend
end

If you read the code in actionpack/lib/action_controller/helpers.rb it
really isn't that hard to see what's going on

if in the controller you write say

helper :url_writer

then the helper method takes the case branch for a symbol. which is:

file_name = arg.to_s.underscore + '_helper'
class_name = file_name.camelize

begin
require_dependency(file_name)
rescue LoadError => load_error
requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1]
if requiree == file_name
msg = "Missing helper file helpers/#{file_name}.rb"
raise LoadError.new(msg).copy_blame!(load_error)
else
raise
end
end

add_template_helper(class_name.constantize)

So it computes a file name from the argument, in this case
"url_writer.rb", and a class (actually a Module) name "UrlWriter"

Then require dependency loads the file if necessary, and finally.

class_name.constantize turns teh class name into the actual module,
and passes it as the argument to add_template_helper which is defined
above as:

def add_template_helper(helper_module) #:nodoc:
master_helper_module.module_eval { include helper_module }
end

So it is effectively the same thing as what I first wrote.

--
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale
 
M

Mike Papper

Hi and thanks for explaining the helper method. I understand whats going
on except for

master_helper_module.module_eval

-what is master_helper_module ? (Presumably it has the effect of
"include ModuleName - but how that makes self.class.XYZ work is still
not understood).

----------------
Back to the UrlWriter module...

I did some tests and noticed that self.included is not called when a
module is extended (versus included). For UrlWriter, this means that
this code doesnt get executed:
def self.included(base) #:nodoc:
ActionController::Routing::Routes.install_helpers(base)
base.mattr_accessor :default_url_options
base.default_url_options ||= default_url_options
end

I changed my code to explicitly call that method after I extended, ala:
def self.staticmethod
extend ActionController::UrlWriter
ActionController::UrlWriter.included(self) # the class
self.default_url_options = {} # doesnt seem to do much

but still this line from UrlWriter.url_for fails:

self.class.default_url_options.merge(options)

As you said, self.class refers to Class so I dont see how that line
works when the module is included as a helper. Can you explain this??

Mike

Rick said:
helper but cannot really make any sense of it.
MyController < ActionController::Base
include UrlWriter # include not extend
end

If you read the code in actionpack/lib/action_controller/helpers.rb it
really isn't that hard to see what's going on

if in the controller you write say

helper :url_writer

then the helper method takes the case branch for a symbol. which is:

file_name = arg.to_s.underscore + '_helper'
class_name = file_name.camelize

begin
require_dependency(file_name)
rescue LoadError => load_error
requiree = / --
(.*?)(\.rb)?$/.match(load_error.message).to_a[1]
if requiree == file_name
msg = "Missing helper file helpers/#{file_name}.rb"
raise LoadError.new(msg).copy_blame!(load_error)
else
raise
end
end

add_template_helper(class_name.constantize)

So it computes a file name from the argument, in this case
"url_writer.rb", and a class (actually a Module) name "UrlWriter"

Then require dependency loads the file if necessary, and finally.

class_name.constantize turns teh class name into the actual module,
and passes it as the argument to add_template_helper which is defined
above as:

def add_template_helper(helper_module) #:nodoc:
master_helper_module.module_eval { include helper_module }
end

So it is effectively the same thing as what I first wrote.

--
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale
 

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

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top