I have a situation where I need a variable (e.g. foo
) which is used internally within a class (e.g. Parent
) and sub-classes (e.g. Child
). In my case, I'm using Single Table Inheritance. Child
should inherit the variable foo
from Parent
if Child
does not set foo
. If Child
does set foo
, that value should only change foo
for Child
but not in Parent
. This seems like a clear cut case for class_attribute.
The problem is that class_attribute
is not respecting a private
or protected
keyword, so foo
can be accessed from outside of Parent
and Child
, e.g. from the console:
# Now
Parent.foo
=> "Foo"
Child.foo
=> "Foo"/"Bar"
# What I want
Parent.foo
=> 'method_missing': protected method 'foo' called for Parent:Class (NoMethodError)
Child.foo
=> 'method_missing': protected method 'foo' called for Child:Class (NoMethodError)
I'm wondering how to achieve the setup I've described above, including solutions other that class_attribute
. Note, setting instance_accessor
, instance_writer
, and instance_reader
had no effect. Also note that a class instance variable will not work since Child
will not inherit a value for foo
from Parent
.
class Parent < ApplicationRecord
private
class_attribute :foo, default: "Foo"
end
class Child < Parent
# Child may or may not set :foo. If not, the value should be inherited from Parent.
# Setting :foo in Child should NOT change the value of Parent, hence using class_attribute.
# class_attribute :foo, default: "Bar"
end
CodePudding user response:
I think this meets your needs, if I have understood them correctly! Take a look:
class Parent
@@foo = "parent foo"
class << self
private
def foo
@@foo
end
end
end
class Child < Parent
@@foo = "child foo"
end
class AnotherChild < Parent
@@foo = "another child foo"
class << self
def foo
@@foo
end
end
end
puts Parent.foo # private
puts Parent.send(:foo) # private, but accessible via .send
puts Child.send(:foo) # inherits from Parent, => "parent foo"
puts Child.foo # private method
puts AnotherChild.foo # defined in subclass => "child foo"
The class attributes (@@...) are accessed via getters def foo...
, and the getters are constrained by the private
keyword to control the access as you expressed.
CodePudding user response:
The following should work. It uses class instance variables instead of class variables, which can be a bit tricky to implement.
class Parent < ApplicationRecord
@foo = "Parent"
class << self
private
# This will try to get the class instance variable @foo on the current class,
# trying each parent until base_class is reached.
def foo
if self == base_class
@foo
else
@foo || superclass.send(:foo)
end
end
end
end
class Child < Parent
@foo = "Child"
end
class DeepChild < Child
end
puts Parent.foo # private method error
puts Parent.send(:foo) # private, but accessible via .send
puts Child.foo # private methode error
puts Child.send(:foo) # private, but accessible via .send. Child's @foo || Parent's @foo
puts DeepChild.foo # private method error
puts DeepChild.send(:foo) # private, but accessible via .send. DeepChild's @foo || Child's @foo || Parent's @foo