2.5.
Attributes
You've now seen metaprogramming in action
through the console. Your applications will use your model objects
in the same way. One of the drawbacks of Active Record is the
terseness of the source codeit won't tell you much. If you know
what's going on under the covers, though, you can easily understand
what attributes and methods your class supports.
2.5.1.
Columns
Let's review what happens when Ruby loads the
Photo class. From the class name Photo, Active
Record infers that the database table name is photos. It
then queries the database system tables, which have detailed
information about the tables in the database, to get the
photos table definition. Next, it places information about
the definition of each column into the @@columns class variable.
@@columns is an array of Column objects; each
column has these attributes:
name
-
The name of the database column.
type
-
The Ruby type of the attribute this column will
generate.
number
-
A Boolean value that's TRue if the
column's data is numeric. You'll access it through the accessor
method number?.
limit
-
The maximum size of the data element. For
example, for a database column of type varchar(45), the
limit would be 45.
null
-
A Boolean value that's true only if the
column can be set to null. You'll access it through the
accessor method null?.
text
-
A Boolean value that's true only if the
column can be interpreted as text. You'll access it through the
accessor text?.
default
-
The default value you specified in the table
definition.
primary
-
A Boolean value that's true if the
column is the Rails unique identifier. You'll access it through the
accessor method primary?.
Your applications can use this column metadata
to build dynamic user interfaces. Scaffolding, discussed in
"rubyrails-chp-4.html#rubyrails-chp-4">Chapter 4, uses this
technique. Normally, you want to get the data only for a column, so
you'll use an attribute's accessors.
2.5.2.
Accessors
You've seen that you can access the database
columns of a photo by simply calling an accessor like
photo.filename. The Rails implementation isn't necessarily
what you'd expect. You might expect to see the accessor for
filename as a method on photo. Strangely, if you
type:
photo.methods.include? 'filename'
in the console, you get false, which
means that there's no explicit filename accessor for
photo. Active Record uses a Ruby metaprogramming trick to
attach attributes. It overrides the method_missing method,
which gets invoked if you call a nonexistent method of some object.
Consider this program:
class Talker
def method_missing(method)
if method.to_s =~ /say_/
puts $'
end
end
end
This Talker class responds to any
message beginning with say_, even though no method
beginning with say_ exists. For example,
Talker.new.say_hello prints hello. Active Record
uses this trick to implement accessors. As a
consequence, include? returns false for accessors
because the class doesn't include an explicit accessor method.
You'll see later that Active Record also generates custom finders,
like find_by_filename, for each class.
2.5.3.
Identifiers
The id attribute is special to Active
Record because that column serves as the primary key for the
database table. Our migration created the id column, and a
primary key based on the id, automatically. The underlying
table definition, shown in
"#rubyrails-chp-2-fig-2">Figure 2-2, identifies the primary key
with the primary key(id) statement. You might expect
Active Record to recognize the unique identifier by seeing which
columns are included in a table's primary key, but this strategy is
not always possible. Some database managers don't have simple APIs
to discover primary or foreign keys, so Active Record uses the
id naming convention instead (see
"#rubyrails-chp-2-table-2">Table 2-2).
Table 2-2. Active Record adds these
methods and attributes to model objects at runtime
|
Features
|
Purpose
|
|
Methods
|
|
find_by_<column_name>
|
Active Record adds a class method to the class
for each column in the database, including id. For
example, Active Record adds find_by_id,
find_by_name and find_by_email to a class
wrapping a table having id, name, and
email columns.
|
|
find_by_<column_name>_and_<column_name>
|
Active Record also adds finders that combine
groups of attributes. For example, a Person class wrapping
a table with name and email columns would support
Person.find_by_name_and_email(name, email).
|
|
Attributes
|
|
<column_name>
|
Active Record creates an attribute with getters
and setters for each property in the database. For example,
photo.filename = "dog.jpg" would be legal for a
photo instance of a class wrapping a table with a
filename column.
|
|
In a typical Rails migration script, you will
not see the id column. Rails manages the id field
for you by default. In the typical case, Active Record maps the
id onto a database sequence, so the database creates the
initial value of id. You don't have to let Active Record
manage your identifiers. For example, you could have a
Photo class with a timestamp attribute called
created_at:
class Photo < ActiveRecord::Base
set_primary_key "created_at"
end
There are some restrictions, though. The most
prominent restriction (as of Rails 1.1) is that you can't use
composite keys, or primary keys using more than one database
column. If you need to use composite keys, one way to solve the
problem is to introduce a new column to serve as your identifier.
It need not be the primary key.
Alternatively, you could create a database view.
A view is a logical view of
database data. You can access the results of any query as a view.
You could use a view to introduce a new column or to combine
several existing columns into one. Active Record could then use the
view instead of the table.
Rules for updating views vary across database
managers, so depending on your database manager, you'd either have
to customize Active Record or use views only for read-only tables.
Both approaches have been successfully used in production
applications.
|
 |