Reputation: 709
I was wondering how can I access class variables from modules
module Entity
def foo
# puts @@rules
end
end
class Person
include Entity
attr_accessor :id, :name
@@rules = [[:id, :int, :not_null],
[:name, :string, :not_null]]
end
class Car
include Entity
attr_accessor :id, :year
@@rules = [[:id, :string, :not_null],
[:year:, :int, :not_null]]
end
p = Person.new
c = Car.new
p.foo # [[:id, :int, :not_null], [:name, :string, :not_null]]
c.foo # [[:id, :string, :not_null], [:year, :int, :not_null]]
I took a look at cattr_accessor
and mattr_accessor
from ActiveSupport
, but still can't find a way to solve this.
Upvotes: 8
Views: 8344
Reputation: 22315
Class variables in Ruby are weird when it comes to inheritance. Unless you know exactly what you're messing with there, it's best to avoid them. You might think you aren't using inheritance in this case, but what include
actually does is inserts Entity
into the ancestors of Person
. See:
Person.ancestors
# [Person, Entity, Object, Kernel, BasicObject]
The particular behavior is tricky to describe, but the short version is that basically @@rules
is shared between Entity
, Person
, and Car
! Look:
Entity.class_variable_set(:@@rules, 'foo')
puts Car.class_variable_get(:@@rules)
# foo
puts Person.class_variable_get(:@@rules)
# foo
You probably don't want that!
It's better to use a class instance variable here, which is actually separate for each class.
module Entity
# create the class instance variable methods when this is included
def self.included klass
klass.singleton_class.send(:attr_reader, :rules)
end
def foo
puts self.class.rules
end
end
class Person
include Entity
attr_accessor :id, :name
@rules = [[:id, :int, :not_null],
[:name, :string, :not_null]]
end
class Car
include Entity
attr_accessor :id, :year
@rules = [[:id, :string, :not_null],
[:year, :int, :not_null]]
end
Upvotes: 12
Reputation: 19221
This isn't really my answer, it's a variation on @Max's answer, just without exposing the @rules
variable (see @Quarktum's comments).
The difference here is that I'm using the #module_exec
method, which allows instance variable access (unlike #module_eval
).
Also, I'm defining the .foo
and #foo
methods under the scope of the including class, so that the methods are the classes methods rather than the module's methods (test with Car.methods false
to view Car's methods without inheritance).
module Entity
# create the class instance variable methods when this is included
def self.included klass
klass.module_exec do
@rules ||= []
def self.foo
puts @rules
end
def foo
self.class.foo
end
end
end
end
class Person
include Entity
attr_accessor :id, :name
@rules = [[:id, :int, :not_null],
[:name, :string, :not_null]]
end
class Car
include Entity
attr_accessor :id, :year
@rules = [[:id, :string, :not_null],
[:year, :int, :not_null]]
end
Upvotes: 0
Reputation: 884
In addition to the already given answers, here's something I found out sometime back.
module MyModule
@@my_variable = 5
define_singleton_method(:my_variable) do
@@my_variable
end
end
Now you'll be able to access the class variable in two ways:
MyModule::my_variable
or MyModule.my_variable
.
This now works like an attr_reader. You can define a second singleton method for assignment.
Upvotes: 2
Reputation: 7349
It's not the most elegant solution but class_eval works:
module Entity
def foo
self.class.class_eval('@@rules')
end
end
Edit: Actually slightly cleaner may be to use class_variable_get
module Entity
def foo
self.class.class_variable_get(:@@rules)
end
end
Upvotes: 2