Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Agile Web Development With Rails, 2nd Edition (2006).pdf
Скачиваний:
30
Добавлен:
17.08.2013
Размер:
6.23 Mб
Скачать

TABLES AND CLASSES 284

That’s all there is to it—in this case no configuration information (apart from the database connection stuff) is required. Somehow Active Record figured out what we needed and got it right. Let’s have a look at how this works.

17.1Tables and Classes

When you create a subclass of ActiveRecord::Base, you’re creating something that wraps a database table. By default, Active Record assumes that the name of the table is the plural form of the name of the class. If the class name contains multiple capitalized words, the table name is assumed to have underscores between these words. Some irregular plurals are handled.

Class Name

Table Name

Class Name

Table Name

 

 

 

 

Order

orders

LineItem

line_items

 

 

 

 

TaxAgency

tax_agencies

Person

people

 

 

 

 

Batch

batches

Datum

data

 

 

 

 

Diagnosis

diagnoses

Quantity

quantities

 

 

 

 

These rules reflect DHH’s philosophy that class names should be singular while the names of tables should be plural. If you don’t like this behavior, you can change it using the set_table_name directive.

class Sheep < ActiveRecord::Base

 

set_table_name "sheep"

# Not "sheeps"

end

 

class Order < ActiveRecord::Base

 

set_table_name "ord_rev99_x"

# Wrap a legacy table...

end

 

If you don’t like methods called set_xxx, there’s also a more direct form.

class Sheep < ActiveRecord::Base self.table_name = "sheep"

end

17.2Columns and Attributes

Active Record objects correspond to rows in a database table. The objects have attributes corresponding to the columns in the table. You probably noticed that our definition of class Order didn’t mention any of the columns in the orders table. That’s because Active Record determines them dynamically at runtime. Active Record reflects on the schema inside the database to configure the classes that wrap tables.2

2. This isn’t strictly true, because a model may have attributes that aren’t part of the schema. We’ll discuss attributes in more depth in the next chapter, starting on page 380.

Report erratum

COLUMNS AND ATTRIBUTES 285

David Says. . .

Where Are My Attributes?

The notion of a database administrator (DBA) as a separate role from programmer has led some developers to see strict boundaries between code and schema. Active Record blurs that distinction, and no other place is that more apparent than in the lack of explicit attribute definitions in the model.

But fear not. Practice has shown that it makes little difference whether you’re looking at a database schema, a separate XML mapping file, or inline attributes in the model. The composite view is similar to the separations already happening in the Model-View-Control pattern—just on a smaller scale.

Once the discomfort of treating the table schema as part of the model definition has dissipated, you’ll start to realize the benefits of keeping DRY. When you need to add an attribute to the model, you simply create a new migration and reload the application.

Taking the “build” step out of schema evolution makes it just as agile as the rest of the code. It becomes much easier to start with a small schema and extend and change it as needed.

In the Depot application, our orders table is defined by the following migration.

Download depot_r/db/migrate/005_create_orders.rb

def self.up

create_table :orders do |t| t.column :name, :string t.column :address, :text t.column :email, :string

t.column :pay_type, :string, :limit => 10 end

We’ve already written an Order model class as part of the Depot application. Let’s use the handy-dandy script/console command to play with it. First, we’ll ask for a list of column names.

depot> ruby script/console

Loading development environment. >> Order.column_names

=> ["id", "name", "address", "email", "pay_type"]

Then we’ll ask for the details of the pay_type column.

>> Order.columns_hash["pay_type"]

=> #<ActiveRecord::ConnectionAdapters::MysqlColumn:0x23d8b5c @sql_type="varchar(10)", @default=nil, @name="pay_type",

Report erratum

COLUMNS AND ATTRIBUTES 286

SQL Type

Ruby Class

SQL Type

Ruby Class

 

 

 

 

int, integer

Fixnum

float, double

Float

 

 

 

 

decimal, numeric

BigDecimal¹

char, varchar, string

String

 

 

 

 

interval, date

Date

datetime, time

Time

 

 

 

 

clob, blob, text

String

boolean

see text

 

 

 

 

¹ Decimal and numeric columns are mapped to integers when their scale is 0

Figure 17.1: Mapping SQL Types to Ruby Types

@number=false, @limit=10, @text=true, @type=:string, @null=true, @primary=false>

Notice that Active Record has gleaned a fair amount of information about the pay_type column. It knows that it’s a string of at most 10 characters, it has no default value, it isn’t the primary key, and it may contain a null value. This information was obtained by asking the underlying database the first time we tried to use the Order class.

Figure 17.1 shows the mapping between SQL types and their Ruby representation. Decimal columns are slightly tricky: if the schema specifies columns with no decimal places, they are mapped to integers; otherwise they are mapped to Ruby BigDecimal objects, ensuring that no precision is lost.

Accessing Rows and Attributes

Active Record classes correspond to tables in a database. Instances of a class correspond to the individual rows in a database table. Calling Order.find(1), for instance, returns an instance of an Order class containing the data in the row with the primary key of 1.

The attributes of an Active Record instance generally correspond to the data in the corresponding row of the database table. For example, our orders table might contain the following data.

depot>

mysql -u root

depot_development

 

 

mysql>

select * from

orders limit 1;

 

 

+----

+-------------

 

+-------------

 

+-----------------------

+----------

+

| id |

name

|

address

| email

| pay_type |

+----

+-------------

 

+-------------

 

+-----------------------

+----------

+

|

1 |

Dave Thomas

|

123 Main St | customer@pragprog.com

| check

|

+----

+-------------

 

+-------------

 

+-----------------------

+----------

+

1

row in set (0.00

sec)

 

 

 

Report erratum

COLUMNS AND ATTRIBUTES 287

If we fetched this row into an Active Record object, that object would have five attributes. The id attribute would be 1 (a Fixnum), the name attribute the string

"Dave Thomas", and so on.

You access these attributes using accessor methods. Rails automatically constructs both attribute readers and attribute writers when it reflects on the schema.

o = Order.find(1)

 

puts o.name

#=> "Dave Thomas"

o.name = "Fred Smith"

# set the name

Setting the value of an attribute does not change anything in the database— you must save the object for this change to become permanent.

The value returned by the attribute readers is cast by Active Record to an appropriate Ruby type if possible (so, for example, if the database column is a time stamp, a Time object will be returned). If you want to get the raw value of an attribute, append _before_type_cast to its name, as shown in the following code.

account.balance_before_type_cast

#=> "123.4", a string

account.release_date_before_type_cast #=> "20050301"

Inside the code of the model, 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, and others don’t. This makes it hard for Active Record to create an abstraction for 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.3 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

3. Ruby has a simple definition of truth. Any value that is not nil or the constant false is true.

Report erratum