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.
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
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
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.
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
I was reading the jbuilder's README and saw these code:
class Person
# ... Class Definition ... #
def to_builder
Jbuilder.new do |person|
person.(self, :name, :age)
end
end
end
I tried to replicate it myself, and it asks for a call method, so:
class Thing
attr_accessor :name, :age
def call(*args)
puts args.inspect
end
end
Thing.new.(:name, :age) # => [:name, :age]
So why is there a self in the jbuilder call?
self here is just a parameter passed to the Jbuilder's call method.
Jbuilder needs the instance of person (which is self in the code) and the attribute names (:name and :age in the code) to produce the json data.
Example:
class Thing
attr_accessor :name, :age
def call(*args)
puts args.inspect
end
end
class Bar
def to_thing
Thing.new.(self, :name, :age)
end
end
Bar.new.to_thing
Typically when writing a model in Rails you use a DSL to setup various aspects of derived objects, for example:
class Question < ActiveRecord::Base
has_one :category
validates_presence_of :category
end
In this case, "has_one" and "validates_presence_of" create associations and validation call backs on models instantiated from Question.
I want to add a new method called "parent" to be used when defining a class:
class Question
attr_accessor :category
parent :category
end
q = Question.new
q.category = 'a category'
puts q.parent
-> 'a category'
So when objects are instantiated from class, they should have the method "parent" defined.
How do I do this? My first thought was to use a module, but this isn't an instance method, or a class method.
I believe this is what you are looking for:
module QuestionParent
module ClassMethods
def inherited(descendant)
descendant.instance_variable_set(:#parent, parent.dup)
super
end
def parent(args=nil)
#parent ||= args
end
end
module InstanceMethods
def parent
self.send self.class.parent.to_sym
end
end
def self.included(receiver)
receiver.extend ClassMethods
receiver.send :include, InstanceMethods
end
end
class Question
include QuestionParent
attr_accessor :category
parent :category
end
Which produces:
q = Question.new
q.category = 'a category'
puts q.parent
a category
What this does is add a class method parent that will define the class variable #parent, when an instance calls the parent in the InstanceMethod the #parent symbol (here is category) is called.
Suppose I have a DataMapper scope for carnivores, like this:
class Animal
#...
def self.carnivores
all(:diet => 'meat')
end
#...
end
Can I reuse that scope in an association's scope?
class Zoo
#...
def self.with_carnivores
# Use `Animal.carnivores` scope to get zoos that have some?
all(:animals => ???)
end
#...
end
You can do this by going "in reverse" from the association.
class Zoo
#...
def self.with_carnivores
# Assuming `Animal belongs_to :zoo`
Animal.carnivores.zoo
end
#...
end
class Habitat
#...
def self.with_carnivores
# Assuming `Animal has_many :habitats`
Animal.carnivores.habitats
end
#...
end
I'm trying to reuse methods in DataMapper classes. This might be as well a ruby question I think.
class Foo
include DataMapper::Resource
property :name
property ...
def self.special_name
self.all(:name => 'whatever')
end
end
class Bar
include DataMapper::Resource
property :name
property ...
def self.special_name
self.all(:name => 'whatever')
end
end
So the method special_name would be used for both classes as I want to get out the same result. But it also makes use of DataMapper methods like "all". So how would you do this?
Thx
module SpecialName
def self.included(base)
base.property :name, String
base.extend ClassMethods
end
module ClassMethods
def special_name
all(:name => 'whatever')
end
end
end
class Foo
include DataMapper::Resource
include SpecialName
end
For more information on this include/extend idiom, see http://railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/.
module SpecialName
property :name
def self.special_name
self.all(:name => 'whatever')
end
end
class Foo
include DataMapper::Resource
include SpecialName
end
class Bar
include DataMapper::Resource
include SpecialName
end
The "answer" is wrong. See the answer to this questions for more info: How to extend DataMapper::Resource with custom method
Short version: You need to use DataMapper's built-in extenders because a record/row and a model/table have different classes.
DataMapper::Model.append_extensions(MyModule::ClassMethods) #Add to model
DataMapper::Model.append_inclusions(MyModule::InstanceMethods) #Add to record