Scope of Constants in Ruby Modules

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)]
end
end

class ApplicationController < ActionController::Base
USER_KEY = 'my_user'
include Auth
end

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
end
end

class ApplicationController < ActionController::Base
def self.user_key
'my_user'
end
end

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]
end
end

class ApplicationController < ActionController::Base
include Auth
self.user_key = 'my_user'
end

What is the scope of variables and methods included via ruby modules?


What is going on behind the scenes with regards to Var vs var? It appears that capitalizing a variable name is more than just a convention after all.

Yes, of course, it's not a convention. Variables which start with an uppercase letter are constants, variables which start with a lowercase letter are local variables. The two are completely different.

When includeing a module, what kinds of things are passed to the including class, and at what scope?

Nothing gets passed anywhere. includeing a mixin simply makes that mixin the superclass of the class you are includeing it into. That's all. Everything else then works exactly as with classes.

Is there a way to do the opposite? That is, use include to include an instance variable or a class method?

I don't understand this question. Instance variables have nothing to do with mixins or classes. They belong to instances, that's why they are called "instance" variables.

There are no such things as "class methods" in Ruby. Ruby only knows one kind of methods: instance methods. When Rubyists talk to each other, they will sometimes use the term "class method" to mean "singleton method of an object that happens to be a class", but they do that knowing full well that class methods don't actually exist, it's just a shorthand in conversation. (And, of course, singleton methods don't exist either, they are just a convenient way of saying "instance method of the singleton class".)

Ruby: How to access a constant from the class a module is included into

You can reference the class module being included into as self.class and the use const_get or just self.class::CONST, with the latter being slightly faster:

module M
def foo
self.class::CONST
end
end

class A
CONST = "AAAA"
include M
end

class B
CONST = "BBBB"
include M
end

puts A.new.foo # => AAAA
puts B.new.foo # => BBBB

What is the difference in scoping between a global variable and a constant in Ruby?

Global variables are the ones that can be accessed from anywhere. Their scope turns to be the whole of the main object. This means they can be used anywhere in this scope i.e anywhere in the code itself. For instance

module A
module B
class C
$glo = 'this is glo-bal variable'
end
end
end

module D
class E
CON = 'this is con-stant'
def call_glo
puts $glo
end

def call_con
puts CON
end
end

def self.call_con
puts CON
end

E.new.call_glo #=> "this is glo-bal variable"
end

D::E.new.call_glo #=> "this is glo-bal variable"
D::E.new.call_con #=> "this is con-stant"
D.call_con #=> Throws Error Unitialized Constant

While the constants are restricted to the scope they are defined in. They can only be used in the scope they are defined.

Now, as you said Constants starts with capitals, hence all the class names and module names are themselves nothing but Constants.

Now in the above example, you see the call_glo method is called twice. Once from the scope of module D while one from the main object scope, do you see the difference between the instantiation of class E?

In module D it is called without any scope operator :: while outside of module we had to use the scope operator, that is the restriction of scope. Constants are bound to.

Why can't ruby classes inside modules have the same scope as regular classes?


I have a module with a class inside,

No, you don't. You have a module definition with a class definition inside, but that does not make the class a nested class. Ruby does not have nested classes.

Ruby is not Beta, Scala, or Newspeak, there are no nested classes in Ruby.

Nesting a module or class definition inside another module or class definition does not create nesting relationship between the two classes / modules. It only makes the constant which references the class / module part of the outer class' / module's namespace.

In other words, there is no difference between

module Foo
class Bar
end
end

and

class Quux
end

module Foo
Bar = Quux
end

Only the constant is nested, but not the object that is referenced by the constant.

but I find that the class inside can't reach any of the methods in the enclosing module without specifying the module path.

That is precisely because there is no "enclosing module". There is a lexically enclosing module definition but that does not create any form of relationship whatsoever between the Foo class and the MyMod module.

Another way to look at it is that the module_function doesn't seem to carry into the class.

I honestly don't understand what you mean by that, what it means for a method to "carry into a class", but Module#module_function is not magic. It does exactly what the documentation says it does: it takes an instance method of the module, copies it as an instance method of the singleton class of the module, and makes the original instance method private.

You can read its specification in the Ruby/Spec, it is fairly simple. Also, the Rubinius source code, both the basic version for booting the Rubinius kernel and the full version are fairly readable.

In the end, Module#module_function really does not do much more than

class Module
def module_function(*meths)
meths.each do |meth|
define_singleton_method(meth, &instance_method(meth).bind(self))
private meth
end

self
end
end

If you run this, you get an error on the "But I can't..." line of:

undefined local variable or method `meaning'

The reason is simple: neither the class Foo nor any of its superclasses has any method of that name, so of course you get an exception.

But if you remove the MyMod bits around the whole file, it has no problem accessing the outer method.

There is no "outer method". Ruby does not have Beta-like nested classes. That is really the fundamental cause of your misunderstanding. You expect Ruby to behave like Beta, but it just doesn't. Ruby takes inspiration from any languages, most notably (in rough order of importance) Smalltalk, Lisp, Perl, and Clu, but Beta is not among them.

This here works for a completely different reason:

def meaning
42
end

class Foo
def initialize
meaning
end
end

Methods that are defined at the top-level are implicitly defined as private instance methods of Object. This is because the default definee at the top-level is ::Object. Since Foo inherits from Object, method lookup will eventually find the meaning method defined in Object.

Is there an easy way to make these accessible without having to give the full path?

Inheritance. For example, Module#append_features, which is called by Module#include, makes the module the superclass of the including class, and thus all instance methods of the module become part of the method lookup ancestry chain.

An aside: if there is no nesting, then what does Module::nesting do? Well, yeah, that is an unfortunately named method. The term "nested class" or "nested module" has a well-defined meaning in OO going all the way back to Beta. But this method is about a completely different kind of nesting:

It refers to the lexical nesting of module definitions, and not to nesting of modules themselves.

For example, these module definitions all define the exact same module, but the definition text has different nesting:

module Foo
module Bar
module Baz
module Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo::Bar::Baz, Foo::Bar, Foo]
end
end
end
end

module Foo
module Bar
module Baz::Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo::Bar, Foo]
end
end
end

module Foo
module Bar::Baz
module Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo::Bar::Baz, Foo]
end
end
end

module Foo::Bar
module Baz
module Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo::Bar::Baz, Foo::Bar]
end
end
end

module Foo
module Bar::Baz::Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo]
end
end

module Foo::Bar::Baz
module Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo::Bar::Baz]
end
end

module Foo::Bar
module Baz::Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux, Foo::Bar]
end
end

module Foo::Bar::Baz::Qux
p Module.nesting
#=> [Foo::Bar::Baz::Qux]
end

Again, this is purely lexical nesting of the module definition. The module itself is not nested; the module itself is the same in all of these cases. This nesting only affects constant lookup.

Constants are looked up first lexically outwards in enclosing module definitions, then upwards the inheritance chain.

There is another instance where things can be nested: blocks create nested lexical scopes, whereas all other lexical scopes (script, module / class definition, and method definition) don't nest. In other words, blocks and only blocks have access to the local variables (and self) of their enclosing lexical scopes.

How to make a module constant also visible within an eigenclass?


Solution

class << self
def eigen_scope
p [__method__, hello(self::NAME)]
#=> [:eigen_scope, "Hello Otto!"]
end
end

Why does self::NAME work?

  • A::NAME would be the easiest, hard-coded version.
  • B::NAME would also work, because B includes A
  • Inside eigen_scope, self is B, so self::NAME works as well
  • self::NAME would also work in self.class_scope
  • self::NAME wouldn't work in instance_scope : a B instance is not a class/module.

Why doesn't NAME work?

Here's a very good explanation.

constant lookup searches for constants that are defined in
Module.nesting, Module.nesting.first.ancestors, and
Object.ancestors if Module.nesting.first is nil or a module

self is the same in class_scope and eigen_scope.

Module.nesting is different though :

  • [B] for class_scope
  • [#<Class:B>, B] for eigen_scope

So Module.nesting.first.ancestors is :

  • [B, A, Object, Kernel, BasicObject] for class_scope
  • [#<Class:B>, A::ClassMethods, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject] for eigen_scope

A isn't searched, but A::ClassMethods!

So you could define :

module A
module ClassMethods
NAME = 'Bob'
end
end


Related Topics



Leave a reply



Submit