Defining class variables inside class_eval inside Module#included

mongodb, ruby

Solution

One method would be to create accessor methods for the class variable.

module Persist
    def self.included(base)
        # Adds class methods from 'ClassMethods' to including class.
        base.extend(ClassMethods)
        base.class_eval do
            self.collection = Connection.new.db('nameofdb').collection(self.to_s.downcase)
            # ...
        end
    end
    module ClassMethods
        def collection=(value)
            @@collection = value
        end
        def collection
            @@collection
        end
    end
    # Instance methods follow
    def find
        self.class.collection.find().first
        #...
    end
end

class User
    include Persist
end

class Post
    include Persist
end

Another method would be to access the class variables of the including class in the module via the accessors class_variable_set, etc.

def self.included(base)
    base.class_eval do
        class_variable_set('@@collection', Connection.new.db('nameofdb').collection(self.to_s.downcase))
        # …
    end
end

I'll take a stab at answering your question "How is this possible? Isn't the context inside the class_eval block that of the class."

The class_eval method does make self refer to the class it's being invoked on inside the given block. This allows you to invoke class methods, etc. However, class variables will be evaluated in the context that the block is bound to - here, the module.

For instance, try doing this:

class Foo
    @@bar = 1
end

Foo.class_eval { puts @@bar }

This would result in the exception "NameError: uninitialized class variable @@bar in Object". Here the given block is bound to the context of "Object", the top-level namespace.

Problem

How does one define class variables inside a class_eval block? I have the following: ``` module Persist def self.included(base) # base is the class including this module base.class_eval do # class context begin @@collection = Connection.new.db('nameofdb').collection(self.to_s.downcase) def self.get id # Class method #... end end end # Instance methods follow def find @@collection.find().first #... end end class User include Persist end class Post include Persist end ``` The classes User and Post both show `:get` when introspecting using `User.methods` or `Post.methods`. Which makes sense, as they are defined in the context of class_eval and is exactly what I need. Similarly the method `:find` is shown as an instance_method of the individual classes. However, what I thought was a class variable i.e. `@@collection`, turns out to be a Module level class_variable. When I introspect `User.class_variables` or `Post.class_variables`, they turn up empty. However `Persist.class_variables` shows `:@@collection`. How is this possible? Isn't the context inside the `class_eval` block that of the class. So shouldn't the variable `@@collection` get defined on the class and not the module? Also, the value of `@@collection` is always the last Class's name that included it. So in this case it is always 'posts' and never 'users'. I think this is because it's a module level variable, it will change on every include. Is this correct? Finally how would I define a class variable in this context so that each class has it's own `@@collection` definition.

Original source