Reputation: 379
So I want a module with a variable and access methods.
My code looks something like this
module Certificates
module Defaults
class << self
attr_accessor :address
def get_defaults
address = "something"
make_root_cert
end
def make_root_cert
blub = address
# do somthing
end
end
end
I inspected it with pry.
The result is
I used this way of attr_accessor creation in another module and it worked fine. I hope I'm just misunderstanding the way ruby works and somebody can explain why this example doesn't work. Maybe using the implementation details of the ruby object model.
Jeremy is right.
This seems inconsistent to me.
On the other hand address="test" always sets the local variable.
Upvotes: 2
Views: 334
Reputation: 340
This rather confusing behavior occurs because the Ruby interpreter places local variable definition at a higher precedence than method calls. There is consistency here, but unless you know how it works in advance it can be hard to see clearly.
Given that so many things in Ruby are objects and method calls, it might be natural to assume that variable definition was some sort of a method called on something (like Kernel or main or the object in which it was defined or whatever) and that the resulting variable was some sort of object. If this was the case, you would guess that the interpreter would resolve name conflicts between variable definitions and other methods according to the rules of method lookup, and would only define a new variable if it didn't find a method with the same name as the potential variable definition first.
However, variable definition is not a method call, and variables are not objects. Instead, variables are just references to objects, and variable definition is something the interpreter keeps track of below the surface of the language. This is why Kernel.local_variables
returns an array of symbols, and there's no way to get an array of some sort of local variable objects.
So, Ruby needs a special set of rules to handle name conflicts between variables and methods. Non-local variables have a special prefix denoting their scope ($, @, etc.) which fixes this, but not so for local variables. If Ruby required parens after methods, that would also address this problem, but we are given the luxury of not having to do that. To get the convenience of referencing local variables without a prefix and invoking methods without parens, the language just defaults to assuming you want the local variable whenever it's in scope. It could have been designed the other way, but then you would have weird situations where you defined a local variable and it was instantly eclipsed by some faraway method with the same name halfway across the program, so it's probably better like this.
The Ruby Programming Language, p. 88, has this to say:
"...local variables don't have a punctuation character as a prefix. This means that local variable references look just like method invocation expressions. If the Ruby interpreter has seen an assignment to a local variable, it knows it is a variable and not a method, and it can return the value of the variable. If there has been no assignment, then Ruby treats the expression as a method invocation. If no method by that name exists, Ruby raises a
NameError
."
It goes on to explain why you were getting nil
when calling address
in make_root_cert
:
"In general, therefor, attempting to use a local variable before it has been initialized results in an error. There is one quirk--a variable comes into existence when the Ruby interpreter sees an assignment expression for that variable. This is the case even if that assignment is not actually executed. A variable that exists but has not been assigned a value is given the default value
nil
. For example:a = 0.0 if false # This assignment is never executed print a # Prints nil: the variable exists but is not assigned print b # NameError: no variable or method named b exists"
The setter method you get with attr_accessor
leads the interpreter to create a variable before the setter method is ever called, but it has to be called to assign that variable a value other than nil
. address = "something"
in get_defaults
defines a local variable within that method called address
that goes out of scope at the end of the method. When you call make_root_cert
, there's no local variable called address
, so the getter method address
that you got with attr_accessor
is called and returns nil
because the setter method hasn't been called to give it some other value. self.address=
lets the interpreter know that you want the class method address=
instead of a new local variable, resolving the ambiguity.
Upvotes: 0
Reputation: 176743
In your get_defaults
methods, address
is a local variable. To use the setter, you have to type this:
self.address = "something"
That will properly call the address=
method.
Upvotes: 5