Accessing Attributes
If a model object has an attribute named balance, you can access theattribute’s value using the indexing operator, passing it either a string or
a symbol. Here we’ll use symbols.
account[:balance] #=> return current value
account[:balance] = 0.0 #=> set value of balance
However, this is deprecated in normal code, as it considerably reduces
your options should you want to change the underlying implementation
of the attribute in the future. Instead, you should access values or model
attributes using Ruby accessor methods.
account.balance #=> return current value
account.balance = 0.0 #=> set value of balance
The value returned using these two techniques will be cast by Active
Record to an appropriate Ruby type if possible (so, for example, if the
database column is a timestamp, a Time object will be returned). If you
want to get the raw value of an attribute, append _before_type_cast to the
method form of its name, as shown in the following code.
COLUMNS AND ATTRIBUTES 195
David Says. . .
Overriding Model Attributes
Here’s an example of the benefits of using accessors to get at the
attributes of models. Our account model will raise an exception immediately
when someone tries to set a balance below a minimum value.
class Account < ActiveRecord::Base
def balance=(value)
raise BalanceTooLow if value < MINIMUM_LEVEL
self[:balance] = value
end
end
account.balance_before_type_cast #=> "123.4", a string
account.release_date_before_type_cast #=> "20050301"
Finally, inside the code of the model itself, you can use the read_attribute( )
and write_attribute( ) private methods. These take the attribute name as a
string parameter.
Boolean Attributes
Some databases support a boolean column type, others don’t. This makes
it hard for Active Record to abstract booleans. For example, if the underlying
database has no boolean type, some developers use a char(1) column
containing “t” or “f” to represent true or false. Others use integer columns,
where 0 is false and 1 is true. Even if the database supports boolean types
directly (such as MySQL and its bool column type), they might just be
stored as 0 or 1 internally.
The problem is that in Ruby the number 0 and the string “f” are both
interpreted as true values in conditions.4 This means that if you use the
value of the column directly, your code will interpret the column as true
when you intended it to be false.
# DON'T DO THIS
user = Users.find_by_name("Dave")
if user.superuser
grant_privileges
end
4Ruby has a simple definition of truth. Any value that is not nil or the constant false is
true.
To query a column in a condition, you must append a question mark to
the column’s name.
# INSTEAD, DO THIS
user = Users.find_by_name("Dave")
if user.superuser?
grant_privileges
end
This form of attribute accessor looks at the column’s value. It is interpreted
as false only if it is the number zero; one of the strings "0", "f", "false",
or "" (the empty string); a nil; or the constant false. Otherwise it is interpreted
as true.
If you work with legacy schemas or have databases in languages other than
English, the definition of truth in the previous paragraph may not hold.
In these cases, you can override the built-in definition of the predicate
methods. For example, in Dutch, the field might contain J or N (for Ja or
Nee). In this case, you could write
class User < ActiveRecord::Base
def superuser?
self.superuser == 'J'
end
# . . .
end
Storing Structured Data
It is sometimes convenient to store attributes containing arbitrary Ruby
objects directly into database tables. One way that Active Record supports
this is by serializing the Ruby object into a string (in YAML format) and
storing that string in the database column corresponding to the attribute.
In the schema, this column must be defined as type text.
Because Active Record will normally map a character or text column to a
plain Ruby string, you need to tell Active Record to use serialization if you
want to take advantage of this functionality. For example, we might want
to record the last five purchases made by our customers. We’ll create a
table containing a text column to hold this information.
File 6 create table purchases (
id int not null auto_increment,
name varchar(100) not null,
last_five text,
primary key (id)
);
In the Active Record class that wraps this table, we’ll use the serialize( )
declaration to tell Active Record to marshal objects into and out of this
column.
File 8 class Purchase < ActiveRecord::Base
serialize :last_five
# ...
end
When we create new Purchase objects, we can assign any Ruby object to
the last_five column. In this case, we set it to an array of strings.
File 8 purchase = Purchase.new
purchase.name = "Dave Thomas"
purchase.last_five = [ 'shoes', 'shirt', 'socks', 'ski mask', 'shorts' ]
purchase.save
When we later read it in, the attribute is set back to an array.
File 8 purchase = Purchase.find_by_name("Dave Thomas")
pp purchase.last_five
pp purchase.last_five[3]
This code outputs
["shoes", "shirt", "socks", "ski mask", "shorts"]
"ski mask"
Although powerful and convenient, this approach is problematic if you
ever need to be able to use the information in the serialized columns outside
a Ruby application. Unless that application understands the YAML
format, the column contents will be opaque to it. In particular, it will be
difficult to use the structure inside these columns in SQL queries. You
might instead want to consider using object aggregation, described in Section
15.2, Aggregation, on page 247, to achieve a similar effect.