Rails Class Attribute Accessor
Summary
We have seen in the attr_accessor discussion how Ruby provides accessors to get/set the attributes of object instances. Curiously, Ruby does not provide the same facilities for class variables.
Of course this did not deter Rails from adding these facilities in Active Support. To easily understand the code that we will show below, you can refer back to our discussion of how to implement in Ruby (note: just as an experiment, as it is implemented in C) attr_accessor ; the code for cattr_accessor is a bit more complex, but quite similar.
-
The differences with respect to the implementation of attr_accessor (referred above) are the following:
- the dynamic method created must be a class method, so we add the self. to the method name, as in def self.#{name}, where #{name} interpolates the name from the loop outside class_eval
- however, Rails also wants to provide the facility to set the class variables with an instance method, so we also have the def #{name} method
- finally, there is this difference between instance variables and class instance variables: while we can access an instance variable which has not been defined (we obtain nil), we would get an undefined exception if we do the same with a class variable.
And here it is:
class Class def cattr_reader(*names) names.each do |name| class_eval <<-"EOS" unless defined? @@#{name} @@#{name} = nil end def self.#{name} @@#{name} end def #{name} @@#{name} end EOS end enddef cattr_writer(*names) names.each do |name| class_eval <<-"EOS" def self.#{name}=(val) @@#{name}=val end def #{name}=(val) @@#{name}=val end EOS end end def cattr_accessor(*names) cattr_reader(*names) cattr_writer(*names) end end # Class - A quick and simple test:
class A cattr_accessor :asset_host end# first, print asset_host when not assigned p A.asset_host # nil A.asset_host = "http://assets.example.com" p A.asset_host #"http://assets.example.com" - Examples in Rails: the user can serve the javascripts, stylesheets and images
from an asset server, rather than from the public directory (this explains why we
chose as test the previous snippet):
# In ActionController::Base cattr_accessor :asset_host @asset_host = ""# in config/environments/production.rb # (uncommenting the line, for the example) config.action_controller.asset_host = "http://assets.example.com" - another Rails example (more interesting; the class variable is a Hash):
# ActionController::Base @@param_parsers = { Mime::MULTIPART_FORM => :multipart_form, Mime::URL_ENCODED_FORM => :url_encoded_form, Mime::XML => :xml_simple, Mime::JSON => :json } cattr_accessor :param_parsers# in ActionController::AbstractRequest def parse_formatted_request_parameters content_type, boundary = ... mime_type= Mime::Type.lookup(content_type) strategy = ActContr::Base.param_parsers[mime_type] case strategy when :url_encoded_form # .. when :multipart_form # .. - use Class variables when it makes sense; for example, for information that is static, or for configuration information that can be modified by the user. The fact that they are visible by all subclasses is, in this circumstance, a benefit, not a sin.
- Moreover, as Ruby did not provide accessors for class variables, Rails solves the problem, with the simple implementation that we saw above.
- Notice: in the blogs you will sometimes find curious debates
whether the class variables are evil (because they are visible throughout all
the hierarchy of subclasses, etc) or good.
The answer that Rails gives to these types of dilemmas is typical of its philosophy:
[URL: ; Last updated: ]