Correct, since the method is being called in the context of A, it will search starting from A. You can verify this by printing out the Module.nesting value which is used for constant lookup searching:
···
On Aug 14, 2011, at 9:31 AM, Iñaki Baz Castillo wrote:
------------------------
class A
HELLO = "Hello I'm A"
def hello
puts HELLO
end
end
class B < A
HELLO = "Hello I'm B"
def hello2
puts HELLO
end
end
------------------------
B.new.hello
=> "Hello I'm A" (ouch...)
B.new.hello2
=> "Hello I'm B"
-------------------------
class A
HELLO = "Hello I'm A"
def hello
p Module.nesting
puts HELLO
end
end
-------------------------
This will output [A].
So, if a contstant appears within a method, its value is evaluated for
the class in which such method is defined ("hello" is defined in class
A not in B, so it takes HELLO from class A).
I don't like this behavior (just my opinion). Do I miss something?
This behavior can fortunately be overridden by simply overriding the hello method in the B class, meaning that it will use the constant defined in B instead:
--------------------------
class A
HELLO = "Hello I'm A"
class A
class << self
attr_accessor :hello
end @hello = "Hello I'm A"
def hello
puts self.class.hello
end
end
class B < A @hello = "Hello I'm B"
def hello2
puts self.class.hello
end
end
B.new.hello
#=> "Hello I'm B"
B.new.hello2
#=> "Hello I'm B"
···
On Aug 14, 2011, at 10:31 AM, Iñaki Baz Castillo wrote:
So, if a contstant appears within a method, its value is evaluated for
the class in which such method is defined ("hello" is defined in class
A not in B, so it takes HELLO from class A).
I don't like this behavior (just my opinion). Do I miss something?
Yes, but it requires duplicating code. In my case I've a server class:
class Server
def self.foo
puts "I listen in IP #{IP}"
end
end
and then some classes inhereting from it:
class UdpServer < Server
IP = "1.2.3.4"
end
class TcpServer < Server
IP = "1.2.3.5"
end
Of course "TcpServer.foo" fails:
NameError: uninitialized constant Server::IP
So I must use workarounds like:
class Server
def self.foo
puts "I listen in IP #{self::IP}"
end
end
class UdpServer < Server
IP = "1.2.3.4"
end
class TcpServer < Server
IP = "1.2.3.5"
end
Then it works:
TcpServer.foo
=> I listen in IP 1.2.3.5
UdpServer.foo
=> I listen in IP 1.2.3.4
···
2011/8/14 Chris White <cwprogram@live.com>:
This behavior can fortunately be overridden by simply overriding the hello method in the B class, meaning that it will use the constant defined in B instead
I may be late to the party, since it seems like you've already solved
the problem, but Ruby has a const_missing method, which works just
like method_missing. You could use it to search for constants in
various places.
I suppose the question then becomes why they have to be constants at that level. By using a module and doing a mixin (as I notice that Philip just mentioned as I'm writing this):
···
On Aug 14, 2011, at 11:40 AM, Iñaki Baz Castillo wrote:
2011/8/14 Chris White <cwprogram@live.com>:
This behavior can fortunately be overridden by simply overriding the hello method in the B class, meaning that it will use the constant defined in B instead
Yes, but it requires duplicating code. In my case I've a server class:
class Server
def self.foo
puts "I listen in IP #{IP}"
end
end
and then some classes inhereting from it:
class UdpServer < Server
IP = "1.2.3.4"
end
class TcpServer < Server
IP = "1.2.3.5"
end
Of course "TcpServer.foo" fails:
NameError: uninitialized constant Server::IP
-------------------------------------
module Server
def listen(ip)
# Would most likely set instance variable @ip for later use @ip = ip
puts "Listening on #@ip"
end
end
The listen logic is centralized, and as shown in the UdpServer class, you can override the Server listen and still call its core functionality through `super`. This works out because including modules inserts into the object hierarchy as shown by doing a p TcpServer.ancestors:
I may be late to the party, since it seems like you've already solved
the problem, but Ruby has a const_missing method, which works just
like method_missing. You could use it to search for constants in
various places.
Hi. My code uses classes rather than instance of classes. Each class
(i.e. UdpServer, TcpServer) has different attributes (listenting IP,
port, transport, and so) and I don't want (due to efficience) to
assign the same value in initialize method for every instance.
···
2011/8/14 Chris White <cwprogram@live.com>:
The listen logic is centralized, and as shown in the UdpServer class, you can override the Server listen and still call its core functionality through `super`
If they need to stay as classes and not instances, then I recommend the use of a class instance variable:
···
On Aug 14, 2011, at 12:19 PM, Iñaki Baz Castillo wrote:
2011/8/14 Chris White <cwprogram@live.com>:
The listen logic is centralized, and as shown in the UdpServer class, you can override the Server listen and still call its core functionality through `super`
Hi. My code uses classes rather than instance of classes. Each class
(i.e. UdpServer, TcpServer) has different attributes (listenting IP,
port, transport, and so) and I don't want (due to efficience) to
assign the same value in initialize method for every instance.
-------------------------------
class Server
def self.listen
puts "Listening on #@ip"
end
end
What's happening here is that an instance variable is created for both UdpServer and TcpServer. Classes can have instance variables the same as their instances since classes are themselves instances of Class. When listen is called on the class in question, the "self" value is set to the class itself, not Server, and so @ip is the instance variable for the specific server object in question.
Yes, I also us that commonly. However I didn't know that class
instance variables can be defined out of a method. I expected:
class TcpServer < Server
def self.init_variables
@ip="5.6.7.8"
end
end
Good to know that it's not needed
Yup, and the reason why it works is because class definitions are expressions, where the last expression in the class body is the return of the expression. Since in most cases it's a method definition, the result is generally nil. So what happens is:
class TcpServer < Server
def self.init_variables
Notice we have to use self here so it works when calling TcpServer.init_variables. This is because we're at the class declaration stage, so self is set to TcpServer. This binds the method to the specific TcpServer class instead of the instances of the class. So @ip outside of a method declaration is the same principal, as when instance variables are created and accessed, they're bound to the value of self (You can see this at the bytecode / c level here if you want http://blog.rubychest.net/2011/08/know-they-self-instance-variables.html ).