- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •Models, Views, and Controllers
- •Installing Rails
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Unix/Linux
- •Rails and Databases
- •Keeping Up-to-Date
- •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 A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B2: Add Page Decorations
- •Task C: Cart Creation
- •Sessions
- •More Tables, More Models
- •Iteration C1: Creating a Cart
- •Iteration C3: Finishing the Cart
- •Task D: Checkout!
- •Iteration D2: Show Cart Contents on Checkout
- •Task E: Shipping
- •Iteration E1: Basic Shipping
- •Task F: Administrivia
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Finishing Up
- •More Icing on the Cake
- •Task T: Testing
- •Tests Baked Right In
- •Testing Models
- •Testing Controllers
- •Using Mock Objects
- •Test-Driven Development
- •Running Tests with Rake
- •Performance Testing
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Active Support
- •Logging in Rails
- •Debugging Hints
- •Active Record Basics
- •Tables and Classes
- •Primary Keys and IDs
- •Connecting to the Database
- •Relationships between Tables
- •Transactions
- •More Active Record
- •Acts As
- •Aggregation
- •Single Table Inheritance
- •Validation
- •Callbacks
- •Advanced Attributes
- •Miscellany
- •Action Controller and Rails
- •Context and Dependencies
- •The Basics
- •Routing Requests
- •Action Methods
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Builder templates
- •RHTML Templates
- •Helpers
- •Formatting Helpers
- •Linking to Other Pages and Resources
- •Pagination
- •Form Helpers
- •Layouts and Components
- •Adding New Templating Systems
- •Introducing AJAX
- •The Rails Way
- •Advanced Techniques
- •Action Mailer
- •Sending E-mail
- •Receiving E-mail
- •Testing E-mail
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Securing Your Rails Application
- •SQL Injection
- •Cross-Site Scripting (CSS/XSS)
- •Avoid Session Fixation Attacks
- •Creating Records Directly from Form Parameters
- •Knowing That It Works
- •Deployment and Scaling
- •Picking a Production Platform
- •A Trinity of Environments
- •Iterating in the Wild
- •Maintenance
- •Finding and Dealing with Bottlenecks
- •Case Studies: Rails Running Daily
- •Appendices
- •Introduction to Ruby
- •Ruby Names
- •Regular Expressions
- •Source Code
- •Cross-Reference of Code Samples
- •Resources
- •Index
SINGLE TABLE INHERITANCE 253
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
15.3 Single Table Inheritance
When we program with objects and classes, we sometimes use inheritance to express the relationship between abstractions. Our application might deal with people in various roles: customers, employees, managers, and so on. All roles will have some properties in common and other properties that are role specific. We might model this by saying that class Employee and class Customer are both subclasses of class Person and that Manager is in turn a subclass of Employee. The subclasses inherit the properties and responsibilities of their parent class.
In the relational database world, we don’t have the concept of inheritance: relationships are expressed primarily in terms of associations. However, we may need to store an object-oriented object model inside a relational database. There are many ways of mapping one into the other. Possibly the simplest is a scheme called single table inheritance. In it, we map all the classes in the inheritance hierarchy into a single database table. This table contains a column for each of the attributes of all the classes in the hierarchy. It additionally includes a column, by convention called type, that identifies which particular class of object is represented by any particular row. This is illustrated in Figure 15.3, on page 255.
Using single table inheritance in Active Record is straightforward. Define the inheritance hierarchy you need in your model classes, and ensure that the table corresponding to the base class of the hierarchy contains a column for each of the attributes of all the classes in that hierarchy. The table must additionally include a type column, used to discriminate the class of the corresponding model objects.
When defining the table, remember that the attributes of subclasses will be present only in the table rows corresponding to those subclasses; an employee doesn’t have a balance attribute, for example. As a result, you must define the table to allow null values for any column that doesn’t appear in all subclasses. The following is the DDL for the table illustrated in Figure 15.3, on page 255.
Prepared exclusively for Rida Al Barazi
Report erratum
|
|
|
SINGLE TABLE INHERITANCE |
254 |
File 6 |
create table people ( |
|
|
|
|
id |
int |
not null auto_increment, |
|
|
type |
varchar(20) |
not null, |
|
|
/* common attributes */ |
|
|
|
|
name |
varchar(100) |
not null, |
|
|
varchar(100) |
not null, |
|
|
|
/* attributes for type=Customer */ |
|
||
|
balance |
decimal(10,2), |
|
|
|
/* attributes for type=Employee */ |
|
||
|
reports_to |
int, |
|
|
|
dept |
int, |
|
|
|
/* attributes for type=Manager */ |
|
||
|
/* -- none -- */ |
|
|
|
|
constraint fk_reports_to foreign key (reports_to) references people(id), |
|
||
|
primary key (id) |
|
|
|
|
); |
|
|
|
|
We can define our hierarchy of model objects. |
|
||
File 15 |
class Person < ActiveRecord::Base |
|
||
|
end |
|
|
|
|
class Customer < Person |
|
|
|
|
end |
|
|
|
|
class Employee < Person |
|
|
|
|
end |
|
|
|
|
class Manager < Employee |
|
|
|
|
end |
|
|
|
|
Then we create a couple of rows and read them back. |
|
||
File 15 |
Manager.create(:name => 'Bob', :email => "bob@some.add", |
|
||
|
:dept => 12, :reports_to => nil) |
|
Customer.create(:name => 'Sally', :email => "sally@other.add", :balance => 123.45)
person = Person.find(:first)
puts person.class |
#=> Manager |
puts person.name |
#=> Bob |
puts person.dept |
#=> 12 |
person = Person.find_by_name("Sally") |
|
puts person.class |
#=> Customer |
puts person.email |
#=> sally@other.add |
puts person.balance |
#=> 123.45 |
Notice how we ask the base class, Person, to find a row, but the class of the object returned is Manager in one instance and Customer in the next; Active Record determined the type by examining the type column of the row and created the appropriate object.
There’s one fairly obvious constraint when using single table inheritance. Two subclasses can’t have attributes with the same name but with different types, as the two attributes would map to the same column in the underlying schema.
Prepared exclusively for Rida Al Barazi
Report erratum
SINGLE TABLE INHERITANCE 255
|
|
|
|
|
Person |
|
|
|
|
|
|
class Person < ActiveRecord::Base |
||||||||
|
|
|
|
name |
|
|
|
|
|
|
|
|
|
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
# ... |
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
end |
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Customer < Person |
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ... |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
end |
|
|
|||
|
Customer |
|
|
|
Employee |
|
|
|
|
|
||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||
balance |
|
|
|
reports_to |
|
|
|
class Employee < Person |
||||||||||||
|
|
|
|
|
|
|
|
dept |
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
# ... |
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
end |
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Manager < Employee |
|||||
|
|
|
|
|
|
|
|
|
Manager |
|
|
|||||||||
|
|
|
|
|
|
|
|
|
|
|
# ... |
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
end |
|
|
|||
|
people |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
id |
type |
name |
|
balance |
reports_to |
dept |
|
||||||||||||
|
1 |
Customer |
John Doe |
john@doe.com |
|
78.29 |
|
|
|
|
||||||||||
|
2 |
Manager |
Wilma Flint |
wilma@here.com |
|
|
|
|
23 |
|
||||||||||
|
3 |
Customer |
Bert Public |
b@public.net |
|
12.45 |
|
|
|
|
||||||||||
|
4 |
Employee |
Barney Rub |
barney@here.com |
|
|
2 |
|
23 |
|
||||||||||
|
5 |
Employee |
Betty Rub |
betty@here.com |
|
|
2 |
|
23 |
|
||||||||||
|
6 |
Customer |
Ira Buyer |
ira9652@aol.com |
|
-66.75 |
|
|
|
|
||||||||||
|
7 |
Employee |
Dino Dogg |
dino@dig.prg |
|
|
2 |
|
23 |
|
Figure 15.3: Single Table Inheritance: A Hierarchy of Four Classes Mapped into One Table
Prepared exclusively for Rida Al Barazi
Report erratum
VALIDATION 256
David Says. . .
Won’t Subclasses Share All the Attributes in STI?
Yes, but it’s not as big of a problem as you think it would be. As long as the subclasses are more similar than not, you can safely ignore the reports_to attribute when dealing with a customer. You simply just don’t use that attribute.
We’re trading the purity of the customer model for speed (selecting just from the people table is much faster than fetching from a join of people and customers tables) and for ease of implementation.
This works in a lot of cases, but not all. It doesn’t work too well for abstract relationships with very little overlap between the subclasses. For example, a content management system could declare a Content base class and have subclasses such as Article, Image, Page, and so forth. But these subclasses are likely to be wildly different, which will lead to an overly large base table as it has to encompass all the attributes from all the subclasses. In this case, it would be better to use associations and define a ContentMetadata class that all the concrete content classes could do a has_one( ) with.
There’s also a less obvious constraint. The attribute type is also the name of a built-in Ruby method, so accessing it directly to set or change the type of a row may result in stange Ruby messages. Instead, access it implicitly by creating objects of the appropriate class, or access it via the model object’s indexing interface, using something such as
person[:type] = 'Manager'
15.4 Validation
Active Record can validate the contents of a model object. This validation can be performed automatically when an object is saved. You can also programmatically request validation of the current state of a model.
As we mentioned in the previous chapter, Active Record distinguishes between models that correspond to an existing row in the database and those that don’t. The latter are called new records (the new_record?( ) method will return true for them). When you call the save( ) method, Active Record will perform an SQL insert operation for new records and an update for existing ones.
Prepared exclusively for Rida Al Barazi
Report erratum
VALIDATION 257
This distinction is reflected in Active Record’s validation workflow—you can specify validations that are performed on all save operations and other validations that are performed only on creates or updates.
At the lowest level you specify validations by implementing one or more of the methods validate( ), validate_on_create( ), and validate_on_update( ). The validate( ) method is invoked on every save operation. One of the other two is invoked depending on whether the record is new or whether it was previously read from the database.
You can also run validation at any time without saving the model object to the database by calling the valid?( ) method. This invokes the same two validation methods that would be invoked if save( ) had been called.
For example, the following code ensures that the user name column is always set to something valid and that the name is unique for new User objects. (We’ll see later how these types of constraints can be specified more simply.)
class User < ActiveRecord::Base
def validate
unless name && name =~ /^\w+$/ errors.add(:name, "is missing or invalid")
end end
def validate_on_create
if self.find_by_name(name) errors.add(:name, "is already being used")
end end
end
When a validate method finds a problem, it adds a message to the list of errors for this model object using errors.add( ). The first parameter is the name of the offending attribute, and the second is an error message. If you need to add an error message that applies to the model object as a whole, use the add_to_base( ) method instead. (Note that this code uses the support method blank?( ), which returns true if its receiver is nil or an empty string.)
def validate
if name.blank? && email.blank?
errors.add_to_base("You must specify a name or an email address") end
end
As we’ll see on page 353, Rails views can use this list of errors when displaying forms to end users—the fields that have errors will be automatically highlighted, and it’s easy to add a pretty box with an error list to the top of the form.
Prepared exclusively for Rida Al Barazi
Report erratum
VALIDATION 258
You can programmatically get the errors for a particular attribute using errors.on(:name) (aliased to errors[:name]), and you can clear the full list of errors using errors.clear( ). If you look at the RDoc for ActiveRecord::Errors, you’ll find a number of other methods. Most of these have been superseded by higher-level validation helper methods.
Validation Helpers
Some validations are common: this attribute must not be empty, that other attribute must be between 18 and 65, and so on. Active Record has a set of standard helper methods that will add these validations to your model. Each is a class-level method, and all have names that start validates_. Each method takes a list of attribute names optionally followed by a hash of configuration options for the validation.
For example, we could have written the previous validation as
class User < ActiveRecord::Base
validates_format_of :name, |
|
|
:with |
=> |
/^\w+$/, |
:message => |
"is missing or invalid" |
|
validates_uniqueness_of :name, |
|
|
:on |
|
=> :create, |
:message => "is already being used"
end
The majority of the validates_ methods accept :on and :message options. The :on option determines when the validation is applied and takes one of the values :save (the default), :create, or :update. The :message parameter can be used to override the generated error message.
When validation fails, the helpers add an error object to the Active Record model object. This will be associated with the field being validated. After validation, you can access the list of errors by looking at the errors attribute of the model object. When Active Record is used as part of a Rails application, this checking is often done in two steps.
1.The controller attempts to save an Active Record object, but the save fails because of validation problems (returning false). The controller redisplays the form containing the bad data.
2.The view template uses the error_messages_for( ) method to display the error list for the model object, and the user has the opportunity to fix the fields.
We cover the interactions of forms and models in Section 17.8, Error Handling and Model Objects, on page 353.
Prepared exclusively for Rida Al Barazi
Report erratum
VALIDATION 259
Here’s a list of the validation helpers you can use in model objects.
validates_acceptance_of
Validates that a checkbox has been checked.
validates_acceptance_of attr... [ options... ]
Many forms have a checkbox that users must select in order to accept some terms or conditions. This validation simply verifies that this box has been checked by validating that the value of the attribute is the string 1. The attribute itself doesn’t have to be stored in the database (although there’s nothing to stop you storing it if you want to record the confirmation explicitly).
class Order < ActiveRecord::Base validates_acceptance_of :terms,
:message => "Please accept the terms to proceed"
end
Options: |
|
:message |
text Default is “must be accepted.” |
:on |
:save, :create, or :update |
validates_associated
Performs validation on associated objects.
validates_associated name... [ options... ]
Performs validation on the given attributes, which are assumed to be Active Record models. For each attribute where the associated validation fails, a single message will be added to the errors for that attribute (that is, the individual detailed reasons for failure will not appear in this model’s errors).
Be careful not to include a validates_associated( ) call in models that refer to each other: the first will try to validate the second, which in turn will validate the first, and so on, until you run out of stack.
class Order < ActiveRecord::Base has_many :line_items belongs_to :user
validates_associated :line_items,
:message => "are messed up" validates_associated :user
end
Options: |
|
:message |
text Default is “is invalid.” |
:on |
:save, :create, or :update |
Prepared exclusively for Rida Al Barazi
Report erratum
VALIDATION 260
validates_confirmation_of
Validates that a field and its doppelgänger have the same content.
validates_confirmation_of attr... [ options... ]
Many forms require a user to enter some piece of information twice, the second copy acting as a confirmation that the first was not mistyped. If you use the naming convention that the second field has the name of the attribute with _confirmation appended, you can use validates_confirmation_of( ) to check that the two fields have the same value. The second field need not be stored in the database.
For example, a view might contain
<%= password_field "user", "password" %><br />
<%= password_field "user", "password_confirmation" %><br />
Within the User model, you can validate that the two passwords are the same using
class User < ActiveRecord::Base validates_confirmation_of :password
end
Options: |
|
:message |
text Default is “doesn’t match confirmation.” |
:on |
:save, :create, or :update |
validates_each
Validates one or more attributes using a block.
validates_each attr... [ options... ] { |model, attr, value| ... }
Invokes the block for each attribute (skipping those that are nil if :allow_nil is true). Passes in the model being validated, the name of the attribute, and the attribute’s value. As the following example shows, the block should add to the model’s error list if a validation fails.
class User < ActiveRecord::Base
validates_each :name, :email do |model, attr, value| if value =~ /groucho|harpo|chico/i
model.errors.add(attr, "You can't be serious, #{value}")
end end
end
Options: |
|
:allow_nil boolean |
If :allow_nil is true, attributes with values of nil will not be passed into |
|
the block. By default they will. |
:on |
:save, :create, or :update |
Prepared exclusively for Rida Al Barazi
Report erratum
VALIDATION 261
validates_exclusion_of
Validates that attributes are not in a set of values.
validates_exclusion_of attr..., :in => enum [ options... ]
Validates that none of the attributes occurs in enum (any object that supports the include?( ) predicate).
class User < ActiveRecord::Base validates_exclusion_of :genre,
:in => %w{ polka twostep foxtrot }, :message => "no wild music allowed"
validates_exclusion_of :age,
|
|
:in => 13..19, |
|
|
:message => "cannot be a teenager" |
end |
|
|
Options: |
|
|
:allow_nil |
|
enum is not checked if an attribute is nil and the :allow_nil |
|
|
option is true. |
:in (or :within) |
enumerable |
An enumerable object. |
:message |
text |
Default is “is not included in the list.” |
:on |
|
:save, :create, or :update |
validates_format_of
Validates attributes against a pattern.
validates_format_of attr..., :with => regexp [ options... ]
Validates each of the attributes by matching its value against regexp.
class User < ActiveRecord::Base
validates_format_of :length, :with => /^\d+(in|cm)/ end
Options: |
|
:message |
text Default is “is invalid.” |
:on |
:save, :create, or :update |
:with |
The regular expression used to validate the attributes. |
Prepared exclusively for Rida Al Barazi
Report erratum
VALIDATION 262
validates_inclusion_of
Validates that attributes belong to a set of values.
validates_inclusion_of attr..., :in => enum [ options... ]
Validates that the value of each of the attributes occurs in enum (any object that supports the include?( ) predicate).
class User < ActiveRecord::Base validates_inclusion_of :gender,
:in => %w{ male female },
:message => "should be 'male' or 'female'" validates_inclusion_of :age,
|
|
:in => 0..130, |
|
|
:message => "should be between 0 and 130" |
end |
|
|
Options: |
|
|
:allow_nil |
|
enum is not checked if an attribute is nil and the :allow_nil |
|
|
option is true. |
:in (or :within) |
enumerable |
An enumerable object. |
:message |
text |
Default is “is not included in the list.” |
:on |
|
:save, :create, or :update |
validates_length_of
Validates the length of attribute values.
validates_length_of attr..., [ options... ]
Validates that the length of the value of each of the attributes meets some constraint: at least a given length, at most a given length, between two lengths, or exactly a given length. Rather than having a single :message option, this validator allows separate messages for different validation failures, although :message may still be used. In all options, the lengths may not be negative.
class User < ActiveRecord::Base validates_length_of :name, :maximum => 50 validates_length_of :password, :in => 6..20 validates_length_of :address, :minimum => 10,
:message => "seems too short"
end
Prepared exclusively for Rida Al Barazi
Report erratum
VALIDATION 263
Options (for validates_length_of):
:in (or :within) |
range |
The length of value must be in range. |
:is |
integer |
Value must be integer characters long. |
:minimum |
integer |
Value may not be less than the integer characters long. |
:maximum |
integer |
Value may not be greater than integer characters long. |
:message |
text |
The default message depends on the test being performed. The |
|
|
message may contain a single %d sequence, which will be |
|
|
replaced by the maximum, minimum, or exact length required. |
:on |
|
:save, :create, or :update |
:too_long |
text |
A synonym for :message when :maximum is being used. |
:too_short |
text |
A synonym for :message when :minimum is being used. |
:wrong_length |
text |
A synonym for :message when :is is being used. |
validates_numericality_of
Validates that attributes are valid numbers.
validates_numericality_of attr... [ options... ]
Validates that each of the attributes is a valid number. With the :only_integer option, the attributes must consist of an optional sign followed by one or more digits. Without the option (or if the option is not true), any floating-point format accepted by the Ruby Float( ) method is allowed.
class User < ActiveRecord::Base validates_numericality_of :height_in_meters validates_numericality_of :age, :only_integer => true
end
Options: |
|
:message |
text Default is “is not a number.” |
:on |
:save, :create, or :update |
:only_integer |
If true, the attributes must be strings that contain an optional sign |
|
followed only by digits. |
validates_presence_of
Validates that attributes are not empty.
validates_presence_of attr... [ options... ]
Validates that each of the attributes is neither nil nor empty.
class User < ActiveRecord::Base validates_presence_of :name, :address
end
Options: |
|
:message |
text Default is “can’t be empty.” |
:on |
:save, :create, or :update |
Prepared exclusively for Rida Al Barazi
Report erratum