Scope of Constants in Ruby Modules Scope of Constants in Ruby Modules ruby ruby

Scope of Constants in Ruby Modules


The USER_KEY you declared (even conditionally) in Auth is globally known as Auth::USER_KEY. It doesn't get "mixed in" to including modules, though including modules can reference the key in a non-fully-qualified fashion.

If you want each including module (e.g. ApplicationController) to be able to define its own USER_KEY, try this:

module Auth  DEFAULT_USER_KEY = 'user'  def self.included(base)    unless base.const_defined?(:USER_KEY)      base.const_set :USER_KEY, Auth::DEFAULT_USER_KEY    end  end  def authorize    user_id = session[self.class.const_get(:USER_KEY)]  endendclass ApplicationController < ActionController::Base  USER_KEY = 'my_user'  include Authend

If you're going to go to all this trouble, though, you might as well just make it a class method:

module Auth  DEFAULT_USER_KEY = 'user'  def self.included(base)    base.extend Auth::ClassMethods    base.send :include, Auth::InstanceMethods  end  module ClassMethods    def user_key      Auth::DEFAULT_USER_KEY    end  end  module InstanceMethods    def authorize      user_id = session[self.class.user_key]    end  endendclass ApplicationController < ActionController::Base  def self.user_key    'my_user'  endend

or a class-level accessor:

module Auth  DEFAULT_USER_KEY = 'user'  def self.included(base)    base.send :attr_accessor :user_key unless base.respond_to?(:user_key=)    base.user_key ||= Auth::DEFAULT_USER_KEY  end  def authorize    user_id = session[self.class.user_key]  endendclass ApplicationController < ActionController::Base  include Auth  self.user_key = 'my_user'end


Constants don't have global scope in Ruby. Constants can be visible from any scope, but you must specify where the constant is to be found. When you begin a new class, module, or def, you begin a new scope, and if you want a constant from another scope, you have to specify where to find it.

X = 0class C  X = 1  module M    X = 2    class D      X = 3      puts X          # => 3      puts C::X       # => 1      puts C::M::X    # => 2      puts M::X       # => 2      puts ::X        # => 0    end  endend


Here's a simple solution.

Changes:

  • No need to check for existence of USER_KEY.
  • Try to look up the constant on the receiver's module/class (in your case it would be the controller). If it exists, use it, otherwise use the default module/class (see below for what the default is).

.

module Auth  USER_KEY = "user"  def authorize    user_key = self.class.const_defined?(:USER_KEY) ? self.class::USER_KEY : USER_KEY    user_id = session[user_key]  defend

Explanation

The behavior you're seeing isn't specific to rails, but is due to where ruby looks for constants if not explicitly scoped via :: (what I call the "default" above). Constants are looked up using the "lexical scope of the currently executing code". This means that ruby first looks for the constant in the executing code's module (or class), then moves outward to each successive enclosing module (or class) until it finds the constant defined on that scope.

In your controller, you call authorize. But when authorize is executing, the currently executing code is in Auth. So that is where constants are looked up. If Auth didn't have USER_KEY, but an enclosing module has it, then the enclosing one would be used. Example:

module Outer  USER_KEY = 'outer_key'  module Auth     # code here can access USER_KEY without specifying "Outer::"     # ...  endend

A special case of this is the top-level execution environment, which is treated as belonging to class Object.

USER_KEY = 'top-level-key'module Auth  # code here can access the top-level USER_KEY (which is actually Object::USER_KEY)  # ...end

One pitfall is defining a module or class with the scoping operator (::):

module Outer  USER_KEY = 'outer_key'endmodule Outer::Auth  # methods here won't be able to use USER_KEY,  # because Outer isn't lexically enclosing Auth.  # ...end

Note that the constant can be defined much later than the method is defined. The lookup only happens when USER_KEY is accessed, so this works too:

module Auth  # don't define USER_KEY yet  # ...end# you can't call authorize here or you'll get an uninitialized constant errorAuth::USER_KEY = 'user'# now you can call authorize.