- •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
EXTENSIONS TO NUMBERS 251
Our goose/geese pair are an irregular plural, so we could tell the inflector about them using
Inflector.inflections do |inflect| inflect.irregular "goose", "geese"
end
Now Rails gets it right.
depot> ruby script/console
Loading development environment.
>> |
"goose".pluralize |
#=> |
"geese" |
>> |
"geese".singularize |
#=> |
"goose" |
Perhaps surprisingly, defining an irregular plural actually defines plurals for all words that end with the given pattern.
>> |
"canadagoose".pluralize |
#=> |
"canadageese" |
>> |
"wildgeese".singularize |
#=> |
"wildgoose" |
For families of plurals, define pattern-based rules for forming singular and plural forms. For example, the plural of father-in-law is fathers-in-law, mother- in-law becomes mothers-in-law, and so on. You can tell Rails about this by defining the mappings using regular expressions. In this case, you have to tell it both how to make the plural from the singular form and vice versa.
Inflector.inflections do |inflect|
|
inflect.plural(/-in-law$/, "s-in-law") |
||
|
inflect.singular(/s-in-law$/, "-in-law") |
||
end |
|
|
|
>> |
"sister-in-law".pluralize |
#=> |
"sisters-in-law" |
>> |
"brothers-in-law".singularize |
#=> |
"brother-in-law" |
Some words are uncountable (like bugs in my programs). You tell the inflector using the uncountable method.
Inflector.inflections do |inflect| inflect.uncountable("air", "information", "water")
end |
|
|
|
>> |
"water".pluralize |
#=> |
"water" |
>> |
"water".singularize |
#=> |
"water" |
In a Rails application, these changes can go in the file environment.rb in the config directory.
15.4Extensions to Numbers
Integers gain the two instance methods even? and odd?. You can also get the ordinal form of an integer using ordinalize.
puts |
3.ordinalize |
#=> |
"3rd" |
puts |
321.ordinalize |
#=> |
"321st" |
Report erratum
TIME AND DATE EXTENSIONS 252
All numeric objects gain a set of scaling methods. Singular and plural forms are supported.
puts 20.bytes |
#=> 20 |
puts 20.kilobytes |
#=> 20480 |
puts 20.megabytes |
#=> 20971520 |
puts 20.gigabytes |
#=> 21474836480 |
puts 20.terabytes |
#=> 21990232555520 |
puts 20.petabytes |
#=> 22517998136852480 |
puts 1.exabyte |
#=> 1152921504606846976 |
There are also time-based scaling methods. These convert their receiver into the equivalent number of seconds. The months and years methods are not accurate—months are assumed to be 30 days long, years 365 days long. However, the Time class has been extended with methods that give you accurate relative dates (see the description in the section that follows this one). Again, both singular and plural forms are supported.
puts 20.seconds |
#=> 20 |
puts 20.minutes |
#=> 1200 |
puts 20.hours |
#=> 72000 |
puts 20.days |
#=> 1728000 |
puts 20.weeks |
#=> 12096000 |
puts 20.fortnights |
#=> 24192000 |
puts 20.months |
#=> 51840000 |
puts 20.years |
#=> 630720000 |
You can also calculate times relative to some time (by default Time.now) using the methods ago and from_now (or their aliases until and since, respectively).
puts Time.now |
#=> Thu May 18 23:29:14 |
CDT 2006 |
|
puts 20.minutes.ago |
#=> Thu May 18 23:09:14 |
CDT 2006 |
|
puts 20.hours.from_now |
#=> Fri May 19 19:29:14 |
CDT 2006 |
|
puts 20.weeks.from_now |
#=> Thu Oct 05 |
23:29:14 |
CDT 2006 |
puts 20.months.ago |
#=> Sat Sep 25 |
23:29:16 |
CDT 2004 |
puts 20.minutes.until("2006-12-25 12:00:00".to_time) |
|||
|
#=> Mon Dec 25 |
11:40:00 UTC 2006 |
|
puts 20.minutes.since("2006-12-25 12:00:00".to_time) |
|||
|
#=> Mon Dec 25 |
12:20:00 |
UTC 2006 |
How cool is that? And it gets even cooler....
15.5Time and Date Extensions
The Time class gains a number of useful methods, helping you calculate relative times and dates and format time strings. Many of these methods have aliases: see the API documentation for details.
now = Time.now |
|
|
|
|
|
puts now |
#=> Thu |
May 18 |
23:36:10 CDT 2006 |
||
puts now.to_date |
#=> 2006-05-18 |
|
|||
puts |
now.to_s |
#=> |
Thu |
May 18 |
23:36:10 CDT 2006 |
puts |
now.to_s(:short) |
#=> |
18 May 23:36 |
Report erratum
|
|
|
TIME AND DATE EXTENSIONS |
253 |
puts now.to_s(:long) |
#=> May 18, |
2006 23:36 |
|
|
puts now.to_s(:db) |
#=> 2006-05-18 23:36:10 |
|
||
puts now.to_s(:rfc822) |
#=> Thu, 18 |
May 2006 23:36:10 -0500 |
|
|
puts now.ago(3600) |
#=> Thu May |
18 |
22:36:10 CDT 2006 |
|
puts now.at_beginning_of_day |
#=> Thu May |
18 |
00:00:00 CDT 2006 |
|
puts now.at_beginning_of_month |
#=> Mon May |
01 |
00:00:00 CDT 2006 |
|
puts now.at_beginning_of_week |
#=> Mon May |
15 |
00:00:00 CDT 2006 |
|
puts now.at_beginning_of_quarter |
#=> Sat Apr |
01 |
00:00:00 CST 2006 |
|
puts now.at_beginning_of_year |
#=> Sun Jan |
01 |
00:00:00 CST 2006 |
|
puts now.at_midnight |
#=> Thu May |
18 |
00:00:00 CDT 2006 |
|
puts now.change(:hour => 13) |
#=> Thu May |
18 |
13:00:00 CDT 2006 |
|
puts now.last_month |
#=> Tue Apr |
18 |
23:36:10 CDT 2006 |
|
puts now.last_year |
#=> Wed May |
18 |
23:36:10 CDT 2005 |
|
puts now.midnight |
#=> Thu May |
18 |
00:00:00 CDT 2006 |
|
puts now.monday |
#=> Mon May |
15 |
00:00:00 CDT 2006 |
|
puts now.months_ago(2) |
#=> Sat Mar |
18 |
23:36:10 CST 2006 |
|
puts now.months_since(2) |
#=> Tue Jul |
18 |
23:36:10 CDT 2006 |
|
puts now.next_week |
#=> Mon May |
22 |
00:00:00 CDT 2006 |
|
puts now.next_year |
#=> Fri May |
18 |
23:36:10 CDT 2007 |
|
puts now.seconds_since_midnight |
#=> 84970.423472 |
|
||
puts now.since(7200) |
#=> Fri May |
19 |
01:36:10 CDT 2006 |
|
puts now.tomorrow |
#=> Fri May |
19 |
23:36:10 CDT 2006 |
|
puts now.years_ago(2) |
#=> Tue May |
18 |
23:36:10 CDT 2004 |
|
puts now.years_since(2) |
#=> Sun May |
18 |
23:36:10 CDT 2008 |
|
puts now.yesterday |
#=> Wed May |
17 |
23:36:10 CDT 2006 |
|
puts now.advance(:days => 30) |
#=> Sat Jun |
17 |
23:36:10 CDT 2006 |
|
puts Time.days_in_month(2) |
#=> 28 |
|
|
|
puts Time.days_in_month(2, 2000) #=> 29
Date objects also pick up a few useful methods.
date = Date.today |
|
puts date.to_s |
#=> "2006-05-18" |
puts date.to_time |
#=> Thu May 18 00:00:00 CDT 2006 |
puts date.to_s(:short) |
#=> "18 May" |
puts date.to_s(:long) |
#=> "May 18, 2006" |
puts date.to_s(:db) |
#=> "2006-05-18" |
The last of these converts a date into a string that’s acceptable to the default database currently being used by your application. You may have noticed that the Time class has a similar extension for formatting datetime fields in a database-specific format.
You can add your own extensions to date and time formatting. For example, your application may need to display ordinal dates (the number of days into a year). The Ruby Date and Time libraries both support the strftime method for formatting dates, so you could use something like
Report erratum
AN EXTENSION TO RUBY SYMBOLS 254
>> d = Date.today
=> #<Date: 4907769/2,0,2299161> >> d.to_s
=> "2006-05-29"
>> d.strftime("%y-%j") => "06-149"
Instead, though, you might want to encapsulate this formatting by extending the to_s method of dates. In your environment.rb file, add a line like the following.
ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS.merge!( :ordinal => "%Y-%j"
)
Now you can say
any_date.to_s(:ordinal) |
#=> "2006-149" |
You can extend the Time class string formatting as well.
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(
:chatty => "It's %I:%M%p on %A, %B %d, %Y"
) |
|
Time.now.to_s(:chatty) |
#=> "It's 12:49PM on Monday, May 29, 2006" |
There are also two useful time-related methods added to the String class. The methods to_time and to_date return Time and Date objects, respectively.
puts |
"2006-12-25 |
12:34:56".to_time |
#=> |
Mon Dec 25 12:34:56 UTC 2006 |
puts |
"2006-12-25 |
12:34:56".to_date |
#=> |
2006-12-25 |
Active Support also includes a TimeZone class. TimeZone objects encapsulate the names and offset of a time zone. The class contains a list of the world’s time zones. See the Active Support RDoc for details.
15.6An Extension to Ruby Symbols
(This section describes an advanced feature of Ruby and can be safely skipped on the first dozen or so readings....)
We often use iterators where all the block does argument. We did this in our earlier group_by and
is invoke a method on its index_by examples.
groups = posts.group_by {|post| post.author_id}
Rails has a shorthand notation for this. We could have written this code as
groups = posts.group_by(&:author_id)
Similarly, the code
us_states = State.find(:all)
state_lookup = us_states.index_by {|state| state.short_name}
could also be written
us_states = State.find(:all)
state_lookup = us_states.index_by(&:short_name)
Report erratum