Instiki 0.16.5

Update to Rails 2.3.2 (the stable Rails 2.3 release).
Add audio/speex support
Update CHANGELOG
Bump version number
This commit is contained in:
Jacques Distler 2009-03-16 09:55:30 -05:00
parent 801d307405
commit e2ccdfd812
264 changed files with 4850 additions and 1906 deletions

View file

@ -1,7 +1,5 @@
h2. Ruby on Rails 2.3 Release Notes
NOTE: These release notes refer to RC2 of Rails 2.3. This is a release candidate, and not the final version of Rails 2.3. It's intended to be a stable testing release, and we urge you to test your own applications and report any issues to the "Rails Lighthouse":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/overview.
Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the "list of commits":http://github.com/rails/rails/commits/master in the main Rails repository on GitHub or review the +CHANGELOG+ files for the individual Rails components.
endprologue.
@ -22,24 +20,25 @@ Rails has now broken with its CGI past, and uses Rack everywhere. This required
Here's a summary of the rack-related changes:
* +script/server+ has been switched to use Rack, which means it supports any Rack compatible server. +script/server+ will also pick up a rackup configuration file if one exists. By default, it will look for a +config.ru+ file, but you can override this with the +-c+ switch.
* The FCGI handler goes through Rack
* +ActionController::Dispatcher+ maintains its own default middleware stack. Middlewares can be injected in, reordered, and removed. The stack is compiled into a chain on boot. You can configure the middleware stack in +environment.rb+
* The FCGI handler goes through Rack.
* +ActionController::Dispatcher+ maintains its own default middleware stack. Middlewares can be injected in, reordered, and removed. The stack is compiled into a chain on boot. You can configure the middleware stack in +environment.rb+.
* The +rake middleware+ task has been added to inspect the middleware stack. This is useful for debugging the order of the middleware stack.
* The integration test runner has been modified to execute the entire middleware and application stack. This makes integration tests perfect for testing Rack middleware.
* +ActionController::CGIHandler+ is a backwards compatible CGI wrapper around Rack. The +CGIHandler+ is meant to take an old CGI object and convert its environment information into a Rack compatible form.
* +CgiRequest+ and +CgiResponse+ have been removed
* +CgiRequest+ and +CgiResponse+ have been removed.
* Session stores are now lazy loaded. If you never access the session object during a request, it will never attempt to load the session data (parse the cookie, load the data from memcache, or lookup an Active Record object).
* +CGI::Session::CookieStore+ has been replaced by +ActionController::Session::CookieStore+
* +CGI::Session::MemCacheStore+ has been replaced by +ActionController::Session::MemCacheStore+
* +CGI::Session::ActiveRecordStore+ has been replaced by +ActiveRecord::SessionStore+
* You can still change your session store with +ActionController::Base.session_store = :active_record_store+
* Default sessions options are still set with +ActionController::Base.session = { :key => "..." }+
* The mutex that normally wraps your entire request has been moved into middleware, +ActionController::Lock+
* You no longer need to use +CGI::Cookie.new+ in your tests for setting a cookie value. Assigning a +String+ value to request.cookies["foo"] now sets the cookie as expected.
* +CGI::Session::CookieStore+ has been replaced by +ActionController::Session::CookieStore+.
* +CGI::Session::MemCacheStore+ has been replaced by +ActionController::Session::MemCacheStore+.
* +CGI::Session::ActiveRecordStore+ has been replaced by +ActiveRecord::SessionStore+.
* You can still change your session store with +ActionController::Base.session_store = :active_record_store+.
* Default sessions options are still set with +ActionController::Base.session = { :key => "..." }+.
* The mutex that normally wraps your entire request has been moved into middleware, +ActionController::Lock+.
* +ActionController::AbstractRequest+ and +ActionController::Request+ have been unified. The new +ActionController::Request+ inherits from +Rack::Request+. This affects access to +response.headers['type']+ in test requests. Use +response.content_type+ instead.
* +ActiveRecord::QueryCache+ middleware is automatically inserted onto the middleware stack if +ActiveRecord+ has been loaded. This middleware sets up and flushes the per-request Active Record query cache.
* The Rails router and controller classes follow the Rack spec. You can call a controller directly with +SomeController.call(env)+. The router stores the routing parameters in +rack.routing_args+.
* +ActionController::Request+ inherits from +Rack::Request+
* Instead of +config.action_controller.session = { :session_key => 'foo', ...+ use +config.action_controller.session = { :key => 'foo', ...+
* +ActionController::Request+ inherits from +Rack::Request+.
* Instead of +config.action_controller.session = { :session_key => 'foo', ...+ use +config.action_controller.session = { :key => 'foo', ...+.
* Using the +ParamsParser+ middleware preprocesses any XML, JSON, or YAML requests so they can be read normally with any +Rack::Request+ object after it.
h4. Renewed Support for Rails Engines
@ -50,7 +49,7 @@ h3. Documentation
The "Ruby on Rails guides":http://guides.rubyonrails.org/ project has published several additional guides for Rails 2.3. In addition, a "separate site":http://guides.rails.info/ maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the "Rails wiki":http://newwiki.rubyonrails.org/ and early planning for a Rails Book.
* More Information: "Rails Documentation Projects":http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects
* More Information: "Rails Documentation Projects":http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects.
h3. Ruby 1.9.1 Support
@ -140,19 +139,19 @@ end
You can pass most of the +find+ options into +find_in_batches+. However, you cannot specify the order that records will be returned in (they will always be returned in ascending order of primary key, which must be an integer), or use the +:limit+ option. Instead, use the +:batch_size+ option, which defaults to 1000, to set the number of records that will be returned in each batch.
The new +each+ method provides a wrapper around +find_in_batches+ that returns individual records, with the find itself being done in batches (of 1000 by default):
The new +find_each+ method provides a wrapper around +find_in_batches+ that returns individual records, with the find itself being done in batches (of 1000 by default):
<ruby>
Customer.each do |customer|
Customer.find_each do |customer|
customer.update_account_balance!
end
</ruby>
Note that you should only use this method for batch processing: for small numbers of records (less than 1000), you should just use the regular find methods with your own loop.
* More Information:
- "Rails 2.3: Batch Finding":http://afreshcup.com/2009/02/23/rails-23-batch-finding/
- "What's New in Edge Rails: Batched Find":http://ryandaigle.com/articles/2009/2/23/what-s-new-in-edge-rails-batched-find
* More Information (at that point the convenience method was called just +each+):
** "Rails 2.3: Batch Finding":http://afreshcup.com/2009/02/23/rails-23-batch-finding/
** "What's New in Edge Rails: Batched Find":http://ryandaigle.com/articles/2009/2/23/what-s-new-in-edge-rails-batched-find
h4. Multiple Conditions for Callbacks
@ -175,18 +174,6 @@ developers = Developer.find(:all, :group => "salary",
* Lead Contributor: "Emilio Tagua":http://github.com/miloops
h4. Hash Conditions for has_many relationships
You can once again use a hash in conditions for a +has_many+ relationship:
<ruby>
has_many :orders, :conditions => {:status => 'confirmed'}
</ruby>
That worked in Rails 2.1, fails in Rails 2.2, and will now work again in Rails 2.3 (if you're dealing with this issue in Rails 2.2, you can use a string rather than a hash to specify conditions).
* Lead Contributor: "Frederick Cheung":http://www.spacevatican.org/
h4. Reconnecting MySQL Connections
MySQL supports a reconnect flag in its connections - if set to true, then the client will try reconnecting to the server before giving up in case of a lost connection. You can now set +reconnect = true+ for your MySQL connections in +database.yml+ to get this behavior from a Rails application. The default is +false+, so the behavior of existing applications doesn't change.
@ -198,15 +185,17 @@ MySQL supports a reconnect flag in its connections - if set to true, then the cl
h4. Other Active Record Changes
* An extra +AS+ was removed from the generated SQL for has_and_belongs_to_many preloading, making it work better for some databases.
* An extra +AS+ was removed from the generated SQL for +has_and_belongs_to_many+ preloading, making it work better for some databases.
* +ActiveRecord::Base#new_record?+ now returns +false+ rather than +nil+ when confronted with an existing record.
* A bug in quoting table names in some +has_many :through+ associations was fixed.
* You can now specify a particular timestamp for +updated_at+ timestamps: +cust = Customer.create(:name => "ABC Industries", :updated_at => 1.day.ago)+
* Better error messages on failed +find_by_attribute!+ calls.
* Active Record's +to_xml+ support gets just a little bit more flexible with the addition of a +:camelize+ option.
* A bug in canceling callbacks from +before_update+ or +before_create_ was fixed.
* A bug in canceling callbacks from +before_update+ or +before_create+ was fixed.
* Rake tasks for testing databases via JDBC have been added.
* +validates_length_of+ will use a custom error message with the +:in+ or +:within+ options (if one is supplied)
* +validates_length_of+ will use a custom error message with the +:in+ or +:within+ options (if one is supplied).
* Counts on scoped selects now work properly, so you can do things like +Account.scoped(:select => "DISTINCT credit_limit").count+.
* +ActiveRecord::Base#invalid?+ now works as the opposite of +ActiveRecord::Base#valid?+.
h3. Action Controller
@ -299,7 +288,7 @@ In some of the first fruits of the Rails-Merb team merger, Rails 2.3 includes so
h4. Improved Caching Performance
Rails now keeps a per-request local cache of requests, cutting down on unnecessary reads and leading to better site performance. While this work was originally limited to +MemCacheStore+, it is available to any remote store than implements the required methods.
Rails now keeps a per-request local cache of read from the remote cache stores, cutting down on unnecessary reads and leading to better site performance. While this work was originally limited to +MemCacheStore+, it is available to any remote store than implements the required methods.
* Lead Contributor: "Nahum Wild":http://www.motionstandingstill.com/
@ -307,6 +296,8 @@ h4. Localized Views
Rails can now provide localized views, depending on the locale that you have set. For example, suppose you have a +Posts+ controller with a +show+ action. By default, this will render +app/views/posts/show.html.erb+. But if you set +I18n.locale = :da+, it will render +app/views/posts/show.da.html.erb+. If the localized template isn't present, the undecorated version will be used. Rails also includes +I18n#available_locales+ and +I18n::SimpleBackend#available_locales+, which return an array of the translations that are available in the current Rails project.
In addition, you can use the same scheme to localize the rescue files in the +public+ directory: +public/500.da.html+ or +public/404.en.html+ work, for example.
h4. Partial Scoping for Translations
A change to the translation API makes things easier and less repetitive to write key translations within partials. If you call +translate(".foo")+ from the +people/index.html.erb+ template, you'll actually be calling +I18n.translate("people.index.foo")+ If you don't prepend the key with a period, then the API doesn't scope, just as before.
@ -321,6 +312,9 @@ h4. Other Action Controller Changes
* The +:only+ and +:except+ options for +map.resources+ are no longer inherited by nested resources.
* The bundled memcached client has been updated to version 1.6.4.99.
* The +expires_in+, +stale?+, and +fresh_when+ methods now accept a +:public+ option to make them work well with proxy caching.
* The +:requirements+ option now works properly with additional RESTful member routes.
* Shallow routes now properly respect namespaces.
* +polymorphic_url+ does a better job of handling objects with irregular plural names.
h3. Action View
@ -439,6 +433,34 @@ returns
</optgroup>
</ruby>
h4. Disabled Option Tags for Form Select Helpers
The form select helpers (such as +select+ and +options_for_select+) now support a +:disabled+ option, which can take a single value or an array of values to be disabled in the resulting tags:
<ruby>
select(:post, :category, Post::CATEGORIES, :disabled => private)
</ruby>
returns
<ruby>
<select name=“post[category]“>
<option>story</option>
<option>joke</option>
<option>poem</option>
<option disabled=“disabled“>private</option>
</select>
</ruby>
You can also use an anonymous function to determine at runtime which options from collections will be selected and/or disabled:
<ruby>
options_from_collection_for_select(@product.sizes, :name, :id, :disabled => lambda{|size| size.out_of_stock?})
</ruby>
* Lead Contributor: "Tekin Suleyman":http://tekin.co.uk/
* More Information: "New in rails 2.3 - disabled option tags and lambdas for selecting and disabling options from collections":http://tekin.co.uk/2009/03/new-in-rails-23-disabled-option-tags-and-lambdas-for-selecting-and-disabling-options-from-collections/
h4. A Note About Template Loading
Rails 2.3 includes the ability to enable or disable cached templates for any particular environment. Cached templates give you a speed boost because they don't check for a new template file when they're rendered - but they also mean that you can't replace a template "on the fly" without restarting the server.
@ -472,6 +494,17 @@ h4. Object#tap Backport
+Object#tap+ is an addition to "Ruby 1.9":http://www.ruby-doc.org/core-1.9/classes/Object.html#M000309 and 1.8.7 that is similar to the +returning+ method that Rails has had for a while: it yields to a block, and then returns the object that was yielded. Rails now includes code to make this available under older versions of Ruby as well.
h4. Swappable Parsers for XMLmini
The support for XML parsing in ActiveSupport has been made more flexible by allowing you to swap in different parsers. By default, it uses the standard REXML implementation, but you can easily specify the faster LibXML or Nokogiri implementations for your own applications, provided you have the appropriate gems installed:
<ruby>
XmlMini.backend = 'LibXML'
</ruby>
* Lead Contributor: "Bart ten Brinke":http://www.movesonrails.com/
* Lead Contributor: "Aaron Patterson":http://tenderlovemaking.com/
h4. Fractional seconds for TimeWithZone
The +Time+ and +TimeWithZone+ classes include an +xmlschema+ method to return the time in an XML-friendly string. As of Rails 2.3, +TimeWithZone+ supports the same argument for specifying the number of digits in the fractional second part of the returned string that +Time+ does:
@ -494,6 +527,10 @@ h4. Other Active Support Changes
* +ActiveSupport::OrderedHash+: now implements +each_key+ and +each_value+.
* +ActiveSupport::MessageEncryptor+ provides a simple way to encrypt information for storage in an untrusted location (like cookies).
* Active Support's +from_xml+ no longer depends on XmlSimple. Instead, Rails now includes its own XmlMini implementation, with just the functionality that it requires. This lets Rails dispense with the bundled copy of XmlSimple that it's been carting around.
* If you memoize a private method, the result will now be private.
* +String#parameterize+ accepts an optional separator: +"Quick Brown Fox".parameterize('_') => "quick_brown_fox"+.
* +number_to_phone+ accepts 7-digit phone numbers now.
* +ActiveSupport::Json.decode+ now handles +\u0000+ style escape sequences.
h3. Railties
@ -532,6 +569,12 @@ Quite a bit of work was done to make sure that bits of Rails (and its dependenci
You can also specify (by using the new +preload_frameworks+ option) whether the core libraries should be autoloaded at startup. This defaults to +false+ so that Rails autoloads itself piece-by-piece, but there are some circumstances where you still need to bring in everything at once - Passenger and JRuby both want to see all of Rails loaded together.
h4. rake gem Task Rewrite
The internals of the various <code>rake gem</code> tasks have been substantially revised, to make the system work better for a variety of cases. The gem system now knows the difference between development and runtime dependencies, has a more robust unpacking system, gives better information when querying for the status of gems, and is less prone to "chicken and egg" dependency issues when you're bringing things up from scratch. There are also fixes for using gem commands under JRuby and for dependencies that try to bring in external copies of gems that are already vendored.
* Lead Contributor: "David Dollar":http://www.workingwithrails.com/person/12240-david-dollar
h4. Other Railties Changes
* The instructions for updating a CI server to build Rails have been updated and expanded.
@ -543,6 +586,8 @@ h4. Other Railties Changes
* Rails Guides have been converted from AsciiDoc to Textile markup.
* Scaffolded views and controllers have been cleaned up a bit.
* +script/server+ now accepts a <tt>--path</tt> argument to mount a Rails application from a specific path.
* If any configured gems are missing, the gem rake tasks will skip loading much of the environment. This should solve many of the "chicken-and-egg" problems where rake gems:install couldn't run because gems were missing.
* Gems are now unpacked exactly once. This fixes issues with gems (hoe, for instance) which are packed with read-only permissions on the files.
h3. Deprecated

View file

@ -20,7 +20,7 @@ For most conventional RESTful applications, the controller will receive the requ
A controller can thus be thought of as a middle man between models and views. It makes the model data available to the view so it can display that data to the user, and it saves or updates data from the user to the model.
NOTE: For more details on the routing process, see "Rails Routing from the Outside In":routing_outside_in.html.
NOTE: For more details on the routing process, see "Rails Routing from the Outside In":routing.html.
h3. Methods and Actions
@ -83,7 +83,7 @@ class ClientsController < ActionController::Base
end
</ruby>
h4. Hash and array parameters
h4. Hash and Array Parameters
The +params+ hash is not limited to one-dimensional keys and values. It can contain arrays and (nested) hashes. To send an array of values, append an empty pair of square brackets "[]" to the key name:
@ -123,7 +123,7 @@ map.connect "/clients/:status",
In this case, when a user opens the URL +/clients/active+, +params[:status]+ will be set to "active". When this route is used, +params[:foo]+ will also be set to "bar" just like it was passed in the query string. In the same way +params[:action]+ will contain "index".
h4. default_url_options
h4. +default_url_options+
You can set global default parameters that will be used when generating URLs with +default_url_options+. To do this, define a method with that name in your controller:
@ -180,7 +180,7 @@ ActionController::Base.session = {
NOTE: Changing the secret when using the CookieStore will invalidate all existing sessions.
h4. Accessing the session
h4. Accessing the Session
In your controller you can access the session through the +session+ instance method.
@ -235,7 +235,7 @@ end
To reset the entire session, use +reset_session+.
h4. The flash
h4. The Flash
The flash is a special part of the session which is cleared with each request. This means that values stored there will only be available in the next request, which is useful for storing error messages etc. It is accessed in much the same way as the session, like a hash. Let's use the act of logging out as an example. The controller can send a message which will be displayed to the user on the next request:
@ -288,7 +288,7 @@ class MainController < ApplicationController
end
</ruby>
h5. flash.now
h5. +flash.now+
By default, adding values to the flash will make them available to the next request, but sometimes you may want to access those values in the same request. For example, if the +create+ action fails to save a resource and you render the +new+ template directly, that's not going to result in a new request, but you may still want to display a message using the flash. To do this, you can use +flash.now+ in the same way you use the normal +flash+:
@ -381,7 +381,7 @@ end
Now, the +LoginsController+'s +new+ and +create+ actions will work as before without requiring the user to be logged in. The +:only+ option is used to only skip this filter for these actions, and there is also an +:except+ option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place.
h4. After filters and around filters
h4. After Filters and Around Filters
In addition to before filters, you can run filters after an action has run or both before and after. The after filter is similar to the before filter, but because the action has already been run it has access to the response data that's about to be sent to the client. Obviously, after filters can not stop the action from running.
@ -403,7 +403,7 @@ private
end
</ruby>
h4. Other ways to use filters
h4. Other Ways to Use Filters
While the most common way to use filters is by creating private methods and using *_filter to add them, there are two other ways to do the same thing.
@ -517,7 +517,7 @@ h3. The Request and Response Objects
In every controller there are two accessor methods pointing to the request and the response objects associated with the request cycle that is currently in execution. The +request+ method contains an instance of +AbstractRequest+ and the +response+ method returns a response object representing what is going to be sent back to the client.
h4. The +request+ object
h4. The +request+ Object
The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the "API documentation":http://api.rubyonrails.org/classes/ActionController/AbstractRequest.html. Among the properties that you can access on this object are:
@ -538,7 +538,7 @@ h5. +path_parameters+, +query_parameters+, and +request_parameters+
Rails collects all of the parameters sent along with the request in the +params+ hash, whether they are sent as part of the query string or the post body. The request object has three accessors that give you access to these parameters depending on where they came from. The +query_parameters+ hash contains parameters that were sent as part of the query string while the +request_parameters+ hash contains parameters sent as part of the post body. The +path_parameters+ hash contains parameters that were recognized by the routing as being part of the path leading to this particular controller and action.
h4. The response object
h4. The +response+ Object
The response object is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values.
@ -550,7 +550,7 @@ The response object is not usually used directly, but is built up during the exe
|charset|The character set being used for the response. Default is "utf-8".|
|headers|Headers used for the response.|
h5. Setting custom headers
h5. Setting Custom Headers
If you want to set custom headers for a response then +response.headers+ is the place to do it. The headers attribute is a hash which maps header names to their values, and Rails will set some of them automatically. If you want to add or change a header, just assign it to +response.headers+ this way:
@ -565,7 +565,7 @@ Rails comes with two built-in HTTP authentication mechanisms:
* Basic Authentication
* Digest Authentication
h4. HTTP basic authentication
h4. HTTP Basic Authentication
HTTP basic authentication is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser's HTTP basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, +authenticate_or_request_with_http_basic+.
@ -587,7 +587,7 @@ end
With this in place, you can create namespaced controllers that inherit from +AdminController+. The before filter will thus be run for all actions in those controllers, protecting them with HTTP basic authentication.
h4. HTTP digest authentication
h4. HTTP Digest Authentication
HTTP digest authentication is superior to the basic authentication as it does not require the client to send an unencrypted password over the network (though HTTP basic authentication is safe over HTTPS). Using digest authentication with Rails is quite easy and only requires using one method, +authenticate_or_request_with_http_digest+.
@ -640,7 +640,7 @@ end
The +download_pdf+ action in the example above will call a private method which actually generates the PDF document and returns it as a string. This string will then be streamed to the client as a file download and a filename will be suggested to the user. Sometimes when streaming files to the user, you may not want them to download the file. Take images, for example, which can be embedded into HTML pages. To tell the browser a file is not meant to be downloaded, you can set the +:disposition+ option to "inline". The opposite and default value for this option is "attachment".
h4. Sending files
h4. Sending Files
If you want to send a file that already exists on disk, use the +send_file+ method.
@ -662,7 +662,7 @@ WARNING: Be careful when using data coming from the client (params, cookies, etc
TIP: It is not recommended that you stream static files through Rails if you can instead keep them in a public folder on your web server. It is much more efficient to let the user download the file directly using Apache or another web server, keeping the request from unnecessarily going through the whole Rails stack. Although if you do need the request to go through Rails for some reason, you can set the +:x_sendfile+ option to true, and Rails will let the web server handle sending the file to the user, freeing up the Rails process to do other things. Note that your web server needs to support the +X-Sendfile+ header for this to work.
h4. RESTful downloads
h4. RESTful Downloads
While +send_data+ works just fine, if you are creating a RESTful application having separate actions for file downloads is usually not necessary. In REST terminology, the PDF file from the example above can be considered just another representation of the client resource. Rails provides an easy and quite sleek way of doing "RESTful downloads". Here's how you can rewrite the example so that the PDF download is a part of the +show+ action, without any streaming:
@ -712,7 +712,7 @@ Most likely your application is going to contain bugs or otherwise throw an exce
Rails' default exception handling displays a "500 Server Error" message for all exceptions. If the request was made locally, a nice traceback and some added information gets displayed so you can figure out what went wrong and deal with it. If the request was remote Rails will just display a simple "500 Server Error" message to the user, or a "404 Not Found" if there was a routing error or a record could not be found. Sometimes you might want to customize how these errors are caught and how they're displayed to the user. There are several levels of exception handling available in a Rails application:
h4. The default 500 and 404 templates
h4. The Default 500 and 404 Templates
By default a production application will render either a 404 or a 500 error message. These messages are contained in static HTML files in the +public+ folder, in +404.html+ and +500.html+ respectively. You can customize these files to add some extra information and layout, but remember that they are static; i.e. you can't use RHTML or layouts in them, just plain HTML.

View file

@ -12,9 +12,9 @@ h3. Sending Emails
This section will provide a step-by-step guide to creating a mailer and its views.
h4. Walkthrough to generating a mailer
h4. Walkthrough to Generating a Mailer
h5. Create the mailer:
h5. Create the Mailer
<shell>
./script/generate mailer UserMailer
@ -28,7 +28,7 @@ create test/unit/user_mailer_test.rb
So we got the model, the fixtures, and the tests.
h5. Edit the model:
h5. Edit the Model
+app/models/user_mailer.rb+ contains an empty mailer:
@ -60,7 +60,7 @@ Here is a quick explanation of the options presented in the preceding method. Fo
The keys of the hash passed to +body+ become instance variables in the view. Thus, in our example the mailer view will have a +@user+ and a +@url+ instance variables available.
h5. Create a mailer view
h5. Create a Mailer View
Create a file called +welcome_email.text.html.erb+ in +app/views/user_mailer/+. This will be the template used for the email, formatted in HTML:
@ -83,7 +83,7 @@ Create a file called +welcome_email.text.html.erb+ in +app/views/user_mailer/+.
Had we wanted to send text-only emails, the file would have been called +welcome_email.text.plain.erb+. Rails sets the content type of the email to be the one in the filename.
h5. Wire it up so that the system sends the email when a user signs up
h5. Wire It Up So That the System Sends the Email When a User Signs Up
There are three ways to achieve this. One is to send the email from the controller that sends the email, another is to put it in a +before_create+ callback in the user model, and the last one is to use an observer on the user model. Whether you use the second or third methods is up to you, but staying away from the first is recommended. Not because it's wrong, but because it keeps your controller clean, and keeps all logic related to the user model within the user model. This way, whichever way a user is created (from a web form, or from an API call, for example), we are guaranteed that the email will be sent.
@ -112,7 +112,7 @@ end
Notice how we call +deliver_welcome_email+? In Action Mailer we send emails by calling +deliver_&lt;method_name&gt;+. In UserMailer, we defined a method called +welcome_email+, and so we deliver the email by calling +deliver_welcome_email+. The next section will go through how Action Mailer achieves this.
h4. Action Mailer and dynamic deliver_&lt;method_name&gt; methods
h4. Action Mailer and Dynamic +deliver_&lt;method_name&gt;+ methods
So how does Action Mailer understand this +deliver_welcome_email+ call? If you read the documentation (http://api.rubyonrails.org/files/vendor/rails/actionmailer/README.html), you will find this in the "Sending Emails" section:
@ -135,7 +135,7 @@ end
Hence, if the method name starts with +deliver_+ followed by any combination of lowercase letters or underscore, +method_missing+ calls +new+ on your mailer class (+UserMailer+ in our example above), sending the combination of lower case letters or underscore, along with the parameters. The resulting object is then sent the +deliver!+ method, which well... delivers it.
h4. Complete list of Action Mailer user-settable attributes
h4. Complete List of Action Mailer User-Settable Attributes
|bcc| The BCC addresses of the email|
|body| The body of the email. This is either a hash (in which case it specifies the variables to pass to the template when it is rendered), or a string, in which case it specifies the actual body of the message|
@ -184,7 +184,7 @@ end
Just like with controller views, use +yield+ to render the view inside the layout.
h4. Generating URLs in Action Mailer views
h4. Generating URLs in Action Mailer Views
URLs can be generated in mailer views using +url_for+ or named routes.
Unlike controllers, the mailer instance doesn't have any context about the incoming request so you'll need to provide the +:host+, +:controller+, and +:action+:
@ -216,7 +216,7 @@ config.action_mailer.default_url_options = { :host => "example.com" }
If you set a default +:host+ for your mailers you need to pass +:only_path => false+ to +url_for+. Otherwise it doesn't get included.
h4. Sending multipart emails
h4. Sending Multipart Emails
Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have +welcome_email.text.plain.erb+ and +welcome_email.text.html.erb+ in +app/views/user_mailer+, Action Mailer will automatically send a multipart email with the HTML and text versions setup as different parts.
@ -240,7 +240,7 @@ class UserMailer < ActionMailer::Base
end
</ruby>
h4. Sending emails with attachments
h4. Sending Emails with Attachments
Attachments can be added by using the +attachment+ method:
@ -262,6 +262,38 @@ class UserMailer < ActionMailer::Base
end
</ruby>
h4. Sending Multipart Emails with Attachments
Once you use the +attachment+ method, ActionMailer will no longer automagically use the correct template based on the filename. You must declare which template you are using for each content type via the +part+ method.
In the following example, there would be two template files, +welcome_email_html.erb+ and +welcome_email_plain.erb+ in the +app/views/user_mailer+ folder.
<ruby>
class UserMailer < ActionMailer::Base
def welcome_email(user)
recipients user.email_address
subject "New account information"
from "system@example.com"
content_type "multipart/alternative"
part "text/html" do |p|
p.body = render_message("welcome_email_html", :message => "<h1>HTML content</h1>")
end
part "text/plain" do |p|
p.body = render_message("welcome_email_plain", :message => "text content")
end
attachment :content_type => "image/jpeg",
:body => File.read("an-image.jpg")
attachment "application/pdf" do |a|
a.body = generate_your_pdf_here()
end
end
end
</ruby>
h3. Receiving Emails
Receiving and parsing emails with Action Mailer can be a rather complex endeavour. Before your email reaches your Rails app, you would have had to configure your system to somehow forward emails to your app, which needs to be listening for that. So, to receive emails in your Rails app you'll need:
@ -320,7 +352,7 @@ The following configuration options are best made in one of the environment file
|default_implicit_parts_order|When a message is built implicitly (i.e. multiple parts are assembled from templates which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client and appear last in the mime encoded message. You can also pick a different order from inside a method with implicit_parts_order.|
h4. Example Action Mailer configuration
h4. Example Action Mailer Configuration
An example would be:
@ -352,7 +384,7 @@ ActionMailer::Base.smtp_settings = {
}
</ruby>
h4. Configure Action Mailer to recognize HAML templates
h4. Configure Action Mailer to Recognize HAML Templates
In +config/environment.rb+, add the following line:
@ -386,3 +418,7 @@ end
</ruby>
In the test we send the email and store the returned object in the +email+ variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain the what we expect.
h3. Changelog
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213/tickets/25

View file

@ -29,7 +29,7 @@ h3. Object Relational Mapping
The relation between databases and object-oriented software is called ORM, which is short for "Object Relational Mapping". The purpose of an ORM framework is to minimize the mismatch existent between relational databases and object-oriented software. In applications with a domain model, we have objects that represent both the state of the system and the behavior of the real world elements that were modeled through these objects. Since we need to store the system's state somehow, we can use relational databases, which are proven to be an excellent approach to data management. Usually this may become a very hard thing to do, since we need to create an object-oriented model of everything that lives in the database, from simple columns to complicated relations between different tables. Doing this kind of thing by hand is a tedious and error prone job. This is where an ORM framework comes in.
h3. ActiveRecord as an ORM framework
h3. ActiveRecord as an ORM Framework
ActiveRecord gives us several mechanisms, being the most important ones the ability to:
@ -41,7 +41,7 @@ ActiveRecord gives us several mechanisms, being the most important ones the abil
It's easy to see that the Rails Active Record implementation goes way beyond the basic description of the Active Record Pattern.
h3. Active Record inside the MVC model
h3. Active Record Inside the MVC Model
Active Record plays the role of model inside the MVC structure followed by Rails applications. Since model objects should encapsulate both state and logic of your applications, it's ActiveRecord responsibility to deliver you the easiest possible way to recover this data from the database.
@ -81,7 +81,7 @@ There are also some optional column names that will create additional features t
NOTE: While these column names are optional they are in fact reserved by ActiveRecord. Steer clear of reserved keywords unless you want the extra functionality. For example, "type" is a reserved keyword used to designate a table using Single Table Inheritance. If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling.
h3. Creating ActiveRecord models
h3. Creating ActiveRecord Models
It's very easy to create ActiveRecord models. All you have to do is to subclass the ActiveRecord::Base class and you're good to go:
@ -107,7 +107,7 @@ p.name = "Some Book"
puts p.name # "Some Book"
</ruby>
h3. Overriding the naming conventions
h3. Overriding the Naming Conventions
What if you need to follow a different naming convention or need to use your Rails application with a legacy database? No problem, you can easily override the default conventions.

View file

@ -54,7 +54,7 @@ end
Active Record will perform queries on the database for you and is compatible with most database systems (MySQL, PostgreSQL and SQLite to name a few). Regardless of which database system you're using, the Active Record method format will always be the same.
h3. Retrieving objects from the database
h3. Retrieving Objects from the Database
To retrieve objects from the database, Active Record provides a class method called +Model.find+. This method allows you to pass arguments into it to perform certain queries on your database without the need of writing raw SQL.
@ -65,11 +65,11 @@ Primary operation of <tt>Model.find(options)</tt> can be summarized as:
* Instantiate the equivalent Ruby object of the appropriate model for every resulting row.
* Run +after_find+ callbacks if any.
h4. Retrieving a single object
h4. Retrieving a Single Object
Active Record lets you retrieve a single object using three different ways.
h5. Using a primary key
h5. Using a Primary Key
Using <tt>Model.find(primary_key, options = nil)</tt>, you can retrieve the object corresponding to the supplied _primary key_ and matching the supplied options (if any). For example:
@ -87,7 +87,7 @@ SELECT * FROM clients WHERE (clients.id = 10)
<tt>Model.find(primary_key)</tt> will raise an +ActiveRecord::RecordNotFound+ exception if no matching record is found.
h5. Find first
h5. +first+
<tt>Model.first(options = nil)</tt> finds the first record matched by the supplied options. If no +options+ are supplied, the first matching record is returned. For example:
@ -106,7 +106,7 @@ SELECT * FROM clients LIMIT 1
NOTE: +Model.find(:first, options)+ is equivalent to +Model.first(options)+
h5. Find last
h5. +last+
<tt>Model.last(options = nil)</tt> finds the last record matched by the supplied options. If no +options+ are supplied, the last matching record is returned. For example:
@ -126,9 +126,9 @@ SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
NOTE: +Model.find(:last, options)+ is equivalent to +Model.last(options)+
h4. Retrieving multiple objects
h4. Retrieving Multiple Objects
h5. Using multiple primary keys
h5. Using Multiple Primary Keys
<tt>Model.find(array_of_primary_key, options = nil)</tt> also accepts an array of _primary keys_. An array of all the matching records for the supplied _primary keys_ is returned. For example:
@ -166,17 +166,85 @@ SELECT * FROM clients
NOTE: +Model.find(:all, options)+ is equivalent to +Model.all(options)+
h4. Retrieving Multiple Objects in Batches
Sometimes you need to iterate over a large set of records. For example to send a newsletter to all users, to export some data, etc.
The following may seem very straight forward at first:
<ruby>
# Very inefficient when users table has thousands of rows.
User.all.each do |user|
NewsLetter.weekly_deliver(user)
end
</ruby>
But if the total number of rows in the table is very large, the above approach may vary from being under performant to just plain impossible.
This is because +User.all+ makes Active Record fetch _the entire table_, build a model object per row, and keep the entire array in the memory. Sometimes that is just too many objects and demands too much memory.
h5. +find_each+
To efficiently iterate over a large table, Active Record provides a batch finder method called +find_each+:
<ruby>
User.find_each do |user|
NewsLetter.weekly_deliver(user)
end
</ruby>
*Configuring the batch size*
Behind the scenes +find_each+ fetches rows in batches of +1000+ and yields them one by one. The size of the underlying batches is configurable via the +:batch_size+ option.
To fetch +User+ records in batch size of +5000+:
<ruby>
User.find_each(:batch_size => 5000) do |user|
NewsLetter.weekly_deliver(user)
end
</ruby>
*Starting batch find from a specific primary key*
Records are fetched in ascending order on the primary key, which must be an integer. The +:start+ option allows you to configure the first ID of the sequence if the lowest is not the one you need. This may be useful for example to be able to resume an interrupted batch process if it saves the last processed ID as a checkpoint.
To send newsletters only to users with the primary key starting from +2000+:
<ruby>
User.find_each(:batch_size => 5000, :start => 2000) do |user|
NewsLetter.weekly_deliver(user)
end
</ruby>
*Additional options*
+find_each+ accepts the same options as the regular +find+ method. However, +:order+ and +:limit+ are needed internally and hence not allowed to be passed explicitly.
h5. +find_in_batches+
You can also work by chunks instead of row by row using +find_in_batches+. This method is analogous to +find_each+, but it yields arrays of models instead:
<ruby>
# Works in chunks of 1000 invoices at a time.
Invoice.find_in_batches(:include => :invoice_lines) do |invoices|
export.add_invoices(invoices)
end
</ruby>
The above will yield the supplied block with +1000+ invoices every time.
h3. Conditions
The +find+ method allows you to specify conditions to limit the records returned, representing the WHERE-part of the SQL statement. Conditions can either be specified as a string, array, or hash.
The +find+ method allows you to specify conditions to limit the records returned, representing the +WHERE+-part of the SQL statement. Conditions can either be specified as a string, array, or hash.
h4. Pure string conditions
h4. Pure String Conditions
If you'd like to add conditions to your find, you could just specify them in there, just like +Client.first(:conditions => "orders_count = '2'")+. This will find all clients where the +orders_count+ field's value is 2.
WARNING: Building your own conditions as pure strings can leave you vulnerable to SQL injection exploits. For example, +Client.first(:conditions => "name LIKE '%#{params[:name]}%'")+ is not safe. See the next section for the preferred way to handle conditions using an array.
h4. Array conditions
h4. Array Conditions
Now what if that number could vary, say as a argument from somewhere, or perhaps from the user's level status somewhere? The find then becomes something like:
@ -208,11 +276,11 @@ Client.first(:conditions => "orders_count = #{params[:orders]}")
is because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database *as-is*. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string.
TIP: For more information on the dangers of SQL injection, see the "Ruby on Rails Security Guide":../security.html#_sql_injection.
TIP: For more information on the dangers of SQL injection, see the "Ruby on Rails Security Guide":security.html#sql-injection.
h5. Placeholder conditions
h5. Placeholder Conditions
Similar to the +(?)+ replacement style of params, you can also specify keys/values hash in your Array conditions:
Similar to the +(?)+ replacement style of params, you can also specify keys/values hash in your array conditions:
<ruby>
Client.all(:conditions =>
@ -221,7 +289,7 @@ Client.all(:conditions =>
This makes for clearer readability if you have a large number of variable conditions.
h5. Range conditions
h5. Range Conditions
If you're looking for a range inside of a table (for example, users created in a certain timeframe) you can use the conditions option coupled with the +IN+ SQL statement for this. If you had two dates coming in from a controller you could do something like this to look for a range:
@ -243,7 +311,7 @@ SELECT * FROM users WHERE (created_at IN
'2008-12-27','2008-12-28','2008-12-29','2008-12-30','2008-12-31'))
</sql>
h5. Time and Date conditions
h5. Time and Date Conditions
Things can get *really* messy if you pass in Time objects as it will attempt to compare your field to *every second* in that range:
@ -280,15 +348,15 @@ Client.all(:conditions =>
["created_at >= ? AND created_at <= ?", params[:start_date], params[:end_date]])
</ruby>
Just like in Ruby. If you want a shorter syntax be sure to check out the "Hash Conditions":hash-conditions section later on in the guide.
Just like in Ruby. If you want a shorter syntax be sure to check out the "Hash Conditions":#hash-conditions section later on in the guide.
h4. Hash conditions
h4. Hash Conditions
Active Record also allows you to pass in a hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them:
NOTE: Only equality, range and subset checking are possible with Hash conditions.
h5. Equality conditions
h5. Equality Conditions
<ruby>
Client.all(:conditions => { :locked => true })
@ -300,7 +368,7 @@ The field name does not have to be a symbol it can also be a string:
Client.all(:conditions => { 'locked' => true })
</ruby>
h5. Range conditions
h5. Range Conditions
The good thing about this is that we can pass in a range for our fields without it generating a large query as shown in the preamble of this section.
@ -314,9 +382,9 @@ This will find all clients created yesterday by using a +BETWEEN+ SQL statement:
SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00')
</sql>
This demonstrates a shorter syntax for the examples in "Array Conditions":#arrayconditions
This demonstrates a shorter syntax for the examples in "Array Conditions":#array-conditions
h5. Subset conditions
h5. Subset Conditions
If you want to find records using the +IN+ expression you can pass an array to the conditions hash:
@ -330,7 +398,7 @@ This code will generate SQL like this:
SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
</sql>
h3. Find options
h3. Find Options
Apart from +:conditions+, +Model.find+ takes a variety of other options via the options hash for customizing the resulting record set.
@ -370,13 +438,13 @@ Or ordering by multiple fields:
Client.all(:order => "orders_count ASC, created_at DESC")
</ruby>
h4. Selecting specific fields
h4. Selecting Specific Fields
By default, <tt>Model.find</tt> selects all the fields from the result set using +select *+.
To select only a subset of fields from the result set, you can specify the subset via +:select+ option on the +find+.
NOTE: If the +:select+ option is used, all the returning objects will be "read only":#readonlyobjects.
NOTE: If the +:select+ option is used, all the returning objects will be "read only":#readonly-objects.
<br />
@ -470,7 +538,7 @@ SELECT * FROM orders GROUP BY date(created_at) HAVING created_at > '2009-01-15'
This will return single order objects for each day, but only for the last month.
h4. Readonly objects
h4. Readonly Objects
To explicitly disallow modification/destroyal of the matching records returned by +Model.find+, you could specify the +:readonly+ option as +true+ to the find call.
@ -488,7 +556,7 @@ client.locked = false
client.save
</ruby>
h4. Locking records for update
h4. Locking Records for Update
Locking is helpful for preventing the race conditions when updating records in the database and ensuring atomic updated. Active Record provides two locking mechanism:
@ -562,31 +630,31 @@ Item.transaction do
end
</ruby>
h3. Joining tables
h3. Joining Tables
<tt>Model.find</tt> provides a +:joins+ option for specifying +JOIN+ clauses on the resulting SQL. There multiple different ways to specify the +:joins+ option:
h4. Using a string SQL fragment
h4. Using a String SQL Fragment
You can just supply the raw SQL specifying the +JOIN+ clause to the +:joins+ option. For example:
<ruby>
Client.all(:joins => 'LEFT OUTER JOIN addresses ON addresses.client_id = client.id')
Client.all(:joins => 'LEFT OUTER JOIN addresses ON addresses.client_id = clients.id')
</ruby>
This will result in the following SQL:
<sql>
SELECT clients.* FROM clients INNER JOIN addresses ON addresses.client_id = clients.id
SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id
</sql>
h4. Using Array/Hash of named associations
h4. Using Array/Hash of Named Associations
WARNING: This method only works with +INNER JOIN+,
<br />
Active Record lets you use the names of the "associations":association_basics.html defined on the Model, as a shortcut for specifying the +:joins+ option.
Active Record lets you use the names of the "associations":association_basics.html defined on the model as a shortcut for specifying the +:joins+ option.
For example, consider the following +Category+, +Post+, +Comments+ and +Guest+ models:
@ -613,7 +681,7 @@ end
Now all of the following will produce the expected join queries using +INNER JOIN+:
h5. Joining a single association
h5. Joining a Single Association
<ruby>
Category.all :joins => :posts
@ -626,7 +694,7 @@ SELECT categories.* FROM categories
INNER JOIN posts ON posts.category_id = categories.id
</sql>
h5. Joining multiple associations
h5. Joining Multiple Associations
<ruby>
Post.all :joins => [:category, :comments]
@ -640,21 +708,21 @@ SELECT posts.* FROM posts
INNER JOIN comments ON comments.post_id = posts.id
</sql>
h5. Joining nested associations (single level)
h5. Joining Nested Associations (Single Level)
<ruby>
Post.all :joins => {:comments => :guest}
</ruby>
h5. Joining nested associations (multiple level)
h5. Joining Nested Associations (Multiple Level)
<ruby>
Category.all :joins => {:posts => [{:comments => :guest}, :tags]}
</ruby>
h4. Specifying conditions on the joined tables
h4. Specifying Conditions on the Joined Tables
You can specify conditions on the joined tables using the regular "Array":#arrayconditions and "String":#purestringconditions conditions. "Hash conditions":#hashconditions provides a special syntax for specifying conditions for the joined tables:
You can specify conditions on the joined tables using the regular "Array":#array-conditions and "String":#pure-string-conditions conditions. "Hash conditions":#hash-conditions provides a special syntax for specifying conditions for the joined tables:
<ruby>
time_range = (Time.now.midnight - 1.day)..Time.now.midnight
@ -670,7 +738,7 @@ Client.all :joins => :orders, :conditions => {:orders => {:created_at => time_ra
This will find all clients who have orders that were created yesterday, again using a +BETWEEN+ SQL expression.
h3. Eager loading associations
h3. Eager Loading Associations
Eager loading is the mechanism for loading the associated records of the objects returned by +Model.find+ using as few queries as possible.
@ -710,11 +778,11 @@ SELECT addresses.* FROM addresses
WHERE (addresses.client_id IN (1,2,3,4,5,6,7,8,9,10))
</sql>
h4. Eager loading multiple associations
h4. Eager Loading Multiple Associations
Active Record lets you eager load any possible number of associations with a single +Model.find+ call by using Array, Hash or a nested Hash of Array/Hash with +:include+ find option.
Active Record lets you eager load any possible number of associations with a single +Model.find+ call by using an array, hash, or a nested hash of array/hash with the +:include+ option.
h5. Array of multiple associations
h5. Array of Multiple Associations
<ruby>
Post.all :include => [:category, :comments]
@ -722,7 +790,7 @@ Post.all :include => [:category, :comments]
This loads all the posts and the associated category and comments for each post.
h5. Nested assocaitions hash
h5. Nested Associations Hash
<ruby>
Category.find 1, :include => {:posts => [{:comments => :guest}, :tags]}
@ -730,17 +798,17 @@ Category.find 1, :include => {:posts => [{:comments => :guest}, :tags]}
The above code finds the category with id 1 and eager loads all the posts associated with the found category. Additionally, it will also eager load every posts' tags and comments. Every comment's guest association will get eager loaded as well.
h4. Specifying conditions on eager loaded associations
h4. Specifying Conditions on Eager Loaded Associations
Even though Active Record lets you specify conditions on the eager loaded associations just like +:joins+, the recommended way is to use ":joins":#joiningtables instead.
Even though Active Record lets you specify conditions on the eager loaded associations just like +:joins+, the recommended way is to use ":joins":#joining-tables instead.
h3. Dynamic finders
h3. Dynamic Finders
For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +name+ on your Client model for example, you get +find_by_name+ and +find_all_by_name+ for free from Active Record. If you have also have a +locked+ field on the Client model, you also get +find_by_locked+ and +find_all_by_locked+.
For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +name+ on your +Client+ model for example, you get +find_by_name+ and +find_all_by_name+ for free from Active Record. If you have also have a +locked+ field on the +Client+ model, you also get +find_by_locked+ and +find_all_by_locked+.
You can do +find_last_by_*+ methods too which will find the last record matching your argument.
You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an ActiveRecord::RecordNotFound error if they do not return any records, like +Client.find_by_name!("Ryan")+
You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an +ActiveRecord::RecordNotFound+ error if they do not return any records, like +Client.find_by_name!("Ryan")+
If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields for example +Client.find_by_name_and_locked("Ryan", true)+.
@ -761,9 +829,9 @@ COMMIT
client = Client.find_or_initialize_by_name('Ryan')
</ruby>
will either assign an existing client object with the name 'Ryan' to the client local variable, or initialize a new object similar to calling +Client.new(:name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it.
will either assign an existing client object with the name "Ryan" to the client local variable, or initialize a new object similar to calling +Client.new(:name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it.
h3. Finding By SQL
h3. Finding by SQL
If you'd like to use your own SQL to find records in a table you can use +find_by_sql+. The +find_by_sql+ method will return an array of objects even the underlying query returns just a single record. For example you could run this query:
@ -775,7 +843,7 @@ Client.find_by_sql("SELECT * FROM clients
+find_by_sql+ provides you with a simple way of making custom calls to the database and retrieving instantiated objects.
h3. select_all
h3. +select_all+
<tt>find_by_sql</tt> has a close relative called +connection#select_all+. +select_all+ will retrieve objects from the database using custom SQL just like +find_by_sql+ but will not instantiate them. Instead, you will get an array of hashes where each hash indicates a record.

View file

@ -76,7 +76,7 @@ In Rails, an _association_ is a connection between two Active Record models. Ass
In the remainder of this guide, you'll learn how to declare and use the various forms of associations. But first, a quick introduction to the situations where each association type is appropriate.
h4. The +belongs_to+ association
h4. The +belongs_to+ Association
A +belongs_to+ association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes customers and orders, and each order can be assigned to exactly one customer, you'd declare the order model this way:
@ -88,7 +88,7 @@ end
!images/belongs_to.png(belongs_to Association Diagram)!
h4. The +has_one+ association
h4. The +has_one+ Association
A +has_one+ association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model. For example, if each supplier in your application has only one account, you'd declare the supplier model like this:
@ -100,7 +100,7 @@ end
!images/has_one.png(has_one Association Diagram)!
h4. The +has_many+ association
h4. The +has_many+ Association
A +has_many+ association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a +belongs_to+ association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing customers and orders, the customer model could be declared like this:
@ -114,7 +114,7 @@ NOTE: The name of the other model is pluralized when declaring a +has_many+ asso
!images/has_many.png(has_many Association Diagram)!
h4. The +has_many :through+ association
h4. The +has_many :through+ Association
A +has_many :through+ association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding _through_ a third model. For example, consider a medical practice where patients make appointments to see physicians. The relevant association declarations could look like this:
@ -155,7 +155,7 @@ class Paragraph < ActiveRecord::Base
end
</ruby>
h4. The +has_one :through+ association
h4. The +has_one :through+ Association
A +has_one :through+ association sets up a one-to-one connection with another model. This association indicates that the declaring model can be matched with one instance of another model by proceeding _through_ a third model. For example, if each supplier has one account, and each account is associated with one account history, then the customer model could look like this:
@ -177,7 +177,7 @@ end
!images/has_one_through.png(has_one :through Association Diagram)!
h4. The +has_and_belongs_to_many+ association
h4. The +has_and_belongs_to_many+ Association
A +has_and_belongs_to_many+ association creates a direct many-to-many connection with another model, with no intervening model. For example, if your application includes assemblies and parts, with each assembly having many parts and each part appearing in many assemblies, you could declare the models this way:
@ -193,7 +193,7 @@ end
!images/habtm.png(has_and_belongs_to_many Association Diagram)!
h4. Choosing between +belongs_to+ and +has_one+
h4. Choosing Between +belongs_to+ and +has_one+
If you want to set up a 11 relationship between two models, you'll need to add +belongs_to+ to one, and +has_one+ to the other. How do you know which is which?
@ -235,7 +235,7 @@ end
NOTE: Using +t.integer :supplier_id+ makes the foreign key naming obvious and explicit. In current versions of Rails, you can abstract away this implementation detail by using +t.references :supplier+ instead.
h4. Choosing between +has_many :through+ and +has_and_belongs_to_many+
h4. Choosing Between +has_many :through+ and +has_and_belongs_to_many+
Rails offers two different ways to declare a many-to-many relationship between models. The simpler way is to use +has_and_belongs_to_many+, which allows you to make the association directly:
@ -272,7 +272,7 @@ The simplest rule of thumb is that you should set up a +has_many :through+ relat
You should use +has_many :through+ if you need validations, callbacks, or extra attributes on the join model.
h4. Polymorphic associations
h4. Polymorphic Associations
A slightly more advanced twist on associations is the _polymorphic association_. With polymorphic associations, a model can belong to more than one other model, on a single association. For example, you might have a picture model that belongs to either an employee model or a product model. Here's how this could be declared:
@ -333,7 +333,7 @@ end
!images/polymorphic.png(Polymorphic Association Diagram)!
h4. Self joins
h4. Self Joins
In designing a data model, you will sometimes find a model that should have a relation to itself. For example, you may want to store all employees in a single database model, but be able to trace relationships such as between manager and subordinates. This situation can be modeled with self-joining associations:
@ -356,7 +356,7 @@ Here are a few things you should know to make efficient use of Active Record ass
* Updating the schema
* Controlling association scope
h4. Controlling caching
h4. Controlling Caching
All of the association methods are built around caching, which keeps the result of the most recent query available for further operations. The cache is even shared across methods. For example:
@ -375,15 +375,15 @@ customer.orders(true).empty? # discards the cached copy of orders
# and goes back to the database
</ruby>
h4. Avoiding name collisions
h4. Avoiding Name Collisions
You are not free to use just any name for your associations. Because creating an association adds a method with that name to the model, it is a bad idea to give an association a name that is already used for an instance method of +ActiveRecord::Base+. The association method would override the base method and break things. For instance, +attributes+ or +connection+ are bad names for associations.
h4. Updating the schema
h4. Updating the Schema
Associations are extremely useful, but they are not magic. You are responsible for maintaining your database schema to match your associations. In practice, this means two things, depending on what sort of associations you are creating. For +belongs_to+ associations you need to create foreign keys, and for +has_and_belongs_to_many+ associations you need to create the appropriate join table.
h5. Creating foreign Keys for +belongs_to+ associations
h5. Creating Foreign Keys for +belongs_to+ Associations
When you declare a +belongs_to+ association, you need to create foreign keys as appropriate. For example, consider this model:
@ -413,7 +413,7 @@ end
If you create an association some time after you build the underlying model, you need to remember to create an +add_column+ migration to provide the necessary foreign key.
h5. Creating join tables for +has_and_belongs_to_many+ associations
h5. Creating Join Tables for +has_and_belongs_to_many+ Associations
If you create a +has_and_belongs_to_many+ association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the +:join_table+ option, Active Record creates the name by using the lexical order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering.
@ -450,7 +450,7 @@ end
We pass +:id => false+ to +create_table+ because that table does not represent a model. That's required for the association to work properly. If you observe any strange behaviour in a +has_and_belongs_to_many+ association like mangled models IDs, or exceptions about conflicting IDs chances are you forgot that bit.
h4. Controlling association scope
h4. Controlling Association Scope
By default, associations look for objects only within the current module's scope. This can be important when you declare Active Record models within a module. For example:
@ -510,11 +510,11 @@ h3. Detailed Association Reference
The following sections give the details of each type of association, including the methods that they add and the options that you can use when declaring an association.
h4. +belongs_to+ association reference
h4. +belongs_to+ Association Reference
The +belongs_to+ association creates a one-to-one match with another model. In database terms, this association says that this class contains the foreign key. If the other class contains the foreign key, then you should use +has_one+ instead.
h5. Methods added by +belongs_to+
h5. Methods Added by +belongs_to+
When you declare a +belongs_to+ association, the declaring class automatically gains four methods related to the association:
@ -724,7 +724,7 @@ NOTE: There's no need to use +:include+ for immediate associations - that is, if
h6. +:polymorphic+
Passing +true+ to the +:polymorphic+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphicassociations">earlier in this guide</a>.
Passing +true+ to the +:polymorphic+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphic-associations">earlier in this guide</a>.
h6. +:readonly+
@ -740,7 +740,7 @@ h6. +:validate+
If you set the +:validate+ option to +true+, then associated objects will be validated whenever you save this object. By default, this is +false+: associated objects will not be validated when this object is saved.
h5. How to know whether there's an associated object?
h5. How To Know Whether There's an Associated Object?
To know whether there's and associated object just check <tt><em>association</em>.nil?</tt>:
@ -750,15 +750,15 @@ if @order.customer.nil?
end
</ruby>
h5. When are objects saved?
h5. When are Objects Saved?
Assigning an object to a +belongs_to+ association does _not_ automatically save the object. It does not save the associated object either.
h4. +has_one+ association reference
h4. +has_one+ Association Reference
The +has_one+ association creates a one-to-one match with another model. In database terms, this association says that the other class contains the foreign key. If this class contains the foreign key, then you should use +belongs_to+ instead.
h5. Methods added by +has_one+
h5. Methods Added by +has_one+
When you declare a +has_one+ association, the declaring class automatically gains four methods related to the association:
@ -848,7 +848,7 @@ The +has_one+ association supports these options:
h6. +:as+
Setting the +:as+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphicassociations">earlier in this guide</a>.
Setting the +:as+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphic-associations">earlier in this guide</a>.
h6. +:autosave+
@ -952,13 +952,13 @@ The +:source_type+ option specifies the source association type for a +has_one :
h6. :through
The +:through+ option specifies a join model through which to perform the query. +has_one :through+ associations were discussed in detail <a href="#thehas-onethroughassociation">earlier in this guide</a>.
The +:through+ option specifies a join model through which to perform the query. +has_one :through+ associations were discussed in detail <a href="#the-has-one-through-association">earlier in this guide</a>.
h6. +:validate+
If you set the +:validate+ option to +true+, then associated objects will be validated whenever you save this object. By default, this is +false+: associated objects will not be validated when this object is saved.
h5. How to know whether there's an associated object?
h5. How To Know Whether There's an Associated Object?
To know whether there's and associated object just check <tt><em>association</em>.nil?</tt>:
@ -968,7 +968,7 @@ if @supplier.account.nil?
end
</ruby>
h5. When are objects saved?
h5. When are Objects Saved?
When you assign an object to a +has_one+ association, that object is automatically saved (in order to update its foreign key). In addition, any object being replaced is also automatically saved, because its foreign key will change too.
@ -978,11 +978,11 @@ If the parent object (the one declaring the +has_one+ association) is unsaved (t
If you want to assign an object to a +has_one+ association without saving the object, use the <tt><em>association</em>.build</tt> method.
h4. +has_many+ association reference
h4. +has_many+ Association Reference
The +has_many+ association creates a one-to-many relationship with another model. In database terms, this association says that the other class will have a foreign key that refers to instances of this class.
h5. Methods added
h5. Methods Added
When you declare a +has_many+ association, the declaring class automatically gains 13 methods related to the association:
@ -1158,7 +1158,7 @@ The +has_many+ association supports these options:
h6. +:as+
Setting the +:as+ option indicates that this is a polymorphic association, as discussed <a href="#polymorphicassociations">earlier in this guide</a>.
Setting the +:as+ option indicates that this is a polymorphic association, as discussed <a href="#polymorphic-associations">earlier in this guide</a>.
h6. +:autosave+
@ -1210,7 +1210,7 @@ NOTE: This option is ignored when you use the +:through+ option on the associati
h6. +:extend+
The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#associationextensions">later in this guide</a>.
The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#association-extensions">later in this guide</a>.
h6. +:finder_sql+
@ -1323,7 +1323,7 @@ The +:source_type+ option specifies the source association type for a +has_many
h6. +:through+
The +:through+ option specifies a join model through which to perform the query. +has_many :through+ associations provide a way to implement many-to-many relationships, as discussed <a href="#thehas-manythroughassociation">earlier in this guide</a>.
The +:through+ option specifies a join model through which to perform the query. +has_many :through+ associations provide a way to implement many-to-many relationships, as discussed <a href="#the-has-many-through-association">earlier in this guide</a>.
h6. +:uniq+
@ -1333,7 +1333,7 @@ h6. +:validate+
If you set the +:validate+ option to +false+, then associated objects will not be validated whenever you save this object. By default, this is +true+: associated objects will be validated when this object is saved.
h5. When are objects saved?
h5. When are Objects Saved?
When you assign an object to a +has_many+ association, that object is automatically saved (in order to update its foreign key). If you assign multiple objects in one statement, then they are all saved.
@ -1343,11 +1343,11 @@ If the parent object (the one declaring the +has_many+ association) is unsaved (
If you want to assign an object to a +has_many+ association without saving the object, use the <tt><em>collection</em>.build</tt> method.
h4. +has_and_belongs_to_many+ association reference
h4. +has_and_belongs_to_many+ Association Reference
The +has_and_belongs_to_many+ association creates a many-to-many relationship with another model. In database terms, this associates two classes via an intermediate join table that includes foreign keys referring to each of the classes.
h5. Methods added
h5. Methods Added
When you declare a +has_and_belongs_to_many+ association, the declaring class automatically gains 13 methods related to the association:
@ -1391,7 +1391,7 @@ assemblies.build(attributes = {}, ...)
assemblies.create(attributes = {})
</ruby>
h6. Additional column methods
h6. Additional Column Methods
If the join table for a +has_and_belongs_to_many+ association has additional columns beyond the two foreign keys, these columns will be added as attributes to records retrieved via that association. Records returned with additional attributes will always be read-only, because Rails cannot save changes to those attributes.
@ -1589,7 +1589,7 @@ Normally Rails automatically generates the proper SQL to remove links between th
h6. +:extend+
The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#associationextensions">later in this guide</a>.
The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#association-extensions">later in this guide</a>.
h6. +:finder_sql+
@ -1670,7 +1670,7 @@ h6. +:validate+
If you set the +:validate+ option to +false+, then associated objects will not be validated whenever you save this object. By default, this is +true+: associated objects will be validated when this object is saved.
h5. When are objects saved?
h5. When are Objects Saved?
When you assign an object to a +has_and_belongs_to_many+ association, that object is automatically saved (in order to update the join table). If you assign multiple objects in one statement, then they are all saved.
@ -1680,7 +1680,7 @@ If the parent object (the one declaring the +has_and_belongs_to_many+ associatio
If you want to assign an object to a +has_and_belongs_to_many+ association without saving the object, use the <tt><em>collection</em>.build</tt> method.
h4. Association callbacks
h4. Association Callbacks
Normal callbacks hook into the lifecycle of Active Record objects, allowing you to work with those objects at various points. For example, you can use a +:before_save+ callback to cause something to happen just before an object is saved.
@ -1724,7 +1724,7 @@ end
If a +before_add+ callback throws an exception, the object does not get added to the collection. Similarly, if a +before_remove+ callback throws an exception, the object does not get removed from the collection.
h4. Association extensions
h4. Association Extensions
You're not limited to the functionality that Rails automatically builds into association proxy objects. You can also extend these objects through anonymous modules, adding new finders, creators, or other methods. For example:

View file

@ -4,6 +4,13 @@ Everyone caches. This guide will teach you what you need to know about
avoiding that expensive round-trip to your database and returning what you
need to return to those hungry web clients in the shortest time possible.
After reading this guide, you should be able to use and configure:
* Page, action, and fragment caching
* Sweepers
* Alternative cache stores
* Conditional GET support
endprologue.
h3. Basic Caching
@ -13,8 +20,7 @@ provides by default without the use of any third party plugins.
To get started make sure +config.action_controller.perform_caching+ is set
to +true+ for your environment. This flag is normally set in the
corresponding config/environments/*.rb and caching is disabled by default
there for development and test, and enabled for production.
corresponding config/environments/*.rb. By default, caching is disabled for development and test, and enabled for production.
<ruby>
config.action_controller.perform_caching = true
@ -29,9 +35,9 @@ applied to every situation (such as pages that need authentication) and since
the webserver is literally just serving a file from the filesystem, cache
expiration is an issue that needs to be dealt with.
So, how do you enable this super-fast cache behavior? Simple, let's say you
have a controller called ProductsController and a 'list' action that lists all
the products
So, how do you enable this super-fast cache behavior? Suppose you
have a controller called +ProductsController+ and an +index+ action that lists all
the products. You could enable caching for this action like this:
<ruby>
class ProductsController < ActionController
@ -44,34 +50,33 @@ end
</ruby>
The first time anyone requests products/index, Rails will generate a file
called +index.html+ and the webserver will then look for that file before it
passes the next request for products/index to your Rails application.
called +index.html+. If a web server see this file, it will be served in response to the
next request for products/index, without your Rails application being called.
By default, the page cache directory is set to Rails.public_path (which is
usually set to +RAILS_ROOT + "/public"+) and this can be configured by
usually set to +File.join(self.root, "public")+ - that is, the public directory under your Rails application's root). This can be configured by
changing the configuration setting +config.action_controller.page_cache_directory+.
Changing the default from /public helps avoid naming conflicts, since you may
want to put other static html in /public, but changing this will require web
server reconfiguration to let the web server know where to serve the cached
files from.
The Page Caching mechanism will automatically add a +.html+ extension to
The page caching mechanism will automatically add a +.html+ extension to
requests for pages that do not have an extension to make it easy for the
webserver to find those pages and this can be configured by changing the
webserver to find those pages. This can be configured by changing the
configuration setting +config.action_controller.page_cache_extension+.
In order to expire this page when a new product is added we could extend our
example controller like this:
In order to expire this page when a new product is added you could extend the products controller like this:
<ruby>
class ProductsController < ActionController
caches_page :list
caches_page :index
def list; end
def index; end
def create
expire_page :action => :list
expire_page :action => :index
end
end
@ -80,19 +85,19 @@ end
If you want a more complicated expiration scheme, you can use cache sweepers
to expire cached objects when things change. This is covered in the section on Sweepers.
Note: Page caching ignores all parameters, so /products/list?page=1 will be written out to the filesystem as /products/list.html and if someone requests /products/list?page=2, they will be returned the same result as page=1, so be careful when page caching GET parameters in the URL!
Note: Page caching ignores all parameters, so /products/list?page=1 will be written out to the filesystem as /products/list.html and if someone requests /products/list?page=2, they will be returned the same result as page=1. Be careful when page caching GET parameters in the URL!
h4. Action Caching
One of the issues with Page Caching is that you cannot use it for pages that
require to restrict access somehow. This is where Action Caching comes in.
Action Caching works like Page Caching except for the fact that the incoming
web request does go from the webserver to the Rails stack and Action Pack so
that before filters can be run on it before the cache is served, so that
authentication and other restrictions can be used while still serving the
One of the issues with page caching is that you cannot use it for pages that
require checking code to determine whether the user should be permitted access. This is where Action Caching comes in.
action caching works like page caching except for the fact that the incoming
web request does go from the web server to the Rails stack and Action Pack so
that before filters can be run on it before the cache is served. This allows you to use
authentication and other restrictions while still serving the
result of the output from a cached copy.
Clearing the cache works in the exact same way as with Page Caching.
Clearing the cache works in the exact same way as with page caching.
Let's say you only wanted authenticated users to edit or create a Product
object, but still cache those pages:
@ -101,13 +106,13 @@ object, but still cache those pages:
class ProductsController < ActionController
before_filter :authenticate, :only => [ :edit, :create ]
caches_page :list
caches_page :index
caches_action :edit
def list; end
def index; end
def create
expire_page :action => :list
expire_page :action => :index
expire_action :action => :edit
end
@ -116,19 +121,19 @@ class ProductsController < ActionController
end
</ruby>
And you can also use +:if+ (or +:unless+) to pass a Proc that specifies when the
You can also use +:if+ (or +:unless+) to pass a Proc that specifies when the
action should be cached. Also, you can use +:layout => false+ to cache without
layout so that dynamic information in the layout such as logged in user info
layout so that dynamic information in the layout such as the name of the logged-in user
or the number of items in the cart can be left uncached. This feature is
available as of Rails 2.2.
You can modify the default action cache path by passing a +:cache_path+ option.
This will be passed directly to ActionCachePath.path_for. This is handy for
This will be passed directly to +ActionCachePath.path_for+. This is handy for
actions with multiple possible routes that should be cached differently. If
a block is given, it is called with the current controller instance.
Finally, if you are using memcached, you can also pass +:expires_in+. In fact,
all parameters not used by caches_action are sent to the underlying cache
all parameters not used by +caches_action+ are sent to the underlying cache
store.
h4. Fragment Caching
@ -162,52 +167,56 @@ could use this piece of code:
</ruby>
The cache block in our example will bind to the action that called it and is
written out to the same place as the Action Cache, which means that if you
written out to the same place as the action cache, which means that if you
want to cache multiple fragments per action, you should provide an +action_suffix+ to the cache call:
<ruby>
<% cache(:action => 'recent', :action_suffix => 'all_products') do %>
<% cache(:action => 'recent', :action_suffix => 'all_prods') do %>
All available products:
</ruby>
and you can expire it using the +expire_fragment+ method, like so:
You can expire the cache using the +expire_fragment+ method, like so:
<ruby>
expire_fragment(:controller => 'products', :action => 'recent', :action_suffix => 'all_products)
expire_fragment(:controller => 'products', :action => 'recent',
:action_suffix => 'all_prods)
</ruby>
If you don't want the cache block to bind to the action that called it, You can
also use globally keyed fragments by calling the cache method with a key, like
If you don't want the cache block to bind to the action that called it, you can
also use globally keyed fragments. To do this, call the +cache+ method with a key, like
so:
<ruby>
<% cache(:key => ['all_available_products', @latest_product.created_at].join(':')) do %>
<% cache(:key =>
['all_available_products', @latest_product.created_at].join(':')) do %>
All available products:
<% end %>
</ruby>
This fragment is then available to all actions in the ProductsController using
This fragment is then available to all actions in the +ProductsController+ using
the key and can be expired the same way:
<ruby>
expire_fragment(:key => ['all_available_products', @latest_product.created_at].join(':'))
expire_fragment(:key =>
['all_available_products', @latest_product.created_at].join(':'))
</ruby>
h4. Sweepers
Cache sweeping is a mechanism which allows you to get around having a ton of
expire_{page,action,fragment} calls in your code by moving all the work
required to expire cached content into a +ActionController::Caching::Sweeper+
class that is an Observer and looks for changes to an object via callbacks,
and when a change occurs it expires the caches associated with that object n
+expire_{page,action,fragment}+ calls in your code. It does this by moving all the work
required to expire cached content into na +ActionController::Caching::Sweeper+
class. This class is an Observer that looks for changes to an object via callbacks,
and when a change occurs it expires the caches associated with that object in
an around or after filter.
Continuing with our Product controller example, we could rewrite it with a
sweeper such as the following:
sweeper like this:
<ruby>
class StoreSweeper < ActionController::Caching::Sweeper
observe Product # This sweeper is going to keep an eye on the Product model
# This sweeper is going to keep an eye on the Product model
observe Product
# If our sweeper detects that a Product was created call this
def after_create(product)
@ -230,13 +239,13 @@ class StoreSweeper < ActionController::Caching::Sweeper
expire_page(:controller => '#{record}', :action => 'list')
# Expire a fragment
expire_fragment(:controller => '#{record}', :action => 'recent', :action_suffix => 'all_products')
expire_fragment(:controller => '#{record}',
:action => 'recent', :action_suffix => 'all_products')
end
end
</ruby>
Then we add it to our controller to tell it to call the sweeper when certain
actions are called. So, if we wanted to expire the cached content for the
The sweeper has to be added to the controller that will use it. So, if we wanted to expire the cached content for the
list and edit actions when the create action was called, we could do the
following:
@ -263,9 +272,9 @@ end
h4. SQL Caching
Query caching is a Rails feature that caches the result set returned by each
query so that if Rails encounters the same query again for that request, it
query. If Rails encounters the same query again during the current request, it
will used the cached result set as opposed to running the query against the
database again.
database.
For example:
@ -304,9 +313,9 @@ database again the second time that finder is called.
Query caches are created at the start of an action and destroyed at the end of
that action and thus persist only for the duration of the action.
h4. Cache stores
h4. Cache Stores
Rails (as of 2.1) provides different stores for the cached data for action and
Rails (as of 2.1) provides different stores for the cached data created by action and
fragment caches. Page caches are always stored on disk.
Rails 2.1 and above provide ActiveSupport::Cache::Store which can be used to
@ -314,7 +323,7 @@ cache strings. Some cache store implementations, like MemoryStore, are able to
cache arbitrary Ruby objects, but don't count on every cache store to be able
to do that.
The default cache stores provided include:
The default cache stores provided with Rails include:
1) ActiveSupport::Cache::MemoryStore: A cache store implementation which stores
everything into memory in the same process. If you're running multiple Ruby on
@ -335,13 +344,12 @@ need thread-safety.
ActionController::Base.cache_store = :memory_store
</ruby>
2) ActiveSupport::Cache::FileStore: Cached data is stored on the disk, this is
2) ActiveSupport::Cache::FileStore: Cached data is stored on the disk. This is
the default store and the default path for this store is: /tmp/cache. Works
well for all types of environments and allows all processes running from the
same application directory to access the cached content. If /tmp/cache does not
exist, the default store becomes MemoryStore.
<ruby>
ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
</ruby>
@ -351,7 +359,6 @@ DRb process that all servers communicate with. This works for all environments
and only keeps one cache around for all processes, but requires that you run
and manage a separate DRb process.
<ruby>
ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
</ruby>
@ -361,27 +368,28 @@ Rails uses the bundled memcached-client gem by default. This is currently the
most popular cache store for production websites.
Special features:
* Clustering and load balancing. One can specify multiple memcached servers,
* Clustering and load balancing. One can specify multiple memcached servers,
and MemCacheStore will load balance between all available servers. If a
server goes down, then MemCacheStore will ignore it until it goes back
online.
* Time-based expiry support. See write and the +:expires_in+ option.
* Per-request in memory cache for all communication with the MemCache server(s).
* Time-based expiry support. See +write+ and the +:expires_in+ option.
* Per-request in memory cache for all communication with the MemCache server(s).
It also accepts a hash of additional options:
* +:namespace+- specifies a string that will automatically be prepended to keys when accessing the memcached store.
* +:readonly+- a boolean value that when set to true will make the store read-only, with an error raised on any attempt to write.
* +:multithread+ - a boolean value that adds thread safety to read/write operations - it is unlikely you'll need to use this option as the Rails threadsafe! method offers the same functionality.
* +:namespace+- specifies a string that will automatically be prepended to keys when accessing the memcached store.
* +:readonly+- a boolean value that when set to true will make the store read-only, with an error raised on any attempt to write.
* +:multithread+ - a boolean value that adds thread safety to read/write operations - it is unlikely you'll need to use this option as the Rails threadsafe! method offers the same functionality.
The read and write methods of the MemCacheStore accept an options hash too.
When reading you can specify +:raw => true+ to prevent the object being
marshaled
(by default this is false which means the raw value in the cache is passed to
Marshal.load before being returned to you.)
+Marshal.load+ before being returned to you.)
When writing to the cache it is also possible to specify +:raw => true+ means
the value is not passed to Marshal.dump before being stored in the cache (by
When writing to the cache it is also possible to specify +:raw => true+. This means
that the value is not passed to +Marshal.dump+ before being stored in the cache (by
default this is false).
The write method also accepts an +:unless_exist+ flag which determines whether
@ -416,11 +424,11 @@ ActionController::Base.cache_store = :compressed_mem_cache_store, "localhost"
ActionController::Base.cache_store = MyOwnStore.new("parameter")
</ruby>
+Note: config.cache_store can be used in place of
ActionController::Base.cache_store in your Rails::Initializer.run block in
environment.rb+
NOTE: +config.cache_store+ can be used in place of
+ActionController::Base.cache_store+ in the +Rails::Initializer.run+ block in
environment.rb.
In addition to all of this, Rails also adds the ActiveRecord::Base#cache_key
In addition to all of this, Rails also adds the +ActiveRecord::Base#cache_key+
method that generates a key using the class name, id and updated_at timestamp
(if available).
@ -432,9 +440,9 @@ Rails.cache.write("city", "Duckburgh")
Rails.cache.read("city") # => "Duckburgh"
</ruby>
h3. Conditional GET support
h3. Conditional GET Support
Conditional GETs are a facility of the HTTP spec that provide a way for web
Conditional GETs are a feature of the HTTP specification that provide a way for web
servers to tell browsers that the response to a GET request hasn't changed
since the last request and can be safely pulled from the browser cache.

View file

@ -24,7 +24,7 @@ There are a few commands that are absolutely critical to your everyday usage of
Let's create a simple Rails application to step through each of these commands in context.
h4. rails
h4. +rails+
The first thing we'll want to do is create a new Rails application by running the +rails+ command after installing Rails.
@ -48,7 +48,7 @@ Rails will set you up with what seems like a huge amount of stuff for such a tin
INFO: This output will seem very familiar when we get to the +generate+ command. Creepy foreshadowing!
h4. server
h4. +server+
Let's try it! The +server+ command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to view your work through a web browser.
@ -71,7 +71,7 @@ WHOA. With just three commands we whipped up a Rails server listening on port 30
See? Cool! It doesn't do much yet, but we'll change that.
h4. generate
h4. +generate+
The +generate+ command uses templates to create a whole lot of things. You can always find out what's available by running +generate+ by itself. Let's do that:
@ -248,15 +248,15 @@ Let's see the interface Rails created for us. ./script/server; http://localhost:
We can create new high scores (55,160 on Space Invaders!)
h4. console
h4. +console+
The +console+ command lets you interact with your Rails application from the command line. On the underside, +script/console+ uses IRB, so if you've ever used it, you'll be right at home. This is useful for testing out quick ideas with code and changing data server-side without touching the website.
h4. dbconsole
h4. +dbconsole+
+dbconsole+ figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3.
h4. plugin
h4. +plugin+
The +plugin+ command simplifies plugin management; think a miniature version of the Gem utility. Let's walk through installing a plugin. You can call the sub-command *discover*, which sifts through repositories looking for plugins, or call *source* to add a specific repository of plugins, or you can specify the plugin location directly.
@ -272,7 +272,7 @@ $ ./script/plugin install http://svn.techno-weenie.net/projects/plugins/acts_as_
...
</shell>
h4. runner
h4. +runner+
<tt>runner</tt> runs Ruby code in the context of Rails non-interactively. For instance:
@ -280,7 +280,7 @@ h4. runner
$ ./script/runner "Model.long_running_method"
</shell>
h4. destroy
h4. +destroy+
Think of +destroy+ as the opposite of +generate+. It'll figure out what generate did, and undo it. Believe you-me, the creation of this tutorial used this command many times!
@ -309,7 +309,7 @@ $ ./script/destroy model Oops
notempty app
</shell>
h4. about
h4. +about+
Check it: Version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version! +about+ is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation.
@ -335,7 +335,7 @@ h3. The Rails Advanced Command Line
The more advanced uses of the command line are focused around finding useful (even surprising at times) options in the utilities, and fitting utilities to your needs and specific work flow. Listed here are some tricks up Rails' sleeve.
h4. Rails with databases and SCM
h4. Rails with Databases and SCM
When creating a new Rails application, you have the option to specify what kind of database and what kind of source code management system your application is going to use. This will save you a few minutes, and certainly many keystrokes.
@ -393,7 +393,7 @@ development:
It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the +rails+ command to generate the basis of your app.
h4. server with different backends
h4. +server+ with Different Backends
Many people have created a large number different web servers in Ruby, and many of them can be used to run Rails. Since version 2.3, Rails uses Rack to serve its webpages, which means that any webserver that implements a Rack handler can be used. This includes WEBrick, Mongrel, Thin, and Phusion Passenger (to name a few!).
@ -534,25 +534,25 @@ rake tmp:sockets:clear # Clears all files in tmp/sockets
Let's take a look at some of these 80 or so rake tasks.
h5. db: Database
h5. +db:+ Database
The most common tasks of the +db:+ Rake namespace are +migrate+ and +create+, and it will pay off to try out all of the migration rake tasks (+up+, +down+, +redo+, +reset+). +rake db:version+ is useful when troubleshooting, telling you the current version of the database.
h5. doc: Documentation
h5. +doc:+ Documentation
If you want to strip out or rebuild any of the Rails documentation (including this guide!), the +doc:+ namespace has the tools. Stripping documentation is mainly useful for slimming your codebase, like if you're writing a Rails application for an embedded platform.
h5. gems: Ruby gems
h5. +gems:+ Ruby gems
You can specify which gems your application uses, and +rake gems:install+ will install them for you. Look at your environment.rb to learn how with the *config.gem* directive.
NOTE: +gems:unpack+ will unpack, that is internalize your application's Gem dependencies by copying the Gem code into your vendor/gems directory. By doing this you increase your codebase size, but simplify installation on new hosts by eliminating the need to run +rake gems:install+, or finding and installing the gems your application uses.
h5. notes: Code note enumeration
h5. +notes:+ Code note enumeration
These tasks will search through your code for commented lines beginning with "FIXME", "OPTIMIZE", "TODO", or any custom annotation (like XXX) and show you them.
h5. rails: Rails-specific tasks
h5. +rails:+ Rails-specific tasks
In addition to the +gems:unpack+ task above, you can also unpack the Rails backend specific gems into vendor/rails by calling +rake rails:freeze:gems+, to unpack the version of Rails you are currently using, or +rake rails:freeze:edge+ to unpack the most recent (cutting, bleeding edge) version.
@ -560,7 +560,7 @@ When you have frozen the Rails gems, Rails will prefer to use the code in vendor
After upgrading Rails, it is useful to run +rails:update+, which will update your config and scripts directories, and upgrade your Rails-specific javascript (like Scriptaculous).
h5. test: Rails tests
h5. +test:+ Rails tests
INFO: A good description of unit testing in Rails is given in "A Guide to Testing Rails Applications":testing.html
@ -568,15 +568,15 @@ Rails comes with a test suite called Test::Unit. It is through the use of tests
The +test:+ namespace helps in running the different tests you will (hopefully!) write.
h5. time: Timezones
h5. +time:+ Timezones
You can list all the timezones Rails knows about with +rake time:zones:all+, which is useful just in day-to-day life.
h5. tmp: Temporary files
h5. +tmp:+ Temporary files
The tmp directory is, like in the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. The +tmp:+ namespace tasks will help you clear them if you need to if they've become overgrown, or create them in case of an +rm -rf *+ gone awry.
h5. Miscellaneous tasks
h5. Miscellaneous Tasks
+rake stats+ is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio.
@ -584,3 +584,6 @@ h5. Miscellaneous tasks
+rake routes+ will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with.
h3. Changelog
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213/tickets/29

View file

@ -7,12 +7,12 @@ endprologue.
h3. How to Contribute?
* We have an open commit policy: anyone is welcome to contribute, but you'll need to ask for commit access.
* PM lifo at "GitHub":http://github.om asking for "docrails":http://github.com/lifo/docrails commit access.
* PM lifo at "GitHub":http://github.com asking for "docrails":http://github.com/lifo/docrails/tree/master commit access.
* Guides are written in Textile, and reside at railties/guides/source in the docrails project.
* All images are in the railties/guides/images directory.
* Sample format : "Active Record Associations":http://github.com/lifo/docrails/blob/3e56a3832415476fdd1cb963980d0ae390ac1ed3/railties/guides/source/association_basics.textile
* Sample output : "Active Record Associations":http://guides.rails.info/association_basics.html
* You can build the Guides during testing by running railties/guides/rails_guides.rb.
* You can build the Guides during testing by running +rake guides+ in the +railties+ directory.
h3. What to Contribute?
@ -44,26 +44,21 @@ For each completed guide, the lead contributor will receive all of the following
h3. Rules
* Guides are licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 License.
* Guides are licensed under a Creative Commons Attribution-Share Alike 3.0 License.
* If you're not sure whether a guide is actively being worked on, stop by IRC and ask.
* If the same guide writer wants to write multiple guides, that's ideally the situation we'd love to be in! However, that guide writer will only receive the cash prize for all the subsequent guides (and not the GitHub or RPM prizes).
* Our review team will have the final say on whether the guide is complete and of good enough quality.
h3. Reviewers
These are the main reviewers and editors for the guides:
* Hongli Lai
* Mike Gunderloy
* Pratik Naik
* Xavier Noria
All authors should read and follow the "Rails Guides Conventions":http://wiki.github.com/lifo/docrails/rails-guides-conventions and the "Rails API Documentation Conventions":http://wiki.github.com/lifo/docrails/rails-api-documentation-conventions.
h3. Translations
The translation effort for the Rails Guides is just getting underway. We know about projects to translate the Guides into Spanish, Portuguese, Polish, and French. For more details or to get involved see the "Translating Rails Guides":http://wiki.github.com/lifo/docrails/translating-rails-guides page.
h3. Mailing List
"Ruby on Rails: Documentation":http://groups.google.com/group/rubyonrails-docs is the mailing list for all the guides/documentation related discussions.
h3. IRC Channel
==#docrails @ irc.freenode.net==
@ -72,6 +67,5 @@ h3. Contact
If you have any questions or need any clarification, feel free to contact:
* IRC : lifo, mikeg1a, fxn, or FooBarWidget in #docrails
* IRC : lifo, mikeg1a or fxn in #docrails
* Email : pratiknaik aT gmail

View file

@ -0,0 +1,239 @@
h2. Contributing to Rails
This guide covers ways in which _you_ can become a part of the ongoing development of Rails. After reading it, you should be familiar with:
* Using Lighthouse to report issues with Rails
* Cloning edge Rails and running the test suite
* Helping to resolve existing issues
* Contributing to the Rails documentation
* Contributing to the Rails code
Rails is not "someone else's framework." Over the years, hundreds of people have contributed code ranging from a single character to massive architectural changes, all with the goal of making Rails better for everyone. Even if you don't feel up to writing code yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches to contributing documentation.
endprologue.
h3. Reporting a Rails Issue
Rails uses a "Lighthouse project":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/ to track issues (primarily bugs and contributions of new code). If you've found a bug in Rails, this is the place to start.
NOTE: Bugs in the most recent released version of Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this Guide you'll find out how to get edge Rails for testing.
h4. Creating a Bug Report
If you've found a problem in Rails, you can start by "adding a new ticket":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new to the Rails Lighthouse. At the minimum, your ticket needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need to at least post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix.
You shouldn't assign the bug to a particular core developer (through the *Who's Responsible* select list) unless you know for sure which developer will be handling any patch. The core team periodically reviews issues and assigns developers and milestones to them.
You should set tags for your issue. Use the "bug" tag for a bug report, and add the "patch" tag if you are attaching a patch. Try to find some relevant tags from the existing tag list (which will appear as soon as you start typing in the *Choose some tags* textbox), rather than creating new tags.
Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kind of bug, you're creating this ticket in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the ticket automatically will see any activity or that others will jump to fix it. Creating a ticket like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with a "I'm having this problem too" comment.
h4. Special Treatment for Security Issues
If you've found a security vulnerability in Rails, please do *not* report it via a Lighthouse ticket. Lighthouse tickets are public as soon as they are entered. Instead, you should use the dedicated email address "security@rubyonrails.org":mailto:security@rubyonrails.org to report any vulnerabilities. This alias is monitored and the core team will work with you to quickly and completely address any such vulnerabilities.
h4. What About Feature Requests?
Please don't put "feature request" tickets into Lighthouse. If there's a new feature that you want to see added to Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to Rails. If you enter a wishlist item in Lighthouse with no code, you can expect it to be marked "invalid" as soon as it's reviewed.
h3. Running the Rails Test Suite
To move on from submitting bugs to helping resolve existing issues or contributing your own code to Rails, you _must_ be able to run the Rails test suite. In this section of the guide you'll learn how to set up the tests on your own computer.
h4. Install git
Rails uses git for source code control. You wont be able to do anything without the Rails source code, and this is a prerequisite. The "git homepage":http://git-scm.com/ has installation instructions. If youre on OS X, use the "Git for OS X":http://code.google.com/p/git-osx-installer/ installer. If you're unfamiliar with git, there are a variety of resources on the net that will help you learn more:
* "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/everyday.html will teach you just enough about git to get by.
* The "PeepCode screencast":https://peepcode.com/products/git on git ($9) is easier to follow.
* "GitHub":http://github.com/guides/home offers links to a variety of git resources.
h4. Get the Rails Source Code
Dont fork the main Rails repository. Instead, you want to clone it to your own computer. Navigate to the folder where you want the source code (it will create its own /rails subdirectory) and run:
<shell>
git clone git://github.com/rails/rails.git
cd rails
</shell>
h4. Set up and Run the Tests
All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. For the tests that touch the database, this means creating the databases. If you're using MySQL:
<shell>
mysql> create database activerecord_unittest;
mysql> create database activerecord_unittest2;
mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.*
to 'rails'@'localhost';
mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.*
to 'rails'@'localhost';
</shell>
If youre using another database, check the files under +activerecord/test/connections+ in the Rails source code for default connection information. You can edit these files if you _must_ on your machine to provide different credentials, but obviously you should not push any such changes back to Rails.
Now if you go back to the root of the Rails source on your machine and run +rake+ with no parameters, you should see every test in all of the Rails components pass. If you want to run the all ActiveRecord tests (or just a single one) with another database adapter, enter this from the +activerecord+ directory:
<shell>
rake test_sqlite3
rake test_sqlite3 TEST=test/cases/validations_test.rb
</shell>
You can change +sqlite3+ with +jdbcmysql+, +jdbcsqlite3+, +jdbcpostgresql+, +mysql+ or +postgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the Rails continuous integration server runs.
NOTE: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, SQLite 2, and SQLite 3. Subtle differences between the various Active Record database adapters have been behind the rejection of many patches that looked OK when tested only against MySQL.
h3. Helping to Resolve Existing Issues
As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the "open tickets":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets?q=state%3Aopen list in Lighthouse, you'll find hundreds of issues already requiring attention. What can you do for these? Quite a bit, actually:
h4. Verifying Bug Reports
For starters, it helps to just verify bug reports. Can you reproduce the reported issue on your own computer? If so, you can add a comment to the ticket saying that you're seeing the same thing.
If something is very vague, can you help squish it down into something specific? Maybe you can provide additional information to help reproduce a bug, or eliminate needless steps that aren't required to help demonstrate the problem.
If you find a bug report without a test, it's very useful to contribute a failing test. This is also a great way to get started exploring the Rails source: looking at the existing test files will teach you how to write more tests for Rails. New tests are best contributed in the form of a patch, as explained later on in the "Contributing to the Rails Code" section.
Anything you can do to make bug reports more succinct or easier to reproduce is a help to folks trying to write code to fix those bugs - whether you end up writing the code yourself or not.
h4. Testing Patches
You can also help out by examining patches that have been submitted to Rails via Lighthouse. To apply someone's changes you need to first create a branch of the Rails source code:
<shell>
git checkout -b testing_branch
</shell>
Then you can apply their patch:
<shell>
git am < their-patch-file.diff
</shell>
After applying a patch, test it out! Here are some things to think about:
* Does the patch actually work?
* Are you happy with the tests? Can you follow what they're testing? Are there any tests missing?
* Does the documentation still seem right to you?
* Do you like the implementation? Can you think of a nicer or faster way to implement a part of their change?
Once you're happy that the patch contains a good change, comment on the Lighthouse ticket indicating your approval. Your comment should indicate that you like the change and what you like about it. Something like:
<blockquote>
I like the way you've restructured that code in generate_finder_sql, much nicer. The tests look good too.
</blockquote>
If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the patch. Once three people have approved it, add the "verified" tag. This will bring it to the attention of a core team member who will review the changes looking for the same kinds of things.
h3. Contributing to the Rails Documentation
Another area where you can help out if you're not yet ready to take the plunge to writing Rails core code is with Rails documentation. You can help with the Rails Guides or the Rails API documentation.
TIP: "docrails":http://github.com/lifo/docrails/tree/master is the documentation branch for Rails with an *open commit policy*. You can simply PM "lifo":http://github.com/lifo on Github and ask for the commit rights. Documentation changes made as part of the "docrails":http://github.com/lifo/docrails/tree/master project, are merged back to the Rails master code from time to time. Check out the "original announcement":http://weblog.rubyonrails.org/2008/5/2/help-improve-rails-documentation-on-git-branch for more details.
h4. The Rails Guides
The "Rails Guides":http://guides.rubyonrails.org/ are a set of online resources that are designed to make people productive with Rails and to understand how all of the pieces fit together. These guides (including this one!) are written as part of the "docrails":http://github.com/lifo/docrails/tree/master project. If you have an idea for a new guide, or improvements for an existing guide, you can refer to the "contribution page":contribute.html for instructions on getting involved.
h4. The Rails API Documentation
The "Rails API documentation":http://api.rubyonrails.org/ is automatically generated from the Rails source code via "RDoc":http://rdoc.rubyforge.org/. If you find some part of the documentation to be incomplete, confusing, or just plain wrong, you can step in and fix it.
To contribute an update to the API documentation, you can contact "lifo":http://github.com/lifo on GitHub and ask for commit rights to the docrails repository and push your changes to the docrails repository. Please follow the "docrails RDoc conventions":http://wiki.github.com/lifo/docrails/rails-api-documentation-conventions when contributing the changes.
h3. The Rails Wiki
The "Rails wiki":http://wiki.rubyonrails.org/ is a collection of user-generated and freely-editable information about Rails. It covers everything from getting started to FAQs to how-tos and popular plugins. To contribute to the wiki, just find some useful information that isn't there already and add it. There are style guidelines to help keep the wiki a coherent resources; see the section on "contributing to the wiki":http://wiki.rubyonrails.org/#contributing_to_the_wiki for more details.
h3. Contributing to the Rails Code
When you're ready to take the plunge, one of the most helpful ways to contribute to Rails is to actually submit source code. Here's a step-by-step listing of the things you need to do to make this a successful experience.
h4. Learn the Language and the Framework
Learn at least _something_ about Ruby and Rails. If you dont understand the syntax of the language, common Ruby idioms, and the code that already exists in Rails, youre unlikely to be able to build a good patch (that is, one that will get accepted). You dont have to know every in-and-out of the language and the framework; some of the Rails code is fiendishly complex. But Rails is probably not appropriate as the first place that you ever write Ruby code. You should at least understand (though not necessarily memorize) "The Ruby Programming Language":http://www.amazon.com/gp/product/0596516177?ie=UTF8&linkCode=as2&camp=1789&creative=390957&creativeASIN=0596516177 and have browsed the Rails source code.
h4. Fork the Rails Source Code
Fork Rails. Youre not going to put your patches right into the master branch, OK? This is where you need that copy of Rails that you cloned earlier. Think of a name for your new branch and run
<shell>
git checkout -b my_new_branch
</shell>
It doesnt really matter what name you use, because this branch will only exist on your local computer.
h4. Write Your Code
Now get busy and add your code to Rails (or edit the existing code). Youre on your branch now, so you can write whatever you want (you can check to make sure youre on the right branch with +git branch -a+). But if youre planning to submit your change back for inclusion in Rails, keep a few things in mind:
* Get the code right
* Use Rails idioms and helpers
* Include tests that fail without your code, and pass with it
* Update the documentation
h4. Sanity Check
You should not be the only person who looks at the code before you submit it. You know at least one other Rails developer, right? Show them what youre doing and ask for feedback. Doing this in private before you push a patch out publicly is the “smoke test” for a patch: if you cant convince one other developer of the beauty of your code, youre unlikely to convince the core team either.
h4. Commit Your Changes
When you're happy with the code on your computer, you need to commit the changes to git:
<shell>
git commit -a -m "Here is a commit message"
</shell>
h4. Update Rails
Update your copy of Rails. Its pretty likely that other changes to core Rails have happened while you were working. Go get them:
<shell>
git checkout master
git pull
</shell>
Now reapply your patch on top of the latest changes:
<shell>
git checkout my_new_branch
git rebase master
</shell>
No conflicts? Tests still pass? Change still seems reasonable to you? Then move on.
h4. Create a Patch
Now you can create a patch file to share with other developers (and with the Rails core team). Still in your branch, run
<shell>
git commit -a
git format-patch master --stdout > my_new_patch.diff
</shell>
Sanity check the results of this operation: open the diff file in your text editor of choice and make sure that no unintended changes crept in.
h4. Create a Lighthouse Ticket
Now create a ticket with your patch. Go to the "new ticket":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new page at Lighthouse. Fill in a reasonable title and description, remember to attach your patch file, and tag the ticket with the patch tag and whatever other subject area tags make sense.
h4. Get Some Feedback
Now you need to get other people to look at your patch, just as you've looked at other people's patches. You can use the rubyonrails-core mailing list or the #rails-contrib channel on IRC freenode for this. You might also try just talking to Rails developers that you know.
h4. Iterate as Necessary
Its entirely possible that the feedback you get will suggest changes. Dont get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then its worth making the tweaks and resubmitting. If the feedback is that your code doesnt belong in the core, you might still think about releasing it as a plugin.
And then...think about your next contribution!
h3. Changelog
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/64
* March 2, 2009: Initial draft by "Mike Gunderloy":credits.html#mgunderloy

View file

@ -5,6 +5,28 @@ p. We'd like to thank the following people for their tireless contributions to t
<% end %>
<h3 class="section">Rails Documentation Team</h3>
<% author('Mike Gunderloy', 'mgunderloy') do %>
Mike Gunderloy is a consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails activism team":http://rubyonrails.org/activists . He brings 25 years of experience in a variety of languages to bear on his current work with Rails. His near-daily links and other blogging can be found at "A Fresh Cup":http://afreshcup.com and he "twitters":http://twitter.com/MikeG1 too much.
<% end %>
<% author('Pratik Naik', 'lifo') do %>
Pratik Naik is a Ruby on Rails consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails core team":http://rubyonrails.org/core. He maintains a blog at "has_many :bugs, :through => :rails":http://m.onkey.org and has an active "twitter account":http://twitter.com/lifo.
<% end %>
<% author('Xavier Noria', 'fxn', 'fxn.jpg') do %>
Xavier Noria has been around dynamic languages since 2000. He fell in love with Rails in 2005, and cofounded Rails-based software company <a href="http://www.aspgems.com">ASPgems</a> in mid-2006. Xavier is president of the <a href="http://www.srug.org/">Spanish Ruby Users Group</a> and has been involved in Rails in several ways. He enjoys combining his passion for Rails and his past life as a proofreader of math textbooks. Oh, he also "tweets":http://twitter.com/fxn!
<% end %>
<h3 class="section">Rails Guides Designers</h3>
<% author('Jason Zimdars', 'jz') do %>
Jason Zimdars is an experienced creative director and web designer who has lead UI and UX design for numerous websites and web applications. You can see more of his design and writing at <a href="http://www.thinkcage.com/">Thinkcage.com</a> or follow him on <a href="http://twitter.com/JZ">Twitter</a>.
<% end %>
<h3 class="section">Rails Guides Authors</h3>
<% author('Frederick Cheung', 'fcheung') do %>
Frederick Cheung is Chief Wizard at Texperts where he has been using Rails since 2006. He is based in Cambridge (UK) and when not consuming fine ales he blogs at "spacevatican.org":http://www.spacevatican.org.
<% end %>
@ -17,23 +39,14 @@ p. We'd like to thank the following people for their tireless contributions to t
Jeff Dean is a software engineer with "Pivotal Labs":http://pivotallabs.com.
<% end %>
<% author('Mike Gunderloy', 'mgunderloy') do %>
Mike Gunderloy is a consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails activism team":http://rubyonrails.org/activists . He brings 25 years of experience in a variety of languages to bear on his current work with Rails. His near-daily links and other blogging can be found at "A Fresh Cup":http://afreshcup.com and he "twitters":http://twitter.com/MikeG1 too much.
<% end %>
<% author('Cássio Marques', 'cmarques') do %>
Cássio Marques is a Brazilian software developer working with different programming languages such as Ruby, JavaScript, CPP and Java, as an independent consultant. He blogs at "/* CODIFICANDO */":http://cassiomarques.wordpress.com, which is mainly written in Portuguese, but will soon get a new section for posts with English translation.
<% end %>
<% author('Pratik Naik', 'lifo') do %>
Pratik Naik is a Ruby on Rails consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails core team":http://rubyonrails.com/core. He maintains a blog at "has_many :bugs, :through => :rails":http://m.onkey.org and has an active "twitter account":http://twitter.com/lifo.
<% author('Emilio Tagua', 'miloops') do %>
Emilio Tagua -- a.k.a. miloops -- is an Argentinian entrepreneur, developer, open source contributor and Rails evangelist. Cofounder of "Eventioz":http://www.eventioz.com. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as miloops.
Emilio Tagua -- a.k.a. miloops -- is an Argentinian entrepreneur, developer, open source contributor and Rails evangelist. Cofounder of "Eventioz":http://eventioz.com. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as miloops.
<% end %>
<% author('Heiko Webers', 'hawe') do %>
Heiko Webers is the founder of "bauland42":http://www.bauland42.de, a German web application security consulting and development company focused on Ruby on Rails. He blogs at the "Ruby on Rails Security Project":http://www.rorsecurity.info. After 10 years of desktop application development, Heiko has rarely looked back.
<% end %>
<% end %>

View file

@ -17,7 +17,7 @@ One common task is to inspect the contents of a variable. In Rails, you can do t
* +to_yaml+
* +inspect+
h4. debug
h4. +debug+
The +debug+ helper will return a &lt;pre&gt;-tag that renders the object using the YAML format. This will generate human-readable data from any object. For example, if you have this code in a view:
@ -46,7 +46,7 @@ attributes_cache: {}
Title: Rails debugging guide
</yaml>
h4. to_yaml
h4. +to_yaml+
Displaying an instance variable, or any other object or method, in yaml format can be achieved this way:
@ -76,7 +76,7 @@ attributes_cache: {}
Title: Rails debugging guide
</yaml>
h4. inspect
h4. +inspect+
Another useful method for displaying object values is +inspect+, especially when working with arrays or hashes. This will print the object value as a string. For example:
@ -96,7 +96,7 @@ Will be rendered as follows:
Title: Rails debugging guide
</pre>
h4. Debugging Javascript
h4. Debugging JavaScript
Rails has built-in support to debug RJS, to active it, set +ActionView::Base.debug_rjs+ to _true_, this will specify whether RJS responses should be wrapped in a try/catch block that alert()s the caught exception (and then re-raises it).
@ -118,7 +118,7 @@ h3. The Logger
It can also be useful to save information to log files at runtime. Rails maintains a separate log file for each runtime environment.
h4. What is The Logger?
h4. What is the Logger?
Rails makes use of Ruby's standard +logger+ to write log information. You can also substitute another logger such as +Log4R+ if you wish.
@ -209,7 +209,7 @@ Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localh
Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels, to avoid filling your production logs with useless trivia.
h3. Debugging with ruby-debug
h3. Debugging with +ruby-debug+
When your code is behaving in unexpected ways, you can try printing to logs or the console to diagnose the problem. Unfortunately, there are times when this sort of error tracking is not effective in finding the root cause of a problem. When you actually need to journey into your running source code, the debugger is your best companion.

View file

@ -1,4 +1,4 @@
h2. Rails form helpers
h2. Rails Form helpers
Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of form control naming and their numerous attributes. Rails deals away with these complexities by providing view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use.
@ -16,7 +16,7 @@ endprologue.
NOTE: This guide is not intended to be a complete documentation of available form helpers and their arguments. Please visit "the Rails API documentation":http://api.rubyonrails.org/ for a complete reference.
h3. Dealing With Basic Forms
h3. Dealing with Basic Forms
The most basic form helper is +form_tag+.
@ -43,7 +43,7 @@ If you carefully observe this output, you can see that the helper generated some
NOTE: Throughout this guide, this +div+ with the hidden input will be stripped away to have clearer code samples.
h4. A Generic search form
h4. A Generic Search Form
Probably the most minimal form often seen on the web is a search form with a single text input for search terms. This form consists of:
@ -82,7 +82,7 @@ Besides +text_field_tag+ and +submit_tag+, there is a similar helper for _every_
TIP: For every form input, an ID attribute is generated from its name ("q" in the example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript.
h4. Multiple hashes in form helper calls
h4. Multiple Hashes in Form Helper Calls
By now you've seen that the +form_tag+ helper accepts 2 arguments: the path for the action and an options hash. This hash specifies the method of form submission and HTML options such as the form element's class.
@ -104,7 +104,7 @@ This is a common pitfall when using form helpers, since many of them accept mult
WARNING: Do not delimit the second hash without doing so with the first hash, otherwise your method invocation will result in an +expecting tASSOC+ syntax error.
h4. Helpers for generating form elements
h4. Helpers for Generating Form Elements
Rails provides a series of helpers for generating form elements such as checkboxes, text fields, radio buttons, and so on. These basic helpers, with names ending in <notextile>_tag</notextile> such as +text_field_tag+, +check_box_tag+, etc., generate just a single +&lt;input&gt;+ element. The first parameter to these is always the name of the input. In the controller this name will be the key in the +params+ hash used to get the value entered by the user. For example, if the form contains
@ -140,7 +140,7 @@ output:
The second parameter to +check_box_tag+ is the value of the input. This is the value that will be submitted by the browser if the checkbox is ticked (i.e. the value that will be present in the +params+ hash). With the above form you would check the value of +params[:pet_dog]+ and +params[:pet_cat]+ to see which pets the user owns.
h5. Radio buttons
h5. Radio Buttons
Radio buttons, while similar to checkboxes, are controls that specify a set of options in which they are mutually exclusive (i.e. the user can only pick one):
@ -162,7 +162,7 @@ As with +check_box_tag+ the second parameter to +radio_button_tag+ is the value
IMPORTANT: Always use labels for each checkbox and radio button. They associate text with a specific option and provide a larger clickable region.
h4. Other helpers of interest
h4. Other Helpers of Interest
Other form controls worth mentioning are the text area, password input and hidden input:
@ -183,9 +183,9 @@ Hidden inputs are not shown to the user, but they hold data like any textual inp
TIP: If you're using password input fields (for any purpose), you might want to prevent their values showing up in application logs by activating +filter_parameter_logging(:password)+ in your ApplicationController.
h3. Dealing With Model Objects
h3. Dealing with Model Objects
h4. Model object helpers
h4. Model Object Helpers
A particularly common task for a form is editing or creating a model object. While the +*_tag+ helpers can certainly be used for this task they are somewhat verbose as for each tag you would have to ensure the correct parameter name is used and set the default value of the input appropriately. Rails provides helpers tailored to this task. These helpers lack the <notextile>_tag</notextile> suffix, for example +text_field+, +text_area+.
@ -207,7 +207,7 @@ WARNING: You must pass the name of an instance variable, i.e. +:person+ or +"per
Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the "Active Record Validations and Callbacks":./activerecord_validations_callbacks.html#_using_the_tt_errors_tt_collection_in_your_view_templates guide.
h4. Binding a form to an object
h4. Binding a Form to an Object
While this is an increase in comfort it is far from perfect. If Person has many attributes to edit then we would be repeating the name of the edited object many times. What we want to do is somehow bind a form to a model object, which is exactly what +form_for+ does.
@ -276,7 +276,7 @@ which produces the following output:
The object yielded by +fields_for+ is a form builder like the one yielded by +form_for+ (in fact +form_for+ calls +fields_for+ internally).
h4. Relying on record identification
h4. Relying on Record Identification
The Article model is directly available to users of the application, so -- following the best practices for developing with Rails -- you should declare it *a resource*.
@ -302,7 +302,7 @@ Rails will also automatically set the +class+ and +id+ of the form appropriately
WARNING: When you're using STI (single-table inheritance) with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify the model name, +:url+, and +:method+ explicitly.
h5. Dealing with namespaces
h5. Dealing with Namespaces
If you have created namespaced routes, +form_for+ has a nifty shorthand for that too. If your application has an admin namespace then
@ -343,7 +343,7 @@ output:
When parsing POSTed data, Rails will take into account the special +_method+ parameter and acts as if the HTTP method was the one specified inside it ("PUT" in this example).
h3. Making select boxes with ease
h3. Making Select Boxes with Ease
Select boxes in HTML require a significant amount of markup (one +OPTION+ element for each option to choose from), therefore it makes the most sense for them to be dynamically generated.
@ -360,7 +360,7 @@ Here is what the markup might look like:
Here you have a list of cities whose names are presented to the user. Internally the application only wants to handle their IDs so they are used as the options' value attribute. Let's see how Rails can help out here.
h4. The select and options tag
h4. The Select and Option Tags
The most generic helper is +select_tag+, which -- as the name implies -- simply generates the +SELECT+ tag that encapsulates an options string:
@ -404,7 +404,7 @@ Whenever Rails sees that the internal value of an option being generated matches
TIP: The second argument to +options_for_select+ must be exactly equal to the desired internal value. In particular if the value is the integer 2 you cannot pass "2" to +options_for_select+ -- you must pass 2. Be aware of values extracted from the +params+ hash as they are all strings.
h4. Select boxes for dealing with models
h4. Select Boxes for Dealing with Models
In most cases form controls will be tied to a specific database model and as you might expect Rails provides helpers tailored for that purpose. Consistent with other form helpers, when dealing with models you drop the +_tag+ suffix from +select_tag+:
@ -429,7 +429,7 @@ As with other helpers, if you were to use the +select+ helper on a form builder
WARNING: If you are using +select+ (or similar helpers such as +collection_select+, +select_tag+) to set a +belongs_to+ association you must pass the name of the foreign key (in the example above +city_id+), not the name of association itself. If you specify +city+ instead of +city_id+ Active Record will raise an error along the lines of <pre> ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) </pre> when you pass the +params+ hash to +Person.new+ or +update_attributes+. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. You may wish to consider the use of +attr_protected+ and +attr_accessible+. For further details on this, see the "Ruby On Rails Security Guide":security.html#_mass_assignment.
h4. Option tags from a collection of arbitrary objects
h4. Option Tags from a Collection of Arbitrary Objects
Generating options tags with +options_for_select+ requires that you create an array containing the text and value for each option. But what if you had a City model (perhaps an Active Record one) and you wanted to generate option tags from a collection of those objects? One solution would be to make a nested array by iterating over them:
@ -454,7 +454,7 @@ To recap, +options_from_collection_for_select+ is to +collection_select+ what +o
NOTE: Pairs passed to +options_for_select+ should have the name first and the id second, however with +options_from_collection_for_select+ the first argument is the value method and the second the text method.
h4. Time zone and country select
h4. Time Zone and Country Select
To leverage time zone support in Rails, you have to ask your users what time zone they are in. Doing so would require generating select options from a list of pre-defined TimeZone objects using +collection_select+, but you can simply use the +time_zone_select+ helper that already wraps this:
@ -475,7 +475,7 @@ The date and time helpers differ from all the other form helpers in two importan
Both of these families of helpers will create a series of select boxes for the different components (year, month, day etc.).
h4. Barebones helpers
h4. Barebones Helpers
The +select_*+ family of helpers take as their first argument an instance of Date, Time or DateTime that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example
@ -499,7 +499,7 @@ Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, pa
The +:prefix+ option is the key used to retrieve the hash of date components from the +params+ hash. Here it was set to +start_date+, if omitted it will default to +date+.
h4. Model object helpers
h4. Model Object Helpers
+select_date+ does not work well with forms that update or create Active Record objects as Active Record expects each element of the +params+ hash to correspond to one attribute.
The model object helpers for dates and times submit parameters with special names, when Active Record sees parameters with such names it knows they must be combined with the other parameters and given to a constructor appropriate to the column type. For example:
@ -524,7 +524,7 @@ which results in a +params+ hash like
When this is passed to +Person.new+ (or +update_attributes+), Active Record spots that these parameters should all be used to construct the +birth_date+ attribute and uses the suffixed information to determine in which order it should pass these parameters to functions such as +Date.civil+.
h4. Common options
h4. Common Options
Both families of helpers use the same core set of functions to generate the individual select tags and so both accept largely the same options. In particular, by default Rails will generate year options 5 years either side of the current year. If this is not an appropriate range, the +:start_year+ and +:end_year+ options override this. For an exhaustive list of the available options, refer to the "API documentation":http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html.
@ -532,7 +532,7 @@ As a rule of thumb you should be using +date_select+ when working with model obj
NOTE: In many cases the built-in date pickers are clumsy as they do not aid the user in working out the relationship between the date and the day of the week.
h4. Individual components
h4. Individual Components
Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component +select_year+, +select_month+, +select_day+, +select_hour+, +select_minute+, +select_second+. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for +select_year+, "month" for +select_month+ etc.) although this can be overriden with the +:field_name+ option. The +:prefix+ option works in the same way that it does for +select_date+ and +select_time+ and has the same default value.
@ -563,7 +563,7 @@ The following two forms both upload a file.
Rails provides the usual pair of helpers: the barebones +file_field_tag+ and the model oriented +file_field+. The only difference with other helpers is that you cannot set a default value for file inputs as this would have no meaning. As you would expect in the first case the uploaded file is in +params[:picture]+ and in the second case in +params[:person][:picture]+.
h4. What gets uploaded
h4. What Gets Uploaded
The object in the +params+ hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an +original_filename+ attribute containing the name the file had on the user's computer and a +content_type+ attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in +#{Rails.root}/public/uploads+ under the same name as the original file (assuming the form was the one in the previous example).
@ -605,9 +605,9 @@ can be replaced with
by defining a LabellingFormBuilder class similar to the following:
<ruby>
class LabellingFormBuilder < FormBuilder
class LabellingFormBuilder < ActionView::Helpers::FormBuilder
def text_field(attribute, options={})
label(attribute) + text_field(attribute, options)
label(attribute) + super
end
end
</ruby>
@ -631,7 +631,7 @@ Fundamentally HTML forms don't know about any sort of structured data, all they
TIP: You may find you can try out examples in this section faster by using the console to directly invoke Rails' parameter parser. For example <pre> ActionController::UrlEncodedPairParser.parse_query_parameters "name=fred&phone=0123456789" # => {"name"=>"fred", "phone"=>"0123456789"} </pre>
h4. Basic structures
h4. Basic Structures
The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in +params+. For example if a form contains
@ -669,7 +669,7 @@ Normally Rails ignores duplicate parameter names. If the parameter name contains
This would result in +params[:person][:phone_number]+ being an array.
h4. Combining them
h4. Combining Them
We can mix and match these two concepts. For example, one element of a hash might be an array as in the previous example, or you can have an array of hashes. For example a form might let you create any number of addresses by repeating the following form fragment
@ -685,7 +685,7 @@ There's a restriction, however, while hashes can be nested arbitrarily, only one
WARNING: Array parameters do not play well with the +check_box+ helper. According to the HTML specification unchecked checkboxes submit no value. However it is often convenient for a checkbox to always submit a value. The +check_box+ helper fakes this by creating a second hidden input with the same name. If the checkbox is unchecked only the hidden input is submitted and if it is checked then both are submitted but the value submitted by the checkbox takes precedence. When working with array parameters this duplicate submission will confuse Rails since duplicate input names are how it decides when to start a new array element. It is preferable to either use +check_box_tag+ or to use hashes instead of arrays.
h4. Using form helpers
h4. Using Form Helpers
The previous sections did not use the Rails form helpers at all. While you can craft the input names yourself and pass them directly to helpers such as +text_field_tag+ Rails also provides higher level support. The two tools at your disposal here are the name parameter to +form_for+ and +fields_for+ and the +:index+ option that helpers take.
@ -746,7 +746,7 @@ As a shortcut you can append [] to the name and omit the +:index+ option. This i
produces exactly the same output as the previous example.
h3. Building Complex forms
h3. Building Complex Forms
Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. While this guide has shown you all the pieces necessary to handle this, Rails does not yet have a standard end-to-end way of accomplishing this, but many have come up with viable approaches. These include:

View file

@ -1,4 +1,4 @@
h2. Getting Started With Rails
h2. Getting Started with Rails
This guide covers getting up and running with Ruby on Rails. After reading it, you should be familiar with:
@ -23,7 +23,7 @@ It is highly recommended that you *familiarize yourself with Ruby before diving
* "Mr. Neighborlys Humble Little Ruby Book":http://www.humblelittlerubybook.com
* "Programming Ruby":http://www.rubycentral.com/book
* "Whys (Poignant) Guide to Ruby":http://poignantguide.net/ruby
* "Whys (Poignant) Guide to Ruby":http://poignantguide.net/ruby/
h3. What is Rails?
@ -115,6 +115,7 @@ If youd like more details on REST as an architectural style, these resources
* "A Brief Introduction to REST":http://www.infoq.com/articles/rest-introduction by Stefan Tilkov
* "An Introduction to REST":http://bitworking.org/news/373/An-Introduction-to-REST (video tutorial) by Joe Gregorio
* "Representational State Transfer":http://en.wikipedia.org/wiki/Representational_State_Transfer article in Wikipedia
* "How to GET a Cup of Coffee":http://www.infoq.com/articles/webber-rest-workflow by Jim Webber, Savas Parastatidis & Ian Robinson
h3. Creating a New Rails Project
@ -174,7 +175,7 @@ In any case, Rails will create a folder in your working directory called <tt>blo
|log/|Application log files.|
|public/|The only folder seen to the world as-is. This is where your images, javascript, stylesheets (CSS), and other static files go.|
|script/|Scripts provided by Rails to do recurring tasks, such as benchmarking, plugin installation, and starting the console or the web server.|
|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing_rails_applications.html|
|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html|
|tmp/|Temporary files|
|vendor/|A place for third-party code. In a typical Rails application, this includes Ruby Gems, the Rails source code (if you install it into your project) and plugins containing additional prepackaged functionality.|
@ -309,7 +310,7 @@ This line illustrates one tiny bit of the "convention over configuration" approa
Now if you navigate to +http://localhost:3000+ in your browser, you'll see the +home/index+ view.
NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing_outside_in.html.
NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing.html.
h3. Getting Up and Running Quickly With Scaffolding
@ -471,7 +472,7 @@ end
This code sets the +@posts+ instance variable to an array of all posts in the database. +Post.find(:all)+ or +Post.all+ calls the +Post+ model to return all of the posts that are currently in the database, with no limiting conditions.
TIP: For more information on finding records with Active Record, see "Active Record Finders":finders.html.
TIP: For more information on finding records with Active Record, see "Active Record Query Interface":active_record_querying.html.
The +respond_to+ block handles both HTML and XML calls to this action. If you browse to +http://localhost:3000/posts.xml+, you'll see all of the posts in XML format. The HTML format looks for a view in +app/views/posts/+ with a name that corresponds to the action name. Rails makes all of the instance variables from the action available to the view. Here's +app/view/posts/index.html.erb+:
@ -845,7 +846,7 @@ end
Rails runs _before filters_ before any action in the controller. You can use the +:only+ clause to limit a before filter to only certain actions, or an +:except+ clause to specifically skip a before filter for certain actions. Rails also allows you to define _after filters_ that run after processing an action, as well as _around filters_ that surround the processing of actions. Filters can also be defined in external classes to make it easy to share them between controllers.
For more information on filters, see the "Action Controller Basics":actioncontroller_basics.html guide.
For more information on filters, see the "Action Controller Overview":action_controller_overview.html guide.
h3. Adding a Second Model

View file

@ -12,37 +12,37 @@ So, in the process of _internationalizing_ your Rails application you have to:
In the process of _localizing_ your application you'll probably want to do following three things:
* Replace or supplement Rails' default locale -- eg. date and time formats, month names, ActiveRecord model names, etc
* Abstract texts in your application into keyed dictionaries -- eg. flash messages, static texts in your views, etc
* Replace or supplement Rails' default locale -- e.g. date and time formats, month names, Active Record model names, etc
* Abstract strings in your application into keyed dictionaries -- e.g. flash messages, static text in your views, etc.
* Store the resulting dictionaries somewhere
This guide will walk you through the I18n API and contains a tutorial how to internationalize a Rails application from the start.
endprologue.
NOTE: The Ruby I18n framework provides you with all neccessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See Rails "I18n Wiki":http://rails-i18n.org/wiki for more information.
NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See the Rails "I18n Wiki":http://rails-i18n.org/wiki for more information.
h3. How I18n in Ruby on Rails works
h3. How I18n in Ruby on Rails Works
Internationalization is a complex problem. Natural languages differ in so many ways (eg. in pluralization rules) that it is hard to provide tools for solving all problems at once. For that reason the Rails I18n API focuses on:
Internationalization is a complex problem. Natural languages differ in so many ways (e.g. in pluralization rules) that it is hard to provide tools for solving all problems at once. For that reason the Rails I18n API focuses on:
* providing support for English and similar languages out of the box
* making it easy to customize and extend everything for other languages
As part of this solution, *every static string in the Rails framework* -- eg. Active Record validation messages, time and date formats -- *has been internationalized*, so _localization_ of a Rails application means "over-riding" these defaults.
As part of this solution, *every static string in the Rails framework* -- e.g. Active Record validation messages, time and date formats -- *has been internationalized*, so _localization_ of a Rails application means "over-riding" these defaults.
h4. The overall architecture of the library
h4. The Overall Architecture of the Library
Thus, the Ruby I18n gem is split into two parts:
* The public API of the i18n framework -- a Ruby module with public methods and definitions how the library works
* The public API of the i18n framework -- a Ruby module with public methods that define how the library works
* A default backend (which is intentionally named _Simple_ backend) that implements these methods
As a user you should always only access the public methods on the I18n module, but it is useful to know about the capabilities of the backend.
NOTE: It is possible (or even desirable) to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section "Using different backends":#usingdifferentbackends below.
NOTE: It is possible (or even desirable) to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section "Using different backends":#using-different-backends below.
h4. The public I18n API
h4. The Public I18n API
The most important methods of the I18n API are:
@ -70,34 +70,34 @@ backend # Use a different backend
So, let's internationalize a simple Rails application from the ground up in the next chapters!
h3. Setup the Rails application for internationalization
h3. Setup the Rails Application for Internationalization
There are just a few, simple steps to get up and running with I18n support for your application.
There are just a few simple steps to get up and running with I18n support for your application.
h4. Configure the I18n module
h4. Configure the I18n Module
Following the _convention over configuration_ philosophy, Rails will set-up your application with reasonable defaults. If you need different settings, you can overwrite them easily.
Following the _convention over configuration_ philosophy, Rails will set up your application with reasonable defaults. If you need different settings, you can overwrite them easily.
Rails adds all +.rb+ and +.yml+ files from +config/locales+ directory to your *translations load path*, automatically.
Rails adds all +.rb+ and +.yml+ files from the +config/locales+ directory to your *translations load path*, automatically.
See the default +en.yml+ locale in this directory, containing a sample pair of translation strings:
The default +en.yml+ locale in this directory contains a sample pair of translation strings:
<ruby>
en:
hello: "Hello world"
</ruby>
This means, that in the +:en+ locale, the key _hello_ will map to _Hello world_ string. Every string inside Rails is internationalized in this way, see for instance Active Record validation messages in the "+activerecord/lib/active_record/locale/en.yml+":http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml file or time and date formats in the "+activesupport/lib/active_support/locale/en.yml+":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend.
This means, that in the +:en+ locale, the key _hello_ will map to the _Hello world_ string. Every string inside Rails is internationalized in this way, see for instance Active Record validation messages in the "+activerecord/lib/active_record/locale/en.yml+":http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml file or time and date formats in the "+activesupport/lib/active_support/locale/en.yml+":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend.
The I18n library will use *English* as a *default locale*, ie. if you don't set a different locale, +:en+ will be used for looking up translations.
The I18n library will use *English* as a *default locale*, i.e. if you don't set a different locale, +:en+ will be used for looking up translations.
NOTE: The i18n library takes *pragmatic approach* to locale keys (after "some discussion":http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like +:en+, +:pl+, not the _region_ part, like +:en-US+ or +:en-UK+, which are traditionally used for separating "languages" and "regional setting" or "dialects". (For instance, in the +:en-US+ locale you would have $ as a currency symbol, while in +:en-UK+, you would have £. Also, insults would be different in American and British English :) Reason for this pragmatic approach is that most of the time, you usually care about making your application available in different "languages", and working with locales is much simpler this way. However, nothing stops you from separating regional and other settings in the traditional way. In this case, you could eg. inherit from the default +en+ locale and then provide UK specific settings in a +:en-UK+ dictionary.
NOTE: The i18n library takes a *pragmatic approach* to locale keys (after "some discussion":http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like +:en+, +:pl+, not the _region_ part, like +:en-US+ or +:en-UK+, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as +:cz+, +:th+ or +:es+ (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the +:en-US+ locale you would have $ as a currency symbol, while in +:en-UK+, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a +:en-UK+ dictionary. Various "Rails I18n plugins":http://rails-i18n.org/wiki such as "Globalize2":http://github.com/joshmh/globalize2/tree/master may help you implement it.
The *translations load path* (+I18n.load_path+) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you.
NOTE: The backend will lazy-load these translations when a translation is looked up for the first time. This makes it possible to just swap the backend with something else even after translations have already been announced.
The default +environment.rb+ files has instruction how to add locales from another directory and how to set different default locale. Just uncomment and edit the specific lines.
The default +environment.rb+ files has instruction how to add locales from another directory and how to set a different default locale. Just uncomment and edit the specific lines.
<ruby>
# The internationalization framework can be changed
@ -107,31 +107,32 @@ The default +environment.rb+ files has instruction how to add locales from anoth
# config.i18n.default_locale = :de
</ruby>
h4. Optional: custom I18n configuration setup
h4. Optional: Custom I18n Configuration Setup
For the sake of completeness, let's mention that if you do not want to use the +environment.rb+ file for some reason, you can always wire up things manually, too.
To tell the I18n library where it can find your custom translation files you can specify the load path anywhere in your application - just make sure it gets run before any translations are actually looked up. You might also want to change the default locale. The simplest thing possible is to put the following into an *initializer*:
To tell the I18n library where it can find your custom translation files you can specify the load path anywhere in your application - just make sure it gets run before any translations are actually looked up. You might also want to change the default locale. The simplest thing possible is to put the following into an initializer:
<ruby>
# in config/initializer/locale.rb
# tell the I18n library where to find your translations
I18n.load_path << Dir[ File.join(RAILS_ROOT, 'lib', 'locale', '*.{rb,yml}') ]
I18n.load_path << Dir[ File.join(RAILS_ROOT, 'lib', 'locale',
'*.{rb,yml}') ]
# set default locale to something else then :en
# set default locale to something other than :en
I18n.default_locale = :pt
</ruby>
h4. Setting and passing the locale
h4. Setting and Passing the Locale
If you want to translate your Rails application to a *single language other than English* (the default locale), you can set I18n.default_locale to your locale in +environment.rb+ or an initializer as shown above, and it will persist through the requests.
However, you would probably like to *provide support for more locales* in your application. In such case, you need to set and pass the locale between requests.
WARNING: You may be tempted to store choosed locale in a _session_ or a _cookie_. *Do not do so*. The locale should be transparent and a part of the URL. This way you don't break people's basic assumptions about the web itself: if you send a URL of some page to a friend, she should see the same page, same content. A fancy word for this would be that you're being "_RESTful_":http://en.wikipedia.org/wiki/Representational_State_Transfer. Read more about RESTful approach in "Stefan Tilkov's articles":http://www.infoq.com/articles/rest-introduction. There may be some exceptions to this rule, which are discussed below.
WARNING: You may be tempted to store the chosen locale in a _session_ or a _cookie_. *Do not do so*. The locale should be transparent and a part of the URL. This way you don't break people's basic assumptions about the web itself: if you send a URL of some page to a friend, she should see the same page, same content. A fancy word for this would be that you're being "_RESTful_":http://en.wikipedia.org/wiki/Representational_State_Transfer. Read more about the RESTful approach in "Stefan Tilkov's articles":http://www.infoq.com/articles/rest-introduction. There may be some exceptions to this rule, which are discussed below.
The _setting part_ is easy. You can set locale in a +before_filter+ in the ApplicationController like this:
The _setting part_ is easy. You can set the locale in a +before_filter+ in the ApplicationController like this:
<ruby>
before_filter :set_locale
@ -141,13 +142,13 @@ def set_locale
end
</ruby>
This requires you to pass the locale as a URL query parameter as in +http://example.com/books?locale=pt+. (This is eg. Google's approach). So +http://localhost:3000?locale=pt+ will load the Portugese localization, whereas +http://localhost:3000?locale=de+ would load the German localization, and so on. You may skip the next section and head over to the *Internationalize your application* section, if you want to try things out by manually placing locale in the URL and reloading the page.
This requires you to pass the locale as a URL query parameter as in +http://example.com/books?locale=pt+. (This is, for example, Google's approach.) So +http://localhost:3000?locale=pt+ will load the Portugese localization, whereas +http://localhost:3000?locale=de+ would load the German localization, and so on. You may skip the next section and head over to the *Internationalize your application* section, if you want to try things out by manually placing the locale in the URL and reloading the page.
Of course, you probably don't want to manually include locale in every URL all over your application, or want the URLs look differently, eg. the usual +http://example.com/pt/books+ versus +http://example.com/en/books+. Let's discuss the different options you have.
Of course, you probably don't want to manually include the locale in every URL all over your application, or want the URLs look differently, e.g. the usual +http://example.com/pt/books+ versus +http://example.com/en/books+. Let's discuss the different options you have.
IMPORTANT: Following examples rely on having locales loaded into your application available as an array of strings like +["en", "es", "gr"]+. This is not inclued in current version of Rails 2.2 -- forthcoming Rails version 2.3 will contain easy accesor +available_locales+. (See "this commit":http://github.com/svenfuchs/i18n/commit/411f8fe7 and background at "Rails I18n Wiki":http://rails-i18n.org/wiki/pages/i18n-available_locales.)
IMPORTANT: The following examples rely on having available locales loaded into your application as an array of strings like +["en", "es", "gr"]+. This is not included in the current version of Rails 2.2 -- the forthcoming Rails version 2.3 will contain the easy accessor +available_locales+. (See "this commit":http://github.com/svenfuchs/i18n/commit/411f8fe7c8f3f89e9b6b921fa62ed66cb92f3af4 and background at "Rails I18n Wiki":http://rails-i18n.org/wiki/pages/i18n-available_locales.)
So, for having available locales easily available in Rails 2.2, we have to include this support manually in an initializer, like this:
So, for having available locales easily accessible in Rails 2.2, we have to include this support manually in an initializer, like this:
<ruby>
# config/initializers/available_locales.rb
@ -180,11 +181,11 @@ class ApplicationController < ActionController::Base
end
</ruby>
h4. Setting locale from the domain name
h4. Setting the Locale from the Domain Name
One option you have is to set the locale from the domain name where your application runs. For example, we want +www.example.com+ to load English (or default) locale, and +www.example.es+ to load Spanish locale. Thus the _top-level domain name_ is used for locale setting. This has several advantages:
One option you have is to set the locale from the domain name where your application runs. For example, we want +www.example.com+ to load the English (or default) locale, and +www.example.es+ to load the Spanish locale. Thus the _top-level domain name_ is used for locale setting. This has several advantages:
* Locale is an _obvious_ part of the URL
* The locale is an _obvious_ part of the URL
* People intuitively grasp in which language the content will be displayed
* It is very trivial to implement in Rails
* Search engines seem to like that content in different languages lives at different, inter-linked domains
@ -208,7 +209,7 @@ def extract_locale_from_tld
end
</ruby>
We can also set the locale from the _subdomain_ in very similar way:
We can also set the locale from the _subdomain_ in a very similar way:
<ruby>
# Get locale code from request subdomain (like http://it.application.local:3000)
@ -231,15 +232,15 @@ assuming you would set +APP_CONFIG[:deutsch_website_url]+ to some value like +ht
This solution has aforementioned advantages, however, you may not be able or may not want to provide different localizations ("language versions") on different domains. The most obvious solution would be to include locale code in the URL params (or request path).
h4. Setting locale from the URL params
h4. Setting the Locale from the URL Params
Most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the +I18n.locale = params[:locale]+ _before_filter_ in the first example. We would like to have URLs like +www.example.com/books?locale=ja+ or +www.example.com/ja/books+ in this case.
The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the +I18n.locale = params[:locale]+ _before_filter_ in the first example. We would like to have URLs like +www.example.com/books?locale=ja+ or +www.example.com/ja/books+ in this case.
This approach has almost the same set of advantages as setting the locale from domain name: namely that it's RESTful and in accord with rest of the World Wide Web. It does require a little bit more work to implement, though.
This approach has almost the same set of advantages as setting the locale from the domain name: namely that it's RESTful and in accord with the rest of the World Wide Web. It does require a little bit more work to implement, though.
Getting the locale from +params+ and setting it accordingly is not hard; including it in every URL and thus *passing it through the requests* is. To include an explicit option in every URL (eg. +link_to( books_url(:locale => I18n.locale) )+) would be tedious and probably impossible, of course.
Getting the locale from +params+ and setting it accordingly is not hard; including it in every URL and thus *passing it through the requests* is. To include an explicit option in every URL (e.g. +link_to( books_url(:locale => I18n.locale))+) would be tedious and probably impossible, of course.
Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its "+*ApplicationController#default_url_options*+":http://api.rubyonrails.org/classes/ActionController/Base.html#M000515, which is useful precisely in this scenario: it enables us to set "defaults" for "+url_for+":http://api.rubyonrails.org/classes/ActionController/Base.html#M000503 and helper methods dependent on it (by implementing/overriding this method).
Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its "+ApplicationController#default_url_options+":http://api.rubyonrails.org/classes/ActionController/Base.html#M000515, which is useful precisely in this scenario: it enables us to set "defaults" for "+url_for+":http://api.rubyonrails.org/classes/ActionController/Base.html#M000503 and helper methods dependent on it (by implementing/overriding this method).
We can include something like this in our ApplicationController then:
@ -251,20 +252,20 @@ def default_url_options(options={})
end
</ruby>
Every helper method dependent on +url_for+ (eg. helpers for named routes like +root_path+ or +root_url+, resource routes like +books_path+ or +books_url+, etc.) will now *automatically include the locale in the query string*, like this: +http://localhost:3001/?locale=ja+.
Every helper method dependent on +url_for+ (e.g. helpers for named routes like +root_path+ or +root_url+, resource routes like +books_path+ or +books_url+, etc.) will now *automatically include the locale in the query string*, like this: +http://localhost:3001/?locale=ja+.
You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of application domain: and URLs should reflect this.
You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of the application domain: and URLs should reflect this.
You probably want URLs look like this: +www.example.com/en/books+ (which loads English locale) and +www.example.com/nl/books+ (which loads Netherlands locale). This is achievable with the "over-riding +default_url_options+" strategy from above: you just have to set up your routes with "+path_prefix+":http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354 option in this way:
You probably want URLs to look like this: +www.example.com/en/books+ (which loads the English locale) and +www.example.com/nl/books+ (which loads the Netherlands locale). This is achievable with the "over-riding +default_url_options+" strategy from above: you just have to set up your routes with "+path_prefix+":http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354 option in this way:
<ruby>
# config/routes.rb
map.resources :books, :path_prefix => '/:locale'
</ruby>
Now, when you call +books_path+ method you should get +"/en/books"+ (for the default locale). An URL like +http://localhost:3001/nl/books+ should load the Netherlands locale, then, and following calls to +books_path+ should return +"/nl/books"+ (because the locale changed).
Now, when you call the +books_path+ method you should get +"/en/books"+ (for the default locale). An URL like +http://localhost:3001/nl/books+ should load the Netherlands locale, then, and following calls to +books_path+ should return +"/nl/books"+ (because the locale changed).
Of course, you need to take special care of root URL (usually "homepage" or "dashboard") of your application. An URL like +http://localhost:3001/nl+ will not work automatically, because the +map.root :controller => "dashboard"+ declaration in your +routes.rb+ doesn't take locale into account. (And rightly so. There's only one "root" URL.)
Of course, you need to take special care of the root URL (usually "homepage" or "dashboard") of your application. An URL like +http://localhost:3001/nl+ will not work automatically, because the +map.root :controller => "dashboard"+ declaration in your +routes.rb+ doesn't take locale into account. (And rightly so: there's only one "root" URL.)
You would probably need to map URLs like these:
@ -275,18 +276,18 @@ map.dashboard '/:locale', :controller => "dashboard"
Do take special care about the *order of your routes*, so this route declaration does not "eat" other ones. (You may want to add it directly before the +map.root+ declaration.)
IMPORTANT: This solution has currently one rather big *downside*. Due to the _default_url_options_ implementation, you have to pass the +:id+ option explicitely, like this: +link_to 'Show', book_url(:id => book)+ and not depend on Rails' magic in code like +link_to 'Show', book+. If this should be a problem, have a look on two plugins which simplify working with routes in this way: Sven Fuchs's "_routing_filter_":http://github.com/svenfuchs/routing-filter/tree/master and Raul Murciano's "_translate_routes_":http://github.com/raul/translate_routes/tree/master. See also the page "How to encode the current locale in the URL":http://rails-i18n.org/wiki/pages/how-to-encode-the-current-locale-in-the-url in the Rails i18n Wiki.
IMPORTANT: This solution has currently one rather big *downside*. Due to the _default_url_options_ implementation, you have to pass the +:id+ option explicitely, like this: +link_to 'Show', book_url(:id => book)+ and not depend on Rails' magic in code like +link_to 'Show', book+. If this should be a problem, have a look at two plugins which simplify work with routes in this way: Sven Fuchs's "routing_filter":http://github.com/svenfuchs/routing-filter/tree/master and Raul Murciano's "translate_routes":http://github.com/raul/translate_routes/tree/master. See also the page "How to encode the current locale in the URL":http://rails-i18n.org/wiki/pages/how-to-encode-the-current-locale-in-the-url in the Rails i18n Wiki.
h4. Setting locale from the client supplied information
h4. Setting the Locale from the Client Supplied Information
In specific cases, it would make sense to set locale from client supplied information, ie. not from URL. This information may come for example from users' preffered language (set in their browser), can be based on users' geographical location inferred from their IP, or users can provide it simply by choosing locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites -- see the box about _sessions_, _cookies_ and RESTful architecture above.
In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' prefered language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites -- see the box about _sessions_, _cookies_ and RESTful architecture above.
h5. Using Accept-Language
h5. Using +Accept-Language+
One source of client supplied information would be an +Accept-Language+ HTTP header. People may "set this in their browser":http://www.w3.org/International/questions/qa-lang-priorities or other clients (such as _curl_).
A trivial implementation of using +Accept-Language+ header would be:
A trivial implementation of using an +Accept-Language+ header would be:
<ruby>
def set_locale
@ -300,21 +301,21 @@ def extract_locale_from_accept_language_header
end
</ruby>
Of course, in production environment you would need much robust code, and could use a plugin such as Iaian Hecker's "http_accept_language":http://github.com/iain/http_accept_language or even Rack middleware such as Ryan Tomayko's "locale":http://github.com/rtomayko/rack-contrib/blob/master/lib/rack/locale.rb.
Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's "http_accept_language":http://github.com/iain/http_accept_language/tree/master or even Rack middleware such as Ryan Tomayko's "locale":http://github.com/rtomayko/rack-contrib/blob/master/lib/rack/locale.rb.
h5. Using GeoIP (or similar) database
h5. Using GeoIP (or Similar) Database
Another way of choosing the locale from client's information would be to use a database for mapping client IP to region, such as "GeoIP Lite Country":http://www.maxmind.com/app/geolitecountry. The mechanics of the code would be very similar to the code above -- you would need to query database for user's IP, and lookup your preffered locale for the country/region/city returned.
Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as "GeoIP Lite Country":http://www.maxmind.com/app/geolitecountry. The mechanics of the code would be very similar to the code above -- you would need to query the database for the user's IP, and look up your prefered locale for the country/region/city returned.
h5. User profile
h5. User Profile
You can also provide users of your application with means to set (and possibly over-ride) locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above -- you'd probably let users choose a locale from a dropdown list and save it to their profile in database. Then you'd set the locale to this value.
You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above -- you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value.
h3. Internationalizing your application
h3. Internationalizing your Application
OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale should be used and how to preserve it between requests. With that in place, you're now ready for the really interesting stuff.
OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests. With that in place, you're now ready for the really interesting stuff.
Let's _internationalize_ our application, ie. abstract every locale-specific parts, and that _localize_ it, ie. provide neccessary translations for these abstracts.
Let's _internationalize_ our application, i.e. abstract every locale-specific parts, and then _localize_ it, i.e. provide neccessary translations for these abstracts.
You most probably have something like this in one of your applications:
@ -359,7 +360,7 @@ When you now render this view, it will show an error message which tells you tha
!images/i18n/demo_translation_missing.png(rails i18n demo translation missing)!
NOTE: Rails adds a +t+ (+translate+) helper method to your views so that you do not need to spell out +I18n.t+ all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a +<span class="translation_missing">+.
NOTE: Rails adds a +t+ (+translate+) helper method to your views so that you do not need to spell out +I18n.t+ all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a +&lt;span class="translation_missing"&gt;+.
So let's add the missing translations into the dictionary files (i.e. do the "localization" part):
@ -385,9 +386,9 @@ And when you change the URL to pass the pirate locale (+http://localhost:3000?lo
NOTE: You need to restart the server when you add new locale files.
You may use YAML (+.yml+) or plain Ruby (+.rb+) files for storing your translations in SimpleStore. YAML is the preffered option among Rails developers, has one big disadvantage, though. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into Ruby file.)
You may use YAML (+.yml+) or plain Ruby (+.rb+) files for storing your translations in SimpleStore. YAML is the prefered option among Rails developers. However, it has one big disadvantage. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into a Ruby file.)
h4. Adding Date/Time formats
h4. Adding Date/Time Formats
OK! Now let's add a timestamp to the view, so we can demo the *date/time localization* feature as well. To localize the time format you pass the Time object to +I18n.l+ or (preferably) use Rails' +#l+ helper. You can pick a format by passing the +:format+ option -- by default the +:default+ format is used.
@ -412,11 +413,17 @@ So that would give you:
!images/i18n/demo_localized_pirate.png(rails i18n demo localized time to pirate)!
TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected. Of course, there's a great chance that somebody already did all the work by *translating Rails's defaults for your locale*. See the "rails-i18n repository at Github":http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale for an archive of various locale files. When you put such file(s) in +config/locale/+ directory, they will automatically ready for use.
TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected (at least for the 'pirate' locale). Of course, there's a great chance that somebody already did all the work by *translating Rails's defaults for your locale*. See the "rails-i18n repository at Github":http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale for an archive of various locale files. When you put such file(s) in +config/locale/+ directory, they will automatically be ready for use.
h4. Organization of locale files
h4. Localized Views
When you are using the default SimpleStore, shipped with the i18n library, you store dictionaries in plain-text files on the disc. Putting translations for all parts of your application in one file per locale could be hard to manage. You can store these files in a hierarchy which makes sense to you.
Rails 2.3 introduces another convenient localization feature: localized views (templates). Let's say you have a _BooksController_ in your application. Your _index_ action renders content in +app/views/books/index.html.erb+ template. When you put a _localized variant_ of this template: *+index.es.html.erb+* in the same directory, Rails will render content in this template, when the locale is set to +:es+. When the locale is set to the default locale, the generic +index.html.erb+ view will be used. (Future Rails versions may well bring this _automagic_ localization to assets in +public+, etc.)
You can make use of this feature, e.g. when working with a large amount of static content, which would be clumsy to put inside YAML or Ruby dictionaries. Bear in mind, though, that any change you would like to do later to the template must be propagated to all of them.
h4. Organization of Locale Files
When you are using the default SimpleStore shipped with the i18n library, dictionaries are stored in plain-text files on the disc. Putting translations for all parts of your application in one file per locale could be hard to manage. You can store these files in a hierarchy which makes sense to you.
For example, your +config/locale+ directory could look like this:
@ -443,13 +450,18 @@ For example, your +config/locale+ directory could look like this:
|-----en.rb
</pre>
This way, you can separate model and model attribute names from text inside views, and all of this from the "defaults" (eg. date and time formats).
This way, you can separate model and model attribute names from text inside views, and all of this from the "defaults" (e.g. date and time formats). Other stores for the i18n library could provide different means of such separation.
Other stores for the i18n library could provide different means of such separation.
NOTE: The default locale loading mechanism in Rails does not load locale files in nested dictionaries, like we have here. So, for this to work, we must explicitly tell Rails to look further:
<ruby>
# config/environment.rb
config.i18n.load_path += Dir[File.join(RAILS_ROOT, 'config', 'locales', '**', '*.{rb,yml}')]
</ruby>
Do check the "Rails i18n Wiki":http://rails-i18n.org/wiki for list of tools available for managing translations.
h3. Overview of the I18n API features
h3. Overview of the I18n API Features
You should have good understanding of using the i18n library now, knowing all neccessary aspects of internationalizing a basic Rails application. In the following chapters, we'll cover it's features in more depth.
@ -458,11 +470,11 @@ Covered are features like these:
* looking up translations
* interpolating data into translations
* pluralizing translations
* localizing dates, numbers, currency etc.
* localizing dates, numbers, currency, etc.
h4. Looking up translations
h4. Looking up Translations
h5. Basic lookup, scopes and nested keys
h5. Basic Lookup, Scopes and Nested Keys
Translations are looked up by keys which can be both Symbols or Strings, so these calls are equivalent:
@ -471,63 +483,80 @@ I18n.t :message
I18n.t 'message'
</ruby>
+translate+ also takes a +:scope+ option which can contain one or many additional keys that will be used to specify a “namespace” or scope for a translation key:
The +translate+ method also takes a +:scope+ option which can contain one or more additional keys that will be used to specify a “namespace” or scope for a translation key:
<ruby>
I18n.t :invalid, :scope => [:active_record, :error_messages]
I18n.t :invalid, :scope => [:activerecord, :errors, :messages]
</ruby>
This looks up the +:invalid+ message in the Active Record error messages.
Additionally, both the key and scopes can be specified as dot separated keys as in:
Additionally, both the key and scopes can be specified as dot-separated keys as in:
<ruby>
I18n.translate :"active_record.error_messages.invalid"
I18n.translate :"activerecord.errors.messages.invalid"
</ruby>
Thus the following calls are equivalent:
<ruby>
I18n.t 'active_record.error_messages.invalid'
I18n.t 'error_messages.invalid', :scope => :active_record
I18n.t :invalid, :scope => 'active_record.error_messages'
I18n.t :invalid, :scope => [:active_record, :error_messages]
I18n.t 'activerecord.errors.messages.invalid'
I18n.t 'errors.messages.invalid', :scope => :active_record
I18n.t :invalid, :scope => 'activerecord.errors.messages'
I18n.t :invalid, :scope => [:activerecord, :errors, :messages]
</ruby>
h5. Defaults
When a default option is given its value will be returned if the translation is missing:
When a +:default+ option is given, its value will be returned if the translation is missing:
<ruby>
I18n.t :missing, :default => 'Not here'
# => 'Not here'
</ruby>
If the default value is a Symbol it will be used as a key and translated. One can provide multiple values as default. The first one that results in a value will be returned.
If the +:default+ value is a Symbol, it will be used as a key and translated. One can provide multiple values as default. The first one that results in a value will be returned.
E.g. the following first tries to translate the key +:missing+ and then the key +:also_missing.+ As both do not yield a result the string "Not here" will be returned:
E.g., the following first tries to translate the key +:missing+ and then the key +:also_missing.+ As both do not yield a result, the string "Not here" will be returned:
<ruby>
I18n.t :missing, :default => [:also_missing, 'Not here']
# => 'Not here'
</ruby>
h5. Bulk and namespace lookup
h5. Bulk and Namespace Lookup
To lookup multiple translations at once an array of keys can be passed:
To look up multiple translations at once, an array of keys can be passed:
<ruby>
I18n.t [:odd, :even], :scope => 'active_record.error_messages'
I18n.t [:odd, :even], :scope => 'activerecord.errors.messages'
# => ["must be odd", "must be even"]
</ruby>
Also, a key can translate to a (potentially nested) hash as grouped translations. E.g. one can receive all Active Record error messages as a Hash with:
Also, a key can translate to a (potentially nested) hash of grouped translations. E.g., one can receive _all_ Active Record error messages as a Hash with:
<ruby>
I18n.t 'active_record.error_messages'
I18n.t 'activerecord.errors.messages'
# => { :inclusion => "is not included in the list", :exclusion => ... }
</ruby>
h5. "Lazy" Lookup
Rails 2.3 implements a convenient way to look up the locale inside _views_. When you have the following dictionary:
<yaml>
es:
books:
index:
title: "Título"
</yaml>
you can look up the +books.index.title+ value *inside* +app/views/books/index.html.erb+ template like this (note the dot):
<ruby>
<%= t '.title' %>
</ruby>
h4. Interpolation
In many cases you want to abstract your translations so that *variables can be interpolated into the translation*. For this reason the I18n API provides an interpolation feature.
@ -540,12 +569,11 @@ I18n.translate :thanks, :name => 'Jeremy'
# => 'Thanks Jeremy!'
</ruby>
If a translation uses +:default+ or +:scope+ as a interpolation variable an I+18n::ReservedInterpolationKey+ exception is raised. If a translation expects an interpolation variable but it has not been passed to +#translate+ an +I18n::MissingInterpolationArgument+ exception is raised.
If a translation uses +:default+ or +:scope+ as an interpolation variable, an I+18n::ReservedInterpolationKey+ exception is raised. If a translation expects an interpolation variable, but this has not been passed to +#translate+, an +I18n::MissingInterpolationArgument+ exception is raised.
h4. Pluralization
In English there's only a singular and a plural form for a given string, e.g. "1 message" and "2 messages". Other languages ("Arabic":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ar, "Japanese":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ja, "Russian":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ru and many more) have different grammars that have additional or less "plural forms":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html. Thus, the I18n API provides a flexible pluralization feature.
In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ("Arabic":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ar, "Japanese":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ja, "Russian":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ru and many more) have different grammars that have additional or fewer "plural forms":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html. Thus, the I18n API provides a flexible pluralization feature.
The +:count+ interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by CLDR:
@ -566,13 +594,13 @@ entry[count == 1 ? 0 : 1]
I.e. the translation denoted as +:one+ is regarded as singular, the other is used as plural (including the count being zero).
If the lookup for the key does not return an Hash suitable for pluralization an +18n::InvalidPluralizationData+ exception is raised.
If the lookup for the key does not return a Hash suitable for pluralization, an +18n::InvalidPluralizationData+ exception is raised.
h4. Setting and passing a locale
h4. Setting and Passing a Locale
The locale can be either set pseudo-globally to +I18n.locale+ (which uses +Thread.current+ like, e.g., +Time.zone+) or can be passed as an option to +#translate+ and +#localize+.
If no locale is passed +I18n.locale+ is used:
If no locale is passed, +I18n.locale+ is used:
<ruby>
I18n.locale = :de
@ -587,15 +615,15 @@ I18n.t :foo, :locale => :de
I18n.l Time.now, :locale => :de
</ruby>
+I18n.locale+ defaults to +I18n.default_locale+ which defaults to :+en+. The default locale can be set like this:
The +I18n.locale+ defaults to +I18n.default_locale+ which defaults to :+en+. The default locale can be set like this:
<ruby>
I18n.default_locale = :de
</ruby>
h3. How to store your custom translations
h3. How to Store your Custom Translations
The shipped Simple backend allows you to store translations in both plain Ruby and YAML format. [2]
The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format. [2]
For example a Ruby Hash providing translations can look like this:
@ -617,9 +645,9 @@ pt:
bar: baz
</ruby>
As you see in both cases the toplevel key is the locale. +:foo+ is a namespace key and +:bar+ is the key for the translation "baz".
As you see, in both cases the toplevel key is the locale. +:foo+ is a namespace key and +:bar+ is the key for the translation "baz".
Here is a "real" example from the ActiveSupport +en.yml+ translations YAML file:
Here is a "real" example from the Active Support +en.yml+ translations YAML file:
<ruby>
en:
@ -639,11 +667,11 @@ I18n.t :short, :scope => 'date.formats'
I18n.t :short, :scope => [:date, :formats]
</ruby>
Generally we recommend using YAML as a format for storing translations. There are cases though where you want to store Ruby lambdas as part of your locale data, e.g. for special date.
Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats.
h4. Translations for Active Record models
h4. Translations for Active Record Models
You can use the methods +Model.human_name+ and +Model.human_attribute_name(attribute)+ to transparently lookup translations for your model and attribute names.
You can use the methods +Model.human_name+ and +Model.human_attribute_name(attribute)+ to transparently look up translations for your model and attribute names.
For example when you add the following translations:
@ -660,9 +688,9 @@ en:
Then +User.human_name+ will return "Dude" and +User.human_attribute_name(:login)+ will return "Handle".
h5. Error message scopes
h5. Error Message Scopes
Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes and/or validations. It also transparently takes single table inheritance into account.
Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes, and/or validations. It also transparently takes single table inheritance into account.
This gives you quite powerful means to flexibly adjust your messages to your application's needs.
@ -674,23 +702,23 @@ class User < ActiveRecord::Base
end
</ruby>
The key for the error message in this case is +:blank+. Active Record will lookup this key in the namespaces:
The key for the error message in this case is +:blank+. Active Record will look up this key in the namespaces:
<ruby>
activerecord.errors.messages.models.[model_name].attributes.[attribute_name]
activerecord.errors.messages.models.[model_name]
activerecord.errors.models.[model_name].attributes.[attribute_name]
activerecord.errors.models.[model_name]
activerecord.errors.messages
</ruby>
Thus, in our example it will try the following keys in this order and return the first result:
<ruby>
activerecord.errors.messages.models.user.attributes.name.blank
activerecord.errors.messages.models.user.blank
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
</ruby>
When your models are additionally using inheritance then the messages are looked up for the inherited model class names are looked up.
When your models are additionally using inheritance then the messages are looked up in the inheritance chain.
For example, you might have an Admin model inheriting from User:
@ -710,9 +738,9 @@ activerecord.errors.models.user.blank
activerecord.errors.messages.blank
</ruby>
This way you can provide special translations for various error messages at different points in your models inheritance chain and in the attributes, models or default scopes.
This way you can provide special translations for various error messages at different points in your models inheritance chain and in the attributes, models, or default scopes.
h5. Error message interpolation
h5. Error Message Interpolation
The translated model name, translated attribute name, and value are always available for interpolation.
@ -743,9 +771,9 @@ So, for example, instead of the default error message +"can not be blank"+ you c
| validates_numericality_of | :odd | :odd | -|
| validates_numericality_of | :even | :even | -|
h5. Translations for the Active Record error_messages_for helper
h5. Translations for the Active Record +error_messages_for+ Helper
If you are using the Active Record +error_messages_for+ helper you will want to add translations for it.
If you are using the Active Record +error_messages_for+ helper, you will want to add translations for it.
Rails ships with the following translations:
@ -760,44 +788,44 @@ en:
body: "There were problems with the following fields:"
</yaml>
h4. Overview of other built-in methods that provide I18n support
h4. Overview of Other Built-In Methods that Provide I18n Support
Rails uses fixed strings and other localizations, such as format strings and other format information in a couple of helpers. Here's a brief overview.
h5. ActionView helper methods
h5. Action View Helper Methods
* +distance_of_time_in_words+ translates and pluralizes its result and interpolates the number of seconds, minutes, hours and so on. See "datetime.distance_in_words":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L51 translations.
* +distance_of_time_in_words+ translates and pluralizes its result and interpolates the number of seconds, minutes, hours, and so on. See "datetime.distance_in_words":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L51 translations.
* +datetime_select+ and +select_month+ use translated month names for populating the resulting select tag. See "date.month_names":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15 for translations. +datetime_select+ also looks up the order option from "date.order":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18 (unless you pass the option explicitely). All date select helpers translate the prompt using the translations in the "datetime.prompts":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L83 scope if applicable.
* +datetime_select+ and +select_month+ use translated month names for populating the resulting select tag. See "date.month_names":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15 for translations. +datetime_select+ also looks up the order option from "date.order":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18 (unless you pass the option explicitely). All date selection helpers translate the prompt using the translations in the "datetime.prompts":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L83 scope if applicable.
* The +number_to_currency+, +number_with_precision+, +number_to_percentage+, +number_with_delimiter+ and +humber_to_human_size+ helpers use the number format settings located in the "number":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2 scope.
* The +number_to_currency+, +number_with_precision+, +number_to_percentage+, +number_with_delimiter+, and +number_to_human_size+ helpers use the number format settings located in the "number":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2 scope.
h5. Active Record methods
h5. Active Record Methods
* +human_name+ and +human_attribute_name+ use translations for model names and attribute names if available in the "activerecord.models":http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L43 scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes".
* +ActiveRecord::Errors#generate_message+ (which is used by Active Record validations but may also be used manually) uses +human_name+ and +human_attribute_name+ (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes".
*+ ActiveRecord::Errors#full_messages+ prepends the attribute name to the error message using a separator that will be looked up from "activerecord.errors.format.separator":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L91 (and defaults to +' '+).
*+ ActiveRecord::Errors#full_messages+ prepends the attribute name to the error message using a separator that will be looked up from "activerecord.errors.format.separator":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L91 (and which defaults to +'&nbsp;'+).
h5. ActiveSupport methods
h5. Active Support Methods
* +Array#to_sentence+ uses format settings as given in the "support.array":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L30 scope.
h3. Customize your I18n setup
h3. Customize your I18n Setup
h4. Using different backends
h4. Using Different Backends
For several reasons the shipped Simple backend only does the "simplest thing that ever could work" _for Ruby on Rails_ [3] ... which means that it is only guaranteed to work for English and, as a side effect, languages that are very similar to English. Also, the simple backend is only capable of reading translations but can not dynamically store them to any format.
For several reasons the Simple backend shipped with Active Support only does the "simplest thing that could possibly work" _for Ruby on Rails_ [3] ... which means that it is only guaranteed to work for English and, as a side effect, languages that are very similar to English. Also, the simple backend is only capable of reading translations but can not dynamically store them to any format.
That does not mean you're stuck with these limitations though. The Ruby I18n gem makes it very easy to exchange the Simple backend implementation with something else that fits better for your needs. E.g. you could exchange it with Globalize's Static backend:
That does not mean you're stuck with these limitations, though. The Ruby I18n gem makes it very easy to exchange the Simple backend implementation with something else that fits better for your needs. E.g. you could exchange it with Globalize's Static backend:
<ruby>
I18n.backend = Globalize::Backend::Static.new
</ruby>
h4. Using different exception handlers
h4. Using Different Exception Handlers
The I18n API defines the following exceptions that will be raised by backends when the corresponding unexpected conditions occur:
@ -810,11 +838,11 @@ ReservedInterpolationKey # the translation contains a reserved interpolation
UnknownFileType # the backend does not know how to handle a file type that was added to I18n.load_path
</ruby>
The I18n API will catch all of these exceptions when they were thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for +MissingTranslationData+ exceptions. When a +MissingTranslationData+ exception has been caught it will return the exceptions error message string containing the missing key/scope.
The I18n API will catch all of these exceptions when they are thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for +MissingTranslationData+ exceptions. When a +MissingTranslationData+ exception has been caught, it will return the exceptions error message string containing the missing key/scope.
The reason for this is that during development you'd usually want your views to still render even though a translation is missing.
In other contexts you might want to change this behaviour though. E.g. the default exception handling does not allow to catch missing translations during automated tests easily. For this purpose a different exception handler can be specified. The specified exception handler must be a method on the I18n module:
In other contexts you might want to change this behaviour, though. E.g. the default exception handling does not allow to catch missing translations during automated tests easily. For this purpose a different exception handler can be specified. The specified exception handler must be a method on the I18n module:
<ruby>
module I18n
@ -828,9 +856,9 @@ I18n.exception_handler = :just_raise_that_exception
This would re-raise all caught exceptions including +MissingTranslationData+.
Another example where the default behaviour is less desirable is the Rails TranslationHelper which provides the method +#t+ (as well as +#translate+). When a +MissingTranslationData+ exception occurs in this context the helper wraps the message into a span with the CSS class +translation_missing+.
Another example where the default behaviour is less desirable is the Rails TranslationHelper which provides the method +#t+ (as well as +#translate+). When a +MissingTranslationData+ exception occurs in this context, the helper wraps the message into a span with the CSS class +translation_missing+.
To do so the helper forces +I18n#translate+ to raise exceptions no matter what exception handler is defined by setting the +:raise+ option:
To do so, the helper forces +I18n#translate+ to raise exceptions no matter what exception handler is defined by setting the +:raise+ option:
<ruby>
I18n.t :foo, :raise => true # always re-raises exceptions from the backend
@ -838,16 +866,16 @@ I18n.t :foo, :raise => true # always re-raises exceptions from the backend
h3. Conclusion
At this point you hopefully have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project.
At this point you should have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project.
If you find anything missing or wrong in this guide please file a ticket on "our issue tracker":http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview. If you want to discuss certain portions or have questions please sign up to our "mailinglist":http://groups.google.com/group/rails-i18n.
h3. Contributing to Rails I18n
I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in plugins and real applications first and then cherry-picking the best bread of most widely useful features second for inclusion to the core.
I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in plugins and real applications first, and only then cherry-picking the best-of-bread of most widely useful features for inclusion in the core.
Thus we encourage everybody to experiment with new ideas and features in plugins or other libraries and make them available to the community. (Don't forget to announce your work on our "mailinglist":http://groups.google.com/group/rails-i18n!)
Thus we encourage everybody to experiment with new ideas and features in plugins or other libraries and make them available to the community. (Don't forget to announce your work on our "mailing list":http://groups.google.com/group/rails-i18n!)
If you find your own locale (language) missing from our "example translations data":http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale repository for Ruby on Rails, please "_fork_":http://github.com/guides/fork-a-project-and-submit-your-modifications the repository, add your data and send a "pull request":http://github.com/guides/pull-requests.
@ -876,7 +904,7 @@ fn1. Or, to quote "Wikipedia":http://en.wikipedia.org/wiki/Internationalization_
fn2. Other backends might allow or require to use other formats, e.g. a GetText backend might allow to read GetText files.
fn3. One of these reasons is that we don't want to any unnecessary load for applications that do not need any I18n capabilities, so we need to keep the I18n library as simple as possible for English. Another reason is that it is virtually impossible to implement a one-fits-all solution for all problems related to I18n for all existing languages. So a solution that allows us to exchange the entire implementation easily is appropriate anyway. This also makes it much easier to experiment with custom features and extensions.
fn3. One of these reasons is that we don't want to imply any unnecessary load for applications that do not need any I18n capabilities, so we need to keep the I18n library as simple as possible for English. Another reason is that it is virtually impossible to implement a one-fits-all solution for all problems related to I18n for all existing languages. So a solution that allows us to exchange the entire implementation easily is appropriate anyway. This also makes it much easier to experiment with custom features and extensions.
h3. Changelog

View file

@ -32,7 +32,7 @@ h3. Models
This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner.
<% end %>
<% guide("Active Record Validations and Callbacks", 'activerecord_validations_callbacks.html', :ticket => 26) do %>
<% guide("Active Record Validations and Callbacks", 'activerecord_validations_callbacks.html') do %>
This guide covers how you can use Active Record validations and callbacks.
<% end %>
@ -40,7 +40,7 @@ h3. Models
This guide covers all the associations provided by Active Record.
<% end %>
<% guide("Active Record Query Interface", 'active_record_querying.html', :ticket => 16) do %>
<% guide("Active Record Query Interface", 'active_record_querying.html') do %>
This guide covers the database query interface provided by Active Record.
<% end %>
</dl>
@ -77,7 +77,7 @@ h3. Digging Deeper
This guide covers Rails integration with Rack and interfacing with other Rack components.
<% end %>
<% guide("Rails Internationalization API", 'i18n.html', :ticket => 23) do %>
<% guide("Rails Internationalization API", 'i18n.html') do %>
This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on.
<% end %>
@ -117,4 +117,8 @@ h3. Digging Deeper
Various caching techniques provided by Rails.
<% end %>
<% guide("Contributing to Rails", 'contributing_to_rails.html') do %>
Rails is not "somebody else's framework." This guide covers a variety of ways that you can get involved in the ongoing development of Rails.
<% end %>
</dl>

View file

@ -16,15 +16,15 @@
<body class="guide">
<div id="topNav">
<div class="wrapper">
<strong>More at <a href="http://www.rubyonrails.org/">rubyonrails.org:</a> </strong>
<a href="http://www.rubyonrails.org/">Overview</a> |
<a href="http://www.rubyonrails.org/download">Download</a> |
<a href="http://www.rubyonrails.org/deploy">Deploy</a> |
<strong>More at <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
<a href="http://rubyonrails.org/">Overview</a> |
<a href="http://rubyonrails.org/download">Download</a> |
<a href="http://rubyonrails.org/deploy">Deploy</a> |
<a href="http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/overview">Code</a> |
<a href="http://www.rubyonrails.org/screencasts">Screencasts</a> |
<a href="http://www.rubyonrails.org/documentation">Documentation</a> |
<a href="http://www.rubyonrails.org/ecosystem">Ecosystem</a> |
<a href="http://www.rubyonrails.org/community">Community</a> |
<a href="http://rubyonrails.org/screencasts">Screencasts</a> |
<a href="http://rubyonrails.org/documentation">Documentation</a> |
<a href="http://rubyonrails.org/ecosystem">Ecosystem</a> |
<a href="http://rubyonrails.org/community">Community</a> |
<a href="http://weblog.rubyonrails.org/">Blog</a>
</div>
</div>
@ -63,6 +63,9 @@
<dd><a href="plugins.html">The Basics of Creating Rails Plugins</a></dd>
<dd><a href="configuring.html">Configuring Rails Applications</a></dd>
<dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
<dd><a href="command_line.html">Rails Command Line Tools and Rake Tasks</a></dd>
<dd><a href="caching_with_rails.html">Caching with Rails</a></dd>
<dd><a href="contributing_to_rails.html">Contributing to Rails</a></dd>
</dl>
</div>
</li>
@ -92,7 +95,7 @@
<hr class="hide" />
<div id="footer">
<div class="wrapper">
<p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0">Creative Commons Attribution-Share Alike 3.0</a> License</a></p>
<p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share Alike 3.0</a> License</a></p>
<p>"Rails", "Ruby on Rails", and the Rails logo are trademarks of David Heinemeier Hansson. All rights reserved.</p>
</div>
</div>

View file

@ -39,7 +39,7 @@ Rails will automatically render +app/views/books/show.html.erb+ after running th
NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), +.rjs+ for RJS (javascript with embedded ruby) and +.builder+ for Builder (XML generator). You'll also find +.rhtml+ used for ERB templates and +.rxml+ for Builder templates, but those extensions are now formally deprecated and will be removed from a future version of Rails.
h4. Using render
h4. Using +render+
In most cases, the +ActionController::Base#render+ method does the heavy lifting of rendering your application's content for use by a browser. There are a variety of ways to customize the behavior of +render+. You can render the default view for a Rails template, or a specific template, or a file, or inline code, or nothing at all. You can render text, JSON, or XML. You can specify the content type or HTTP status of the rendered response as well.
@ -140,7 +140,7 @@ NOTE: By default, the file is rendered without using the current layout. If you
TIP: If you're running on Microsoft Windows, you should use the +:file+ option to render a file, because Windows filenames do not have the same format as Unix filenames.
h5. Using render with :inline
h5. Using +render+ with +:inline+
The +render+ method can do without a view completely, if you're willing to use the +:inline+ option to supply ERB as part of the method call. This is perfectly valid:
@ -158,7 +158,7 @@ render :inline =>
"xml.p {'Horrid coding practice!'}", :type => :builder
</ruby>
h5. Using render with :update
h5. Using +render+ with +:update+
You can also render javascript-based page updates inline using the +:update+ option to +render+:
@ -212,7 +212,7 @@ render :js => "alert('Hello Rails');"
This will send the supplied string to the browser with a MIME type of +text/javascript+.
h5. Options for render
h5. Options for +render+
Calls to the +render+ method generally accept four options:
@ -221,7 +221,7 @@ Calls to the +render+ method generally accept four options:
* +:status+
* +:location+
h6. The :content_type Option
h6. The +:content_type+ Option
By default, Rails will serve the results of a rendering operation with the MIME content-type of +text/html+ (or +application/json+ if you use the +:json+ option, or +application/xml+ for the +:xml+ option.). There are times when you might like to change this, and you can do so by setting the +:content_type+ option:
@ -229,7 +229,7 @@ By default, Rails will serve the results of a rendering operation with the MIME
render :file => filename, :content_type => 'application/rss'
</ruby>
h6. The :layout Option
h6. The +:layout+ Option
With most of the options to +render+, the rendered content is displayed as part of the current layout. You'll learn more about layouts and how to use them later in this guide.
@ -245,7 +245,7 @@ You can also tell Rails to render with no layout at all:
render :layout => false
</ruby>
h6. The :status Option
h6. The +:status+ Option
Rails will automatically generate a response with the correct HTML status code (in most cases, this is +200 OK+). You can use the +:status+ option to change this:
@ -256,7 +256,7 @@ render :status => :forbidden
Rails understands either numeric status codes or symbols for status codes. You can find its list of status codes in +actionpack/lib/action_controller/status_codes.rb+. You can also see there how Rails maps symbols to status codes.
h6. The :location Option
h6. The +:location+ Option
You can use the +:location+ option to set the HTTP +Location+ header:
@ -316,7 +316,7 @@ Now, if the current user is a special user, they'll get a special layout when vi
<ruby>
class ProductsController < ApplicationController
layout proc{ |controller| controller.
layout proc { |controller| controller.request.xhr? ? 'popup' : 'application' }
# ...
end
</ruby>
@ -327,13 +327,12 @@ Layouts specified at the controller level support +:only+ and +:except+ options
<ruby>
class ProductsController < ApplicationController
layout "inventory", :only => :index
layout "product", :except => [:index, :rss]
#...
end
</ruby>
With those declarations, the +inventory+ layout would be used only for the +index+ method, the +product+ layout would be used for everything else except the +rss+ method, and the +rss+ method will have its layout determined by the automatic layout rules.
With this declaration, the +product+ layout would be used for everything but the +rss+ and +index+ methods.
h6. Layout Inheritance
@ -403,6 +402,7 @@ def show
if @book.special?
render :action => "special_show"
end
render :action => "regular_show"
end
</ruby>
@ -414,10 +414,24 @@ def show
if @book.special?
render :action => "special_show" and return
end
render :action => "regular_show"
end
</ruby>
h4. Using redirect_to
Note that the implicit render done by ActionController detects if +render+ has been called, and thus avoids this error. So this code will work with problems:
<ruby>
def show
@book = Book.find(params[:id])
if @book.special?
render :action => "special_show"
end
end
</ruby>
This will render a book with +special?+ set with the +special_show+ template, while other books will render with the default +show+ template.
h4. Using +redirect_to+
Another way to handle returning responses to an HTTP request is with +redirect_to+. As you've seen, +render+ tells Rails which view (or other asset) to use in constructing a response. The +redirect_to+ method does something completely different: it tells the browser to send a new request for a different URL. For example, you could redirect from wherever you are in your code to the index of photos in your application with this call:
@ -441,7 +455,7 @@ redirect_to photos_path, :status => 301
Just like the +:status+ option for +render+, +:status+ for +redirect_to+ accepts both numeric and symbolic header designations.
h5. The Difference Between render and redirect
h5. The Difference Between +render+ and +redirect_to+
Sometimes inexperienced developers conceive of +redirect_to+ as a sort of +goto+ command, moving execution from one place to another in your Rails code. This is _not_ correct. Your code stops running and waits for a new request for the browser. It just happens that you've told the browser what request it should make next, by sending back an HTTP 302 status code.
@ -455,7 +469,7 @@ end
def show
@book = Book.find(params[:id])
if @book.nil?
render :action => "index" and return
render :action => "index"
end
end
</ruby>
@ -470,14 +484,14 @@ end
def show
@book = Book.find(params[:id])
if @book.nil?
redirect_to :action => "index" and return
redirect_to :action => "index"
end
end
</ruby>
With this code, the browser will make a fresh request for the index page, the code in the +index+ method will run, and all will be well.
h4. Using head To Build Header-Only Responses
h4. Using +head+ To Build Header-Only Responses
The +head+ method exists to let you send back responses to the browser that have only headers. It provides a more obvious alternative to calling +render :nothing+. The +head+ method takes one response, which is interpreted as a hash of header names and values. For example, you can return only an error header:
@ -514,7 +528,7 @@ You can use these tags in layouts or other views, although the tags other than +
WARNING: The asset tags do _not_ verify the existence of the assets at the specified locations; they simply assume that you know what you're doing and generate the link.
h5. Linking to Feeds with auto_discovery_link_tag
h5. Linking to Feeds with +auto_discovery_link_tag+
The +auto_discovery_link_tag+ helper builds HTML that most browsers and newsreaders can use to detect the presences of RSS or ATOM feeds. It takes the type of the link (+:rss+ or +:atom+), a hash of options that are passed through to url_for, and a hash of options for the tag:
@ -529,7 +543,7 @@ There are three tag options available for +auto_discovery_link_tag+:
* +:type+ specifies an explicit MIME type. Rails will generate an appropriate MIME type automatically
* +:title+ specifies the title of the link
h5. Linking to Javascript Files with javascript_include_tag
h5. Linking to Javascript Files with +javascript_include_tag+
The +javascript_include_tag+ helper returns an HTML +script+ tag for each source provided. Rails looks in +public/javascripts+ for these files by default, but you can specify a full path relative to the document root, or a URL, if you prefer. For example, to include +public/javascripts/main.js+:
@ -588,7 +602,7 @@ By default, the combined file will be delivered as +javascripts/all.js+. You can
You can even use dynamic paths such as +cache/#{current_site}/main/display+.
h5. Linking to CSS Files with stylesheet_link_tag
h5. Linking to CSS Files with +stylesheet_link_tag+
The +stylesheet_link_tag+ helper returns an HTML +<link>+ tag for each source provided. Rails looks in +public/stylesheets+ for these files by default, but you can specify a full path relative to the document root, or a URL, if you prefer. For example, to include +public/stylesheets/main.cs+:
@ -647,7 +661,7 @@ By default, the combined file will be delivered as +stylesheets/all.css+. You ca
You can even use dynamic paths such as +cache/#{current_site}/main/display+.
h5. Linking to Images with image_tag
h5. Linking to Images with +image_tag+
The +image_tag+ helper builds an HTML +&lt;image&gt;+ tag to the specified file. By default, files are loaded from +public/images+. If you don't specify an extension, +.png+ is assumed by default:
@ -673,7 +687,7 @@ There are also three special options you can use with +image_tag+:
* +:size+ specifies both width and height, in the format "{width}x{height}" (for example, "150x125")
* +:mouseover+ sets an alternate image to be used when the onmouseover event is fired.
h4. Understanding yield
h4. Understanding +yield+
Within the context of a layout, +yield+ identifies a section where content from the view should be inserted. The simplest way to use this is to have a single +yield+, into which the entire contents of the view currently being rendered is inserted:
@ -702,7 +716,7 @@ You can also create a layout with multiple yielding regions:
The main body of the view will always render into the unnamed +yield+. To render content into a named +yield+, you use the +content_for+ method.
h4. Using content_for
h4. Using +content_for+
The +content_for+ method allows you to insert content into a +yield+ block in your layout. You only use +content_for+ to insert content in named yields. For example, this view would work with the layout that you just saw:
@ -915,7 +929,7 @@ You may find that your application requires a layout that differs slightly from
Suppose you have the follow +ApplicationController+ layout:
* +app/views/layouts/application.erb+
* +app/views/layouts/application.html.erb+
<erb>
<html>
@ -934,7 +948,7 @@ Suppose you have the follow +ApplicationController+ layout:
On pages generated by +NewsController+, you want to hide the top menu and add a right menu:
* +app/views/layouts/news.erb+
* +app/views/layouts/news.html.erb+
<erb>
<% content_for :stylesheets do %>

View file

@ -15,7 +15,7 @@ You'll learn all about migrations including:
endprologue.
h3. Anatomy Of A Migration
h3. Anatomy of a Migration
Before I dive into the details of a migration, here are a few examples of the sorts of things you can do:
@ -60,7 +60,7 @@ to have already opted in, so we use the User model to set the flag to +true+ for
NOTE: Some "caveats":#using-models-in-your-migrations apply to using models in your migrations.
h4. Migrations are classes
h4. Migrations are Classes
A migration is a subclass of <tt>ActiveRecord::Migration</tt> that implements two class methods: +up+ (perform the required transformations) and +down+ (revert them).
@ -76,11 +76,11 @@ Active Record provides methods that perform common data definition tasks in a da
* +add_index+
* +remove_index+
If you need to perform tasks specific to your database (for example create a "foreign key":#active-recordand-referential-integrity constraint) then the +execute+ function allows you to execute arbitrary SQL. A migration is just a regular Ruby class so you're not limited to these functions. For example after adding a column you could write code to set the value of that column for existing records (if necessary using your models).
If you need to perform tasks specific to your database (for example create a "foreign key":#active-record-and-referential-integrity constraint) then the +execute+ function allows you to execute arbitrary SQL. A migration is just a regular Ruby class so you're not limited to these functions. For example after adding a column you could write code to set the value of that column for existing records (if necessary using your models).
On databases that support transactions with statements that change the schema (such as PostgreSQL), migrations are wrapped in a transaction. If the database does not support this (for example MySQL and SQLite) then when a migration fails the parts of it that succeeded will not be rolled back. You will have to unpick the changes that were made by hand.
h4. What's in a name
h4. What's in a Name
Migrations are stored in files in +db/migrate+, one for each migration class. The name of the file is of the form +YYYYMMDDHHMMSS_create_products.rb+, that is to say a UTC timestamp identifying the migration followed by an underscore followed by the name of the migration. The migration class' name must match (the camelcased version of) the latter part of the file name. For example +20080906120000_create_products.rb+ should define +CreateProducts+ and +20080906120001_add_details_to_products.rb+ should define +AddDetailsToProducts+. If you do feel the need to change the file name then you <em>have to</em> update the name of the class inside or Rails will complain about a missing class.
@ -92,15 +92,15 @@ For example Alice adds migrations +20080906120000+ and +20080906123000+ and Bob
Of course this is no substitution for communication within the team. For example, if Alice's migration removed a table that Bob's migration assumed to exist, then trouble would certainly strike.
h4. Changing migrations
h4. Changing Migrations
Occasionally you will make a mistake when writing a migration. If you have already run the migration then you cannot just edit the migration and run the migration again: Rails thinks it has already run the migration and so will do nothing when you run +rake db:migrate+. You must rollback the migration (for example with +rake db:rollback+), edit your migration and then run +rake db:migrate+ to run the corrected version.
In general editing existing migrations is not a good idea: you will be creating extra work for yourself and your co-workers and cause major headaches if the existing version of the migration has already been run on production machines. Instead you should write a new migration that performs the changes you require. Editing a freshly generated migration that has not yet been committed to source control (or more generally which has not been propagated beyond your development machine) is relatively harmless. Just use some common sense.
h3. Creating A Migration
h3. Creating a Migration
h4. Creating a model
h4. Creating a Model
The model and scaffold generators will create migrations appropriate for adding a new model. This migration will already contain instructions for creating the relevant table. If you tell Rails what columns you want then statements for adding those will also be created. For example, running
@ -130,7 +130,7 @@ end
You can append as many column name/type pairs as you want. By default +t.timestamps+ (which creates the +updated_at+ and +created_at+ columns that
are automatically populated by Active Record) will be added for you.
h4. Creating a standalone migration
h4. Creating a Standalone Migration
If you are creating migrations for other purposes (for example to add a column to an existing table) then you can use the migration generator:
@ -218,7 +218,7 @@ h3. Writing a Migration
Once you have created your migration using one of the generators it's time to get to work!
h4. Creating a table
h4. Creating a Table
Migration method +create_table+ will be one of your workhorses. A typical use would be
@ -268,7 +268,7 @@ end
This may however hinder portability to other databases.
h4. Changing tables
h4. Changing Tables
A close cousin of +create_table+ is +change_table+, used for changing existing tables. It is used in a similar fashion to +create_table+ but the object yielded to the block knows more tricks. For example
@ -292,7 +292,7 @@ rename_column :products, :upccode, :upc_code
You don't have to keep repeating the table name and it groups all the statements related to modifying one particular table. The individual transformation names are also shorter, for example +remove_column+ becomes just +remove+ and +add_index+ becomes just +index+.
h4. Special helpers
h4. Special Helpers
Active Record provides some shortcuts for common functionality. It is for example very common to add both the +created_at+ and +updated_at+ columns and so there is a method that does exactly that:
@ -327,13 +327,13 @@ end
</ruby>
will add an +attachment_id+ column and a string +attachment_type+ column with a default value of 'Photo'.
NOTE: The +references+ helper does not actually create foreign key constraints for you. You will need to use +execute+ for that or a plugin that adds "foreign key support":#active-recordand-referential-integrity.
NOTE: The +references+ helper does not actually create foreign key constraints for you. You will need to use +execute+ for that or a plugin that adds "foreign key support":#active-record-and-referential-integrity.
If the helpers provided by Active Record aren't enough you can use the +execute+ function to execute arbitrary SQL.
For more details and examples of individual methods check the API documentation, in particular the documentation for "<tt>ActiveRecord::ConnectionAdapters::SchemaStatements</tt>":http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "<tt>ActiveRecord::ConnectionAdapters::TableDefinition</tt>":http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "<tt>ActiveRecord::ConnectionAdapters::Table</tt>":http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+).
For more details and examples of individual methods check the API documentation, in particular the documentation for "<tt>ActiveRecord::ConnectionAdapters::SchemaStatements</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "<tt>ActiveRecord::ConnectionAdapters::TableDefinition</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "<tt>ActiveRecord::ConnectionAdapters::Table</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+).
h4. Writing your down method
h4. Writing Your +down+ Method
The +down+ method of your migration should revert the transformations done by the +up+ method. In other words the database schema should be unchanged if you do an +up+ followed by a +down+. For example if you create a table in the +up+ method you should drop it in the +down+ method. It is wise to do things in precisely the reverse order to in the +up+ method. For example
@ -384,7 +384,7 @@ rake db:migrate VERSION=20080906120000
If this is greater than the current version (i.e. it is migrating upwards) this will run the +up+ method on all migrations up to and including 20080906120000, if migrating downwards this will run the +down+ method on all the migrations down to, but not including, 20080906120000.
h4. Rolling back
h4. Rolling Back
A common task is to rollback the last migration, for example if you made a mistake in it and wish to correct it. Rather than tracking down the version number associated with the previous migration you can run
@ -410,7 +410,7 @@ Neither of these Rake tasks do anything you could not do with +db:migrate+, they
Lastly, the +db:reset+ task will drop the database, recreate it and load the current schema into it.
NOTE: This is not the same as running all the migrations - see the section on "schema.rb":#schemadumpingandyou.
NOTE: This is not the same as running all the migrations - see the section on "schema.rb":#schema-dumping-and-you.
h4. Being Specific
@ -422,7 +422,7 @@ rake db:migrate:up VERSION=20080906120000
will run the +up+ method from the 20080906120000 migration. These tasks check whether the migration has already run, so for example +db:migrate:up VERSION=20080906120000+ will do nothing if Active Record believes that 20080906120000 has already been run.
h4. Being talkative
h4. Being Talkative
By default migrations tell you exactly what they're doing and how long it took. A migration creating a table and adding an index might produce output like this
@ -482,7 +482,7 @@ generates the following output
If you just want Active Record to shut up then running +rake db:migrate VERBOSE=false+ will suppress any output.
h3. Using Models In Your Migrations
h3. Using Models in Your Migrations
When creating or updating data in a migration it is often tempting to use one of your models. After all they exist to provide easy access to the underlying data. This can be done but some caution should be observed.
@ -506,7 +506,7 @@ end
</ruby>
The migration has its own minimal copy of the +Product+ model and no longer cares about the +Product+ model defined in the application.
h4. Dealing with changing models
h4. Dealing with Changing Models
For performance reasons information about the columns a model has is cached. For example if you add a column to a table and then try and use the corresponding model to insert a new row it may try and use the old column information. You can force Active Record to re-read the column information with the +reset_column_information+ method, for example
@ -528,9 +528,9 @@ end
</ruby>
h3. Schema dumping and you
h3. Schema Dumping and You
h4. What are schema files for?
h4. What are Schema Files for?
Migrations, mighty as they may be, are not the authoritative source for your database schema. That role falls to either +db/schema.rb+ or an SQL file which Active Record generates by examining the database. They are not designed to be edited, they just represent the current state of the database.
@ -540,7 +540,7 @@ For example, this is how the test database is created: the current development d
Schema files are also useful if you want a quick look at what attributes an Active Record object has. This information is not in the model's code and is frequently spread across several migrations but is all summed up in the schema file. The "annotate_models":http://agilewebdevelopment.com/plugins/annotate_models plugin, which automatically adds (and updates) comments at the top of each model summarising the schema, may also be of interest.
h4. Types of schema dumps
h4. Types of Schema Dumps
There are two ways to dump the schema. This is set in +config/environment.rb+ by the +config.active_record.schema_format+ setting, which may be either +:sql+ or +:ruby+.
@ -572,7 +572,7 @@ Instead of using Active Record's schema dumper the database's structure will be
By definition this will be a perfect copy of the database's structure but this will usually prevent loading the schema into a database other than the one used to create it.
h4. Schema dumps and source control
h4. Schema Dumps and Source Control
Because schema dumps are the authoritative source for your database schema, it is strongly recommended that you check them into source control.

View file

@ -0,0 +1,222 @@
h2. Rails nested model forms
Creating a form for a model _and_ its associations can become quite tedious. Therefor Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations.
In this guide you will:
* do stuff
endprologue.
NOTE: This guide assumes the user knows how to use the "Rails form helpers":form_helpers.html in general. Also, its *not* an API reference. For a complete reference please visit "the Rails API documentation":http://api.rubyonrails.org/.
h3. Model setup
To be able to use the nested model functionality in your forms, the model will need to support some basic operations.
First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The +fields_for+ form helper will look for this method to decide whether or not a nested model form should be build.
If the associated object is an array a form builder will be yielded for each object, else only a single form builder will be yielded.
Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the +:address+ attribute, the +fields_for+ form helper will look for a method on the Person instance named +address_attributes=+.
h4. ActiveRecord::Base model
For an ActiveRecord::Base model and association this writer method is commonly defined with the +accepts_nested_attributes_for+ class method:
h5. has_one
<ruby>
class Person < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address
end
</ruby>
h5. belongs_to
<ruby>
class Person < ActiveRecord::Base
belongs_to :firm
accepts_nested_attributes_for :firm
end
</ruby>
h5. has_many / has_and_belongs_to_many
<ruby>
class Person < ActiveRecord::Base
has_many :projects
accepts_nested_attributes_for :projects
end
</ruby>
h4. Custom model
As you might have inflected from this explanation, you _dont_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behaviour:
h5. Single associated object
<ruby>
class Person
def address
Address.new
end
def address_attributes=(attributes)
# ...
end
end
</ruby>
h5. Association collection
<ruby>
class Person
def projects
[Project.new, Project.new]
end
def projects_attributes=(attributes)
# ...
end
end
</ruby>
NOTE: See (TODO) in the advanced section for more information on how to deal with the CRUD operations in your custom model.
h3. Views
h4. Controller code
A nested model form will _only_ be build if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first.
Consider the following typical RESTful controller which will prepare a new Person instance and its +address+ and +projects+ associations before rendering the +new+ template:
<ruby>
class PeopleController < ActionController:Base
def new
@person = Person.new
@person.built_address
2.times { @person.projects.build }
end
def create
@person = Person.new(params[:person])
if @person.save
# ...
end
end
end
</ruby>
NOTE: Obviously the instantiation of the associated object(s) can become tedious and not DRY, so you might want to move that into the model itself. ActiveRecord::Base provides an +after_initialize+ callback which is a good way to refactor this.
h4. Form code
Now that you have a model instance, with the appropriate methods and associated object(s), you can start building the nested model form.
h5. Standard form
Start out with a regular RESTful form:
<erb>
<% form_for @person do |f| %>
<%= f.text_field :name %>
<% end %>
</erb>
This will generate the following html:
<html>
<form action="/people" class="new_person" id="new_person" method="post">
<input id="person_name" name="person[name]" size="30" type="text" />
</form>
</html>
h5. Nested form for a single associated object
Now add a nested form for the +address+ association:
<erb>
<% form_for @person do |f| %>
<%= f.text_field :name %>
<% f.fields_for :address do |af| %>
<%= f.text_field :street %>
<% end %>
<% end %>
</erb>
This generates:
<html>
<form action="/people" class="new_person" id="new_person" method="post">
<input id="person_name" name="person[name]" size="30" type="text" />
<input id="person_address_attributes_street" name="person[address_attributes][street]" size="30" type="text" />
</form>
</html>
Notice that +fields_for+ recognized the +address+ as an association for which a nested model form should be build by the way it has namespaced the +name+ attribute.
When this form is posted the Rails parameter parser will construct a hash like the following:
<ruby>
{
"person" => {
"name" => "Eloy Duran",
"address_attributes" => {
"street" => "Nieuwe Prinsengracht"
}
}
}
</ruby>
Thats it. The controller will simply pass this hash on to the model from the +create+ action. The model will then handle building the +address+ association for you and automatically save it when the parent (+person+) is saved.
h5. Nested form for a collection of associated objects
The form code for an association collection is pretty similar to that of a single associated object:
<erb>
<% form_for @person do |f| %>
<%= f.text_field :name %>
<% f.fields_for :projects do |pf| %>
<%= f.text_field :name %>
<% end %>
<% end %>
</erb>
Which generates:
<html>
<form action="/people" class="new_person" id="new_person" method="post">
<input id="person_name" name="person[name]" size="30" type="text" />
<input id="person_projects_attributes_0_name" name="person[projects_attributes][0][name]" size="30" type="text" />
<input id="person_projects_attributes_1_name" name="person[projects_attributes][1][name]" size="30" type="text" />
</form>
</html>
As you can see it has generated 2 +project name+ inputs, one for each new +project+ thats build in the controllers +new+ action. Only this time the +name+ attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as:
<ruby>
{
"person" => {
"name" => "Eloy Duran",
"projects_attributes" => {
"0" => { "name" => "Project 1" },
"1" => { "name" => "Project 2" }
}
}
}
</ruby>
You can basically see the +projects_attributes+ hash as an array of attribute hashes. One for each model instance.
NOTE: The reason that +fields_for+ constructed a form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep.
TIP: You _can_ however pass an array to the writer method generated by +accepts_nested_attributes_for+ if you're using plain Ruby or some other API access. See (TODO) for more info and example.

View file

@ -32,7 +32,7 @@ end
This example is a simple performance test case for profiling a GET request to the application's homepage.
h4. Generating performance tests
h4. Generating Performance Tests
Rails provides a generator called +performance_test+ for creating new performance tests:
@ -95,7 +95,7 @@ class Post < ActiveRecord::Base
end
</ruby>
h5. Controller example
h5. Controller Example
Because performance tests are a special kind of integration test, you can use the +get+ and +post+ methods in them.
@ -121,9 +121,9 @@ class PostPerformanceTest < ActionController::PerformanceTest
end
</ruby>
You can find more details about the +get+ and +post+ methods in the link:../testing_rails_applications.html#mgunderloy[Testing Rails Applications] guide.
You can find more details about the +get+ and +post+ methods in the "Testing Rails Applications":testing.html guide.
h5. Model example
h5. Model Example
Even though the performance tests are integration tests and hence closer to the request/response cycle by nature, you can still performance test pure model code.
@ -173,13 +173,13 @@ h4. Metrics
Benchmarking and profiling run performance tests in various modes described below.
h5. Wall time
h5. Wall Time
Wall time measures the real world time elapsed during the test run. It is affected by any other processes concurrently running on the system.
Mode: Benchmarking
h5. Process time
h5. Process Time
Process time measures the time taken by the process. It is unaffected by any other processes running concurrently on the same system. Hence, process time is likely to be constant for any given performance test, irrespective of the machine load.
@ -197,19 +197,19 @@ Objects measures the number of objects allocated for the performance test case.
Mode: Benchmarking, Profiling "Requires GC Patched Ruby":#installing-gc-patched-ruby
h5. GC runs
h5. GC Runs
GC Runs measures the number of times GC was invoked for the performance test case.
Mode: Benchmarking "Requires GC Patched Ruby":#installing-gc-patched-ruby
h5. GC time
h5. GC Time
GC Time measures the amount of time spent in GC for the performance test case.
Mode: Benchmarking "Requires GC Patched Ruby":#installing-gc-patched-ruby
h4. Understanding the output
h4. Understanding the Output
Performance tests generate different outputs inside +tmp/performance+ directory depending on their mode and metric.
@ -217,7 +217,7 @@ h5. Benchmarking
In benchmarking mode, performance tests generate two types of outputs:
h6. Command line
h6. Command Line
This is the primary form of output in benchmarking mode. Example:
@ -230,7 +230,7 @@ BrowsingTest#test_homepage (31 ms warmup)
gc_time: 19 ms
</shell>
h6. CSV files
h6. CSV Files
Performance test results are also appended to +.csv+ files inside +tmp/performance+. For example, running the default +BrowsingTest#test_homepage+ will generate following five files:
@ -262,7 +262,7 @@ h5. Profiling
In profiling mode, you can choose from four types of output.
h6. Command line
h6. Command Line
This is a very basic form of output in profiling mode:
@ -283,15 +283,15 @@ Graph output shows how long each method takes to run, which methods call it and
h6. Tree
Tree output is profiling information in calltree format for use by http://kcachegrind.sourceforge.net/html/Home.html[kcachegrind] and similar tools.
Tree output is profiling information in calltree format for use by "kcachegrind":http://kcachegrind.sourceforge.net/html/Home.html and similar tools.
h4. Tuning test runs
h4. Tuning Test Runs
By default, each performance test is run +4 times+ in benchmarking mode and +1 time+ in profiling. However, test runs can easily be configured.
WARNING: Performance test configurability is not yet enabled in Rails. But it will be soon.
h4. Performance test environment
h4. Performance Test Environment
Performance tests are run in the +development+ environment. But running performance tests will set the following configuration parameters:
@ -303,7 +303,7 @@ Rails.logger.level = ActiveSupport::BufferedLogger::INFO
As +ActionController::Base.perform_caching+ is set to +true+, performance tests will behave much as they do in the +production+ environment.
h4. Installing GC-patched Ruby
h4. Installing GC-Patched Ruby
To get the best from Rails performance tests, you need to build a special Ruby binary with some super powers - "GC patch":http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch for measuring GC Runs/Time and memory/object allocation.
@ -313,7 +313,7 @@ h5. Installation
Compile Ruby and apply this "GC Patch":http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch.
h5. Download and extract
h5. Download and Extract
<shell>
[lifo@null ~]$ mkdir rubygc
@ -322,13 +322,13 @@ h5. Download and extract
[lifo@null ~]$ cd <ruby-version>
</shell>
h5. Apply the patch
h5. Apply the Patch
<shell>
[lifo@null ruby-version]$ curl http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch | patch -p0
</shell>
h5. Configure and install
h5. Configure and Install
The following will install ruby in your home directory's +/rubygc+ directory. Make sure to replace +<homedir>+ with a full patch to your actual home directory.
@ -337,7 +337,7 @@ The following will install ruby in your home directory's +/rubygc+ directory. Ma
[lifo@null ruby-version]$ make && make install
</shell>
h5. Prepare aliases
h5. Prepare Aliases
For convenience, add the following lines in your +~/.profile+:
@ -349,7 +349,7 @@ alias gcirb='~/rubygc/bin/irb'
alias gcrails='~/rubygc/bin/rails'
</shell>
h5. Install Rubygems and dependency gems
h5. Install Rubygems and Dependency Gems
Download "Rubygems":http://rubyforge.org/projects/rubygems and install it from source. Rubygem's README file should have necessary installation instructions.
@ -446,11 +446,11 @@ This benchmarks the code enclosed in the +Project.benchmark("Creating project")
Creating project (185.3ms)
</ruby>
Please refer to the "API docs":http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001336 for additional options to +benchmark()+
Please refer to the "API docs":http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M001336 for additional options to +benchmark()+
h4. Controller
Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715
Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.org/classes/ActionController/Benchmarking/ClassMethods.html#M000715
<ruby>
def process_projects
@ -465,7 +465,7 @@ NOTE: +benchmark+ is a class method inside controllers
h4. View
And in "views":http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715:
And in "views":http://api.rubyonrails.org/classes/ActionController/Benchmarking/ClassMethods.html#M000715:
<erb>
<% benchmark("Showing projects partial") do %>
@ -496,21 +496,21 @@ Michael Koziarski has an "interesting blog post":http://www.therailsway.com/2009
h3. Useful Links
h4. Rails plugins and gems
h4. Rails Plugins and Gems
* "Rails Analyzer":http://rails-analyzer.rubyforge.org
* "Palmist":http://www.flyingmachinestudios.com/projects
* "Palmist":http://www.flyingmachinestudios.com/projects/
* "Rails Footnotes":http://github.com/josevalim/rails-footnotes/tree/master
* "Query Reviewer":http://github.com/dsboulder/query_reviewer/tree/master
h4. Generic tools
h4. Generic Tools
* "httperf":http://www.hpl.hp.com/research/linux/httperf
* "httperf":http://www.hpl.hp.com/research/linux/httperf/
* "ab":http://httpd.apache.org/docs/2.2/programs/ab.html
* "JMeter":http://jakarta.apache.org/jmeter
* "JMeter":http://jakarta.apache.org/jmeter/
* "kcachegrind":http://kcachegrind.sourceforge.net/html/Home.html
h4. Tutorials and documentation
h4. Tutorials and Documentation
* "ruby-prof API Documentation":http://ruby-prof.rubyforge.org
* "Request Profiling Railscast":http://railscasts.com/episodes/98-request-profiling - Outdated, but useful for understanding call graphs

View file

@ -31,7 +31,7 @@ endprologue.
h3. Setup
h4. Create the basic app
h4. Create the Basic Application
The examples in this guide require that you have a working rails application. To create a simple rails app execute:
@ -49,7 +49,7 @@ Then navigate to http://localhost:3000/birds. Make sure you have a functioning
NOTE: The aforementioned instructions will work for sqlite3. For more detailed instructions on how to create a rails app for other databases see the API docs.
h4. Generate the plugin skeleton
h4. Generate the Plugin Skeleton
Rails ships with a plugin generator which creates a basic plugin skeleton. Pass the plugin name, either 'CamelCased' or 'under_scored', as an argument. Pass +--with-generator+ to add an example generator also.
@ -91,7 +91,7 @@ create vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb
create vendor/plugins/yaffle/generators/yaffle/USAGE
</pre>
h4. Organize your files
h4. Organize Your Files
To make it easy to organize your files and to make the plugin more compatible with GemPlugins, start out by altering your file system to look like this:
@ -211,7 +211,7 @@ end
Now whenever you write a test that requires the database, you can call 'load_schema'.
h4. Run the plugin tests
h4. Run the Plugin Tests
Once you have these files in place, you can write your first test to ensure that your plugin-testing setup is correct. By default rails generates a file in 'vendor/plugins/yaffle/test/yaffle_test.rb' with a sample test. Replace the contents of that file with:
@ -275,7 +275,7 @@ rake DB=postgresql
Now you are ready to test-drive your plugin!
h3. Extending core classes
h3. Extending Core Classes
This section will explain how to add a method to String that will be available anywhere in your rails app.
@ -339,7 +339,7 @@ $ ./script/console
=> "squawk! Hello World"
</shell>
h4. Working with init.rb
h4. Working with +init.rb+
When rails loads plugins it looks for the file named 'init.rb' or 'rails/init.rb'. However, when the plugin is initialized, 'init.rb' is invoked via +eval+ (not +require+) so it has slightly different behavior.
@ -369,7 +369,7 @@ class ::Hash
end
</ruby>
h3. Add an 'acts_as' method to Active Record
h3. Add an "acts_as" Method to Active Record
A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you want to write a method called 'acts_as_yaffle' that adds a 'squawk' method to your models.
@ -425,7 +425,7 @@ end
With structure you can easily separate the methods that will be used for the class (like +Hickwall.some_method+) and the instance (like +@hickwell.some_method+).
h4. Add a class method
h4. Add a Class Method
This plugin will expect that you've added a method to your model named 'last_squawk'. However, the plugin users might have already defined a method on their model named 'last_squawk' that they use for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'.
@ -478,7 +478,7 @@ end
ActiveRecord::Base.send :include, Yaffle
</ruby>
h4. Add an instance method
h4. Add an Instance Method
This plugin will add a method named 'squawk' to any Active Record objects that call 'acts_as_yaffle'. The 'squawk' method will simply set the value of one of the fields in the database.
@ -800,7 +800,7 @@ Many plugins ship with generators. When you created the plugin above, you speci
Building generators is a complex topic unto itself and this section will cover one small aspect of generators: generating a simple text file.
h4. Testing generators
h4. Testing Generators
Many rails plugin authors do not test their generators, however testing generators is quite simple. A typical generator test does the following:
@ -864,7 +864,7 @@ class YaffleDefinitionGenerator < Rails::Generator::Base
end
</ruby>
h4. The USAGE file
h4. The +USAGE+ File
If you plan to distribute your plugin, developers will expect at least a minimum of documentation. You can add simple documentation to the generator by updating the USAGE file.
@ -891,7 +891,7 @@ Description:
Adds a file with the definition of a Yaffle to the app's main directory
</shell>
h3. Add a custom generator command
h3. Add a Custom Generator Command
You may have noticed above that you can used one of the built-in rails migration commands +migration_template+. If your plugin needs to add and remove lines of text from existing files you will need to write your own generator methods.
@ -1144,7 +1144,7 @@ end
Here are a few possibilities for how to allow developers to use your plugin migrations:
h4. Create a custom rake task
h4. Create a Custom Rake Task
* *vendor/plugins/yaffle/tasks/yaffle_tasks.rake:*
@ -1165,7 +1165,7 @@ namespace :db do
end
</ruby>
h4. Call migrations directly
h4. Call Migrations Directly
* *vendor/plugins/yaffle/lib/yaffle.rb:*
@ -1191,7 +1191,7 @@ end
NOTE: several plugin frameworks such as Desert and Engines provide more advanced plugin functionality.
h4. Generate migrations
h4. Generate Migrations
Generating migrations has several advantages over other methods. Namely, you can allow other developers to more easily customize the migration. The flow looks like this:
@ -1417,7 +1417,7 @@ h4. References
* http://www.mbleigh.com/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins
* http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2.
h4. Contents of 'lib/yaffle.rb'
h4. Contents of +lib/yaffle.rb+
* *vendor/plugins/yaffle/lib/yaffle.rb:*
@ -1440,7 +1440,7 @@ end
# end
</ruby>
h4. Final plugin directory structure
h4. Final Plugin Directory Structure
The final plugin should have a directory structure that looks something like this:

View file

@ -15,7 +15,7 @@ h3. Introduction to Rack
bq. Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call.
- "Rack API Documentation":http://rack.rubyforge.org/doc
- "Rack API Documentation":http://rack.rubyforge.org/doc/
Explaining Rack is not really in the scope of this guide. In case you are not familiar with Rack's basics, you should check out the following links:
@ -30,7 +30,7 @@ h4. Rails Application's Rack Object
<tt>ActionController::Dispatcher.new</tt> is the primary Rack application object of a Rails application. Any Rack compliant web server should be using +ActionController::Dispatcher.new+ object to serve a Rails application.</p>
h4. script/server
h4. +script/server+
<tt>script/server</tt> does the basic job of creating a +Rack::Builder+ object and starting the webserver. This is Rails' equivalent of Rack's +rackup+ script.
@ -55,7 +55,7 @@ Middlewares used in the code above are primarily useful only in the development
|Rails::Rack::Static|Serves static files inside +RAILS_ROOT/public+ directory|
|Rails::Rack::Debugger|Starts Debugger|
h4. rackup
h4. +rackup+
To use +rackup+ instead of Rails' +script/server+, you can put the following inside +config.ru+ of your Rails application's root directory:
@ -119,9 +119,7 @@ h5. Adding a Middleware
You can add a new middleware to the middleware stack using any of the following methods:
* +config.middleware.add(new_middleware, args)+ - Adds the new middleware at the bottom of the middleware stack.
* +config.middleware.insert(index, new_middleware, args)+ - Adds the new middleware at the position specified by +index+ in the middleware stack.
* +config.middleware.use(new_middleware, args)+ - Adds the new middleware at the bottom of the middleware stack.
* +config.middleware.insert_before(existing_middleware, new_middleware, args)+ - Adds the new middleware before the specified existing middleware in the middleware stack.
@ -153,6 +151,16 @@ You can swap an existing middleware in the middleware stack using +config.middle
config.middleware.swap ActionController::Failsafe, Lifo::Failsafe
</ruby>
h5. Middleware Stack is an Array
The middleware stack behaves just like a normal +Array+. You can use any +Array+ methods to insert, reorder, or remove items from the stack. Methods described in the section above are just convenience methods.
For example, the following removes the middleware matching the supplied class name:
<ruby>
config.middleware.delete(middleware)
</ruby>
h4. Internal Middleware Stack
Much of Action Controller's functionality is implemented as Middlewares. The following table explains the purpose of each of them:
@ -197,10 +205,32 @@ use Rack::Head
run ActionController::Dispatcher.new
</shell>
h4. Using Rack Builder
The following shows how to replace use +Rack::Builder+ instead of the Rails supplied +MiddlewareStack+.
<strong>Clear the existing Rails middleware stack</strong>
<ruby>
# environment.rb
config.middleware.clear
</ruby>
<br />
<strong>Add a +config.ru+ file to +RAILS_ROOT+</strong>
<ruby>
# config.ru
use MyOwnStackFromStratch
run ActionController::Dispatcher.new
</ruby>
h3. Rails Metal Applications
Rails Metal applications are minimal Rack applications specially designed for integrating with a typical Rails application. As Rails Metal Applications skip all of the Action Controller stack, serving a request has no overhead from the Rails framework itself. This is especially useful for infrequent cases where the performance of the full stack Rails framework is an issue.
Ryan Bates' railscast on the "Rails Metal":http://railscasts.com/episodes/150-rails-metal provides a nice walkthrough generating and using Rails Metal.
h4. Generating a Metal Application
Rails provides a generator called +metal+ for creating a new Metal application:
@ -226,6 +256,8 @@ class Poller
end
</ruby>
Metal applications within +app/metal+ folders in plugins will also be discovered and added to the list
Metal applications are an optimization. You should make sure to "understand the related performance implications":http://weblog.rubyonrails.org/2008/12/20/performance-of-rails-metal before using it.
h4. Execution Order
@ -244,10 +276,31 @@ def call(env)
end
</ruby>
In the code above, +@metals+ is an ordered ( alphabetical ) hash of metal applications. Due to the alphabetical ordering, +aaa.rb+ will come before +bbb.rb+ in the metal chain.
In the code above, +@metals+ is an ordered hash of metal applications. Due to the default alphabetical ordering, +aaa.rb+ will come before +bbb.rb+ in the metal chain.
It is, however, possible to override the default ordering in your environment. Simply add a line like the following to +config/environment.rb+
<ruby>
config.metals = ["Bbb", "Aaa"]
</ruby>
Each string in the array should be the name of your metal class. If you do this then be warned that any metal applications not listed will not be loaded.
WARNING: Metal applications cannot return the HTTP Status +404+ to a client, as it is used for continuing the Metal chain execution. Please use normal Rails controllers or a custom middleware if returning +404+ is a requirement.
h3. Resources
h4. Learning Rack
* "Official Rack Website":http://rack.github.com
* "Introducing Rack":http://chneukirchen.org/blog/archive/2007/02/introducing-rack.html
* "Ruby on Rack #1 - Hello Rack!":http://m.onkey.org/2008/11/17/ruby-on-rack-1
* "Ruby on Rack #2 - The Builder":http://m.onkey.org/2008/11/18/ruby-on-rack-2-rack-builder
h4. Understanding Middlewares
* "Railscast on Rack Middlewares":http://railscasts.com/episodes/151-rack-middleware
h3. Changelog
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/58

View file

@ -39,7 +39,7 @@ Then the routing engine is the piece that translates that to a link to a URL suc
NOTE: Patient needs to be declared as a resource for this style of translation via a named route to be available.
h3. Quick Tour of Routes.rb
h3. Quick Tour of +routes.rb+
There are two components to routing in Rails: the routing engine itself, which is supplied as part of Rails, and the file +config/routes.rb+, which contains the actual routes that will be used by your application. Learning exactly what you can put in +routes.rb+ is the main topic of this guide, but before we dig in let's get a quick overview.
@ -221,7 +221,7 @@ Although the conventions of RESTful routing are likely to be sufficient for many
You can also add additional routes via the +:member+ and +:collection+ options, which are discussed later in this guide.
h5. Using :controller
h5. Using +:controller+
The +:controller+ option lets you use a controller name that is different from the public-facing resource name. For example, this routing entry:
@ -270,7 +270,7 @@ end
That would give you routing for +admin/photos+ and +admin/videos+ controllers.
h5. Using :singular
h5. Using +:singular+
If for some reason Rails isn't doing what you want in converting the plural resource name to a singular name in member routes, you can override its judgment with the +:singular+ option:
@ -280,9 +280,9 @@ map.resources :teeth, :singular => "tooth"
TIP: Depending on the other code in your application, you may prefer to add additional rules to the +Inflector+ class instead.
h5. Using :requirements
h5. Using +:requirements+
You an use the +:requirements+ option in a RESTful route to impose a format on the implied +:id+ parameter in the singular routes. For example:
You can use the +:requirements+ option in a RESTful route to impose a format on the implied +:id+ parameter in the singular routes. For example:
<ruby>
map.resources :photos, :requirements => {:id => /[A-Z][A-Z][0-9]+/}
@ -290,11 +290,11 @@ map.resources :photos, :requirements => {:id => /[A-Z][A-Z][0-9]+/}
This declaration constrains the +:id+ parameter to match the supplied regular expression. So, in this case, +/photos/1+ would no longer be recognized by this route, but +/photos/RR27+ would.
h5. Using :conditions
h5. Using +:conditions+
Conditions in Rails routing are currently used only to set the HTTP verb for individual routes. Although in theory you can set this for RESTful routes, in practice there is no good reason to do so. (You'll learn more about conditions in the discussion of classic routing later in this guide.)
h5. Using :as
h5. Using +:as+
The +:as+ option lets you override the normal naming for the actual generated paths. For example:
@ -315,7 +315,7 @@ will recognize incoming URLs containing +image+ but route the requests to the Ph
NOTE: The helpers will be generated with the name of the resource, not the path name. So in this case, you'd still get +photos_path+, +new_photo_path+, and so on.
h5. Using :path_names
h5. Using +:path_names+
The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in URLs:
@ -338,7 +338,7 @@ TIP: If you find yourself wanting to change this option uniformly for all of you
config.action_controller.resources_path_names = { :new => 'make', :edit => 'change' }
</ruby>
h5. Using :path_prefix
h5. Using +:path_prefix+
The +:path_prefix+ option lets you add additional parameters that will be prefixed to the recognized paths. For example, suppose each photo in your application belongs to a particular photographer. In that case, you might declare this route:
@ -357,7 +357,7 @@ NOTE: In most cases, it's simpler to recognize URLs of this sort by creating nes
NOTE: You can also use +:path_prefix+ with non-RESTful routes.
h5. Using :name_prefix
h5. Using +:name_prefix+
You can use the :name_prefix option to avoid collisions between routes. This is most useful when you have two resources with the same name that use +:path_prefix+ to map differently. For example:
@ -372,7 +372,7 @@ This combination will give you route helpers such as +photographer_photos_path+
NOTE: You can also use +:name_prefix+ with non-RESTful routes.
h5. Using :only and :except
h5. Using +:only+ and +:except+
By default, Rails creates routes for all seven of the default actions (index, show, new, create, edit, update, and destroy) for every RESTful route in your application. You can use the +:only+ and +:except+ options to fine-tune this behavior. The +:only+ option specifies that only certain routes should be generated:
@ -432,7 +432,7 @@ In addition to the routes for magazines, this declaration will also create route
This will also create routing helpers such as +magazine_ads_url+ and +edit_magazine_ad_path+.
h5. Using :name_prefix
h5. Using +:name_prefix+
The +:name_prefix+ option overrides the automatically-generated prefix in nested route helpers. For example,
@ -457,7 +457,7 @@ ads_url(@magazine)
edit_ad_path(@magazine, @ad)
</ruby>
h5. Using :has_one and :has_many
h5. Using +:has_one+ and +:has_many+
The +:has_one+ and +:has_many+ options provide a succinct notation for simple nested routes. Use +:has_one+ to nest a singleton resource, or +:has_many+ to nest a plural resource:
@ -748,7 +748,7 @@ end
The importance of +map.with_options+ has declined with the introduction of RESTful routes.
h3. Formats and respond_to
h3. Formats and +respond_to+
There's one more way in which routing can do different things depending on differences in the incoming HTTP request: by issuing a response that corresponds to what the request specifies that it will accept. In Rails routing, you can control this with the special +:format+ parameter in the route.
@ -796,7 +796,7 @@ h3. The Empty Route
Don't confuse the default routes with the empty route. The empty route has one specific purpose: to route requests that come in to the root of the web site. For example, if your site is example.com, then requests to +http://example.com+ or +http://example.com/+ will be handled by the empty route.
h4. Using map.root
h4. Using +map.root+
The preferred way to set up the empty route is with the +map.root+ command:
@ -829,7 +829,7 @@ h3. Inspecting and Testing Routes
Routing in your application should not be a "black box" that you never open. Rails offers built-in tools for both inspecting and testing routes.
h4. Seeing Existing Routes with rake
h4. Seeing Existing Routes with +rake+
If you want a complete list of all of the available routes in your application, run the +rake routes+ command. This will dump all of your routes to the console, in the same order that they appear in +routes.rb+. For each route, you'll see:
@ -851,7 +851,7 @@ TIP: You'll find that the output from +rake routes+ is much more readable if you
h4. Testing Routes
Routes should be included in your testing strategy (just like the rest of your application). Rails offers three "built-in assertions":http://api.rubyonrails.com/classes/ActionController/Assertions/RoutingAssertions.html designed to make testing routes simpler:
Routes should be included in your testing strategy (just like the rest of your application). Rails offers three "built-in assertions":http://api.rubyonrails.org/classes/ActionController/Assertions/RoutingAssertions.html designed to make testing routes simpler:
* +assert_generates+
* +assert_recognizes+

View file

@ -23,13 +23,13 @@ The Gartner Group however estimates that 75% of attacks are at the web applicati
The threats against web applications include user account hijacking, bypass of access control, reading or modifying sensitive data, or presenting fraudulent content. Or an attacker might be able to install a Trojan horse program or unsolicited e-mail sending software, aim at financial enrichment or cause brand name damage by modifying company resources. In order to prevent attacks, minimize their impact and remove points of attack, first of all, you have to fully understand the attack methods in order to find the correct countermeasures. That is what this guide aims at.
In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the <a href="#additionalresources">Additional Resources</a> chapter). I do it manually because thats how you find the nasty logical security problems.
In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the <a href="#additional-resources">Additional Resources</a> chapter). I do it manually because thats how you find the nasty logical security problems.
h3. Sessions
A good place to start looking at security is with sessions, which can be vulnerable to particular attacks.
h4. What are sessions?
h4. What are Sessions?
-- _HTTP is a stateless protocol. Sessions make it stateful._
@ -49,7 +49,7 @@ h4. Session id
A session id consists of the hash value of a random string. The random string is the current time, a random number between 0 and 1, the process id number of the Ruby interpreter (also basically a random number) and a constant string. Currently it is not feasible to brute-force Rails' session ids. To date MD5 is uncompromised, but there have been collisions, so it is theoretically possible to create another input text with the same hash value. But this has had no security impact to date.
h4. Session hijacking
h4. Session Hijacking
-- _Stealing a user's session id lets an attacker use the web application in the victim's name._
@ -67,7 +67,7 @@ Hence, the cookie serves as temporary authentication for the web application. Ev
The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from $10$1000 (depending on the available amount of funds), $0.40$20 for credit card numbers, $1$8 for online auction site accounts and $4$30 for email passwords, according to the "Symantec Global Internet Security Threat Report":http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf.
h4. Session guidelines
h4. Session Guidelines
-- _Here are some general guidelines on sessions._
@ -77,7 +77,7 @@ This will also be a good idea, if you modify the structure of an object and old
* _(highlight)Critical data should not be stored in session_. If the user clears his cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data.
h4. Session storage
h4. Session Storage
-- _Rails provides several storage mechanisms for the session hashes. The most important are ActiveRecordStore and CookieStore._
@ -100,7 +100,7 @@ config.action_controller.session = {
There are, however, derivatives of CookieStore which encrypt the session hash, so the client cannot see it.
h4. Replay attacks for CookieStore sessions
h4. Replay Attacks for CookieStore Sessions
-- _Another sort of attack you have to be aware of when using CookieStore is the replay attack._
@ -116,7 +116,7 @@ Including a nonce (a random value) in the session solves replay attacks. A nonce
The best _(highlight)solution against it is not to store this kind of data in a session, but in the database_. In this case store the credit in the database and the logged_in_user_id in the session.
h4. Session fixation
h4. Session Fixation
-- _Apart from stealing a user's session id, the attacker may fix a session id known to him. This is called session fixation._
@ -131,7 +131,7 @@ This attack focuses on fixing a user's session id known to the attacker, and for
# As the new trap session is unused, the web application will require the user to authenticate.
# From now on, the victim and the attacker will co-use the web application with the same session: The session became valid and the victim didn't notice the attack.
h4. Session fixation Countermeasures
h4. Session Fixation Countermeasures
-- _One line of code will protect you from session fixation._
@ -145,7 +145,7 @@ If you use the popular RestfulAuthentication plugin for user management, add res
Another countermeasure is to _(highlight)save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _(highlight)These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way.
h4. Session expiry
h4. Session Expiry
-- _Sessions that never expire extend the time-frame for attacks such as cross-site reference forgery (CSRF), session hijacking and session fixation._
@ -172,7 +172,7 @@ self.delete_all "updated_at < '#{time.to_s(:db)}' OR
created_at < '#{2.days.ago.to_s(:db)}'"
</ruby>
h3. Cross-Site Reference Forgery (CSRF)
h3. Cross-Site Request Forgery (CSRF)
-- _This attack method works by including malicious code or a link in a page that accesses a web application that the user is believed to have authenticated. If the session for that web application has not timed out, an attacker may execute unauthorized commands._
@ -278,7 +278,7 @@ Another redirection and self-contained XSS attack works in Firefox and Opera by
This example is a Base64 encoded JavaScript which displays a simple message box. In a redirection URL, an attacker could redirect to this URL with the malicious code in it. As a countermeasure, _(highlight)do not allow the user to supply (parts of) the URL to be redirected to_.
h4. File uploads
h4. File Uploads
-- _Make sure file uploads don't overwrite important files, and process media files asynchronously._
@ -303,7 +303,7 @@ A significant disadvantage of synchronous processing of file uploads (as the att
The solution to this is best to _(highlight)process media files asynchronously_: Save the media file and schedule a processing request in the database. A second process will handle the processing of the file in the background.
h4. Executable code in file uploads
h4. Executable Code in File Uploads
-- _Source code in uploaded files may be executed when placed in specific directories. Do not place file uploads in Rails' /public directory if it is Apache's home directory._
@ -311,7 +311,7 @@ The popular Apache web server has an option called DocumentRoot. This is the hom
_(highlight)If your Apache DocumentRoot points to Rails' /public directory, do not put file uploads in it_, store files at least one level downwards.
h4. File downloads
h4. File Downloads
-- _Make sure users cannot download arbitrary files._
@ -333,11 +333,11 @@ send_file filename, :disposition => 'inline'
Another (additional) approach is to store the file names in the database and name the files on the disk after the ids in the database. This is also a good approach to avoid possible code in an uploaded file to be executed. The attachment_fu plugin does this in a similar way.
h3. Intranet and Admin security
h3. Intranet and Admin Security
-- _Intranet and administration interfaces are popular attack targets, because they allow privileged access. Although this would require several extra-security measures, the opposite is the case in the real world._
In 2007 there was the first tailor-made "Trojan":http://www.symantec.com/enterprise/security_response/weblog/2007/08/a_monster_trojan.html which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.
In 2007 there was the first tailor-made trojan which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.
*XSS* If your application re-displays malicious user input from the extranet, the application will be vulnerable to XSS. User names, comments, spam reports, order addresses are just a few uncommon examples, where there can be XSS.
@ -347,15 +347,15 @@ Refer to the Injection section for countermeasures against XSS. It is _(highligh
*CSRF* Cross-Site Reference Forgery (CSRF) is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface.
A real-world example is a "router reconfiguration by CSRF":http://www.symantec.com/enterprise/security_response/weblog/2008/01/driveby_pharming_in_the_wild.html. The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had his credentials stolen.
A real-world example is a "router reconfiguration by CSRF":http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352. The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had his credentials stolen.
Another example changed Google Adsense's e-mail address and password by "CSRF":http://www.0x000000.com/index.php?i=213&bin=11010101. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change his credentials.
Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change his credentials.
Another popular attack is to spam your web application, your blog or forum to propagate malicious XSS. Of course, the attacker has to know the URL structure, but most Rails URLs are quite straightforward or they will be easy to find out, if it is an open-source application's admin interface. The attacker may even do 1,000 lucky guesses by just including malicious IMG-tags which try every possible combination.
For _(highlight)countermeasures against CSRF in administration interfaces and Intranet applications, refer to the countermeasures in the CSRF section_.
h4. Additional precautions
h4. Additional Precautions
The common admin interface works like this: it's located at www.example.com/admin, may be accessed only if the admin flag is set in the User model, re-displays user input and allows the admin to delete/add/edit whatever data desired. Here are some thoughts about this:
@ -365,7 +365,7 @@ The common admin interface works like this: it's located at www.example.com/admi
* _(highlight)Put the admin interface to a special sub-domain_ such as admin.application.com and make it a separate application with its own user management. This makes stealing an admin cookie from the usual domain, www.application.com, impossible. This is because of the same origin policy in your browser: An injected (XSS) script on www.application.com may not read the cookie for admin.application.com and vice-versa.
h3. Mass assignment
h3. Mass Assignment
-- _Without any precautions Model.new(params[:model]) allows attackers to set any database column's value._
@ -392,15 +392,31 @@ params[:user] #=> {:name => “ow3ned”, :admin => true}
So if you create a new user using mass-assignment, it may be too easy to become an administrator.
Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in rails 2.3. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example:
<ruby>
class Person < ActiveRecord::Base
has_many :credits
accepts_nested_attributes_for :children
end
class Child < ActiveRecord::Base
belongs_to :person
end
</ruby>
As a result, the vulnerability is extended beyond simply exposing column assignment, allowing attackers the ability to create entirely new records in referenced tables (children in this case).
h4. Countermeasures
To avoid this, Rails provides two class methods in your ActiveRecord class to control access to your attributes. The attr_protected method takes a list of attributes that will not be accessible for mass-assignment. For example:
To avoid this, Rails provides two class methods in your Active Record class to control access to your attributes. The +attr_protected+ method takes a list of attributes that will not be accessible for mass-assignment. For example:
<ruby>
attr_protected :admin
</ruby>
A much better way, because it follows the whitelist-principle, is the +attr_accessible+ method. It is the exact opposite of attr_protected, because _(highlight)it takes a list of attributes that will be accessible_. All other attributes will be protected. This way you won't forget to protect attributes when adding new ones in the course of development. Here is an example:
A much better way, because it follows the whitelist-principle, is the +attr_accessible+ method. It is the exact opposite of +attr_protected+, because _(highlight)it takes a list of attributes that will be accessible_. All other attributes will be protected. This way you won't forget to protect attributes when adding new ones in the course of development. Here is an example:
<ruby>
attr_accessible :name
@ -416,7 +432,15 @@ params[:user] #=> {:name => "ow3ned", :admin => true}
@user.admin #=> true
</ruby>
h3. User management
A more paranoid technique to protect your whole project would be to enforce that all models whitelist their accessible attributes. This can be easily achieved with a very simple initializer:
<ruby>
ActiveRecord::Base.send(:attr_accessible, nil)
</ruby>
This will create an empty whitelist of attributes available for mass assignment for all models in your app. As such, your models will need to explicitly whitelist accessible parameters by using an +attr_accessible+ declaration. This technique is best applied at the start of a new project. However, for an existing project with a thorough set of functional tests, it should be straightforward and relatively quick to insert this initializer, run your tests, and expose each attribute (via +attr_accessible+) as dictated by your failing tests.
h3. User Management
-- _Almost every web application has to deal with authorization and authentication. Instead of rolling your own, it is advisable to use common plug-ins. But keep them up-to-date, too. A few additional precautions can make your application even more secure._
@ -443,7 +467,7 @@ SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1
And thus it found the first user in the database, returned it and logged him in. You can find out more about it in "my blog post":http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/. _(highlight)It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this.
h4. Brute-forcing accounts
h4. Brute-Forcing Accounts
-- _Brute-force attacks on accounts are trial and error attacks on the login credentials. Fend them off with more generic error messages and possibly require to enter a CAPTCHA._
@ -455,7 +479,7 @@ However, what most web application designers neglect, are the forgot-password pa
In order to mitigate such attacks, _(highlight)display a generic error message on forgot-password pages, too_. Moreover, you can _(highlight)require to enter a CAPTCHA after a number of failed logins from a certain IP address_. Note, however, that this is not a bullet-proof solution against automatic programs, because these programs may change their IP address exactly as often. However, it raises the barrier of an attack.
h4. Account hijacking
h4. Account Hijacking
-- _Many web applications make it easy to hijack user accounts. Why not be different and make it more difficult?_
@ -508,7 +532,7 @@ By default, Rails logs all requests being made to the web application. But log f
filter_parameter_logging :password
</ruby>
h4. Good passwords
h4. Good Passwords
-- _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._
@ -520,7 +544,7 @@ It is interesting that only 4% of these passwords were dictionary words and the
A good password is a long alphanumeric combination of mixed cases. As this is quite hard to remember, it is advisable to enter only the _(highlight)first letters of a sentence that you can easily remember_. For example "The quick brown fox jumps over the lazy dog" will be "Tqbfjotld". Note that this is just an example, you should not use well known phrases like these, as they might appear in cracker dictionaries, too.
h4. Regular expressions
h4. Regular Expressions
-- _A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z._
@ -544,7 +568,7 @@ Whereas %0A is a line feed in URL encoding, so Rails automatically converts it t
/\A[\w\.\-\+]+\z/
</ruby>
h4. Privilege escalation
h4. Privilege Escalation
-- _Changing a single parameter may give the user unauthorized access. Remember that every parameter may be changed, no matter how much you hide or obfuscate it._
@ -605,7 +629,7 @@ SELECT * FROM projects WHERE name = '' OR 1 --'
The two dashes start a comment ignoring everything after it. So the query returns all records from the projects table including those blind to the user. This is because the condition is true for all records.
h5. Bypassing authorization
h5. Bypassing Authorization
Usually a web application includes access control. The user enters his login credentials, the web applications tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user.
@ -621,7 +645,7 @@ SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'&gt;'1'
This will simply find the first record in the database, and grants access to this user.
h5. Unauthorized reading
h5. Unauthorized Reading
The UNION statement connects two SQL queries and returns the data in one set. An attacker can use it to read arbitrary data from the database. Let's take the example from above:
@ -668,7 +692,7 @@ h4. Cross-Site Scripting (XSS)
-- _The most widespread, and one of the most devastating security vulnerabilities in web applications is XSS. This malicious attack injects client-side executable code. Rails provides helper methods to fend these attacks off._
h5. Entry points
h5. Entry Points
An entry point is a vulnerable URL and its parameters where an attacker can start an attack.
@ -676,7 +700,7 @@ The most common entry points are message posts, user comments, and guest books,
XSS attacks work like this: An attacker injects some code, the web application saves it and displays it on a page, later presented to a victim. Most XSS examples simply display an alert box, but it is more powerful than that. XSS can steal the cookie, hijack the session, redirect the victim to a fake website, display advertisements for the benefit of the attacker, change elements on the web site to get confidential information or install malicious software through security holes in the web browser.
During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The "Symantec Global Internet Security threat report":http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf also documented 239 browser plug-in vulnerabilities in the last six months of 2007. "Mpack":http://pandalabs.pandasecurity.com/archive/MPack-uncovered_2100_.aspx is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites "were hacked":http://www.0x000000.com/?i=556 like this, among them the British government, United Nations, and many more high targets.
During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The "Symantec Global Internet Security threat report":http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf also documented 239 browser plug-in vulnerabilities in the last six months of 2007. "Mpack":http://pandalabs.pandasecurity.com/archive/MPack-uncovered_2100_.aspx is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites were hacked like this, among them the British government, United Nations, and many more high targets.
A relatively new, and unusual, form of entry points are banner advertisements. In earlier 2008, malicious code appeared in banner ads on popular sites, such as MySpace and Excite, according to "Trend Micro":http://blog.trendmicro.com/myspace-excite-and-blick-serve-up-malicious-banner-ads/.
@ -697,7 +721,7 @@ This JavaScript code will simply display an alert box. The next examples do exac
<table background="javascript:alert('Hello')">
</html>
h6. Cookie theft
h6. Cookie Theft
These examples don't do any harm so far, so let's see how an attacker can steal the user's cookie (and thus hijack the user's session). In JavaScript you can use the document.cookie property to read and write the document's cookie. JavaScript enforces the same origin policy, that means a script from one domain cannot access cookies of another domain. The document.cookie property holds the cookie of the originating web server. However, you can read and write this property, if you embed the code directly in the HTML document (as it happens with XSS). Inject this anywhere in your web application to see your own cookie on the result page:
@ -727,7 +751,7 @@ With web page defacement an attacker can do a lot of things, for example, presen
<iframe name=”StatPage” src="http://58.xx.xxx.xxx" width=5 height=5 style=”display:none”></iframe>
</html>
This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iFrame is taken from an "actual attack":http://www.symantec.com/enterprise/security_response/weblog/2007/06/italy_under_attack_mpack_gang.html on legitimate Italian sites using the "Mpack attack framework":http://isc.sans.org/diary.html?storyid=3015. Mpack tries to install malicious software through security holes in the web browser very successfully, 50% of the attacks succeed.
This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iframe is taken from an actual attack on legitimate Italian sites using the "Mpack attack framework":http://isc.sans.org/diary.html?storyid=3015. Mpack tries to install malicious software through security holes in the web browser very successfully, 50% of the attacks succeed.
A more specialized attack could overlap the entire web site or display a login form, which looks the same as the site's original, but transmits the user name and password to the attackers site. Or it could use CSS and/or JavaScript to hide a legitimate link in the web application, and display another one at its place which redirects to a fake web site.
@ -772,7 +796,7 @@ Network traffic is mostly based on the limited Western alphabet, so new characte
This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus “get to know your enemy”, is the "Hackvertor":http://www.businessinfo.co.uk/labs/hackvertor/hackvertor.php. Rails sanitize() method does a good job to fend off encoding attacks.
h5. Examples from the underground
h5. Examples from the Underground
_In order to understand today's attacks on web applications, it's best to take a look at some real-world attack vectors._
@ -786,7 +810,7 @@ The following is an excerpt from the "Js.Yamanner@m":http://www.symantec.com/sec
The worms exploits a hole in Yahoo's HTML/JavaScript filter, which usually filters all target and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application.
Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details and a video demonstration on "Rosario Valotta's website":http://rosario.valotta.googlepages.com/home. Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with.
Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on "Rosario Valotta's paper":http://www.xssed.com/article/9/Paper_A_PoC_of_a_cross_webmail_worm_XWW_called_Njuda_connection/. Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with.
In December 2006, 34,000 actual user names and passwords were stolen in a "MySpace phishing attack":http://news.netcraft.com/archives/2006/10/27/myspace_accounts_compromised_by_phishers.html. The idea of the attack was to create a profile page named “login_home_index_html”, so the URL looked very convincing. Specially-crafted HTML and CSS was used to hide the genuine MySpace content from the page and instead display its own login form.
@ -834,11 +858,10 @@ This example, again, showed that a blacklist filter is never complete. However,
h4. Textile Injection
-- _If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://whytheluckystiff.net/ruby/redcloth/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS._
-- _If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://redcloth.org/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS._
For example, RedCloth translates +_test_+ to &lt;em&gt;test&lt;em&gt;, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS. Get the "all-new version 4":http://www.redcloth.org that removed serious bugs. However, even that version has "some security bugs":http://www.rorsecurity.info/journal/2008/10/13/new-redcloth-security.html, so the countermeasures still apply. Here is an example for version 3.0.4:
<ruby>
RedCloth.new('<script>alert(1)</script>').to_html
# => "<script>alert(1)</script>"
@ -947,7 +970,7 @@ Content-Type: text/html
Under certain circumstances this would present the malicious HTML to the victim. However, this seems to work with Keep-Alive connections, only (and many browsers are using one-time connections). But you can't rely on this. _(highlight)In any case this is a serious bug, and you should update your Rails to version 2.0.5 or 2.1.2 to eliminate Header Injection (and thus response splitting) risks._
h3. Additional resources
h3. Additional Resources
The security landscape shifts and it is important to keep up to date, because missing a new vulnerability can be catastrophic. You can find additional resources about (Rails) security here:
@ -955,7 +978,6 @@ The security landscape shifts and it is important to keep up to date, because mi
* Subscribe to the Rails security "mailing list":http://groups.google.com/group/rubyonrails-security
* "Keep up to date on the other application layers":http://secunia.com/ (they have a weekly newsletter, too)
* A "good security blog":http://ha.ckers.org/blog/ including the "Cross-Site scripting Cheat Sheet":http://ha.ckers.org/xss.html
* Another "good security blog":http://www.0x000000.com/ with some Cheat Sheets, too
h3. Changelog

View file

@ -20,7 +20,7 @@ h3. Introduction to Testing
Testing support was woven into the Rails fabric from the beginning. It wasn't an "oh! let's bolt on support for running tests because they're new and cool" epiphany. Just about every Rails application interacts heavily with a database - and, as a result, your tests will need a database to interact with as well. To write efficient tests, you'll need to understand how to set up this database and populate it with sample data.
h4. The 3 Environments
h4. The Three Environments
Every Rails application you build has 3 sides: a side for production, a side for development, and a side for testing.
@ -52,7 +52,7 @@ h4. The Low-Down on Fixtures
For good tests, you'll need to give some thought to setting up test data. In Rails, you can handle this by defining and customizing fixtures.
h5. What Are Fixtures?
h5. What are Fixtures?
_Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and assume one of two formats: *YAML* or *CSV*. In this guide we will use *YAML* which is the preferred format.
@ -110,9 +110,9 @@ h5. Fixtures in Action
Rails by default automatically loads all fixtures from the 'test/fixtures' folder for your unit and functional test. Loading involves three steps:
* Remove any existing data from the table corresponding to the fixture
* Load the fixture data into the table
* Dump the fixture data into a variable in case you want to access it directly
* Remove any existing data from the table corresponding to the fixture
* Load the fixture data into the table
* Dump the fixture data into a variable in case you want to access it directly
h5. Hashes with Special Powers
@ -140,9 +140,9 @@ h3. Unit Testing your Models
In Rails, unit tests are what you write to test your models.
For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practises. I will be using examples from this generated code and would be supplementing it with additional examples where necessary.
For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. I will be using examples from this generated code and would be supplementing it with additional examples where necessary.
NOTE: For more information on Rails _scaffolding_, refer to "Getting Started with Rails":../getting_started_with_rails.html
NOTE: For more information on Rails _scaffolding_, refer to "Getting Started with Rails":getting_started.html
When you use +script/generate scaffold+, for a resource among other things it creates a test stub in the +test/unit+ folder:
@ -396,7 +396,7 @@ There are a bunch of different types of assertions you can use. Here's the compl
|+assert_no_match( regexp, string, [msg] )+ |Ensures that a string doesn't matches the regular expression.|
|+assert_in_delta( expecting, actual, delta, [msg] )+ |Ensures that the numbers +expecting+ and +actual+ are within +delta+ of each other.|
|+assert_throws( symbol, [msg] ) { block }+ |Ensures that the given block throws the symbol.|
|+assert_raises( exception1, exception2, ... ) { block }+ |Ensures that the given block raises one of the given exceptions.|
|+assert_raise( exception1, exception2, ... ) { block }+ |Ensures that the given block raises one of the given exceptions.|
|+assert_nothing_raised( exception1, exception2, ... ) { block }+ |Ensures that the given block doesn't raise one of the given exceptions.|
|+assert_instance_of( class, obj, [msg] )+ |Ensures that +obj+ is of the +class+ type.|
|+assert_kind_of( class, obj, [msg] )+ |Ensures that +obj+ is or descends from +class+.|
@ -429,7 +429,7 @@ h3. Functional Tests for Your Controllers
In Rails, testing the various actions of a single controller is called writing functional tests for that controller. Controllers handle the incoming web requests to your application and eventually respond with a rendered view.
h4. What to include in your Functional Tests
h4. What to Include in your Functional Tests
You should test for things such as:
@ -500,7 +500,7 @@ If you're familiar with the HTTP protocol, you'll know that +get+ is a type of r
All of request types are methods that you can use, however, you'll probably end up using the first two more often than the others.
h4. The 4 Hashes of the Apocalypse
h4. The Four Hashes of the Apocalypse
After a request has been made by using one of the 5 methods (+get+, +post+, etc.) and processed, you will have 4 Hash objects ready for use:
@ -582,9 +582,9 @@ assert_select "ol" do
end
</ruby>
The +assert_select+ assertion is quite powerful. For more advanced usage, refer to its "documentation":http://api.rubyonrails.com/classes/ActionController/Assertions/SelectorAssertions.html.
The +assert_select+ assertion is quite powerful. For more advanced usage, refer to its "documentation":http://api.rubyonrails.org/classes/ActionController/Assertions/SelectorAssertions.html.
h5. Additional View-based Assertions
h5. Additional View-Based Assertions
There are more assertions that are primarily used in testing views:
@ -631,7 +631,7 @@ end
Integration tests inherit from +ActionController::IntegrationTest+. This makes available some additional helpers to use in your integration tests. Also you need to explicitly include the fixtures to be made available to the test.
h4. Helpers Available for Integration tests
h4. Helpers Available for Integration Tests
In addition to the standard testing helpers, there are some additional helpers available to integration tests:
@ -741,7 +741,7 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai
|+rake test:uncommitted+ |Runs all the tests which are uncommitted. Only supports Subversion|
|+rake test:plugins+ |Run all the plugin tests from +vendor/plugins/*/**/test+ (or specify with +PLUGIN=_name_+)|
h3. Brief Note About Test::Unit
h3. Brief Note About +Test::Unit+
Ruby ships with a boat load of libraries. One little gem of a library is +Test::Unit+, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in +Test::Unit::Assertions+. The class +ActiveSupport::TestCase+ which we have been using in our unit and functional tests extends +Test::Unit::TestCase+ that it is how we can use all the basic assertions in our tests.
@ -872,7 +872,7 @@ For the purposes of unit testing a mailer, fixtures are used to provide an examp
When you generated your mailer, the generator creates stub fixtures for each of the mailers actions. If you didn't use the generator you'll have to make those files yourself.
h5. The Basic Test case
h5. The Basic Test Case
Here's a unit test to test a mailer named +UserMailer+ whose action +invite+ is used to send an invitation to a friend. It is an adapted version of the base test created by the generator for an +invite+ action.