including instance methods and setting an instance variable

L

Leon Bogaert

Hi all,

I'm trying to include a module and in that module I want to set an
instance variable. I have the following:

class Queue
include LBo::InstanceMethods
end

module LBo
module InstanceMethods
attr_accessor :position
self.position = 1
end
end

q = Queue.new
q.position

But I'm getting this error:
undefined method `position=' for LBo::InstanceMethods:Module
(NoMethodError)

I'm feeling like a moron because I can't understand why this is not
working.
I tried setting the @position variable directly, but when I do
q.position it gives back nil.

Could somebody lend me a hand with this one?

Thanks in advance!
 
D

David A. Black

Hi --

Hi all,

I'm trying to include a module and in that module I want to set an
instance variable. I have the following:

class Queue
include LBo::InstanceMethods
end

module LBo
module InstanceMethods
attr_accessor :position
self.position = 1

self at this point is the Module object LBo::InstanceMethods. You're
calling the #position method on that Module object.
end
end

q = Queue.new
q.position

But I'm getting this error:
undefined method `position=' for LBo::InstanceMethods:Module
(NoMethodError)

See above.
I'm feeling like a moron because I can't understand why this is not
working.
I tried setting the @position variable directly, but when I do
q.position it gives back nil.

Could somebody lend me a hand with this one?

It's mostly about 'self'. You have to be aware of what self is when
you use it. Inside a module definition, it's the Module object itself.
Inside an instance method, it's the instance.

The job of the module is (mainly) to define instance methods that will
be executed by objects yet unborn. The module itself has no direct
access to the instance variables of those objects. But it can define
methods that do things with them.

For example:

module LBo
module InstanceMethods
attr_writer :position
def position
@position ||= 1
end
end
end

class C
include LBo::InstanceMethods
end

C.new.position # 1

Here, I'm writing an instance method #position, which when executed
with a C instance as 'self' will set the appropriate instance
variable if that variable isn't set already.


David
 
L

Leon Bogaert

Can I "connect" with the instance if it get's instanciated? Because I
wanted to do something like:

if not self.respond_to? :each
return false
end

Thanks!
 
R

Robert Klemme

Can I "connect" with the instance if it get's instanciated? Because I
wanted to do something like:

if not self.respond_to? :each
return false
end

The bit above can be shortened to

return false unless respond_to? :each


There are at least these three options:

1. do not check at all

This is my preferred solution, as it is the simplest and works well.

irb(main):001:0> class Foo
irb(main):002:1> include Enumerable
irb(main):003:1> end
=> Foo
irb(main):004:0> f=Foo.new
=> #<Foo:0x7ff8bff4>
irb(main):005:0> f.map {|x| x+1}
NoMethodError: undefined method `each' for #<Foo:0x7ff8bff4>
from (irb):5:in `map'
from (irb):5
from :0

2. check on the class level, this should generally be sufficient

irb(main):001:0> module Checker
irb(main):002:1> def self.included(cl)
irb(main):003:2> cl.instance_method :each
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> class Baz
irb(main):007:1> include Checker
irb(main):008:1> end
NameError: undefined method `each' for class `Baz'
from (irb):3:in `instance_method'
from (irb):3:in `included'
from (irb):7:in `include'
from (irb):7
from :0
irb(main):009:0> Baz
=> Baz
irb(main):010:0> Baz.ancestors
=> [Baz, Checker, Object, Kernel]
irb(main):011:0>

Note that even the exception does not prevent inclusion. But you can
react on this and define the method for example.


3. on instantiation, in this case you need to be in the inheritance chain

irb(main):001:0> module Checker
irb(main):002:1> def initialize(*a,&b)
irb(main):003:2> method :each
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> class Foo
irb(main):007:1> include Checker
irb(main):008:1> def initialize
irb(main):009:2> super
irb(main):010:2> end
irb(main):011:1> end
=> nil
irb(main):012:0> Foo.new
NameError: undefined method `each' for class `Foo'
from (irb):3:in `method'
from (irb):3:in `initialize'
from (irb):9:in `initialize'
from (irb):12:in `new'
from (irb):12
from :0
irb(main):013:0>


4. check when individual objects are extended

