Setters and getters provide a great deal of convenience and flexibility with classes. Today, lets take a quick look into
cattr_accessor in Rails and possible things to watch out for.
cattr_accessor which is also an alias to
mattr_accessor (c = Class, m = Module, you get the idea) is described as:
Defines both class and instance accessors for class attributes.
Which makes it convenient to use the accessor under different situations. Something like this:
class A cattr_accessor :foo end A.foo = 10 A.foo => 10 A.new.foo => 10
Lets dive in further, and see how it works with subclasses
class A cattr_accessor :foo end # Subclass B = Class.new(A) A.foo = 10 A.foo => 10 B.foo => 10 # Setting value of cattr_accessor from a subclass B.foo = 20 => 20 A.foo => 20
Yep, we can freely update the attribute from its subclass. This is by design and it feels very interesting. However, in large codebases, where you are DRY-ing up your code or subclassing the parent class to extend functionalities, this behavior of
cattr_accessor can start to feel risky (scary?). It has too much influence and power 🙃.
Perhaps, if a class is defining a
cattr_accessor, from design principles perspective, it may make the best sense to never subclass it and to avoid scenarios of accidentally updating the attribute. Which can get pretty hard to debug.
Here is a another simplifeid example of
cattr_accessor I came across in an open source project (now deprecated), which showed a pretty risky implementation.
module A def self.const_missing(name) klass = OtherClass klass.foo = "A" self.const_set(name, klass) klass end end module B def self.const_missing(name) klass = OtherClass klass.foo = "B" self.const_set(name, klass) klass end end class OtherClass cattr_accessor :foo end
Here, we are only setting the attribute when a new constant is defined. Running this code in Rails application’s runtime and interchangeably using
B modules introduced very unexpected behavior in application logic.
A::ConstFoo.foo => A B::ConstFoo.foo => B A::ConstFoo.foo => B B::ConstFoo.foo => B
The expected value is not returned and the attribute value is from the last time
const_missing was called. That being said, I personally try to stay away from
cattr_accessor. Unless, there is a strong use case and a reason to use it.