Reputation: 2103
I have a class with attr_accessor set like this:
class Human
ATTRIBUTES = [:name]
attr_accessor *ATTRIBUTES
end
it works like a charm, allows me to keep attributes inside ATTRIBUTES constant. Problem is I would like to have a class Student inheriting from a Human class, without the need to put attr_accessor every time. Basically what i would like to have is this:
class Student < Human
ATTRIBUTES = [:name, :school]
end
unfortunately when i do
Student.new.school
i get no method error, because attr_accessor is loaded from Human and not a Student. What construction should i use to accomplish my goal?
Upvotes: 2
Views: 1522
Reputation: 3847
Well, while I don't get the need to keep the attributes in a array, Student
class will already inherit the attr_accessor
's defined in it's parent class.
For example:
class Human
attr_accessor :name, :gender
end
class Student < Human
attr_accessor :school
end
Student class now has :name, :gender and :school attr_accessor
's:
> Student.new.respond_to?(:name)
=> true
> Student.new.respond_to?(:name=)
=> true
> Student.new.respond_to?(:school)
=> true
> Student.new.respond_to?(:school=)
=> true
Human also responds to :name
and :gender
> Human.new.respond_to?(:name)
=> true
> Human.new.respond_to?(:gender)
=> true
But not to school
> Human.new.respond_to?(:school)
=> false
It's cleaner, it's the ruby way, easier to understand.
Upvotes: 2
Reputation: 6753
I personally agree with @lcguida's answer, but I came up with a little experiment if you insist on following the pattern you proposed. The other answers already covered why your solution didn't work, so I'm not getting into that here.
The first thing that came to mind was to call attr_accessor
on the self.inherited
callback on the parent class, but unfortunately the child's body is not loaded until later. Even so, where there's a will, there's a way. If you're using Ruby 2.0 or later, the following implementation will work.
module LazyAttrAccessorizer
def self.extended(obj)
TracePoint.trace(:end) do |t|
if obj == t.self
obj.send :attr_accessor, *obj::ATTRIBUTES
t.disable
end
end
end
end
class Human
extend LazyAttrAccessorizer
ATTRIBUTES = [:name]
def self.inherited(subclass)
subclass.extend LazyAttrAccessorizer
end
end
class Student < Human
ATTRIBUTES = [:name, :school]
# ATTRIBUTES = [:school] would also work as expected, but I think you'd like to be literal there.
end
> Student.new.respond_to?(:name)
=> true
> Student.new.respond_to?(:school)
=> true
Upvotes: 3