irb(main):001:0> module Checker
irb(main):002:1> def self.extended(o)
irb(main):003:2> o.method :each
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> x = Object.new
=> #<Object:0x7ff89f4c>
irb(main):007:0> x.extend Checker
NameError: undefined method `each' for class `Object'
from (irb):3:in `method'
from (irb):3:in `extended'
from (irb):7:in `extend'
from (irb):7
from :0
irb(main):008:0>


Kind regards

robert
 
L

Leon Bogaert

Thanks Robert!

So it is *never* possible to set an instance variable via a mixin?
Except of course, via a method that gets run when the, uhm..., stuff has
transformed into an object? :)
 
D

David A. Black

Hi --

This guy: http://www.ruby-forum.com/topic/142444
had the same question as me. Reading that post made things a lot
clearer.

It's difficult to apprehend how this stuff works in Ruby. Especially if
you read some stuff like instance_eval and the lot.

It's not that hard; it's all based on a small number of principles
that never change.

1. Some object is always "self"
2. Every instance variable belongs to "self"
3. obj.instance_eval {...} temporarily (for the duration of the code
block) sets self to obj.

The hardest one seems to be #2. But it makes sense once you realize
that there is *always* a "self". So when you see this:

module M
@x = 1
def my_method
@x = 1
end
end

you're seeing two "self" contexts: the outer level of the module
definition (where self is M) and the inside of the method definition
(where self is, or rather will be, whatever object calls my_method.
Therefore, the two "@x"s have nothing whatsoever to do with each
other.

I should add:

4. Classes and modules are objects

which is, as I'm fond of saying to my trainees, the answer to about
75% of all questions about Ruby :)

You also have to keep in mind that self is not the same as local
scope. self changes, and local scope changes, but not always in sync
with each other. They're two different things.


David
 
L

Leon Bogaert

Ah thanks David.

And that's of course why

Module X
instance_eval do
@testing = [1,2,3]
end
end

String.new('test').include(X)

will never work. Because you can't get self to become the instantiated
object.
 
R

Robert Klemme

Ah thanks David.

And that's of course why

Module X
instance_eval do
@testing = [1,2,3]
end
end

This sets an instance variable of X.
String.new('test').include(X)

'test'.include X

This is sufficient, because "" and '' are object constructors:

irb#1(main):001:0> 3.times { puts "foo".object_id }
1073547530
1073547510
1073547490
=> 3
will never work. Because you can't get self to become the instantiated
object.

Which is kind of obvious because the object does not even exist when you
define the module. :)

If you want a module to manipulate instance variables you do it the same
way as with ordinary class instance methods. The major difference is
that it's harder to initialize them during object creation because the
class needs to cooperate (calling super in #initialize). The more
robust approach is to assume that they are not initialized as David has
shown in his example with @position.

Kind regards

robert
 
R

Robert Dober

Ah thanks David.

And that's of course why

Module X
instance_eval do
@testing = [1,2,3]
end
end

This sets an instance variable of X.

String.new('test').include(X)

'test'.include X
Do you mean extend?
And if so what good would that do, even if we had a reference to test
as the module does not define any methods.

I am very confused by this thread but somehow have the feeling that OP
wants this

module X
def set
@x=[*1..3]
end
end
class C
attr_reader :x
include X
end
c=C.new
c.set
p c.x

and might get confused by
a='test'.extend X
a.set
p a.instance_variable_get("@x")

but at least it clearly shows that the included or extended method
that was defined in a module does precisely what OP doubted possible:
Accessing ivars.

HTH
Robert
 
R

Robert Klemme

Ah thanks David.

And that's of course why

Module X
instance_eval do
@testing = [1,2,3]
end
end

This sets an instance variable of X.

String.new('test').include(X)

'test'.include X
Do you mean extend?

You are right, my answer was too short. The bit I showed is indeed
equivalent to the original - however both are dysfunctional. :)
And if so what good would that do, even if we had a reference to test
as the module does not define any methods.

My point was that String.new "foo" is superfluous because "foo" does
already create a new object.
I am very confused by this thread

Please don't. I would feel sorry to have caused confusion. :)
but somehow have the feeling that OP
wants this

Actually we (I) do not exactly now what the OP exactly wants to do. So
far I know only that he wants to access instance variables from a
module. I am guessing that he thinks it's somehow different than from
class instance methods. But it isn't (apart from the initialization
story, see previous posting).

A small addition: modules are a nice case where accessing instance
variables via accessor methods instead of directly pays off because then
you can centralize initialization:

# a bit silly example
module ItemManager
def items
@items ||= []
end

def add(item)
items << item
end

def delete(item)
items.delete item
end

def reorder
items.sort!
end
end
but at least it clearly shows that the included or extended method
that was defined in a module does precisely what OP doubted possible:
Accessing ivars.

Correct.

Happy Easter

robert
 
R

Rick DeNatale

A small addition: modules are a nice case where accessing instance
variables via accessor methods instead of directly pays off because then
you can centralize initialization:

# a bit silly example
module ItemManager
def items
@items ||= []
end

I'd say that this defers rather than centralizes initialization. In
fact it might be said that it decentralizes it, since the
initialization of such instance variables don't occur in an initialize
method.

That's not a criticism. This is a form of the lazy initialization
pattern which is common in dynamic languages. The neat thing about
ruby is that the instance variables don't even exist until they are
needed, unlike in languages like Smalltalk where they need to be
pre-declared in the class definition.

I'm reminded that there used to be a tendency among some Smalltalkers
to call this "laissez faire" initialization, which would be an
entirely different thing I think. This just showed a lack of
knowledge of French, and the use of this phrase instead of lazy
initialization was one of Ralph Johnson's pet peeves IIRC.
 
R

Robert Klemme

A small addition: modules are a nice case where accessing instance
variables via accessor methods instead of directly pays off because then
you can centralize initialization:

# a bit silly example
module ItemManager
def items
@items ||= []
end

I'd say that this defers rather than centralizes initialization. In
fact it might be said that it decentralizes it, since the
initialization of such instance variables don't occur in an initialize
method.

I can see what you mean, but it is still in a *single* and hence central
location. The alternative would be to spread the @items ||= [] all over
the methods defined in the module (maybe I should've mentioned that in
the first place). It's not as central as #initialize since that method
is /usually/ (nut not always) used to initialize an object's state
though, so I can 45% agree - but also have to 55% disagree. :)

Kind regards

robert
 
R

Robert Dober

I am very confused by this thread
Please don't. I would feel sorry to have caused confusion. :)
No not your post, I meant what OP really meant, but see below...
Actually we (I) do not exactly now what the OP exactly wants to do. That's what I meant above ;)
So
far I know only that he wants to access instance variables from a
module. I am guessing that he thinks it's somehow different than from
class instance methods. But it isn't (apart from the initialization
story, see previous posting).
That is the key sentence he should read I agree, and Rick's technique
is often useful in doing so especially
in cases where the Mixin does not mix #initialize in (does not define
a method initialize).
A small addition: modules are a nice case where accessing instance
variables via accessor methods instead of directly pays off because then
you can centralize initialization:
Yup; I all invite you to read Ricks Blog he wrote a great summary of
that "pattern" on his Blog.
Did you have problems with spam Rick, as comments are disabled? Wanted
to tell you how much I enjoyed the read, I honestly
think it is a valuable addition to what Kent says in the book.
 
R

Rick DeNatale

Yup; I all invite you to read Ricks Blog he wrote a great summary of
that "pattern" on his Blog.
Thanks.

Did you have problems with spam Rick, as comments are disabled? Wanted
to tell you how much I enjoyed the read, I honestly
think it is a valuable addition to what Kent says in the book.

I've got it set up to disable comments once an article is 30 days old.

Before that comments are moderated.
 
R

Robert Klemme

Ah I have heard of that (RSS) already, I will try to let Firefox, work
it out :-0

Frankly, I would not do that. In fact, I have tried it but FF's RSS
reading is pretty awkward IMHO. I prefer Google's RSS reader or
NewsGator because they have the advantage to be webbased and are so good
suited when you change systems frequently. My 0.02 EUR.

Kind regards

robert
 

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,744
Messages
2,569,480
Members
44,900
Latest member
Nell636132

Latest Threads

Top