ActiveSupport is the libraryof utilitymethods that Rails uses. We examine them in detail here for two reasons. First, theycan be useful to our application code—we can directlyuse manyof these libraries and methods to our advantage when writing Rails applications. Secondly, we can learn many things about Ruby programming by dissecting these parts. They are small and relatively easy to digest.
Dependencies
autoloads
missing constants by trying to find the file associated with the
constant. When you attempt to access a nonexistent constant, such as
Message, Dependencies
will try to
find and load message.rb from any directory in
Dependencies.load_paths
.
Dependencies
defines Module#const_missing
and Class#const_missing
, which both proxy to
Dependencies.load_missing_constant(const_parent,
const_id)
. That method searches the load paths for a file
with the appropriate name; if found, Dependencies
loads the file and ensures that
it defined the appropriate constant.
Alternatively, Rails will create an empty module to satisfy
nesting in the case of nested models and controllers. If a
directorynamed app/models/store/ exists, Store
will be created as an empty module, by
the following process:
const_missing calls
Dependencies.load_missing_constant(Object,:Store)
.
load_missing_constant
attempts to find and load store.rb somewhere
in its list of load paths (Dependencies.load_paths)
. It fails to
find such a file.
load_missing_constant
sees that app/models/store exists and is a
directory. It creates a module, assigns it to the appropriate
constant, and returns.
The ActiveSupport::Deprecation
module provides a
method bywhich old APIs are marked for removal. At its core, it is
just a fancywarning mechanism. When old APIs are used, they generate a
warning in development or test mode. Deprecation warnings are invoked through the Active Support::Deprecation.warn(message,
callstack)
method. The ActiveSupport::Deprecation.silence
method
silences those warnings for the duration of the provided
block.
The deprecate
class method
provides an easy way to mark a method as deprecated while still making it available. It decorates
the given method with the deprecation warning.
def find_first(conditions = nil, orderings = nil, joins = nil) # :nodoc: find(:first, :conditions => conditions, :order => orderings, :joins => joins) end deprecate :find_first => "use find(:first, ...)"
ActiveSupport::Deprecation.behavior
is a
Proc
that is called when a
deprecation warning is triggered. It takes two arguments: the
deprecation message and the callstack. It can be replaced to modify
the default behavior. By default, in the test environment, deprecation
warnings print to the standard error stream. In development, they go
to the logger. In production, deprecation warnings are
silenced.
ActionController defines a set of objects that are made available to controllers. These
objects used to be publicly available instance variables; this usage is now deprecated. For
example, the session object (available to controllers through the
session
and session=
methods) used to be accessible as
@session
. This creates a problem:
how do we intercept access to these deprecated instance variables?
Ruby isn't so helpful as to provide us a hook that informs us upon
instance variable access.
Rails uses a neat trick: a proxyclass. The protected objects
were moved out of those instance variables and moved into "internal"
instance variables (which begin with an underscore). The old
instance variables were replaced with instances of Active Support::
Deprecation::DeprecatedInstanceVariableProxy
, which
proxies to the real objects. When a method is called on these
proxies (such as @session.id
), a
warning is generated before delegating the call to the real object.
This is a common Ruby trick that is used in several other places in
Rails—replacing a standard object with a proxy object that responds
in a special way to certain methods.
The Inflector
module provides
a set of simple transformations on English words to facilitate
ActiveRecord's manipulations of class and table names. Following the
policy of "convention over configuration," for example, a model class
named Message
would correspond to a
table name of messages
.
The core of Inflector
is the
pluralization rules, contained in inflections.rb.
The default set of rules is fairly broad, but additional rules can be
added easily in config/initializers/
inflections.rb or a similar configuration file, after the
framework loads:
Inflector.inflections do |inflect| inflect.plural /^(ox)$/i, '\1\2en' inflect.singular /^(ox)en/i, '\1' inflect.irregular 'octopus', 'octopi' inflect.uncountable "equipment" end
Inflector.inflections
yields
a singleton instance of the inflections object. Rules are prepended to
the list of inflections, so these rules will override the default as
long as they are loaded after the Rails framework. Another consequence
of the load order is that the rules should be ordered from most
general to most specific within a block; the last appropriate
inflection rule seen will be used.
The regular expression captures and backreferences ensure that
initial capitals are handled correctly; the initial letters (upper-or
lowercase) are captured with a case-insensitive regex and substituted
into the replacement. The irregular
and uncountable
rules take care of
that work for us:
"ox".pluralize # => "oxen" "Ox".pluralize # => "Oxen" "Octopus".pluralize # => "Octopi" "Equipment".pluralize # => "Equipment"
Inflector's
module methods,
which actuallyperform the transformations, are proxied from the core
extensions to String
and Integer
; they are usually not called
directly on the Inflector module object. [18] See the respective sections in the Core Extensions
documentation later in the chapter for more information.
json.rb, json/encoders.rb, json/encoders/core.rb
JSON (JavaScript Object Notation, pronounced "Jason") is a
lightweight subset of Java-Script's notation for literal objects (hash tables) used for data
interchange on the Web. Active
Support::JSON
provides encoders for most basic Ruby data
types. The encoders are proxied by the Object#to_json
method, added by Core
Extensions.
(1..5).to_json # => "[1, 2, 3, 4, 5]" {:a => 1, :b => [2, 3]}.to_json # => "{b: [2, 3], a: 1}"
The JSON library protects against circular references, which cannot be encoded into JSON:
a = {} b = {:a => a} a[:b] = b a # => {:b=>{:a=>{...}}} a.to_json # !> ActiveSupport::JSON::CircularReferenceError: object references itself
whiny_nil.rb
The extensions to NilClass
are an ingenious part of ActiveSupport. They are designed to help trap
unexpected nil
values as earlyas
possible, and to provide more sensible error messages to the developer
when nil
is encountered.
Without these extensions, calling one of Array's
instance methods on an object that
happened to be nil
would generate a
standard NoMethodError
if NilClass
did not also contain the method.
This could be frustrating to track down, especiallyif the method was
called from deep in the framework.
Whiny Nil intercepts those NoMethodErrors
with method_missing
and makes a suggestion about
the type of object the developer may have been expecting (either
Array
or ActiveRecord::Base
), based on the name of
the method called.
nil.sort # !> NoMethodError: You have a nil object when you didn't expect it! # !> You might have expected an instance of Array. nil.save # !> NoMethodError: You have a nil object when you didn't expect it! # !> You might have expected an instance of ActiveRecord::Base.
The Whiny Nil extensions also redefine NilClass#id
, which raises an error. Without
this extension, nil.id
would return
4 (Ruby's immediate representation of nil
, the same as nil.object_id
). This would be confusing when
chaining methods together. Under the Whiny Nil system, this raises a more informative
exception:
nil.id # !> RuntimeError: Called id for nil, which would mistakenly be 4 - # !> if you really wanted the id of nil, use object_id
The Whiny Nil system can be turned off in the Rails
configuration bysetting config.whiny_
nils
to false
.