How to access an included module's methods from within another included module?
-
01-07-2021 - |
質問
module Foo
attr_accessor :val
end
module Bar
def bar
val = 50
end
end
class FooBar
include Foo
include Bar
end
fb = FooBar.new
puts fb.bar # 50
puts fb.val # nil
I would like to, from within Bar, change Foo's "val" value.
Just so you know, I'm trying to do this because I want to add user authentication through a Facebook Canvas app in Clearance (0.8.2 (old, I know)). The variable that I want to change is https://github.com/thoughtbot/clearance/blob/b8ccca00d91cb336e044b76b5c6ae1d8de9d2d8d/lib/clearance/authentication.rb#L25.
This module gets included into ApplicationController, and I'm including another module afterwards (FacebookAuthenticationHelper) that does something like
def authenticate_facebook
current_user ||= facebook_user if coming_from_facebook?
end
I would love to know if there's a better way to do this as well. I'm not using OAuth, I'm just saving Facebook's user_id sends to my app from signed_request into my DB.
Thanks!
解決
In ruby, any statement of the form varname = value
will create a local variable named varname
if it doesn't already exist. This is even true within class methods where the class has a setter method of the same name. Furthermore, if a local variable exists, it takes precedence over getters and setters. For example:
class Demo
attr_accessor :foo
def demonstrate!
@foo = 1 #1: Set member variable foo, so we get meaningful output
puts foo #2: Prints 1 (this calls the getter)
puts self.foo #3: Prints 1 (also calls the getter)
foo = 2 #4: Creates a LOCAL variable named foo with the value 2
puts foo #5: Prints 2 (locals take precedence over getters)
puts self.foo #6: Prints 1 (calls the getter - the member variable hasn't changed)
self.foo = 3 #7: Use SELF to ensure you call a getter or setter
puts foo #8: Prints 2 (the local again)
puts self.foo #9: Prints 3 (the member, which is now 3)
end
end
The bad thing about this system is: Look at lines 2 and 5. The same exact code does different things! On line 2, the local variable foo
didn't exist yet, so ruby did the "next best" thing and called the getter. But on line 5, the local foo
exists, so it takes precedence.
I'd say that this is bad language design: If setters can't be called without self
, why should it be okay for getters - especially when it can lead to rickety, context-sensitive code like the above? But it's what we have to work with, and it leads to some general guidelines:
- If you can, use
@foo
. It's obvious what that does. - If you're using a getter or setter, always use
self
, even when it's not strictly necessary. This makes it obvious what you're calling. It also guarantees your code won't behave differently if you add a local with the same name later. - If you do 1 and 2, you can assume that anything without a
@
or aself
is a local variable (or a function call, but you can probably tell those apart by name).
And that ended up being a bit long-winded, but I was unable to find a good demonstration of the getter/setter/local problem to link to. So maybe this is it - hope it's helpful!