attr_accessor across multiple classes? - ruby

I have a single module with lots of different classes (divided into separate files). Each class has the same set of attr_accessor, so how can I reuse that instead of having to repeat the attr_accessor block?
What I'm currently doing....
# dog.rb
module Animals
class Dog
attr_accessor :name, :color, :age
end
end
# cat.rb
module Animals
class Cat
attr_accessor :name, :color, :age
end
end
# rodent.rb
module Animals
class Rodent
attr_accessor :name, :color, :age
end
end
I tried doing this with no luck...
# animals.rb
module Animals
attr_accessor :name, :color, :age
end
I need to access these modules directly across my app (it's a Rails app). For example: Animals::Dog.give_water

Your use of the module Animal is wrong. Using it as a namespace does not do anything good for your purpose. You should include them.
module Animals
attr_accessor :name, :color, :age
end
class Dog
include Animals
end
class Cat
include Animals
end
class Rodent
include Animals
end
Or, you can turn Animal into a class, and subclass from that.
class Animals
attr_accessor :name, :color, :age
end
class Dog < Animals
end
class Cat < Animals
end
class Rodent < Animals
end
By the way, a class already implies that it has possibly multiple instances, so it is redundant to have a plural name for a class. And you are also inconsistent about it.

Related

How to access class variables from module?

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

Ruby dot parenthesis call syntax

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

Defining class methods in Ruby

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.

Can a DataMapper scope use an associated model's scope?

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

Ruby reuse methods in DataMapper classes (helper)

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

Resources