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

MISCELLANY 320

This is not always directly enforceable by Active Record or Ruby—you could, for example, use the replace method of the String class to change the value of one of the attributes of a composite object. However, should you do this, Active Record will ignore the change if you subsequently save the model object.

The correct way to change the value of the columns associated with a composite attribute is to assign a new composite object to that attribute.

customer = Customer.find(123) old_name = customer.name

customer.name = Name.new(old_name.first, old_name.initials, "Smith") customer.save

17.7Miscellany

This section contains various Active Record–related topics that just didn’t seem to fit anywhere else.

Object Identity

Model objects redefine the Ruby id and hash methods to reference the model’s primary key. This means that model objects with valid ids may be used as hash keys. It also means that unsaved model objects cannot reliably be used as hash keys (because they won’t yet have a valid id).

Two model objects are considered equal (using ==) if they are instances of the same class and have the same primary key. This means that unsaved model objects may compare as equal even if they have different attribute data. If you find yourself comparing unsaved model objects (which is not a particularly frequent operation), you might need to override the == method.

Using the Raw Connection

You can execute SQL statements using the underlying Active Record connection adapter. This is useful for those (rare) circumstances when you need to interact with the database outside the context of an Active Record model class.

At the lowest level, you can call execute to run a (database-dependent) SQL statement. The return value depends on the database adapter being used. For MySQL, for example, it returns a Mysql::Result object. If you really need to work down at this low level, you’d probably need to read the details of this call from the code itself. Fortunately, you shouldn’t have to, because the database adapter layer provides a higher-level abstraction.

The select_all method executes a query and returns an array of attribute hashes corresponding to the result set.

res = Order.connection.select_all("select id, quantity*unit_price as total " + " from line_items")

p res

Report erratum

MISCELLANY 321

This produces something like

[{"total"=>"29.95", "id"=>"91"}, {"total"=>"59.90", "id"=>"92"}, {"total"=>"44.95", "id"=>"93"}]

The select_one method returns a single hash, derived from the first row in the result set.

Have a look at the Rails API documentation for AbstractAdapter for a full list of the low-level connection methods available.

The Case of the Missing ID

There’s a hidden danger when you use your own finder SQL to retrieve rows into Active Record objects.

Active Record uses a row’s id column to keep track of where data belongs. If you don’t fetch the id with the column data when you use find_by_sql, you won’t be able to store the result in the database. Unfortunately, Active Record still tries and fails silently. The following code, for example, will not update the database.

result = LineItem.find_by_sql("select quantity from line_items") result.each do |li|

li.quantity += 2 li.save

end

Perhaps one day Active Record will detect the fact that the id is missing and throw an exception in these circumstances. In the meantime, the moral is clear: always fetch the primary key column if you intend to save an Active Record object into the database. In fact, unless you have a particular reason not to, it’s probably safest to do a select * in custom queries.

Magic Column Names

A number of column names that have special significance to Active Record. Here’s a summary.

created_at, created_on, updated_at, updated_on

Automatically updated with the time stamp of a row’s creation or last update (page 375). Make sure the underlying database column is capable of receiving a date, datetime, or string. Rails applications conventionally use the _on suffix for date columns and the _at suffix for columns that include a time.

lock_version

Rails will track row version numbers and perform optimistic locking if a table contains lock_version (page 389).

Report erratum

MISCELLANY 322

type

Used by single-table inheritance to track the type of a row (page 343).

id

Default name of a table’s primary key column (page 288).

xxx_id

Default name of a foreign key reference to table named with the plural form of xxx (page 323).

xxx_count

Maintains a counter cache for the child table xxx (page 361).

position

The position of this row in a list if acts_as_list is used (page 354).

parent_id

A reference to the id of this row’s parent if acts_as_tree is used (page 356).

Report erratum

Chapter 18

Active Record Part II:

Relationships between Tables

Most applications work with multiple tables in the database, and normally there’ll be relationships between some of these tables. Orders will have multiple line items. A line item will reference a particular product. A product may belong to many different product categories, and the categories may each have a number of different products.

Within the database schema, these relationships are expressed by linking tables based on primary key values.1 If a line item references a product, the line_items table will include a column that holds the primary key value of the corresponding row in the products table. In database parlance, the line_items table is said to have a foreign key reference to the products table.

But that’s all pretty low level. In our application, we want to deal with model objects and their relationships, not database rows and key columns. If an order has a number of line items, we’d like some way of iterating over them. If a line item refers to a product, we’d like to be able to say something simple, such as

price = line_item.product.price

rather than

product_id = line_item.product_id

product

=

Product.find(product_id)

price

=

product.price

1. There’s another style of relationship between model objects in which one model is a subclass of another. We discuss this in Section 18.4, Single-Table Inheritance, on page 343.