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 could overwrite DataMapper's save, delete, destroy! method using module, such as:
require 'data_mapper'
module Record
def self.included(base)
base.class_eval do
include DataMapper::Resource
property :id, DataMapper::Property::Serial
alias :parent_save :save
def save bar
# do additional thing with bar
end
end
end
end
class User
include Record
property :name,String
end
DataMapper.finalize
# so i could call something like this:
x = User.new
x.name = 'something'
x.save 123
how to overwrite create and first_or_create method, when those methods are not found on base.class_eval do?
So i could call something like this:
User.first_or_create additional_param, name: 'something'
You can override class methods by adding this to your class_eval block:
class << self
alias :parent_first_or_create, :first_or_create
def first_or_create
# ...
end
# do other things with class methods
end
I don't understand the keywords like attr_reader or property in the following example:
class Voiture
attr_reader :name
attr_writer :name
property :id, Serial
property :name, String
property :completed_at, DateTime
end
How do they work? How can I create my own? Are they functions, methods?
class MyClass
mymagickstuff :hello
end
That are just class methods. In this example has_foo adds a foo method to an instance that puts a string:
module Foo
def has_foo(value)
class_eval <<-END_OF_RUBY, __FILE__, __LINE__ + 1
def foo
puts "#{value}"
end
END_OF_RUBY
end
end
class Baz
extend Foo
has_foo 'Hello World'
end
Baz.new.foo # => Hello World
Those are class methods, you can add them to a class, or create your own class that has addition methods. In your own class:
class Voiture
def self.my_first_class_method(*arguments)
puts arguments
end
end
Or to add to a class:
Voiture.class_eval do
define_method :my_second_class_method do |*arguments|
puts arguments
end
end
Once such a class method is defined, you can use it like this:
class VoitureChild < Voiture
my_first_class_method "print this"
my_second_class_method "print this"
end
There are also ways to do this by adding modules to a class, which is often how rails does such things, such as using a Concern.
You will want to monkey patch the class Module. That's where methods like attr_reader reside.
class Module
def magic(args)
puts args.inspect
end
end
class A
magic :hello, :hi
end
#=> [:hello, :hi]
As The Tin Man mentioned, monkey-patching base-level classes can be dangerous. Consider it like time-traveling into the past and adding something in the past. Just make sure that what you are adding isn't going to overwrite some other event or else you could come back to a Ruby script/timeline that isn't the same one you left.
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