- •Contents
- •Preface to the Second Edition
- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •The Architecture of Rails Applications
- •Models, Views, and Controllers
- •Active Record: Rails Model Support
- •Action Pack: The View and Controller
- •Installing Rails
- •Your Shopping List
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Linux
- •Development Environments
- •Rails and Databases
- •Rails and ISPs
- •Creating a New Application
- •Hello, Rails!
- •Linking Pages Together
- •What We Just Did
- •Building an Application
- •The Depot Application
- •Incremental Development
- •What Depot Does
- •Task A: Product Maintenance
- •Iteration A1: Get Something Running
- •Iteration A2: Add a Missing Column
- •Iteration A3: Validate!
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B4: Linking to the Cart
- •Task C: Cart Creation
- •Sessions
- •Iteration C1: Creating a Cart
- •Iteration C2: A Smarter Cart
- •Iteration C3: Handling Errors
- •Iteration C4: Finishing the Cart
- •Task D: Add a Dash of AJAX
- •Iteration D1: Moving the Cart
- •Iteration D3: Highlighting Changes
- •Iteration D4: Hide an Empty Cart
- •Iteration D5: Degrading If Javascript Is Disabled
- •What We Just Did
- •Task E: Check Out!
- •Iteration E1: Capturing an Order
- •Task F: Administration
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Iteration F4: A Sidebar, More Administration
- •Task G: One Last Wafer-Thin Change
- •Generating the XML Feed
- •Finishing Up
- •Task T: Testing
- •Tests Baked Right In
- •Unit Testing of Models
- •Functional Testing of Controllers
- •Integration Testing of Applications
- •Performance Testing
- •Using Mock Objects
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Logging in Rails
- •Debugging Hints
- •Active Support
- •Generally Available Extensions
- •Enumerations and Arrays
- •String Extensions
- •Extensions to Numbers
- •Time and Date Extensions
- •An Extension to Ruby Symbols
- •with_options
- •Unicode Support
- •Migrations
- •Creating and Running Migrations
- •Anatomy of a Migration
- •Managing Tables
- •Data Migrations
- •Advanced Migrations
- •When Migrations Go Bad
- •Schema Manipulation Outside Migrations
- •Managing Migrations
- •Tables and Classes
- •Columns and Attributes
- •Primary Keys and IDs
- •Connecting to the Database
- •Aggregation and Structured Data
- •Miscellany
- •Creating Foreign Keys
- •Specifying Relationships in Models
- •belongs_to and has_xxx Declarations
- •Joining to Multiple Tables
- •Acts As
- •When Things Get Saved
- •Preloading Child Rows
- •Counters
- •Validation
- •Callbacks
- •Advanced Attributes
- •Transactions
- •Action Controller: Routing and URLs
- •The Basics
- •Routing Requests
- •Action Controller and Rails
- •Action Methods
- •Cookies and Sessions
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Using Helpers
- •How Forms Work
- •Forms That Wrap Model Objects
- •Custom Form Builders
- •Working with Nonmodel Fields
- •Uploading Files to Rails Applications
- •Layouts and Components
- •Caching, Part Two
- •Adding New Templating Systems
- •Prototype
- •Script.aculo.us
- •RJS Templates
- •Conclusion
- •Action Mailer
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Secure and Deploy Your Application
- •Securing Your Rails Application
- •SQL Injection
- •Creating Records Directly from Form Parameters
- •Avoid Session Fixation Attacks
- •File Uploads
- •Use SSL to Transmit Sensitive Information
- •Knowing That It Works
- •Deployment and Production
- •Starting Early
- •How a Production Server Works
- •Repeatable Deployments with Capistrano
- •Setting Up a Deployment Environment
- •Checking Up on a Deployed Application
- •Production Application Chores
- •Moving On to Launch and Beyond
- •Appendices
- •Introduction to Ruby
- •Classes
- •Source Code
- •Resources
- •Index
- •Symbols
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.