From 886124e688de7b48f32925eca42e4185e487ec84 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 1 Feb 2009 18:25:03 +0000 Subject: Merge docrails --- railties/Rakefile | 4 +- railties/doc/guides/html/2_2_release_notes.html | 5 + railties/doc/guides/html/2_3_release_notes.html | 1033 ++++++++++++++++++++ railties/doc/guides/html/action_mailer_basics.html | 598 ++++++++++- .../doc/guides/html/actioncontroller_basics.html | 52 +- railties/doc/guides/html/active_record_basics.html | 342 +++++++ railties/doc/guides/html/creating_plugins.html | 32 +- railties/doc/guides/html/form_helpers.html | 495 +++++----- .../guides/html/getting_started_with_rails.html | 225 +++-- railties/doc/guides/html/i18n.html | 574 +++++++++-- railties/doc/guides/html/index.html | 10 +- railties/doc/guides/source/2_2_release_notes.txt | 3 +- railties/doc/guides/source/2_3_release_notes.txt | 514 ++++++++++ .../doc/guides/source/action_mailer_basics.txt | 394 +++++++- .../source/actioncontroller_basics/http_auth.txt | 39 +- .../doc/guides/source/active_record_basics.txt | 225 ++--- .../guides/source/creating_plugins/appendix.txt | 2 +- .../creating_plugins/creating_plugins/lib/main.rb | 4 + .../source/creating_plugins/generator_commands.txt | 2 +- .../guides/source/creating_plugins/migrations.txt | 23 +- .../doc/guides/source/creating_plugins/models.txt | 2 +- .../doc/guides/source/creating_plugins/tasks.txt | 4 +- .../guides/source/creating_plugins/test_setup.txt | 3 - railties/doc/guides/source/form_helpers.txt | 439 +++++---- .../guides/source/getting_started_with_rails.txt | 154 ++- railties/doc/guides/source/i18n.txt | 440 +++++++-- railties/doc/guides/source/images/posts_index.png | Bin 0 -> 5824 bytes .../doc/guides/source/images/rails_welcome.png | Bin 0 -> 76496 bytes railties/doc/guides/source/index.txt | 16 +- 29 files changed, 4690 insertions(+), 944 deletions(-) create mode 100644 railties/doc/guides/html/2_3_release_notes.html create mode 100644 railties/doc/guides/html/active_record_basics.html create mode 100644 railties/doc/guides/source/2_3_release_notes.txt create mode 100644 railties/doc/guides/source/creating_plugins/creating_plugins/lib/main.rb create mode 100644 railties/doc/guides/source/images/posts_index.png create mode 100644 railties/doc/guides/source/images/rails_welcome.png (limited to 'railties') diff --git a/railties/Rakefile b/railties/Rakefile index be0f449efc..30d3748441 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -285,7 +285,7 @@ task :guides do asciidoc_conf = 'doc/guides/asciidoc.conf' ignore = ['..', 'icons', 'images', 'templates', 'stylesheets'] - ignore << 'active_record_basics.txt' + #ignore << 'active_record_basics.txt' indexless = ['index.txt', 'authors.txt'] @@ -301,7 +301,7 @@ task :guides do end entries.each do |entry| - next if ignore.include?(entry) + next if ignore.include?(entry) or entry =~ /^\./ # because Vim is always saving hidden swap files if File.directory?(File.join(source, entry)) # If the current entry is a directory, then we will want to compile diff --git a/railties/doc/guides/html/2_2_release_notes.html b/railties/doc/guides/html/2_2_release_notes.html index 0dec014c3e..b94ab7531f 100644 --- a/railties/doc/guides/html/2_2_release_notes.html +++ b/railties/doc/guides/html/2_2_release_notes.html @@ -993,6 +993,11 @@ The %s and %d interpolation syntax for internationalization is Durations of fractional months or fractional years are deprecated. Use Ruby’s core Date and Time class arithmetic instead.

+
  • +

    +Request#relative_url_root is deprecated. Use ActionController::Base.relative_url_root instead. +

    +
  • 12. Credits

    diff --git a/railties/doc/guides/html/2_3_release_notes.html b/railties/doc/guides/html/2_3_release_notes.html new file mode 100644 index 0000000000..51b2b39b26 --- /dev/null +++ b/railties/doc/guides/html/2_3_release_notes.html @@ -0,0 +1,1033 @@ + + + + + Ruby on Rails 2.3 Release Notes + + + + + + + + +
    + + + +
    +

    Ruby on Rails 2.3 Release Notes

    +
    +
    +

    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 in the main Rails repository on GitHub or review the CHANGELOG files for the individual Rails components.

    +
    +
    +

    1. Application Architecture

    +
    +

    There are two major changes in the architecture of Rails applications: complete integration of the Rack modular web server interface, and renewed support for Rails Engines.

    +

    1.1. Rack Integration

    +

    Rails has now broken with its CGI past, and uses Rack everywhere. This required and resulted in a tremendous number of internal changes (but if you use CGI, don’t worry; Rails now supports CGI through a proxy interface.) Still, this is a major change to Rails internals. After upgrading to 2.3, you should test on your local environment and your production environment. Some things to test:

    +
      +
    • +

      +Sessions +

      +
    • +
    • +

      +Cookies +

      +
    • +
    • +

      +File uploads +

      +
    • +
    • +

      +JSON/XML APIs +

      +
    • +
    +

    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 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 converts its environment information into a Rack compatible form. +

      +
    • +
    • +

      +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 +

      +
    • +
    • +

      +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, ... +

      +
    • +
    • +

      +Using the ParamsParser middleware preprocesses any XML, JSON, or YAML requests so they can be read normally with any Rack::Request object after it. +

      +
    • +
    +

    1.2. Renewed Support for Rails Engines

    +

    After some versions without an upgrade, Rails 2.3 offers some new features for Rails Engines (Rails applications that can be embedded within other applications). First, routing files in engines are automatically loaded and reloaded now, just like your routes.rb file (this also applies to routing files in other plugins). Second, if your plugin has an app folder, then app/[models|controllers|helpers] will automatically be added to the Rails load path. Engines also support adding view paths now.

    +
    +

    2. Documentation

    +
    +

    The Ruby on Rails guides project has published several additional guides for Rails 2.3. In addition, a separate site maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the Rails wiki and early planning for a Rails Book.

    +
    +
    +

    3. Active Record

    +
    +

    Active Record gets quite a number of new features and bug fixes in Rails 2.3. The highlights include nested attributes, nested transactions, dynamic scopes, and default scopes.

    +

    3.1. Nested Attributes

    +

    Active Record can now update the attributes on nested models directly, provided you tell it to do so:

    +
    +
    +
    class Book < ActiveRecord::Base
    +  has_one :author
    +  has_many :pages
    +
    +  accepts_nested_attributes_for :author, :pages
    +end
    +

    Turning on nested attributes enables a number of things: automatic (and atomic) saving of a record together with its associated children, child-aware validations, and support for nested forms (discussed later).

    +
    +

    3.2. Nested Transactions

    +

    Active Record now supports nested transactions, a much-requested feature. Now you can write code like this:

    +
    +
    +
    User.transaction do
    +    User.create(:username => 'Admin')
    +    User.transaction(:requires_new => true) do
    +      User.create(:username => 'Regular')
    +      raise ActiveRecord::Rollback
    +    end
    +  end
    +
    +  User.find(:all)  # => Returns only Admin
    +

    Nested transactions let you roll back an inner transaction without affecting the state of the outer transaction. If you want a transaction to be nested, you must explicitly add the :requires_new option; otherwise, a nested transaction simply becomes part of the parent transaction (as it does currently on Rails 2.2). Under the covers, nested transactions are using savepoints, so they’re supported even on databases that don’t have true nested transactions. There is also a bit of magic going on to make these transactions play well with transactional fixtures during testing.

    +
    +

    3.3. Dynamic Scopes

    +

    You know about dynamic finders in Rails (which allow you to concoct methods like find_by_color_and_flavor on the fly) and named scopes (which allow you to encapsulate reusable query conditions into friendly names like currently_active). Well, now you can have dynamic scope methods. The idea is to put together syntax that allows filtering on the fly and method chaining. For example:

    +
    +
    +
    Order.scoped_by_customer_id(12)
    +Order.scoped_by_customer_id(12).find(:all,
    +  :conditions => "status = 'open'")
    +Order.scoped_by_customer_id(12).scoped_by_status("open")
    +

    There’s nothing to define to use dynamic scopes: they just work.

    +
    +

    3.4. Default Scopes

    +

    Rails 2.3 will introduce the notion of default scopes similar to named scopes, but applying to all named scopes or find methods within the model. For example, you can write default_scope :order => name ASC and any time you retrieve records from that model they’ll come out sorted by name (unless you override the option, of course).

    +
    +

    3.5. Multiple Conditions for Callbacks

    +

    When using Active Record callbacks, you can now combine :if and :unless options on the same callback, and supply multiple conditions as an array:

    +
    +
    +
    before_save :update_credit_rating, :if => :active,
    +  :unless => [:admin, :cash_only]
    +
      +
    • +

      +Lead Contributor: L. Caviola +

      +
    • +
    +

    3.6. Find with having

    +

    Rails now has a :having option on find (as well as on has_many and has_and_belongs_to_many associations) for filtering records in grouped finds. As those with heavy SQL backgrounds know, this allows filtering based on grouped results:

    +
    +
    +
    developers =  Developer.find(:all, :group => "salary",
    +  :having => "sum(salary) >  10000", :select => "salary")
    +
    +

    3.7. Hash Conditions for has_many relationships

    +

    You can once again use a hash in conditions for a has_many relationship:

    +
    +
    +
    has_many :orders, :conditions => {:status => 'confirmed'}
    +

    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).

    +
    +

    3.8. 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.

    +
    +

    3.9. 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. +

      +
    • +
    • +

      +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. +

      +
    • +
    • +

      +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) +

      +
    • +
    +
    +

    4. Action Controller

    +
    +

    Action Controller rolls out some significant changes to rendering, as well as improvements in routing and other areas, in this release.

    +

    4.1. Unified Rendering

    +

    ActionController::Base#render is a lot smarter about deciding what to render. Now you can just tell it what to render and expect to get the right results. In older versions of Rails, you often need to supply explicit information to render:

    +
    +
    +
    render :file => '/tmp/random_file.erb'
    +render :template => 'other_controller/action'
    +render :action => 'show'
    +

    Now in Rails 2.3, you can just supply what you want to render:

    +
    +
    +
    render '/tmp/random_file.erb'
    +render 'other_controller/action'
    +render 'show'
    +render :show
    +

    Rails chooses between file, template, and action depending on whether there is a leading slash, an embedded slash, or no slash at all in what’s to be rendered. Note that you can also use a symbol instead of a string when rendering an action. Other rendering styles (:inline, :text, :update, :nothing, :json, :xml, :js) still require an explicit option.

    +

    4.2. Application Controller Renamed

    +

    If you’re one of the people who has always been bothered by the special-case naming of application.rb, rejoice! It’s been reworked to be application_controller.rb in Rails 2.3. In addition, there’s a new rake task, rake rails:update:application_controller to do this automatically for you - and it will be run as part of the normal rake rails:update process.

    + +

    4.3. HTTP Digest Authentication Support

    +

    Rails now has built-in support for HTTP digest authentication. To use it, you call authenticate_or_request_with_http_digest with a block that returns the user’s password (which is then hashed and compared against the transmitted credentials):

    +
    +
    +
    class PostsController < ApplicationController
    +  Users = {"dhh" => "secret"}
    +  before_filter :authenticate
    +
    +  def secret
    +    render :text => "Password Required!"
    +  end
    +
    +  private
    +  def authenticate
    +    realm = "Application"
    +    authenticate_or_request_with_http_digest(realm) do |name|
    +      Users[name]
    +    end
    +  end
    +end
    +
    +

    4.4. More Efficient Routing

    +

    There are a couple of significant routing changes in Rails 2.3. The formatted_ route helpers are gone, in favor just passing in :format as an option. This cuts down the route generation process by 50% for any resource - and can save a substantial amount of memory (up to 100MB on large applications). If your code uses the formatted_ helpers, it will still work for the time being - but that behavior is deprecated and your application will be more efficient if you rewrite those routes using the new standard. Another big change is that Rails now supports multiple routing files, not just routes.rb. You can use RouteSet#add_configuration_file to bring in more routes at any time - without clearing the currently-loaded routes. While this change is most useful for Engines, you can use it in any application that needs to load routes in batches.

    +

    Lead Contributors: Aaron Batalion

    +

    4.5. Rack-based Lazy-loaded Sessions

    +

    A big change pushed the underpinnings of Action Controller session storage down to the Rack level. This involved a good deal of work in the code, though it should be completely transparent to your Rails applications (as a bonus, some icky patches around the old CGI session handler got removed). It’s still significant, though, for one simple reason: non-Rails Rack applications have access to the same session storage handlers (and therefore the same session) as your Rails applications. In addition, sessions are now lazy-loaded (in line with the loading improvements to the rest of the framework). This means that you no longer need to explicitly disable sessions if you don’t want them; just don’t refer to them and they won’t load.

    +

    4.6. MIME Type Handling Changes

    +

    There are a couple of changes to the code for handling MIME types in Rails. First, MIME::Type now implements the =~ operator, making things much cleaner when you need to check for the presence of a type that has synonyms:

    +
    +
    +
    if content_type && Mime::JS =~ content_type
    +  # do something cool
    +end
    +
    +Mime::JS =~ "text/javascript"        => true
    +Mime::JS =~ "application/javascript" => true
    +

    The other change is that the framework now uses the Mime::JS when checking for javascript in various spots, making it handle those alternatives cleanly.

    +

    Lead Contributor: Seth Fitzsimmons

    +

    4.7. Optimization of respond_to

    +

    In some of the first fruits of the Rails-Merb team merger, Rails 2.3 includes some optimizations for the respond_to method, which is of course heavily used in many Rails applications to allow your controller to format results differently based on the MIME type of the incoming request. After eliminating a call to method_missing and some profiling and tweaking, we’re seeing an 8% improvement in the number of requests per second served with a simple respond_to that switches between three formats. The best part? No change at all required to the code of your application to take advantage of this speedup.

    +

    4.8. 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.

    +
    +

    4.9. 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.

    +

    4.10. Other Action Controller Changes

    +
      +
    • +

      +ETag handling has been cleaned up a bit: Rails will now skip sending an ETag header when there’s no body to the response or when sending files with send_file. +

      +
    • +
    • +

      +The fact that Rails checks for IP spoofing can be a nuisance for sites that do heavy traffic with cell phones, because their proxies don’t generally set things up right. If that’s you, you can now set ActionController::Base.ip_spoofing_check = false to disable the check entirely. +

      +
    • +
    • +

      +ActionController::Dispatcher now implements its own middleware stack, which you can see by running rake middleware. +

      +
    • +
    • +

      +Cookie sessions now have persistent session identifiers, with API compatibility with the server-side stores. +

      +
    • +
    • +

      +You can now use symbols for the :type option of send_file and send_data, like this: send_file("fabulous.png", :type => :png). +

      +
    • +
    • +

      +The :only and :except options for map.resources are no longer inherited by nested resources. +

      +
    • +
    +
    +

    5. Action View

    +
    +

    Action View in Rails 2.3 picks up nested model forms, improvements to render, more flexible prompts for the date select helpers, and a speedup in asset caching, among other things.

    +

    5.1. Nested Object Forms

    +

    Provided the parent model accepts nested attributes for the child objects (as discussed in the Active Record section), you can create nested forms using form_for and field_for. These forms can be nested arbitrarily deep, allowing you to edit complex object hierarchies on a single view without excessive code. For example, given this model:

    +
    +
    +
    class Customer < ActiveRecord::Base
    +  has_many :orders
    +
    +  accepts_nested_attributes_for :orders, :allow_destroy => true
    +end
    +

    You can write this view in Rails 2.3:

    +
    +
    +
    <% form_for @customer do |customer_form| %>
    +  <div>
    +    <%= customer_form.label :name, 'Customer Name:' %>
    +    <%= customer_form.text_field :name %>
    +  </div>
    +
    +  <!-- Here we call fields_for on the customer_form builder instance.
    +       The block is called for each member of the orders collection. -->
    +  <% customer_form.fields_for :orders do |order_form| %>
    +      <p>
    +        <div>
    +          <%= order_form.label :number, 'Order Number:' %>
    +          <%= order_form.text_field :number %>
    +        </div>
    +
    +  <!-- The allow_destroy option in the model enables deletion of
    +       child records. -->
    +        <% unless order_form.object.new_record? %>
    +          <div>
    +            <%= order_form.label :_delete, 'Remove:' %>
    +            <%= order_form.check_box :_delete %>
    +          </div>
    +        <% end %>
    +      </p>
    +    <% end %>
    +  <% end %>
    +
    +  <%= customer_form.submit %>
    +<% end %>
    +
    +

    5.2. Smart Rendering of Partials

    +

    The render method has been getting smarter over the years, and it’s even smarter now. If you have an object or a collection and an appropriate partial, and the naming matches up, you can now just render the object and things will work. For example, in Rails 2.3, these render calls will work in your view (assuming sensible naming):

    +
    +
    +
    render @article  # Equivalent of render :partial => 'articles/_article', :object => @article
    +render @articles # Equivalent of render :partial => 'articles/_article', :collection => @articles
    + +

    5.3. Prompts for Date Select Helpers

    +

    In Rails 2.3, you can supply custom prompts for the various date select helpers (date_select, time_select, and datetime_select), the same way you can with collection select helpers. You can supply a prompt string or a hash of individual prompt strings for the various components. You can also just set :prompt to true to use the custom generic prompt:

    +
    +
    +
    select_datetime(DateTime.now, :prompt => true)
    +
    +select_datetime(DateTime.now, :prompt => "Choose date and time")
    +
    +select_datetime(DateTime.now, :prompt =>
    +  {:day => 'Choose day', :month => 'Choose month',
    +   :year => 'Choose year', :hour => 'Choose hour',
    +   :minute => 'Choose minute'})
    +

    Lead Contributor: Sam Oliver

    +

    5.4. AssetTag Timestamp Caching

    +

    You’re likely familiar with Rails' practice of adding timestamps to static asset paths as a "cache buster." This helps ensure that stale copies of things like images and stylesheets don’t get served out of the user’s browser cache when you change them on the server. You can now modify this behavior with the cache_asset_timestamps configuration option for Action View. If you enable the cache, then Rails will calculate the timestamp once when it first serves an asset, and save that value. This means fewer (expensive) file system calls to serve static assets - but it also means that you can’t modify any of the assets while the server is running and expect the changes to get picked up by clients.

    +

    5.5. Asset Hosts as Objects

    +

    Asset hosts get more flexible in edge Rails with the ability to declare an asset host as a specific object that responds to a call. This allows you to to implement any complex logic you need in your asset hosting.

    +
    +

    5.6. grouped_options_for_select Helper Method

    +

    Action View already haD a bunch of helpers to aid in generating select controls, but now there’s one more: grouped_options_for_select. This one accepts an array or hash of strings, and converts them into a string of option tags wrapped with optgroup tags. For example:

    +
    +
    +
    grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]],
    +  "Cowboy Hat", "Choose a product...")
    +

    returns

    +
    +
    +
    <option value="">Choose a product...</option>
    +<optgroup label="Hats">
    +  <option value="Baseball Cap">Baseball Cap</option>
    +  <option selected="selected" value="Cowboy Hat">Cowboy Hat</option>
    +</optgroup>
    +

    5.7. Other Action View Changes

    +
      +
    • +

      +Token generation for CSRF protection has been simplified; now Rails uses a simple random string generated by ActiveSupport::SecureRandom rather than mucking around with session IDs. +

      +
    • +
    • +

      +auto_link now properly applies options (such as :target and :class) to generated e-mail links. +

      +
    • +
    • +

      +The autolink helper has been refactored to make it a bit less messy and more intuitive. +

      +
    • +
    +
    +

    6. Active Support

    +
    +

    Active Support has a few interesting changes, including the introduction of Object#try.

    +

    6.1. Object#try

    +

    A lot of folks have adopted the notion of using try() to attempt operations on objects - It’s especially helpful in views where you can avoid nil-checking by writing code like <%= @person.try(:name) %>. Well, now it’s baked right into Rails. As implemented in Rails, it raises NoMethodError on private methods and always returns nil if the object is nil.

    +
      +
    • +

      +More Information: try(). +

      +
    • +
    +

    6.2. 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.

    +

    6.3. 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:

    +
    +
    +
    >> Time.zone.now.xmlschema(6)
    +=> "2009-01-16T13:00:06.13653Z"
    +

    Lead Contributor: Nicholas Dainty

    +

    6.4. JSON Key Quoting

    +

    If you look up the spec on the "json.org" site, you’ll discover that all keys in a JSON structure must be strings, and they must be quoted with double quotes. Starting with Rails 2.3, we doe the right thing here, even with numeric keys.

    +

    6.5. Other Active Support Changes

    +
      +
    • +

      +You can use Enumerable#none? to check that none of the elements match the supplied block. +

      +
    • +
    • +

      +If you’re using Active Support delegates, the new :allow_nil option lets you return nil instead of raising an exception when the target object is nil. +

      +
    • +
    • +

      +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. +

      +
    • +
    +
    +

    7. Railties

    +
    +

    In addition to the Rack changes covered above, Railties (the core code of Rails itself) sports a number of significant changes, including Rails Metal, application templates, and quiet backtraces.

    +

    7.1. Rails Metal

    +

    Rails Metal is a new mechanism that provides superfast endpoints inside of your Rails applications. Metal classes bypass routing and Action Controller to give you raw speed (at the cost of all the things in Action Controller, of course). This builds on all of the recent foundation work to make Rails a Rack application with an exposed middleware stack.

    + +

    7.2. Application Templates

    +

    Rails 2.3 incorporates Jeremy McAnally’s rg application generator. What this means is that we now have template-based application generation built right into Rails; if you have a set of plugins you include in every application (among many other use cases), you can just set up a template once and use it over and over again when you run the rails command. There’s also a rake task to apply a template to an existing application:

    +
    +
    +
    rake rails:template LOCATION=~/template.rb
    +

    This will layer the changes from the template on top of whatever code the project already contains.

    +
    +

    7.3. Quieter Backtraces

    +

    Building on Thoughtbot’s Quiet Backtrace plugin, which allows you to selectively remove lines from Test::Unit backtraces, Rails 2.3 implements ActiveSupport::BacktraceCleaner and Rails::BacktraceCleaner in core. This supports both filters (to perform regex-based substitutions on backtrace lines) and silencers (to remove backtrace lines entirely). Rails automatically adds silencers to get rid of the most common noise in a new application, and builds a config/backtrace_silencers.rb file to hold your own additions. This feature also enables prettier printing from any gem in the backtrace.

    +

    7.4. Faster Boot Time in Development Mode with Lazy Loading/Autoload

    +

    Quite a bit of work was done to make sure that bits of Rails (and its dependencies) are only brought into memory when they’re actually needed. The core frameworks - Active Support, Active Record, Action Controller, Action Mailer and Action View - are now using autoload to lazy-load their individual classes. This work should help keep the memory footprint down and improve overall Rails performance.

    +

    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.

    +

    7.5. Other Railties Changes

    +
      +
    • +

      +The instructions for updating a CI server to build Rails have been updated and expanded. +

      +
    • +
    • +

      +Internal Rails testing has been switched from Test::Unit::TestCase to ActiveSupport::TestCase, and the Rails core requires Mocha to test. +

      +
    • +
    • +

      +The default environment.rb file has been decluttered. +

      +
    • +
    • +

      +The dbconsole script now lets you use an all-numeric password without crashing. +

      +
    • +
    • +

      +Rails.root now returns a Pathname object, which means you can use it directly with the join method to clean up existing code that uses File.join. +

      +
    • +
    • +

      +Various files in /public that deal with CGI and FCGI dispatching are no longer generated in every Rails application by default (you can still get them if you need them by adding --with-dispatches when you run the rails command, or add them later with rake rails:generate_dispatchers). +

      +
    • +
    +
    +

    8. Deprecated

    +
    +

    A few pieces of older code are deprecated in this release:

    +
      +
    • +

      +If you’re one of the (fairly rare) Rails developers who deploys in a fashion that depends on the inspector, reaper, and spawner scripts, you’ll need to know that those scripts are no longer included in core Rails. If you need them, you’ll be able to pick up copies via the irs_process_scripts plugin. +

      +
    • +
    • +

      +render_component goes from "deprecated" to "nonexistent" in Rails 2.3. If you still need it, you can install the render_component plugin. +

      +
    • +
    • +

      +Support for Rails components has been removed. +

      +
    • +
    • +

      +If you were one of the people who got used to running script/performance/request to look at performance based on integration tests, you need to learn a new trick: that script has been removed from core Rails now. There’s a new request_profiler plugin that you can install to get the exact same functionality back. +

      +
    • +
    • +

      +ActionController::Base#session_enabled? is deprecated because sessions are lazy-loaded now. +

      +
    • +
    • +

      +The :digest and :secret options to protect_from_forgery are deprecated and have no effect. +

      +
    • +
    • +

      +Some integration test helpers have been removed. response.headers["Status"] and headers["Status"] will no longer return anything. Rack does not allow "Status" in its return headers. However you can still use the status and status_message helpers. response.headers["cookie"] and headers["cookie"] will no longer return any CGI cookies. You can inspect headers["Set-Cookie"] to see the raw cookie header or use the cookies helper to get a hash of the cookies sent to the client. +

      +
    • +
    • +

      +formatted_polymorphic_url is deprecated. Use polymorphic_url with :format instead. +

      +
    • +
    +
    +

    9. Credits

    +
    +

    Release notes compiled by Mike Gunderloy

    +
    + +
    +
    + + diff --git a/railties/doc/guides/html/action_mailer_basics.html b/railties/doc/guides/html/action_mailer_basics.html index 56451818eb..22b575af45 100644 --- a/railties/doc/guides/html/action_mailer_basics.html +++ b/railties/doc/guides/html/action_mailer_basics.html @@ -31,25 +31,54 @@

    Chapters

    1. - What is Action Mailer? + Introduction
    2. - Quick walkthrough to creating a Mailer + Sending Emails +
    3. +
    4. + Receiving Emails +
    5. +
    6. + Using Action Mailer Helpers +
    7. +
    8. + Action Mailer Configuration +
    9. Mailer Testing
    10. +
    11. + Epilogue +
    @@ -57,18 +86,19 @@

    Action Mailer Basics

    -

    This guide should provide you with all you need to get started in sending emails from your application, and will also cover how to test your mailers.

    +

    This guide should provide you with all you need to get started in sending and receiving emails from/to your application, and many internals of the ActionMailer class. It will also cover how to test your mailers.

    -

    1. What is Action Mailer?

    +

    1. Introduction

    Action Mailer allows you to send email from your application using a mailer model and views. -Yes, that is correct, in Rails, emails are used by creating Models that inherit from ActionMailer::Base. They live alongside other models in /app/models BUT they have views just like controllers that appear alongside other views in app/views.

    +Yes, that is correct, in Rails, emails are used by creating models that inherit from ActionMailer::Base. They live alongside other models in /app/models BUT they have views just like controllers that appear alongside other views in app/views.

    -

    2. Quick walkthrough to creating a Mailer

    +

    2. Sending Emails

    Let’s say you want to send a welcome email to a user after they signup. Here is how you would go about this:

    -

    2.1. 1. Create the mailer:

    +

    2.1. Walkthrough to generating a Mailer

    +

    2.1.1. Create the mailer:

    end end
    -

    So what do we have here? -recipients: who the recipients are, put in an array for multiple, ie, @recipients = ["user1@example.com", "user2@example.com"] -from: Who the email will appear to come from in the recipients' mailbox -subject: The subject of the email -sent_on: Timestamp for the email -content_type: The content type, by default is text/plain

    +

    So what do we have here?

    +
    + +++ + + + + + + + + + + + + + + + + + + + + + +

    recipients

    who the recipients are, put in an array for multiple, ie, @recipients = ["user1@example.com", "user2@example.com"]

    from

    Who the email will appear to come from in the recipients' mailbox

    subject

    The subject of the email

    sent_on

    Timestamp for the email

    content_type

    The content type, by default is text/plain

    +

    How about @body[:user]? Well anything you put in the @body hash will appear in the mailer view (more about mailer views below) as an instance variable ready for you to use, ie, in our example the mailer view will have a @user instance variable available for its consumption.

    -

    2.3. 3. Create the mailer view

    +

    2.1.3. Create the mailer view

    The file can look like:

    @@ -131,7 +188,6 @@ http://www.gnu.org/software/src-highlite --> </head> <body> <h1>Welcome to example.com, <%= @user.first_name %></h1> - <p> You have successfully signed up to example.com, and your username is: <%= @user.login %>.<br/> To login to the site, just follow this link: <%= @url %>. @@ -139,9 +195,10 @@ http://www.gnu.org/software/src-highlite --> <p>Thanks for joining and have a great day!</p> </body> </html>
    -

    2.4. 4. Wire it up so that the system sends the email when a user signs up

    -

    There are 3 was 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 block 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.

    -

    +

    2.1.4. Wire it up so that the system sends the email when a user signs up

    +

    There are 3 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 block 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.

    +

    Let’s see how we would go about wiring it up using an observer:

    +

    In config/environment.rb:

    UserMailer.deliver_welcome_email(user) end end
    -

    Notice how we call deliver_welcome_email? Where is that method? Well if you remember, we created a method called welcome_email in UserMailer, right? Well, as part of the "magic" of rails, we deliver the email identified by welcome_email by calling deliver_welcome_email.

    -

    That’s it! Now whenever your users signup, they will be greeted with a nice welcome email. Next up, we’ll talk about how to test a mailer model.

    +

    Notice how we call deliver_welcome_email? Where is that method? Well if you remember, we created a method called welcome_email in UserMailer, right? Well, as part of the "magic" of rails, we deliver the email identified by welcome_email by calling deliver_welcome_email. The next section will go through this in more detail.

    +

    That’s it! Now whenever your users signup, they will be greeted with a nice welcome email.

    +

    2.2. Action Mailer and dynamic deliver_ 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:

    +

    You never instantiate your mailer class. Rather, your delivery instance +methods are automatically wrapped in class methods that start with the word +deliver_ followed by the name of the mailer method that you would +like to deliver. The signup_notification method defined above is +delivered by invoking Notifier.deliver_signup_notification.

    +

    So, how exactly does this work?

    +

    In ActionMailer:Base, you will find this:

    +
    +
    +
    def method_missing(method_symbol, *parameters)#:nodoc:
    +  case method_symbol.id2name
    +    when /^create_([_a-z]\w*)/  then new($1, *parameters).mail
    +    when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver!
    +    when "new" then nil
    +    else super
    +  end
    +end
    +

    Ah, this makes things so much clearer :) so 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 parameter. The resulting object is then sent the deliver! method, which well... delivers it.

    +

    2.3. Complete List of ActionMailer user-settable attributes

    +
    + +++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    bcc

    Specify the BCC addresses for the message

    body

    Define the body of the message. 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 text of the message.

    cc

    Specify the CC addresses for the message.

    charset

    Specify the charset to use for the message. This defaults to the default_charset specified for ActionMailer::Base.

    content_type

    Specify the content type for the message. This defaults to <text/plain in most cases, but can be automatically set in some situations.

    from

    Specify the from address for the message.

    reply_to

    Specify the address (if different than the "from" address) to direct replies to this message.

    headers

    Specify additional headers to be added to the message.

    implicit_parts_order

    Specify the order in which parts should be sorted, based on content-type. This defaults to the value for the default_implicit_parts_order.

    mime_version

    Defaults to "1.0", but may be explicitly given if needed.

    recipient

    The recipient addresses for the message, either as a string (for a single address) or an array (for multiple addresses).

    sent_on

    The date on which the message was sent. If not set (the default), the header will be set by the delivery agent.

    subject

    Specify the subject of the message.

    template

    Specify the template name to use for current message. This is the "base" template name, without the extension or directory, and may be used to have multiple mailer methods share the same template.

    +
    +

    2.4. Mailer Views

    +

    Mailer views are located in /app/views/name_of_mailer_class. The specific mailer view is known to the class because it’s name is the same as the mailer method. So for example, in our example from above, our mailer view for the welcome_email method will be in /app/views/user_mailer/welcome_email.html.erb for the html version and welcome_email.txt.erb for the plain text version.

    +

    To change the default mailer view for your action you do something like:

    +
    +
    +
    class UserMailer < ActionMailer::Base
    +
    +  def welcome_email(user)
    +    recipients    user.email
    +    from          "My Awesome Site Notifications<notifications@example.com>"
    +    subject       "Welcome to My Awesome Site"
    +    sent_on       Time.now
    +    body          {:user => user, :url => "http://example.com/login"}
    +    content_type  "text/html"
    +
    +    # change the default from welcome_email.[html, txt].erb
    +    template "some_other_template" # this will be in app/views/user_mailer/some_other_template.[html, txt].erb
    +  end
    +
    +end
    +

    2.5. Action Mailer Layouts

    +

    Just like controller views, you can also have mailer layouts. The layout name needs to end in _mailer to be automatically recognized by your mailer as a layout. So in our UserMailer example, we need to call our layout user_mailer.[html,txt].erb. In order to use a different file just use:

    +
    +
    +
    class UserMailer < ActionMailer::Base
    +
    +  layout 'awesome' # will use awesome.html.erb as the layout
    +
    +end
    +

    Just like with controller views, use yield to render the view inside the layout.

    +

    2.6. Generating URL’s in Action Mailer views

    +

    URLs can be generated in mailer views using url_for or named routes. +Unlike controllers from Action Pack, the mailer instance doesn’t have any context about the incoming request, so you’ll need to provide all of the details needed to generate a URL.

    +

    When using url_for you’ll need to provide the :host, :controller, and :action:

    +
    +
    +
    <%= url_for(:host => "example.com", :controller => "welcome", :action => "greeting") %>
    +
    +

    When using named routes you only need to supply the :host:

    +
    +
    +
    <%= users_url(:host => "example.com") %>
    +
    +

    You will want to avoid using the name_of_route_path form of named routes because it doesn’t make sense to generate relative URLs in email messages. The reason that it doesn’t make sense is because the email is opened on a mail client outside of your environment. Since the email is not being served by your server, a URL like "/users/show/1", will have no context. In order for the email client to properly link to a URL on your server it needs something like "http://yourserver.com/users/show/1".

    +

    It is also possible to set a default host that will be used in all mailers by setting the :host option in +the ActionMailer::Base.default_url_options hash as follows:

    +
    +
    +
    ActionMailer::Base.default_url_options[:host] = "example.com"
    +
    +

    This can also be set as a configuration option in config/environment.rb:

    +
    +
    +
    config.action_mailer.default_url_options = { :host => "example.com" }
    +
    +

    If you do decide to set a default :host for your mailers you will want to use the :only_path => false option when using url_for. This will ensure that absolute URLs are generated because the url_for view helper will, by default, generate relative URLs when a :host option isn’t explicitly provided.

    +

    2.7. 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.txt.erb and welcome_email.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.

    +

    To explicitly specify multipart messages, you can do something like:

    +
    +
    +
    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 :content_type => "text/html",
    +      :body => "<p>html content, can also be the name of an action that you call<p>"
    +
    +    part "text/plain" do |p|
    +      p.body = "text content, can also be the name of an action that you call"
    +    end
    +  end
    +
    +end
    +

    2.8. Sending emails with attachments

    +

    Attachments can be added by using the attachment method:

    +
    +
    +
    class UserMailer < ActionMailer::Base
    +
    +  def welcome_email(user)
    +    recipients      user.email_address
    +    subject         "New account information"
    +    from            "system@example.com"
    +    content_type    "multipart/alternative"
    +
    +    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
    + +

    3. 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:

    +
      +
    1. +

      +Configure your email server to forward emails from the address(es) you would like your app to receive to /path/to/app/script/runner 'UserMailer.receive(STDIN.read)' +

      +
    2. +
    3. +

      +Implement a receive method in your mailer +

      +
    4. +
    +

    Once a method called receive is defined in any mailer, Action Mailer will parse the raw incoming email into an email object, decode it, instantiate a new mailer, and pass the email object to the mailer object‘s receive method. Here’s an example:

    +
    +
    +
    class UserMailer < ActionMailer::Base
    +
    +  def receive(email)
    +    page = Page.find_by_address(email.to.first)
    +    page.emails.create(
    +      :subject => email.subject,
    +      :body => email.body
    +    )
    +
    +    if email.has_attachments?
    +      for attachment in email.attachments
    +        page.attachments.create({
    +          :file => attachment,
    +          :description => email.subject
    +        })
    +      end
    +    end
    +  end
    +
    +
    +end
    +
    +

    4. Using Action Mailer Helpers

    +
    +

    Action Mailer classes have 4 helper methods available to them:

    +
    + +++ + + + + + + + + + + + + + + + + + +

    add_template_helper(helper_module)

    Makes all the (instance) methods in the helper module available to templates rendered through this controller.

    helper(*args, &block)

    Declare a helper: + helper :foo +requires foo_helper and includes FooHelper in the template class. + helper FooHelper +includes FooHelper in the template class. +evaluates the block in the template class, adding method foo. + helper(:three, BlindHelper) { def mice() mice end } +does all three.

    helper_method

    Declare a controller method as a helper. For example, + helper_method :link_to + def link_to(name, options) ... end +makes the link_to controller method available in the view.

    helper_attr

    Declare a controller attribute as a helper. For example, + helper_attr :name + attr_accessor :name +makes the name and name= controller methods available in the view. +The is a convenience wrapper for helper_method.

    +
    +
    +

    5. Action Mailer Configuration

    +
    +

    The following configuration options are best made in one of the environment files (environment.rb, production.rb, etc...)

    +
    + +++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    template_root

    Determines the base from which template references will be made.

    logger

    the logger is used for generating information on the mailing run if available. + Can be set to nil for no logging. Compatible with both Ruby’s own Logger and Log4r loggers.

    smtp_settings

    Allows detailed configuration for :smtp delivery method:

    +
    + +++ + + + + + + + + + + + + + + + + + + + + + + + + + +

    :address

    Allows you to use a remote mail server. Just change it from its default "localhost" setting.

    :port

    On the off chance that your mail server doesn’t run on port 25, you can change it.

    :domain

    If you need to specify a HELO domain, you can do it here.

    :user_name

    If your mail server requires authentication, set the username in this setting.

    :password

    If your mail server requires authentication, set the password in this setting.

    :authentication

    If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of :plain, :login, :cram_md5.

    +

    sendmail_settings

    Allows you to override options for the :sendmail delivery method.

    +
    + +++ + + + + + + + + + +

    :location

    The location of the sendmail executable. Defaults to /usr/sbin/sendmail.

    :arguments

    The command line arguments. Defaults to -i -t.

    +

    raise_delivery_errors

    Whether or not errors should be raised if the email fails to be delivered.

    delivery_method

    Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test.

    perform_deliveries

    Determines whether deliver_* methods are actually carried out. By default they are, + but this can be turned off to help functional testing.

    deliveries

    Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful + for unit and functional testing.

    default_charset

    The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also + pick a different charset from inside a method with charset.

    default_content_type

    The default content type used for the main part of the message. Defaults to "text/plain". You + can also pick a different content type from inside a method with content_type.

    default_mime_version

    The default mime version used for the message. Defaults to 1.0. You + can also pick a different value from inside a method with mime_version.

    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.

    +
    +

    5.1. Example Action Mailer Configuration

    +

    An example would be:

    +
    +
    +
    ActionMailer::Base.delivery_method = :sendmail
    +ActionMailer::Base.sendmail_settings = {
    +  :location => '/usr/sbin/sendmail',
    +  :arguments => '-i -t'
    +}
    +ActionMailer::Base.perform_deliveries = true
    +ActionMailer::Base.raise_delivery_errors = true
    +ActionMailer::Base.default_charset = "iso-8859-1"
    +

    5.2. Action Mailer Configuration for GMail

    + +

    First you must install the action_mailer_tls plugin from http://code.openrain.com/rails/action_mailer_tls/, then all you have to do is configure action mailer.

    +
    +
    +
    ActionMailer::Base.smtp_settings = {
    +  :address        => "smtp.gmail.com",
    +  :port           => 587,
    +  :domain         => "domain.com",
    +  :user_name      => "user@domain.com",
    +  :password       => "password",
    +  :authentication => :plain
    +}
    +

    5.3. Configure Action Mailer to recognize HAML templates

    +

    In environment.rb, add the following line:

    +
    +
    +
    ActionMailer::Base.register_template_extension('haml')
    +
    +

    6. Mailer Testing

    +
    +

    Testing mailers involves 2 things. One is that the mail was queued and the other that the body contains what we expect it to contain. With that in mind, we could test our example mailer from above like so:

    +
    +
    +
    class UserMailerTest < ActionMailer::TestCase
    +    tests UserMailer
    +
    +    def test_welcome_email
    +      user = users(:some_user_in_your_fixtures)
    +
    +      # Send the email, then test that it got queued
    +      email = UserMailer.deliver_welcome_email(user)
    +      assert !ActionMailer::Base.deliveries.empty?
    +
    +      # Test the body of the sent email contains what we expect it to
    +      assert_equal [@user.email],                 email.to
    +      assert_equal "Welcome to My Awesome Site",  email.subject
    +      assert       email.body =~ /Welcome to example.com, #{user.first_name}/
    +    end
    +  end
    +

    What have we done? Well, we sent the email and stored the returned object in the email variable. We then ensured that it was sent (the first assert), then, in the second batch of assertion, we ensure that the email does indeed contain the values that we expect.

    -

    3. Mailer Testing

    +

    7. Epilogue

    +

    This guide presented how to create a mailer and how to test it. In reality, you may find that writing your tests before you actually write your code to be a rewarding experience. It may take some time to get used to TDD (Test Driven Development), but coding this way achieves two major benefits. Firstly, you know that the code does indeed work, because the tests fail (because there’s no code), then they pass, because the code that satisfies the tests was written. Secondly, when you start with the tests, you don’t have to make time AFTER you write the code, to write the tests, then never get around to it. The tests are already there and testing has now become part of your coding regimen.

    diff --git a/railties/doc/guides/html/actioncontroller_basics.html b/railties/doc/guides/html/actioncontroller_basics.html index f5b25a4d7a..201a2c62f0 100644 --- a/railties/doc/guides/html/actioncontroller_basics.html +++ b/railties/doc/guides/html/actioncontroller_basics.html @@ -88,7 +88,14 @@
  • - HTTP Basic Authentication + HTTP Authentications +
  • Streaming and File Downloads @@ -803,9 +810,23 @@ http://www.lorenzobettini.it http://www.gnu.org/software/src-highlite -->
    response.headers["Content-Type"] = "application/pdf"
    -

    10. HTTP Basic Authentication

    +

    10. HTTP Authentications

    -

    Rails comes with built-in HTTP Basic authentication. This 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.

    +

    Rails comes with two built-in HTTP authentication mechanisms :

    +
      +
    • +

      +Basic Authentication +

      +
    • +
    • +

      +Digest Authentication +

      +
    • +
    +

    10.1. 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.

    before_filter :authenticate -private + private def authenticate authenticate_or_request_with_http_basic do |username, password| @@ -827,6 +848,29 @@ private 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.

    +

    10.2. HTTP Digest Authentication

    +

    HTTP Digest authentication is superior to the Basic authentication as it does not require the client to send unencrypted password over the network. Using Digest authentication with Rails is quite easy and only requires using one method, authenticate_or_request_with_http_digest.

    +
    +
    +
    class AdminController < ApplicationController
    +
    +  USERS = { "lifo" => "world" }
    +
    +  before_filter :authenticate
    +
    +  private
    +
    +  def authenticate
    +    authenticate_or_request_with_http_digest do |username|
    +      USERS[username]
    +    end
    +  end
    +
    +end
    +

    As seen in the example above, authenticate_or_request_with_http_digest block takes only one argument - the username. And the block returns the password. Returning false or nil from the authenticate_or_request_with_http_digest will cause authentication failure.

    11. Streaming and File Downloads

    diff --git a/railties/doc/guides/html/active_record_basics.html b/railties/doc/guides/html/active_record_basics.html new file mode 100644 index 0000000000..f99d57faac --- /dev/null +++ b/railties/doc/guides/html/active_record_basics.html @@ -0,0 +1,342 @@ + + + + + Active Record Basics + + + + + + + + +
    + + + +
    +

    Active Record Basics

    +
    +
    +

    This guide will give you a strong grasp of the Active Record pattern and how it can be used with or without Rails. Hopefully, some of the philosophical and theoretical intentions discussed here will also make you a stronger and better developer.

    +

    After reading this guide we hope that you’ll be able to:

    +
      +
    • +

      +Understand the way Active Record fits into the MVC model. +

      +
    • +
    • +

      +Create basic Active Record models and map them with your database tables. +

      +
    • +
    • +

      +Use your models to execute CRUD (Create, Read, Update and Delete) database operations. +

      +
    • +
    • +

      +Follow the naming conventions used by Rails to make developing database applications easier and obvious. +

      +
    • +
    • +

      +Take advantage of the way Active Record maps it’s attributes with the database tables' columns to implement your application’s logic. +

      +
    • +
    • +

      +Use Active Record with legacy databases that do not follow the Rails naming conventions. +

      +
    • +
    +
    +
    +

    1. What’s Active Record

    +
    +

    Rails' ActiveRecord is an implementation of Martin Fowler’s Active Record Design Pattern. This pattern is based on the idea of creating relations between the database and the application in the following way:

    +
      +
    • +

      +Each database table is mapped to a class. +

      +
    • +
    • +

      +Each table column is mapped to an attribute of this class. +

      +
    • +
    • +

      +Each instance of this class is mapped to a single row in the database table. +

      +
    • +
    +

    The definition of the Active Record pattern in Martin Fowler’s words:

    +

    "An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data."

    +
    +

    2. 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 behaviour 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 excelent 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.

    +
    +

    3. ActiveRecord as an ORM framework

    +
    +

    ActiveRecord gives us several mechanisms, being the most important ones the hability to:

    +
      +
    • +

      +Represent models. +

      +
    • +
    • +

      +Represent associations between these models. +

      +
    • +
    • +

      +Represent inheritance hierarquies through related models. +

      +
    • +
    • +

      +Validate models before they get recorded to the database. +

      +
    • +
    • +

      +Perform database operations in an object-oriented fashion. +

      +
    • +
    +

    It’s easy to see that the Rails Active Record implementation goes way beyond the basic description of the Active Record Pattern.

    +
    +

    4. 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 responsability to deliver you the easiest possible way to recover this data from the database.

    +
    +

    5. Convention over Configuration in ActiveRecord

    +
    +

    When writing applications using other programming languages or frameworks, it may be necessary to write a lot of configuration code. This is particulary true for ORM frameworks in general. However, if you follow the conventions adopted by Rails, you’ll need to write very little configuration (in some case no configuration at all) when creating ActiveRecord models. The idea is that if you configure your applications in the very same way most of the times then this should be the default way. In this cases, explicity configuration would be needed only in those cases where you can’t follow the conventions for any reason.

    +

    5.1. Naming Conventions

    +

    By default, ActiveRecord uses some naming conventions to find out how the mapping between models and database tables should be created. Rails will pluralize your class names to find the respective database table. So, for a class Book, you should have a database table called books. The Rails pluralization mechanisms are very powerful, being capable to pluralize (and singularize) both regular and irregular words. When using class names composed of two or more words, the model class name should follow the Ruby conventions, using the camelCase form, while the table name must contain the words separated by underscores. Examples:

    +
      +
    • +

      +Database Table - Plural with underscores separating words i.e. (book_clubs) +

      +
    • +
    • +

      +Model Class - Singular with the first letter of each word capitalized i.e. (BookClub) +

      +
    • +
    +
    + +++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Model / Class Table / Schema

    Post

    posts

    LineItem

    line_items

    Deer

    deer

    Mouse

    mice

    Person

    people

    +
    +

    5.2. Schema Conventions

    +

    ActiveRecord uses naming conventions for the columns in database tables, depending on the purpose of these columns.

    +
      +
    • +

      +Foreign keys - These fields should be named following the pattern table_id i.e. (item_id, order_id). These are the fields that ActiveRecord will look for when you create associations between your models. +

      +
    • +
    • +

      +Primary keys - By default, ActiveRecord will use a integer column named "id" as the table’s primary key. When using Rails Migrations to create your tables, this column will be automaticaly created. +

      +
    • +
    +

    There are also some optional column names that will create additional features to ActiveRecord instances:

    +
      +
    • +

      +created_at / created_on - ActiveRecord will store the current date and time to this field when creating the record. +

      +
    • +
    • +

      +updated_at / updated_on - ActiveRecord will store the current date and times to this field when updating the record. +

      +
    • +
    • +

      +lock_version - Adds optimistic locking to a model. +

      +
    • +
    • +

      +type - Specifies that the model uses Single Table Inheritance +

      +
    • +
    • +

      +(table_name)_count - Used to cache the number of belonging objects on associations. For example, a comments_count column in a Post class that has many instances of Comment will cache the number of existent comments for each post. +

      +
    • +
    +
    + + + +
    +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.
    +
    +
    +

    6. 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:

    +
    +
    +
    class Product < ActiveRecord::Base; end
    +

    This will create a Product model, mapped to a products table at the database. By doing this you’ll also have the hability to map the columns of each row in that table with the attributes of the instances of your model. So, suppose that the products table was created using a SQL sentence like:

    +
    +
    +
    CREATE TABLE products (
    +   id int(11) NOT NULL auto_increment,
    +   name varchar(255),
    +   PRIMARY KEY  (id)
    +);
    +

    Following the table schema above, you would be able to write code like the following:

    +
    +
    +
    p = Product.new
    +p.name = "Some Book"
    +puts p.name # "Some Book"
    +
    +

    7. 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.

    +

    You can use the ActiveRecord::Base.set_table_name method to specify the table name that should be used:

    +
    +
    +
    class Product < ActiveRecord::Base
    +  set_table_name "PRODUCT"
    +end
    +

    It’s also possible to override the column that should be used as the table’s primary key. Use the ActiveRecord::Base.set_primary_key method for that:

    +
    +
    +
    class Product < ActiveRecord::Base
    +  set_primary_key "product_id"
    +end
    +
    +

    8. Validations

    +
    +

    ActiveRecord gives the hability to validate the state of your models before they get recorded into the database. There are several methods that you can use to hook into the lifecycle of your models and validate that an attribute value is not empty or follow a specific format and so on. You can learn more about validations in the Active Record Validations and Callbacks guide.

    +
    +

    9. Callbacks

    +
    +

    ActiveRecord callbacks allow you to attach code to certain events in the lifecycle of your models. This way you can add behaviour to your models by transparently executing code when those events occur, like when you create a new record, update it, destroy it and so on. You can learn more about callbacks in the Active Record Validations and Callbacks guide.

    +
    + +
    +
    + + diff --git a/railties/doc/guides/html/creating_plugins.html b/railties/doc/guides/html/creating_plugins.html index 3347f77228..1c512519f9 100644 --- a/railties/doc/guides/html/creating_plugins.html +++ b/railties/doc/guides/html/creating_plugins.html @@ -768,7 +768,7 @@ ActiveRecord::Base

    As always, start with a test:

    -

    vendor/plugins/yaffle/yaffle/woodpecker_test.rb:

    +

    vendor/plugins/yaffle/test/woodpecker_test.rb:

    end

    Here are a few possibilities for how to allow developers to use your plugin migrations:

    11.1. Create a custom rake task

    -

    vendor/plugins/yaffle/lib/db/migrate/20081116181115_create_birdhouses.rb:

    -
    -
    -
    class CreateBirdhouses < ActiveRecord::Migration
    -  def self.up
    -    create_table :birdhouses, :force => true do |t|
    -      t.string :name
    -      t.timestamps
    -    end
    -  end
    -
    -  def self.down
    -    drop_table :birdhouses
    -  end
    -end
    -

    vendor/plugins/yaffle/tasks/yaffle.rake:

    +

    vendor/plugins/yaffle/tasks/yaffle_tasks.rake:

    After running the test with rake you can make it pass with:

    -

    vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb

    +

    vendor/plugins/yaffle/generators/yaffle_migration/yaffle_migration_generator.rb

    12. Rake tasks

    -

    When you created the plugin with the built-in rails generator, it generated a rake file for you in vendor/plugins/yaffle/tasks/yaffle.rake. Any rake task you add here will be available to the app.

    +

    When you created the plugin with the built-in rails generator, it generated a rake file for you in vendor/plugins/yaffle/tasks/yaffle_tasks.rake. Any rake task you add here will be available to the app.

    Many plugin authors put all of their rake tasks into a common namespace that is the same as the plugin, like so:

    -

    vendor/plugins/yaffle/tasks/yaffle.rake

    +

    vendor/plugins/yaffle/tasks/yaffle_tasks.rake

    -
    class LabellingFormBuilder < FormBuilder
    -  def text_field attribute, options={}
    -    label(attribute) + text_field(attribute, options)
    -  end
    -end
    -

    If you reuse this frequently you could define a labeled_form_for helper that automatically applies the :builder => LabellingFormBuilder option.

    -

    The form builder used also determines what happens when you do

    -
    -
    -
    <%= render :partial => f %>
    -
    -

    If f is an instance of FormBuilder then this will render the form partial, setting the partial’s object to the form builder. If the form builder is of class LabellingFormBuilder then the labelling_form partial would be rendered instead.

    -

    6.1. Scoping out form controls with fields_for

    -

    fields_for creates a form builder in exactly the same way as form_for but doesn’t create the actual <form> tags. It creates a scope around a specific model object like form_for, which is useful for specifying additional model objects in the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for editing both like so:

    -
    -
    -
    <% form_for @person do |person_form| %>
    -  <%= person_form.text_field :name %>
    -  <% fields_for @person.contact_detail do |contact_details_form| %>
    -    <%= contact_details_form.text_field :phone_number %>
    -  <% end %>
    -<% end %>
    -
    -

    which produces the following output:

    -
    -
    -
    <form action="/people/1" class="edit_person" id="edit_person_1" method="post">
    -  <input id="person_name" name="person[name]" size="30" type="text" />
    -  <input id="contact_detail_phone_number" name="contact_detail[phone_number]" size="30" type="text" />
    -</form>
    +
    <%= select_year(2009) %>
    +<%= select_year(Time.now) %>
    +

    will produce the same output if the current year is 2009 and the value chosen by the user can be retrieved by params[:date][:year].

    -

    7. File Uploads

    +

    5. Uploading Files

    -

    A common task is uploading some sort of file, whether it’s a picture of a person or a CSV file containing data to process. The most important thing to remember with file uploads is that the form’s encoding MUST be set to multipart/form-data. If you forget to do this the file will not be uploaded. This can be done by passing :multi_part => true as an HTML option. This means that in the case of form_tag it must be passed in the second options hash and in the case of form_for inside the :html hash.

    +

    A common task is uploading some sort of file, whether it’s a picture of a person or a CSV file containing data to process. The most important thing to remember with file uploads is that the form’s encoding MUST be set to "multipart/form-data". If you forget to do this the file will not be uploaded. This can be done by passing :multi_part => true as an HTML option. This means that in the case of form_tag it must be passed in the second options hash and in the case of form_for inside the :html hash.

    The following two forms both upload a file.

    @@ -766,8 +750,8 @@ http://www.gnu.org/software/src-highlite --> <% end %>

    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].

    -

    7.1. 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).

    +

    5.1. 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).

    If the user has not selected a file the corresponding parameter will be an empty string.
    -

    7.2. Dealing with Ajax

    -

    Unlike other forms making an asynchronous file upload form is not as simple as replacing form_for with remote_form_for. With an AJAX form the serialization is done by javascript running inside the browser and since javascript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission.

    +

    5.2. Dealing with Ajax

    +

    Unlike other forms making an asynchronous file upload form is not as simple as replacing form_for with remote_form_for. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission.

    -

    8. Parameter Names

    +

    6. Customising Form Builders

    -

    As you’ve seen in the previous sections values from forms can appear either at the top level of the params hash or may appear nested in another hash. For example in a standard create -action for a Person model, params[:model] would usually be a hash of all the attributes for the person to create. The params hash can also contain arrays, arrays of hashes and so on.

    -

    Fundamentally HTML forms don’t know about any sort of structured data. All they know about is name-value pairs. Rails tacks some conventions onto parameter names which it uses to express some structure.

    +

    As mentioned previously the object yielded by form_for and fields_for is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way you can also subclass FormBuilder and add the helpers there. For example

    +
    +
    +
    <% form_for @person do |f| %>
    +  <%= text_field_with_label f, :first_name %>
    +<% end %>
    +
    +

    can be replaced with

    +
    +
    +
    <% form_for @person, :builder => LabellingFormBuilder do |f| %>
    +  <%= f.text_field :first_name %>
    +<% end %>
    +
    +

    by defining a LabellingFormBuilder class similar to the following:

    +
    +
    +
    class LabellingFormBuilder < FormBuilder
    +  def text_field(attribute, options={})
    +    label(attribute) + text_field(attribute, options)
    +  end
    +end
    +

    If you reuse this frequently you could define a labeled_form_for helper that automatically applies the :builder => LabellingFormBuilder option.

    +

    The form builder used also determines what happens when you do

    +
    +
    +
    <%= render :partial => f %>
    +
    +

    If f is an instance of FormBuilder then this will render the form partial, setting the partial’s object to the form builder. If the form builder is of class LabellingFormBuilder then the labelling_form partial would be rendered instead.

    +
    +

    7. Understanding Parameter Naming Conventions

    +
    +

    As you’ve seen in the previous sections, values from forms can be at the top level of the params hash or nested in another hash. For example in a standard create +action for a Person model, params[:model] would usually be a hash of all the attributes for the person to create. The params hash can also contain arrays, arrays of hashes and so on.

    +

    Fundamentally HTML forms don’t know about any sort of structured data, all they generate is name-value pairs, where pairs are just plain strings. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses.

    @@ -805,38 +824,34 @@ action for a Person model, params[:model] would usually be a hash of al

    You may find you can try out examples in this section faster by using the console to directly invoke Rails' parameter parser. For example

    -
    ActionController::RequestParser.parse_query_parameters "name=fred&phone=0123456789"
    -#=> {"name"=>"fred", "phone"=>"0123456789"}
    +
    ActionController::UrlEncodedPairParser.parse_query_parameters "name=fred&phone=0123456789"
    +# => {"name"=>"fred", "phone"=>"0123456789"}
    -

    8.1. Basic structures

    -

    The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in the params. For example if a form contains

    +

    7.1. 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

    <input id="person_name" name="person[name]" type="text" value="Henry"/>
    -

    the params hash will contain

    +

    the params hash will contain

    -
    -
    {'person' => {'name' => 'Henry'}}
    +
    +
    {'person' => {'name' => 'Henry'}}
    +

    and params["name"] will retrieve the submitted value in the controller.

    Hashes can be nested as many levels as required, for example

    <input id="person_address_city" name="person[address][city]" type="text" value="New York"/>
    -

    will result in the params hash being

    +

    will result in the params hash being

    -
    -
    {'person' => {'address' => {'city' => 'New York'}}}
    +
    +
    {'person' => {'address' => {'city' => 'New York'}}}
    +

    Normally Rails ignores duplicate parameter names. If the parameter name contains [] then they will be accumulated in an array. If you wanted people to be able to input multiple phone numbers, your could place this in the form:

    @@ -845,7 +860,7 @@ http://www.gnu.org/software/src-highlite --> <input name="person[phone_number][]" type="text"/>

    This would result in params[:person][:phone_number] being an array.

    -

    8.2. Combining them

    +

    7.2. 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

    @@ -853,23 +868,23 @@ http://www.gnu.org/software/src-highlite --> <input name="addresses[][line2]" type="text"/> <input name="addresses[][city]" type="text"/>
    -

    This would result in params[:addresses] being an array of hashes with keys line1, line2 and city. Rails decides to start accumulating values in a new hash whenever it encounters a input name that already exists in the current hash.

    -

    The one restriction is that although hashes can be nested arbitrarily deep then can be only one level of "arrayness". Frequently arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id.

    +

    This would result in params[:addresses] being an array of hashes with keys line1, line2 and city. Rails decides to start accumulating values in a new hash whenever it encounters an input name that already exists in the current hash.

    +

    There’s a restriction, however, while hashes can be nested arbitrarily, only one level of "arrayness" is allowed. Arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id, an array index or some other parameter.

    - +
    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. If the checkbox 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 hash. It is preferable to either use check_box_tag or to use hashes instead of arrays.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.
    -

    8.3. 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/fields_for and the :index option.

    -

    You might want to render a form with a set of edit fields for each of a person’s addresses. Something a little like this will do the trick

    +

    7.3. 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.

    +

    You might want to render a form with a set of edit fields for each of a person’s addresses. For example:

    <% form_for @person do |person_form| %>
    -  <%= person_form.text_field :name%>
    +  <%= person_form.text_field :name %>
       <% for address in @person.addresses %>
         <% person_form.fields_for address, :index => address do |address_form|%>
           <%= address_form.text_field :city %>
    @@ -877,7 +892,7 @@ http://www.gnu.org/software/src-highlite -->
       <% end %>
     <% end %>
    -

    Assuming our person had two addresses, with ids 23 and 45 this would create output similar to this:

    +

    Assuming the person had two addresses, with ids 23 and 45 this would create output similar to this:

    <form action="/people/1" class="edit_person" id="edit_person_1" method="post">
    @@ -886,14 +901,12 @@ http://www.gnu.org/software/src-highlite -->
       <input id="person_address_45_city" name="person[address][45][city]" size="30" type="text" />
     </form>
    -

    This will result in a params hash that looks like

    +

    This will result in a params hash that looks like

    -
    -
    {'person' => {'name' => 'Bob', 'address' => { '23' => {'city' => 'Paris'}, '45' => {'city' => 'London'} }}}
    -

    Rails knows that all these inputs should be part of the person hash because you called fields_for on the first form builder. By specifying an :index option you’re telling rails that instead of naming the inputs person[address][city] it should insert that index surrounded by [] between the address and the city. If you pass an Active Record object as we did then Rails will call to_param on it, which by default returns the database id. This is often useful it is then easy to locate which Address record should be modified but you could pass numbers with some other significance, strings or even nil (which will result in an array parameter being created).

    +
    +
    {'person' => {'name' => 'Bob', 'address' => {'23' => {'city' => 'Paris'}, '45' => {'city' => 'London'}}}}
    +
    +

    Rails knows that all these inputs should be part of the person hash because you called fields_for on the first form builder. By specifying an :index option you’re telling rails that instead of naming the inputs person[address][city] it should insert that index surrounded by [] between the address and the city. If you pass an Active Record object as we did then Rails will call to_param on it, which by default returns the database id. This is often useful as it is then easy to locate which Address record should be modified. You can pass numbers with some other significance, strings or even nil (which will result in an array parameter being created).

    To create more intricate nestings, you can specify the first part of the input name (person[address] in the previous example) explicitly, for example

    @@ -906,7 +919,7 @@ http://www.gnu.org/software/src-highlite -->
    <input id="person_address_primary_1_city" name="person[address][primary][1][city]" size="30" type="text" value="bologna" />
    -

    As a general rule the final input name is the concatenation of the name given to fields_for/form_for, the index value and the name of the attribute. You can also pass an :index option directly to helpers such as text_field, but usually it is less repetitive to specify this at the form builder level rather than on individual input controls.

    +

    As a general rule the final input name is the concatenation of the name given to fields_for/form_for, the index value and the name of the attribute. You can also pass an :index option directly to helpers such as text_field, but it is usually less repetitive to specify this at the form builder level rather than on individual input controls.

    As a shortcut you can append [] to the name and omit the :index option. This is the same as specifing :index => address so

    @@ -916,9 +929,9 @@ http://www.gnu.org/software/src-highlite -->

    produces exactly the same output as the previous example.

    -

    9. Complex forms

    +

    8. Building Complex forms

    -

    Many apps grow beyond simple forms editing a single object. For example when creating a Person instance 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:

    +

    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:

    • @@ -947,7 +960,7 @@ James Golick’s a

    -

    10. Changelog

    +

    9. Changelog

    Authors
      diff --git a/railties/doc/guides/html/getting_started_with_rails.html b/railties/doc/guides/html/getting_started_with_rails.html index a79d9903aa..564d5c7c00 100644 --- a/railties/doc/guides/html/getting_started_with_rails.html +++ b/railties/doc/guides/html/getting_started_with_rails.html @@ -129,6 +129,9 @@
  • + Building a Multi-Model Form +
  • +
  • What’s Next?
  • @@ -164,6 +167,14 @@ How to quickly generate the starting pieces of a Rails application.

  • +
    + + + +
    +Note +This Guide is based on Rails 2.3. Some of the code shown here will not work in older versions of Rails.
    +

    1. This Guide Assumes

    @@ -186,7 +197,7 @@ A working installation of SQLite (preferred

    -

    It is highly recommended that you familiarize yourself with Ruby before diving into Rails. You will find it much easier to follow what’s going on with a Rails application if you understand basic Ruby syntax. Rails isn’t going to magically revolutionize the way you write web applications if you have no experience with the language it uses. There are some good free resources on the net for learning Ruby, including:

    +

    It is highly recommended that you familiarize yourself with Ruby before diving into Rails. You will find it much easier to follow what’s going on with a Rails application if you understand basic Ruby syntax. Rails isn’t going to magically revolutionize the way you write web applications if you have no experience with the language it uses. There are some good free resources on the internet for learning Ruby, including:

    Change the username and password in the development section as appropriate.

    @@ -548,6 +569,14 @@ by Lorenzo Bettini http://www.lorenzobettini.it http://www.gnu.org/software/src-highlite -->
    $ rake db:create
    +
    + + + +
    +Note +Rake is a general-purpose command-runner that Rails uses for many things. You can see the list of available rake commands in your application by running rake -T.
    +

    4. Hello, Rails!

    @@ -581,7 +610,7 @@ by Lorenzo Bettini http://www.lorenzobettini.it http://www.gnu.org/software/src-highlite -->
    $ script/server
    -

    This will fire up the lightweight Webrick web server by default. To see your application in action, open a browser window and navigate to http://localhost:3000. You should see Rails' default information page:

    +

    This will fire up an instance of the Mongrel web server by default (Rails can also use several other web servers). To see your application in action, open a browser window and navigate to http://localhost:3000. You should see Rails' default information page:

    Welcome Aboard screenshot

    @@ -593,7 +622,7 @@ http://www.gnu.org/software/src-highlite --> To stop the web server, hit Ctrl+C in the terminal window where it’s running. In development mode, Rails does not generally require you to stop the server; changes you make in files will be automatically picked up by the server. -

    The "Welcome Aboard" page is the smoke test for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. To view the page you just created, navigate to http://localhost:3000/home/index.

    +

    The "Welcome Aboard" page is the smoke test for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. To view the page you just created, navigate to http://localhost:3000/home/index.

    4.2. Setting the Application Home Page

    You’d probably like to replace the "Welcome Aboard" page with your own application’s home page. The first step to doing this is to delete the default page from your application:

    @@ -650,7 +679,7 @@ http://www.gnu.org/software/src-highlite --> While scaffolding will get you up and running quickly, the "one size fits all" code that it generates is unlikely to be a perfect fit for your application. In most cases, you’ll need to customize the generated code. Many experienced Rails developers avoid scaffolding entirely, preferring to write all or most of their source code from scratch.
    -

    The scaffold generator will build 13 files in your application, along with some folders, and edit one more. Here’s a quick overview of what it creates:

    +

    The scaffold generator will build 14 files in your application, along with some folders, and edit one more. Here’s a quick overview of what it creates:

    - + @@ -721,12 +750,16 @@ cellspacing="0" cellpadding="4"> + + + +

    The Post model

    db/migrate/20081013124235_create_posts.rb

    db/migrate/20090113124235_create_posts.rb

    Migration to create the posts table in your database (your name will include a different timestamp)

    test/unit/post_test.rb

    Unit testing harness for the posts model

    test/unit/helpers/posts_helper_test.rb

    Unit testing harness for the posts helper

    6.1. Running a Migration

    One of the products of the script/generate scaffold command is a database migration. Migrations are Ruby classes that are designed to make it simple to create and modify database tables. Rails uses rake commands to run migrations, and it’s possible to undo a migration after it’s been applied to your database. Migration filenames include a timestamp to ensure that they’re processed in the order that they were created.

    -

    If you look in the db/migrate/20081013124235_create_posts.rb file (remember, yours will have a slightly different name), here’s what you’ll find:

    +

    If you look in the db/migrate/20090113124235_create_posts.rb file (remember, yours will have a slightly different name), here’s what you’ll find:

    by Lorenzo Bettini http://www.lorenzobettini.it http://www.gnu.org/software/src-highlite --> -
    $ rake db:create
    -$ rake db:migrate
    +
    $ rake db:migrate
    +

    Remember, you can’t run migrations before running rake db:create to create your database, as we covered earlier.

    - +
    @@ -842,7 +875,7 @@ title: nil, Tip Unlike the development web server, the console does not automatically load your code afresh for each line. If you make changes, type reload! at the console prompt to load them.Unlike the development web server, the console does not automatically load your code afresh for each line. If you make changes to your models while the console is open, type reload! at the console prompt to load them.

    6.7. Listing All Posts

    @@ -1155,7 +1188,7 @@ http://www.gnu.org/software/src-highlite -->

    At this point, it’s worth looking at some of the tools that Rails provides to eliminate duplication in your code. In particular, you can use partials to clean up duplication in views and filters to help with duplication in controllers.

    7.1. Using Partials to Eliminate View Duplication

    -

    As you saw earlier, the scaffold-generated views for the new and edit actions are largely identical. You can pull the shared code out into a partial template. This requires editing the new and edit views, and adding a new template. The new _form.html.erb template should be saved in the same app/views/posts folder as the files from which it is being extracted:

    +

    As you saw earlier, the scaffold-generated views for the new and edit actions are largely identical. You can pull the shared code out into a partial template. This requires editing the new and edit views, and adding a new template. The new _form.html.erb template should be saved in the same app/views/posts folder as the files from which it is being extracted. Note that the name of this file begins with an underscore; that’s the Rails naming convention for partial templates.

    new.html.erb:

    end 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 guide.

    +

    For more information on filters, see the Action Controller Basics guide.

    8. Adding a Second Model

    @@ -1286,7 +1319,7 @@ http://www.gnu.org/software/src-highlite -->
  • -+db/migrate/20081013214407_create_comments.rb - The migration ++db/migrate/20091013214407_create_comments.rb - The migration

  • @@ -1333,7 +1366,7 @@ by Lorenzo Bettini http://www.lorenzobettini.it http://www.gnu.org/software/src-highlite -->
    $ rake db:migrate
  • -

    Rails is smart enough to only execute the migrations that have not already been run against this particular database.

    +

    Rails is smart enough to only execute the migrations that have not already been run against the current database.

    8.2. Associating Models

    Active Record associations let you easily declare the relationship between two models. In the case of comments and posts, you could write out the relationships this way:

    8.3. Adding a Route

    -

    Routes are entries in the config/routes.rb file that tell Rails how to match incoming HTTP requests to controller actions. Open up that file and find the existing line referring to posts. Then edit it as follows:

    +

    Routes are entries in the config/routes.rb file that tell Rails how to match incoming HTTP requests to controller actions. Open up that file and find the existing line referring to posts (it will be right at the top of the file). Then edit it as follows:

    -
    map.resources :posts do |post|
    -  post.resources :comments
    -end
    +
    map.resources :posts, :has_many => :comments

    This creates comments as a nested resource within posts. This is another part of capturing the hierarchical relationship that exists between posts and comments.

    @@ -1442,7 +1473,7 @@ http://www.gnu.org/software/src-highlite -->

    -

    The controller will be generated with empty methods for each action that you specified in the call to script/generate controller:

    +

    The controller will be generated with empty methods and views for each action that you specified in the call to script/generate controller:

    end end + def destroy + @post = Post.find(params[:post_id]) + @comment = Comment.find(params[:id]) + @comment.destroy + + respond_to do |format| + format.html { redirect_to post_comments_path(@post) } + format.xml { head :ok } + end + end + end

    You’ll see a bit more complexity here than you did in the controller for posts. That’s a side-effect of the nesting that you’ve set up; each request for a comment has to keep track of the post to which the comment is attached.

    In addition, the code takes advantage of some of the methods available for an association. For example, in the new method, it calls

    @@ -1521,7 +1563,7 @@ http://www.gnu.org/software/src-highlite -->

    This creates a new Comment object and sets up the post_id field to have the id from the specified Post object in a single operation.

    8.5. Building Views

    Because you skipped scaffolding, you’ll need to build views for comments "by hand." Invoking script/generate controller will give you skeleton views, but they’ll be devoid of actual content. Here’s a first pass at fleshing out the comment views.

    -

    The index.html.erb view:

    +

    The views/comments/index.html.erb view:

    <%= link_to 'New comment', new_post_comment_path(@post) %> <%= link_to 'Back to Post', @post %>
    -

    The new.html.erb view:

    +

    The views/comments/new.html.erb view:

    <% end %> <%= link_to 'Back', post_comments_path(@post) %>
    -

    The show.html.erb view:

    +

    The views/comments/show.html.erb view:

    <%= link_to 'Edit', edit_post_comment_path(@post, @comment) %> | <%= link_to 'Back', post_comments_path(@post) %>
    -

    The edit.html.erb view:

    +

    The views/comments/edit.html.erb view:

    <%= link_to 'Show', post_comment_path(@post, @comment) %> | <%= link_to 'Back', post_comments_path(@post) %>
    -

    Again, the added complexity here (compared to the views you saw for managing comments) comes from the necessity of juggling a post and its comments at the same time.

    +

    Again, the added complexity here (compared to the views you saw for managing posts) comes from the necessity of juggling a post and its comments at the same time.

    8.6. Hooking Comments to Posts

    -

    As a final step, I’ll modify the show.html.erb view for a post to show the comments on that post, and to allow managing those comments:

    +

    As a next step, I’ll modify the views/posts/show.html.erb view to show the comments on that post, and to allow managing those comments:

    </p> <% end %> -<%= link_to 'Edit', edit_post_path(@post) %> | -<%= link_to 'Back', posts_path %> +<%= link_to 'Edit Post', edit_post_path(@post) %> | +<%= link_to 'Back to Posts', posts_path %> | <%= link_to 'Manage Comments', post_comments_path(@post) %>

    Note that each post has its own individual comments collection, accessible as @post.comments. That’s a consequence of the declarative associations in the models. Path helpers such as post_comments_path come from the nested route declaration in config/routes.rb.

    -

    9. What’s Next?

    +

    9. Building a Multi-Model Form

    +
    +

    Comments and posts are edited on two separate forms - which makes sense, given the flow of this mini-application. But what if you want to edit more than one thing on a single form? Rails 2.3 offers new support for nested forms. Let’s add support for giving each post multiple tags, right in the form where you create the post. First, create a new model to hold the tags:

    +
    +
    +
    $ script/generate model tag name:string post:references
    +

    Run the migration to create the database table:

    +
    +
    +
    $ rake db:migrate
    +

    Next, edit the post.rb file to create the other side of the association, and to tell Rails that you intend to edit tags via posts:

    +
    +
    +
    class Post < ActiveRecord::Base
    +  validates_presence_of :name, :title
    +  validates_length_of :title, :minimum => 5
    +  has_many :comments
    +  has_many :tags
    +
    +  accepts_nested_attributes_for :tags, :allow_destroy => :true  ,
    +        :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
    +end
    +

    The :allow_destroy option on the nested attribute declaration tells Rails to display a "remove" checkbox on the view that you’ll build shortly. The :reject_if option prevents saving new tags that do not have any attributes filled in.

    +

    You’ll also need to modify views/posts/_form.html.erb to include the tags:

    +
    +
    +
    <% @post.tags.build if @post.tags.empty? %>
    +<% form_for(@post) do |post_form| %>
    +  <%= post_form.error_messages %>
    +
    +  <p>
    +    <%= post_form.label :name %><br />
    +    <%= post_form.text_field :name %>
    +  </p>
    +  <p>
    +    <%= post_form.label :title, "title" %><br />
    +    <%= post_form.text_field :title %>
    +  </p>
    +  <p>
    +    <%= post_form.label :content %><br />
    +    <%= post_form.text_area :content %>
    +  </p>
    +  <h2>Tags</h2>
    +  <% post_form.fields_for :tags do |tag_form| %>
    +    <p>
    +      <%= tag_form.label :name, 'Tag:' %>
    +      <%= tag_form.text_field :name %>
    +    </p>
    +    <% unless tag_form.object.nil? || tag_form.object.new_record? %>
    +      <p>
    +        <%= tag_form.label :_delete, 'Remove:' %>
    +        <%= tag_form.check_box :_delete %>
    +      </p>
    +    <% end %>
    +  <% end %>
    +
    +  <p>
    +    <%= post_form.submit "Save" %>
    +  </p>
    +<% end %>
    +

    With these changes in place, you’ll find that you can edit a post and its tags directly on the same view.

    +
    +
    + + +
    +Note +You may want to use javascript to dynamically add additional tags on a single form. For an example of this and other advanced techniques, see the nested model sample application.
    +
    + +

    10. What’s Next?

    Now that you’ve seen your first Rails application, you should feel free to update it and experiment on your own. But you don’t have to do everything without help. As you need assistance getting up and running with Rails, feel free to consult these support resources:

      @@ -1701,41 +1825,16 @@ Running rake doc:rails will put a full copy of the API documentation fo
    -

    10. Changelog

    +

    11. Changelog

    -
      -
    • -

      -November 3, 2008: Formatting patch from Dave Rothlisberger -

      -
    • -
    • -

      -November 1, 2008: First approved version by Mike Gunderloy -

      -
    • -
    • -

      -October 16, 2008: Revised based on feedback from Pratik Naik by Mike Gunderloy (not yet approved for publication) -

      -
    • -
    • -

      -October 13, 2008: First complete draft by Mike Gunderloy (not yet approved for publication) -

      -
    • -
    • -

      -October 12, 2008: More detail, rearrangement, editing by Mike Gunderloy (not yet approved for publication) -

      -
    • -
    • -

      -September 8, 2008: initial version by James Miller (not yet approved for publication) -

      -
    • -
    +

    * +* November 3, 2008: Formatting patch from Dave Rothlisberger +* November 1, 2008: First approved version by Mike Gunderloy +* October 16, 2008: Revised based on feedback from Pratik Naik by Mike Gunderloy (not yet approved for publication) +* October 13, 2008: First complete draft by Mike Gunderloy (not yet approved for publication) +* October 12, 2008: More detail, rearrangement, editing by Mike Gunderloy (not yet approved for publication) +* September 8, 2008: initial version by James Miller (not yet approved for publication)

    diff --git a/railties/doc/guides/html/i18n.html b/railties/doc/guides/html/i18n.html index 8a6e4cc990..b0d52c0ff5 100644 --- a/railties/doc/guides/html/i18n.html +++ b/railties/doc/guides/html/i18n.html @@ -50,16 +50,24 @@
  • Setting and passing the locale
  • +
  • Setting locale from the domain name
  • + +
  • Setting locale from the URL params
  • + +
  • Setting locale from the client supplied information
  • +
  • - Internationalize your application + Internationalizing your application
  • @@ -80,9 +88,9 @@ How to store your custom translations
  • @@ -97,9 +105,18 @@
  • + Conclusion +
  • +
  • + Contributing to Rails I18n +
  • +
  • Resources
  • + Authors +
  • +
  • Footnotes
  • @@ -112,13 +129,51 @@

    The Rails Internationalization (I18n) API

    -

    The Ruby I18n (shorthand for internationalization) gem which is shipped with Ruby on Rails (starting from Rails 2.2) provides an easy-to-use and extensible framework for translating your application to a single custom language other than English or providing multi-language support in your application.

    +

    The Ruby I18n (shorthand for internationalization) gem which is shipped with Ruby on Rails (starting from Rails 2.2) provides an easy-to-use and extensible framework for translating your application to a single custom language other than English or for providing multi-language support in your application.

    +

    The process of "internationalization" usually means to abstract all strings and other locale specific bits (such as date or currency formats) out of your application. The process of "localization" means to provide translations and localized formats for these bits. [1]

    +

    So, in the process of internationalizing your Rails application you have to:

    +
      +
    • +

      +Ensure you have support for i18n +

      +
    • +
    • +

      +Tell Rails where to find locale dictionaries +

      +
    • +
    • +

      +Tell Rails how to set, preserve and switch locale +

      +
    • +
    +

    In the process of localizing your application you’ll probably want to do following three things:

    +
      +
    • +

      +Replace or supplement Rail’s 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 +

      +
    • +
    • +

      +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.

    - +
    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. See Rails I18n Wiki for more information.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 for more information.
    @@ -138,7 +193,7 @@ making it easy to customize and extend everything for other languages

  • -

    As part of this solution, every static string in the Rails framework — eg. ActiveRecord 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 — eg. Active Record validation messages, time and date formats — has been internationalized, so localization of a Rails application means "over-riding" these defaults.

    1.1. The overall architecture of the library

    Thus, the Ruby I18n gem is split into two parts:

    -

    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 ActiveRecord validation messages in the activerecord/lib/active_record/locale/en.yml file or time and date formats in the 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 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 file or time and date formats in the 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.

    +
    + + + +
    +Note +The i18n library takes pragmatic approach to locale keys (after some discussion), 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.
    +

    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.

    @@ -244,8 +307,17 @@ I18n.load_path # set default locale to something else then :en I18n.default_locale = :pt

    2.3. Setting and passing the locale

    -

    By default the I18n library will use :en (English) as a I18n.default_locale for looking up translations (if you do not specify a locale for a lookup).

    -

    If you want to translate your Rails application to a single language other than English you can set I18n.default_locale to your locale. If you want to change the locale on a per-request basis though you can set it in a before_filter on the ApplicationController like this:

    +

    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. Read more about RESTful approach in Stefan Tilkov’s articles. 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:

    before_filter :set_locale
     def set_locale
    -  # if this is nil then I18n.default_locale will be used
    +  # if params[:locale] is nil then I18n.default_locale will be used
       I18n.locale = params[:locale]
     end
    -

    This will already work for URLs where you pass the locale as a query parameter as in example.com?locale=pt (which is what Google also does).

    +

    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.

    +

    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.

    + +
    -Tip +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 and background at Rails I18n Wiki.)
    +
    +

    So, for having available locales easily available in Rails 2.2, we have to include this support manually in an initializer, like this:

    +
    +
    +
    # config/initializers/available_locales.rb
    +#
    +# Get loaded locales conveniently
    +# See http://rails-i18n.org/wiki/pages/i18n-available_locales
    +module I18n
    +  class << self
    +    def available_locales; backend.available_locales; end
    +  end
    +  module Backend
    +    class Simple
    +      def available_locales; translations.keys.collect { |l| l.to_s }.sort; end
    +    end
    +  end
    +end
    +
    +# You need to "force-initialize" loaded locales
    +I18n.backend.send(:init_translations)
    +
    +AVAILABLE_LOCALES = I18n.backend.available_locales
    +RAILS_DEFAULT_LOGGER.debug "* Loaded locales: #{AVAILABLE_LOCALES.inspect}"
    +

    You can then wrap the constant for easy access in ApplicationController:

    +
    +
    +
    class ApplicationController < ActionController::Base
    +  def available_locales; AVAILABLE_LOCALES; end
    +end
    +

    2.4. Setting 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:

    +
    +

    You can implement it like this in your ApplicationController:

    +
    +
    +
    before_filter :set_locale
    +def set_locale
    +  I18n.locale = extract_locale_from_uri
    +end
    +# Get locale from top-level domain or return nil if such locale is not available
    +# You have to put something like:
    +#   127.0.0.1 application.com
    +#   127.0.0.1 application.it
    +#   127.0.0.1 application.pl
    +# in your /etc/hosts file to try this out locally
    +def extract_locale_from_tld
    +  parsed_locale = request.host.split('.').last
    +  (available_locales.include? parsed_locale) ? parsed_locale  : nil
    +end
    +

    We can also set the locale from the subdomain in very similar way:

    +
    +
    +
    # Get locale code from request subdomain (like http://it.application.local:3000)
    +# You have to put something like:
    +#   127.0.0.1 gr.application.local
    +# in your /etc/hosts file to try this out locally
    +def extract_locale_from_subdomain
    +  parsed_locale = request.subdomains.first
    +  (available_locales.include? parsed_locale) ? parsed_locale  : nil
    +end
    +

    If your application includes a locale switching menu, you would then have something like this in it:

    +
    +
    +
    link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['REQUEST_URI']}")
    +

    assuming you would set APP_CONFIG[:deutsch_website_url] to some value like http://www.application.de.

    +

    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).

    +

    2.5. Setting 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.

    +

    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.

    +

    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.

    +

    Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its ApplicationController#default_url_options, which is useful precisely in this scenario: it enables us to set "defaults" for url_for and helper methods dependent on it (by implementing/overriding this method).

    +

    We can include something like this in our ApplicationController then:

    +
    +
    +
    # app/controllers/application_controller.rb
    +def default_url_options(options={})
    +  logger.debug "default_url_options is passed options: #{options.inspect}\n"
    +  { :locale => I18n.locale }
    +end
    +

    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.

    +

    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 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 option in this way:

    +
    +
    +
    # config/routes.rb
    +map.resources :books, :path_prefix => '/:locale'
    +

    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).

    +

    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.)

    +

    You would probably need to map URLs like these:

    +
    +
    +
    # config/routes.rb
    +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 For other URL designs, see How to encode the current locale in the URL.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 and Raul Murciano’s translate_routes. See also the page How to encode the current locale in the URL in the Rails i18n Wiki.
    -

    Now you’ve initialized I18n support for your application and told it which locale should be used. With that in place you’re now ready for the really interesting stuff.

    +

    2.6. Setting 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.

    +

    2.6.1. Using Accept-Language

    +

    One source of client supplied information would be an Accept-Language HTTP header. People may set this in their browser or other clients (such as curl).

    +

    A trivial implementation of using Accept-Language header would be:

    +
    +
    +
    def set_locale
    +  logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
    +  I18n.locale = extract_locale_from_accept_language_header
    +  logger.debug "* Locale set to '#{I18n.locale}'"
    +end
    +private
    +def extract_locale_from_accept_language_header
    +  request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
    +end
    +

    Of course, in production environment you would need much robust code, and could use a plugin such as Iaian Hecker’s http_accept_language.

    +

    2.6.2. 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. 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.

    +

    2.6.3. 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.

    -

    3. Internationalize your application

    +

    3. Internationalizing your application

    -

    The process of "internationalization" usually means to abstract all strings and other locale specific bits out of your application. The process of "localization" means to then provide translations and localized formats for these bits. [1]

    -

    So, let’s internationalize something. You most probably have something like this in one of your applications:

    +

    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.

    +

    Let’s internationalize our application, ie. abstract every locale-specific parts, and that localize it, ie. provide neccessary translations for these abstracts.

    +

    You most probably have something like this in one of your applications:

    # app/views/home/index.html.erb <h1><%=t :hello_world %></h1> <p><%= flash[:notice] %></p>
    -

    When you now render this view it will show an error message that tells you that the translations for the keys :hello_world and :hello_flash are missing.

    +

    When you now render this view, it will show an error message which tells you that the translations for the keys :hello_world and :hello_flash are missing.

    rails i18n demo translation missing

    @@ -320,10 +564,10 @@ http://www.gnu.org/software/src-highlite --> 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">. +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">.
    -

    So let’s add the missing translations (i.e. do the "localization" part):

    +

    So let’s add the missing translations into the dictionary files (i.e. do the "localization" part):

    I18n.t :message
     I18n.t 'message'
    -

    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:

    +

    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:

    I18n.t :invalid, :scope => [:active_record, :error_messages]
    -

    This looks up the :invalid message in the ActiveRecord error messages.

    +

    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:

    I18n.t :missing, :default => 'Not here'
     # => 'Not here'

    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:

    I18n.t [:odd, :even], :scope => 'active_record.error_messages'
     # => ["must be odd", "must be even"]
    -

    Also, a key can translate to a (potentially nested) hash as grouped translations. E.g. one can receive all ActiveRecord error messages as a Hash with:

    +

    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:

    I18n.t 'active_record.error_messages'
     # => { :inclusion => "is not included in the list", :exclusion => ... }

    4.2. 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.

    -

    All options besides :default and :scope that are passed to #translate will be interpolated to the translation:

    +

    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.

    +

    All options besides :default and :scope that are passed to #translate will be interpolated to the translation:

    I18n.backend.store_translations :en, :thanks => 'Thanks {{name}}!'
     I18n.translate :thanks, :name => 'Jeremy'
     # => 'Thanks Jeremy!'
    -

    If a translation uses :default or :scope as a interpolation variable an I18n::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 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.

    4.3. 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, Japanese, Russian and many more) have different grammars that have additional or less plural forms. 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:

    +

    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:

    } I18n.translate :inbox, :count => 2 # => '2 messages'
    -

    The algorithm for pluralizations in :en is as simple as:

    +

    The algorithm for pluralizations in :en is as simple as:

    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 I18n::InvalidPluralizationData exception is raised.

    +

    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.

    4.4. 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:

    +

    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:

    I18n.t :foo, :locale => :de
     I18n.l Time.now, :locale => :de
    -

    I18n.locale defaults to I18n.default_locale which defaults to :en. The default locale can be set like this:

    +

    I18n.locale defaults to I18n.default_locale which defaults to :en. The default locale can be set like this:

    pt:
       foo:
         bar: 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:

    +

    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:

    default: "%Y-%m-%d" short: "%b %d" long: "%B %d, %Y"
    -

    So, all of the following equivalent lookups will return the :short date format "%B %d":

    +

    So, all of the following equivalent lookups will return the :short date format "%B %d":

    I18n.t 'formats.short', :scope => :date I18n.t :short, :scope => 'date.formats' I18n.t :short, :scope => [:date, :formats]
    -

    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

    -

    5.1. Translations for ActiveRecord 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.

    +

    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.

    +

    5.1. 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.

    For example when you add the following translations:

    user: login: "Handle" # will translate User attribute "login" as "Handle"
    -

    Then User.human_name will return "Dude" and User.human_attribute_name(:login) will return "Handle".

    +

    Then User.human_name will return "Dude" and User.human_attribute_name(:login) will return "Handle".

    5.1.1. Error message scopes

    -

    ActiveRecord validation error messages can also be translated easily. ActiveRecord 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.

    -

    Consider a User model with a validates_presence_of validation for the name attribute like this:

    +

    Consider a User model with a validates_presence_of validation for the name attribute like this:

    class User < ActiveRecord::Base
       validates_presence_of :name
     end
    -

    The key for the error message in this case is :blank. ActiveRecord will lookup this key in the namespaces:

    +

    The key for the error message in this case is :blank. Active Record will lookup this key in the namespaces:

    class Admin < User
       validates_presence_of :name
     end
    -

    Then ActiveRecord will look for messages in this order:

    +

    Then Active Record will look for messages in this order:

    one: "1 error prohibited this {{model}} from being saved" other: "{{count}} errors prohibited this {{model}} from being saved" body: "There were problems with the following fields:"
    -

    5.2. Other translations and localizations

    -

    Rails uses fixed strings and other localizations, such as format strings and other format information in a couple of helpers.

    -

    TODO list helpers and available keys

    +

    5.2. 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.

    +

    5.2.1. ActionView helper methods

    +
    +

    5.2.2. Active Record methods

    +
    +

    * 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 (and defaults to ' ').

    +

    5.2.3. ActiveSupport methods

    +

    6. Customize your I18n setup

    @@ -813,7 +1170,7 @@ InvalidPluralizationData # the translation expects an interpolation argument that has not been passed ReservedInterpolationKey # the translation contains a reserved interpolation variable name (i.e. one of: scope, default) UnknownFileType # the backend does not know how to handle a file type that was added to I18n.load_path
    -

    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 exception’s error message string containing the missing key/scope.

    +

    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 exception’s 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:

    @@ -828,9 +1185,9 @@ http://www.gnu.org/software/src-highlite --> end 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.

    -

    To do so the helper forces I18n#translate to raise exceptions no matter what exception handler is defined by setting the :raise option:

    +

    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.

    +

    To do so the helper forces I18n#translate to raise exceptions no matter what exception handler is defined by setting the :raise option:

    I18n.t :foo, :raise => true # always re-raises exceptions from the backend
    -

    7. Resources

    +

    7. 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.

    +

    If you find anything missing or wrong in this guide please file a ticket on our issue tracker. If you want to discuss certain portions or have questions please sign up to our mailinglist.

    +
    +

    8. 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.

    +

    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!)

    +

    If you find your own locale (language) missing from our example translations data repository for Ruby on Rails, please fork the repository, add your data and send a pull request.

    +
    +

    9. Resources

    +
    +
    +

    10. Authors

    +
    +
    +

    If you found this guide useful please consider recommending its authors on workingwithrails.

    -

    8. Footnotes

    +

    11. Footnotes

    [1] Or, to quote Wikipedia: "Internationalization is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes. Localization is the process of adapting software for a specific region or language by adding locale-specific components and translating text."

    [2] Other backends might allow or require to use other formats, e.g. a GetText backend might allow to read GetText files.

    [3] 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.

    -

    9. Changelog

    +

    12. Changelog

    diff --git a/railties/doc/guides/html/index.html b/railties/doc/guides/html/index.html index d7079c9393..16ce603cec 100644 --- a/railties/doc/guides/html/index.html +++ b/railties/doc/guides/html/index.html @@ -136,20 +136,20 @@ understand how to use routing in your own Rails applications, start here.

    Basics of Action Controller

    This guide covers how controllers work and how they fit into the request cycle in your application. It includes sessions, filters, and cookies, data streaming, and dealing with exceptions raised by a request, among other topics.

    +

    Digging Deeper

    -

    Digging Deeper

    This guide covers the command line tools and rake tasks provided by Rails.

    Authors who have contributed to complete guides are listed here.

    -

    This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 License

    +

    This work is licensed under a Creative Commons Attribution-Share Alike 3.0 License

    diff --git a/railties/doc/guides/source/2_2_release_notes.txt b/railties/doc/guides/source/2_2_release_notes.txt index 6aa9fa19ce..d91928489a 100644 --- a/railties/doc/guides/source/2_2_release_notes.txt +++ b/railties/doc/guides/source/2_2_release_notes.txt @@ -430,7 +430,8 @@ Previously the above code made available a local variable called +customer+ insi * The +%s+ and +%d+ interpolation syntax for internationalization is deprecated. * +String#chars+ has been deprecated in favor of +String#mb_chars+. * Durations of fractional months or fractional years are deprecated. Use Ruby's core +Date+ and +Time+ class arithmetic instead. +* +Request#relative_url_root+ is deprecated. Use +ActionController::Base.relative_url_root+ instead. == Credits -Release notes compiled by link:http://afreshcup.com[Mike Gunderloy] +Release notes compiled by link:http://afreshcup.com[Mike Gunderloy] \ No newline at end of file diff --git a/railties/doc/guides/source/2_3_release_notes.txt b/railties/doc/guides/source/2_3_release_notes.txt new file mode 100644 index 0000000000..ebe621871f --- /dev/null +++ b/railties/doc/guides/source/2_3_release_notes.txt @@ -0,0 +1,514 @@ +Ruby on Rails 2.3 Release Notes +=============================== + +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 link:http://github.com/rails/rails/commits/master[list of commits] in the main Rails repository on GitHub or review the +CHANGELOG+ files for the individual Rails components. + +== Application Architecture + +There are two major changes in the architecture of Rails applications: complete integration of the link:http://rack.rubyforge.org/[Rack] modular web server interface, and renewed support for Rails Engines. + +=== Rack Integration + +Rails has now broken with its CGI past, and uses Rack everywhere. This required and resulted in a tremendous number of internal changes (but if you use CGI, don't worry; Rails now supports CGI through a proxy interface.) Still, this is a major change to Rails internals. After upgrading to 2.3, you should test on your local environment and your production environment. Some things to test: + +* Sessions +* Cookies +* File uploads +* JSON/XML APIs + +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 +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 converts its environment information into a Rack compatible form. +* +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+ +* +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', ...+ +* Using the +ParamsParser+ middleware preprocesses any XML, JSON, or YAML requests so they can be read normally with any +Rack::Request+ object after it. + +=== Renewed Support for Rails Engines + +After some versions without an upgrade, Rails 2.3 offers some new features for Rails Engines (Rails applications that can be embedded within other applications). First, routing files in engines are automatically loaded and reloaded now, just like your +routes.rb+ file (this also applies to routing files in other plugins). Second, if your plugin has an app folder, then app/[models|controllers|helpers] will automatically be added to the Rails load path. Engines also support adding view paths now. + +== Documentation + +The link:http://guides.rubyonrails.org/[Ruby on Rails guides] project has published several additional guides for Rails 2.3. In addition, a link:http://guides.rails.info/[separate site] maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the link:http://newwiki.rubyonrails.org/[Rails wiki] and early planning for a Rails Book. + +* More Information: link:http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects[Rails Documentation Projects] + +== Active Record + +Active Record gets quite a number of new features and bug fixes in Rails 2.3. The highlights include nested attributes, nested transactions, dynamic scopes, and default scopes. + +=== Nested Attributes + +Active Record can now update the attributes on nested models directly, provided you tell it to do so: + +[source, ruby] +------------------------------------------------------- +class Book < ActiveRecord::Base + has_one :author + has_many :pages + + accepts_nested_attributes_for :author, :pages +end +------------------------------------------------------- + +Turning on nested attributes enables a number of things: automatic (and atomic) saving of a record together with its associated children, child-aware validations, and support for nested forms (discussed later). + +* Lead Contributor: link_to:http://www.superalloy.nl/blog/[Eloy Duran] +* More Information: link_to:http://weblog.rubyonrails.org/2009/1/26/nested-model-forms[Nested Model Forms] + +=== Nested Transactions + +Active Record now supports nested transactions, a much-requested feature. Now you can write code like this: + +[source, ruby] +------------------------------------------------------- +User.transaction do + User.create(:username => 'Admin') + User.transaction(:requires_new => true) do + User.create(:username => 'Regular') + raise ActiveRecord::Rollback + end + end + + User.find(:all) # => Returns only Admin +------------------------------------------------------- + +Nested transactions let you roll back an inner transaction without affecting the state of the outer transaction. If you want a transaction to be nested, you must explicitly add the +:requires_new+ option; otherwise, a nested transaction simply becomes part of the parent transaction (as it does currently on Rails 2.2). Under the covers, nested transactions are link:http://rails.lighthouseapp.com/projects/8994/tickets/383[using savepoints], so they're supported even on databases that don't have true nested transactions. There is also a bit of magic going on to make these transactions play well with transactional fixtures during testing. + +* Lead Contributors: link:http://www.workingwithrails.com/person/4985-jonathan-viney[Jonathan Viney] and link:http://izumi.plan99.net/blog/[Hongli Lai] + +=== Dynamic Scopes + +You know about dynamic finders in Rails (which allow you to concoct methods like +find_by_color_and_flavor+ on the fly) and named scopes (which allow you to encapsulate reusable query conditions into friendly names like +currently_active+). Well, now you can have dynamic scope methods. The idea is to put together syntax that allows filtering on the fly _and_ method chaining. For example: + +[source, ruby] +------------------------------------------------------- +Order.scoped_by_customer_id(12) +Order.scoped_by_customer_id(12).find(:all, + :conditions => "status = 'open'") +Order.scoped_by_customer_id(12).scoped_by_status("open") +------------------------------------------------------- + +There's nothing to define to use dynamic scopes: they just work. + +* Lead Contributor: link:http://evilmartians.com/[Yaroslav Markin] +* More Information: link:http://ryandaigle.com/articles/2008/12/29/what-s-new-in-edge-rails-dynamic-scope-methods[What's New in Edge Rails: Dynamic Scope Methods]. + +=== Default Scopes + +Rails 2.3 will introduce the notion of _default scopes_ similar to named scopes, but applying to all named scopes or find methods within the model. For example, you can write +default_scope :order => 'name ASC'+ and any time you retrieve records from that model they'll come out sorted by name (unless you override the option, of course). + +* Lead Contributor: Paweł Kondzior +* More Information: link:http://ryandaigle.com/articles/2008/11/18/what-s-new-in-edge-rails-default-scoping[What's New in Edge Rails: Default Scoping] + +=== Multiple Conditions for Callbacks + +When using Active Record callbacks, you can now combine +:if+ and +:unless+ options on the same callback, and supply multiple conditions as an array: + +[source, ruby] +------------------------------------------------------- +before_save :update_credit_rating, :if => :active, + :unless => [:admin, :cash_only] +------------------------------------------------------- +* Lead Contributor: L. Caviola + +=== Find with having + +Rails now has a +:having+ option on find (as well as on +has_many+ and +has_and_belongs_to_many+ associations) for filtering records in grouped finds. As those with heavy SQL backgrounds know, this allows filtering based on grouped results: + +[source, ruby] +------------------------------------------------------- +developers = Developer.find(:all, :group => "salary", + :having => "sum(salary) > 10000", :select => "salary") +------------------------------------------------------- + +* Lead Contributor: link:http://github.com/miloops[Emilio Tagua] + +=== Hash Conditions for +has_many+ relationships + +You can once again use a hash in conditions for a +has_many+ relationship: + +[source, ruby] +------------------------------------------------------- +has_many :orders, :conditions => {:status => 'confirmed'} +------------------------------------------------------- + +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: link:http://www.spacevatican.org/[Frederick Cheung] + +=== 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. + +* Lead Contributor: link:http://twitter.com/dubek[Dov Murik] +* More information: + - link:http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html[Controlling Automatic Reconnection Behavior] + - link:http://groups.google.com/group/rubyonrails-core/browse_thread/thread/49d2a7e9c96cb9f4[MySQL auto-reconnect revisited] + +=== 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. +* +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. +* 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) + +== Action Controller + +Action Controller rolls out some significant changes to rendering, as well as improvements in routing and other areas, in this release. + +=== Unified Rendering + ++ActionController::Base#render+ is a lot smarter about deciding what to render. Now you can just tell it what to render and expect to get the right results. In older versions of Rails, you often need to supply explicit information to render: + +[source, ruby] +------------------------------------------------------- +render :file => '/tmp/random_file.erb' +render :template => 'other_controller/action' +render :action => 'show' +------------------------------------------------------- + +Now in Rails 2.3, you can just supply what you want to render: + +[source, ruby] +------------------------------------------------------- +render '/tmp/random_file.erb' +render 'other_controller/action' +render 'show' +render :show +------------------------------------------------------- +Rails chooses between file, template, and action depending on whether there is a leading slash, an embedded slash, or no slash at all in what's to be rendered. Note that you can also use a symbol instead of a string when rendering an action. Other rendering styles (+:inline+, +:text+, +:update+, +:nothing+, +:json+, +:xml+, +:js+) still require an explicit option. + +=== Application Controller Renamed + +If you're one of the people who has always been bothered by the special-case naming of +application.rb+, rejoice! It's been reworked to be application_controller.rb in Rails 2.3. In addition, there's a new rake task, +rake rails:update:application_controller+ to do this automatically for you - and it will be run as part of the normal +rake rails:update+ process. + +* More Information: + - link:http://afreshcup.com/2008/11/17/rails-2x-the-death-of-applicationrb/[The Death of Application.rb] + - link:http://ryandaigle.com/articles/2008/11/19/what-s-new-in-edge-rails-application-rb-duality-is-no-more[What's New in Edge Rails: Application.rb Duality is no More] + +=== HTTP Digest Authentication Support + +Rails now has built-in support for HTTP digest authentication. To use it, you call +authenticate_or_request_with_http_digest+ with a block that returns the user’s password (which is then hashed and compared against the transmitted credentials): + +[source, ruby] +------------------------------------------------------- +class PostsController < ApplicationController + Users = {"dhh" => "secret"} + before_filter :authenticate + + def secret + render :text => "Password Required!" + end + + private + def authenticate + realm = "Application" + authenticate_or_request_with_http_digest(realm) do |name| + Users[name] + end + end +end +------------------------------------------------------- + +* Lead Contributor: link:http://www.kellogg-assoc.com/[Gregg Kellogg] +* More Information: link:http://ryandaigle.com/articles/2009/1/30/what-s-new-in-edge-rails-http-digest-authentication[What's New in Edge Rails: HTTP Digest Authentication] + +=== More Efficient Routing + +There are a couple of significant routing changes in Rails 2.3. The +formatted_+ route helpers are gone, in favor just passing in +:format+ as an option. This cuts down the route generation process by 50% for any resource - and can save a substantial amount of memory (up to 100MB on large applications). If your code uses the +formatted_+ helpers, it will still work for the time being - but that behavior is deprecated and your application will be more efficient if you rewrite those routes using the new standard. Another big change is that Rails now supports multiple routing files, not just routes.rb. You can use +RouteSet#add_configuration_file+ to bring in more routes at any time - without clearing the currently-loaded routes. While this change is most useful for Engines, you can use it in any application that needs to load routes in batches. + +Lead Contributors: link:http://blog.hungrymachine.com/[Aaron Batalion] + +=== Rack-based Lazy-loaded Sessions + +A big change pushed the underpinnings of Action Controller session storage down to the Rack level. This involved a good deal of work in the code, though it should be completely transparent to your Rails applications (as a bonus, some icky patches around the old CGI session handler got removed). It's still significant, though, for one simple reason: non-Rails Rack applications have access to the same session storage handlers (and therefore the same session) as your Rails applications. In addition, sessions are now lazy-loaded (in line with the loading improvements to the rest of the framework). This means that you no longer need to explicitly disable sessions if you don't want them; just don't refer to them and they won't load. + +=== MIME Type Handling Changes + +There are a couple of changes to the code for handling MIME types in Rails. First, +MIME::Type+ now implements the +=~+ operator, making things much cleaner when you need to check for the presence of a type that has synonyms: + +[source, ruby] +------------------------------------------------------- +if content_type && Mime::JS =~ content_type + # do something cool +end + +Mime::JS =~ "text/javascript" => true +Mime::JS =~ "application/javascript" => true +------------------------------------------------------- + +The other change is that the framework now uses the +Mime::JS+ when checking for javascript in various spots, making it handle those alternatives cleanly. + +Lead Contributor: link:http://www.workingwithrails.com/person/5510-seth-fitzsimmons[Seth Fitzsimmons] + +=== Optimization of +respond_to+ + +In some of the first fruits of the Rails-Merb team merger, Rails 2.3 includes some optimizations for the +respond_to+ method, which is of course heavily used in many Rails applications to allow your controller to format results differently based on the MIME type of the incoming request. After eliminating a call to +method_missing+ and some profiling and tweaking, we're seeing an 8% improvement in the number of requests per second served with a simple +respond_to+ that switches between three formats. The best part? No change at all required to the code of your application to take advantage of this speedup. + +=== 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. + +* Lead Contributor: link:http://www.motionstandingstill.com/[Nahum Wild] + +=== 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. + +=== Other Action Controller Changes + +* ETag handling has been cleaned up a bit: Rails will now skip sending an ETag header when there's no body to the response or when sending files with +send_file+. +* The fact that Rails checks for IP spoofing can be a nuisance for sites that do heavy traffic with cell phones, because their proxies don't generally set things up right. If that's you, you can now set +ActionController::Base.ip_spoofing_check = false+ to disable the check entirely. +* +ActionController::Dispatcher+ now implements its own middleware stack, which you can see by running +rake middleware+. +* Cookie sessions now have persistent session identifiers, with API compatibility with the server-side stores. +* You can now use symbols for the +:type+ option of +send_file+ and +send_data+, like this: +send_file("fabulous.png", :type => :png)+. +* The +:only+ and +:except+ options for +map.resources+ are no longer inherited by nested resources. + +== Action View + +Action View in Rails 2.3 picks up nested model forms, improvements to +render+, more flexible prompts for the date select helpers, and a speedup in asset caching, among other things. + +=== Nested Object Forms + +Provided the parent model accepts nested attributes for the child objects (as discussed in the Active Record section), you can create nested forms using +form_for+ and +field_for+. These forms can be nested arbitrarily deep, allowing you to edit complex object hierarchies on a single view without excessive code. For example, given this model: + +[source, ruby] +------------------------------------------------------- +class Customer < ActiveRecord::Base + has_many :orders + + accepts_nested_attributes_for :orders, :allow_destroy => true +end +------------------------------------------------------- + +You can write this view in Rails 2.3: + +[source, ruby] +------------------------------------------------------- +<% form_for @customer do |customer_form| %> +
    + <%= customer_form.label :name, 'Customer Name:' %> + <%= customer_form.text_field :name %> +
    + + + <% customer_form.fields_for :orders do |order_form| %> +

    +

    + <%= order_form.label :number, 'Order Number:' %> + <%= order_form.text_field :number %> +
    + + + <% unless order_form.object.new_record? %> +
    + <%= order_form.label :_delete, 'Remove:' %> + <%= order_form.check_box :_delete %> +
    + <% end %> +

    + <% end %> + <% end %> + + <%= customer_form.submit %> +<% end %> +------------------------------------------------------- + +* Lead Contributor: link_to:http://www.superalloy.nl/blog/[Eloy Duran] +* More Information: + - link_to:http://weblog.rubyonrails.org/2009/1/26/nested-model-forms[Nested Model Forms] + - link_to:http://github.com/alloy/complex-form-examples/tree/nested_attributes[complex-form-examples] + - link_to:http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes[What's New in Edge Rails: Nested Object Forms] + +=== Smart Rendering of Partials + +The render method has been getting smarter over the years, and it's even smarter now. If you have an object or a collection and an appropriate partial, and the naming matches up, you can now just render the object and things will work. For example, in Rails 2.3, these render calls will work in your view (assuming sensible naming): + +[source, ruby] +------------------------------------------------------- +render @article # Equivalent of render :partial => 'articles/_article', :object => @article +render @articles # Equivalent of render :partial => 'articles/_article', :collection => @articles +------------------------------------------------------- + +* More Information: link:http://ryandaigle.com/articles/2008/11/20/what-s-new-in-edge-rails-render-stops-being-high-maintenance[What's New in Edge Rails: render Stops Being High-Maintenance] + +=== Prompts for Date Select Helpers + +In Rails 2.3, you can supply custom prompts for the various date select helpers (+date_select+, +time_select+, and +datetime_select+), the same way you can with collection select helpers. You can supply a prompt string or a hash of individual prompt strings for the various components. You can also just set +:prompt+ to +true+ to use the custom generic prompt: + +[source, ruby] +------------------------------------------------------- +select_datetime(DateTime.now, :prompt => true) + +select_datetime(DateTime.now, :prompt => "Choose date and time") + +select_datetime(DateTime.now, :prompt => + {:day => 'Choose day', :month => 'Choose month', + :year => 'Choose year', :hour => 'Choose hour', + :minute => 'Choose minute'}) +------------------------------------------------------- + +Lead Contributor: link:http://samoliver.com/[Sam Oliver] + +=== AssetTag Timestamp Caching + +You're likely familiar with Rails' practice of adding timestamps to static asset paths as a "cache buster." This helps ensure that stale copies of things like images and stylesheets don't get served out of the user's browser cache when you change them on the server. You can now modify this behavior with the +cache_asset_timestamps+ configuration option for Action View. If you enable the cache, then Rails will calculate the timestamp once when it first serves an asset, and save that value. This means fewer (expensive) file system calls to serve static assets - but it also means that you can't modify any of the assets while the server is running and expect the changes to get picked up by clients. + +=== Asset Hosts as Objects + +Asset hosts get more flexible in edge Rails with the ability to declare an asset host as a specific object that responds to a call. This allows you to to implement any complex logic you need in your asset hosting. + +* More Information: link:http://github.com/dhh/asset-hosting-with-minimum-ssl/tree/master[asset-hosting-with-minimum-ssl] + +=== grouped_options_for_select Helper Method + +Action View already haD a bunch of helpers to aid in generating select controls, but now there's one more: +grouped_options_for_select+. This one accepts an array or hash of strings, and converts them into a string of +option+ tags wrapped with +optgroup+ tags. For example: + +[source, ruby] +------------------------------------------------------- +grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], + "Cowboy Hat", "Choose a product...") +------------------------------------------------------- + +returns + +[source, ruby] +------------------------------------------------------- + + + + + +------------------------------------------------------- + +=== Other Action View Changes + +* Token generation for CSRF protection has been simplified; now Rails uses a simple random string generated by +ActiveSupport::SecureRandom+ rather than mucking around with session IDs. +* +auto_link+ now properly applies options (such as :target and :class) to generated e-mail links. +* The +autolink+ helper has been refactored to make it a bit less messy and more intuitive. + +== Active Support + +Active Support has a few interesting changes, including the introduction of +Object#try+. + +=== Object#try + +A lot of folks have adopted the notion of using try() to attempt operations on objects - It's especially helpful in views where you can avoid nil-checking by writing code like +<%= @person.try(:name) %>+. Well, now it's baked right into Rails. As implemented in Rails, it raises +NoMethodError+ on private methods and always returns +nil+ if the object is nil. + +* More Information: link:http://ozmm.org/posts/try.html[try()]. + +=== 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. + +=== 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: + +[source, ruby] +------------------------------------------------------- +>> Time.zone.now.xmlschema(6) +=> "2009-01-16T13:00:06.13653Z" +------------------------------------------------------- + +Lead Contributor: link:http://www.workingwithrails.com/person/13536-nicholas-dainty[Nicholas Dainty] + +=== JSON Key Quoting + +If you look up the spec on the "json.org" site, you'll discover that all keys in a JSON structure must be strings, and they must be quoted with double quotes. Starting with Rails 2.3, we doe the right thing here, even with numeric keys. + +=== Other Active Support Changes + +* You can use +Enumerable#none?+ to check that none of the elements match the supplied block. +* If you're using Active Support link:http://afreshcup.com/2008/10/19/coming-in-rails-22-delegate-prefixes/[delegates], the new +:allow_nil+ option lets you return nil instead of raising an exception when the target object is nil. +* +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. + +== Railties + +In addition to the Rack changes covered above, Railties (the core code of Rails itself) sports a number of significant changes, including Rails Metal, application templates, and quiet backtraces. + +=== Rails Metal + +Rails Metal is a new mechanism that provides superfast endpoints inside of your Rails applications. Metal classes bypass routing and Action Controller to give you raw speed (at the cost of all the things in Action Controller, of course). This builds on all of the recent foundation work to make Rails a Rack application with an exposed middleware stack. + +* More Information: + - link:http://weblog.rubyonrails.org/2008/12/17/introducing-rails-metal[Introducing Rails Metal] + - link:http://soylentfoo.jnewland.com/articles/2008/12/16/rails-metal-a-micro-framework-with-the-power-of-rails-m[Rails Metal: a micro-framework with the power of Rails] + - link:http://www.railsinside.com/deployment/180-metal-super-fast-endpoints-within-your-rails-apps.html[Metal: Super-fast Endpoints within your Rails Apps] + - link:http://ryandaigle.com/articles/2008/12/18/what-s-new-in-edge-rails-rails-metal[What's New in Edge Rails: Rails Metal] + +=== Application Templates + +Rails 2.3 incorporates Jeremy McAnally's link:http://github.com/jeremymcanally/rg/tree/master[rg] application generator. What this means is that we now have template-based application generation built right into Rails; if you have a set of plugins you include in every application (among many other use cases), you can just set up a template once and use it over and over again when you run the +rails+ command. There's also a rake task to apply a template to an existing application: + +[source, ruby] +------------------------------------------------------- +rake rails:template LOCATION=~/template.rb +------------------------------------------------------- + +This will layer the changes from the template on top of whatever code the project already contains. + +* Lead Contributor: link:http://www.jeremymcanally.com/[Jeremy McAnally] +* More Info:http://m.onkey.org/2008/12/4/rails-templates[Rails templates] + +=== Quieter Backtraces + +Building on Thoughtbot's link:http://www.thoughtbot.com/projects/quietbacktrace[Quiet Backtrace] plugin, which allows you to selectively remove lines from +Test::Unit+ backtraces, Rails 2.3 implements +ActiveSupport::BacktraceCleaner+ and +Rails::BacktraceCleaner+ in core. This supports both filters (to perform regex-based substitutions on backtrace lines) and silencers (to remove backtrace lines entirely). Rails automatically adds silencers to get rid of the most common noise in a new application, and builds a +config/backtrace_silencers.rb+ file to hold your own additions. This feature also enables prettier printing from any gem in the backtrace. + +=== Faster Boot Time in Development Mode with Lazy Loading/Autoload + +Quite a bit of work was done to make sure that bits of Rails (and its dependencies) are only brought into memory when they're actually needed. The core frameworks - Active Support, Active Record, Action Controller, Action Mailer and Action View - are now using +autoload+ to lazy-load their individual classes. This work should help keep the memory footprint down and improve overall Rails performance. + +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. + +=== Other Railties Changes + +* The instructions for updating a CI server to build Rails have been updated and expanded. +* Internal Rails testing has been switched from +Test::Unit::TestCase+ to +ActiveSupport::TestCase+, and the Rails core requires Mocha to test. +* The default +environment.rb+ file has been decluttered. +* The dbconsole script now lets you use an all-numeric password without crashing. +* Rails.root now returns a Pathname object, which means you can use it directly with the join method to link:http://afreshcup.com/2008/12/05/a-little-rails_root-tidiness/[clean up existing code] that uses File.join. +* Various files in /public that deal with CGI and FCGI dispatching are no longer generated in every Rails application by default (you can still get them if you need them by adding +--with-dispatches+ when you run the rails command, or add them later with +rake rails:generate_dispatchers+). + +== Deprecated + +A few pieces of older code are deprecated in this release: + +* If you're one of the (fairly rare) Rails developers who deploys in a fashion that depends on the inspector, reaper, and spawner scripts, you'll need to know that those scripts are no longer included in core Rails. If you need them, you'll be able to pick up copies via the link:http://github.com/rails/irs_process_scripts/tree[irs_process_scripts] plugin. +* +render_component+ goes from "deprecated" to "nonexistent" in Rails 2.3. If you still need it, you can install the link:http://github.com/rails/render_component/tree/master[render_component plugin]. +* Support for Rails components has been removed. +* If you were one of the people who got used to running +script/performance/request+ to look at performance based on integration tests, you need to learn a new trick: that script has been removed from core Rails now. There’s a new request_profiler plugin that you can install to get the exact same functionality back. +* +ActionController::Base#session_enabled?+ is deprecated because sessions are lazy-loaded now. +* The +:digest+ and +:secret+ options to +protect_from_forgery+ are deprecated and have no effect. +* Some integration test helpers have been removed. +response.headers["Status"]+ and +headers["Status"]+ will no longer return anything. Rack does not allow "Status" in its return headers. However you can still use the +status+ and +status_message+ helpers. +response.headers["cookie"]+ and +headers["cookie"]+ will no longer return any CGI cookies. You can inspect +headers["Set-Cookie"]+ to see the raw cookie header or use the +cookies+ helper to get a hash of the cookies sent to the client. +* +formatted_polymorphic_url+ is deprecated. Use +polymorphic_url+ with +:format+ instead. + +== Credits + +Release notes compiled by link:http://afreshcup.com[Mike Gunderloy] \ No newline at end of file diff --git a/railties/doc/guides/source/action_mailer_basics.txt b/railties/doc/guides/source/action_mailer_basics.txt index c6cd16f10b..c7700d1678 100644 --- a/railties/doc/guides/source/action_mailer_basics.txt +++ b/railties/doc/guides/source/action_mailer_basics.txt @@ -1,16 +1,17 @@ Action Mailer Basics ==================== -This guide should provide you with all you need to get started in sending emails from your application, and will also cover how to test your mailers. +This guide should provide you with all you need to get started in sending and receiving emails from/to your application, and many internals of the ActionMailer class. It will also cover how to test your mailers. -== What is Action Mailer? +== Introduction Action Mailer allows you to send email from your application using a mailer model and views. -Yes, that is correct, in Rails, emails are used by creating Models that inherit from ActionMailer::Base. They live alongside other models in /app/models BUT they have views just like controllers that appear alongside other views in app/views. +Yes, that is correct, in Rails, emails are used by creating models that inherit from ActionMailer::Base. They live alongside other models in /app/models BUT they have views just like controllers that appear alongside other views in app/views. -== Quick walkthrough to creating a Mailer +== Sending Emails Let's say you want to send a welcome email to a user after they signup. Here is how you would go about this: -=== 1. Create the mailer: +=== Walkthrough to generating a Mailer +==== Create the mailer: [source, shell] ------------------------------------------------------- ./script/generate mailer UserMailer @@ -24,7 +25,9 @@ create test/unit/user_mailer_test.rb So we got the model, the fixtures, and the tests all created for us -=== 2. Edit the model: +==== Edit the model: + +If you look at app/models/user_mailer.rb, you will see: [source, ruby] ------------------------------------------------------- class UserMailer < ActionMailer::Base @@ -33,7 +36,6 @@ end ------------------------------------------------------- Lets add a method called welcome_email, that will send an email to the user's registered email address: - [source, ruby] ------------------------------------------------------- class UserMailer < ActionMailer::Base @@ -51,15 +53,18 @@ end ------------------------------------------------------- So what do we have here? -recipients: who the recipients are, put in an array for multiple, ie, @recipients = ["user1@example.com", "user2@example.com"] -from: Who the email will appear to come from in the recipients' mailbox -subject: The subject of the email -sent_on: Timestamp for the email -content_type: The content type, by default is text/plain +[width="100%", cols="20%,80%"] +|====================================================== +|recipients| who the recipients are, put in an array for multiple, ie, @recipients = ["user1@example.com", "user2@example.com"] +|from| Who the email will appear to come from in the recipients' mailbox +|subject| The subject of the email +|sent_on| Timestamp for the email +|content_type| The content type, by default is text/plain +|====================================================== How about @body[:user]? Well anything you put in the @body hash will appear in the mailer view (more about mailer views below) as an instance variable ready for you to use, ie, in our example the mailer view will have a @user instance variable available for its consumption. -=== 3. Create the mailer view +==== Create the mailer view Create a file called welcome_email.html.erb in #{RAILS_ROOT}/app/views/user_mailer/ . This will be the template used for the email. This file will be used for html formatted emails. Had we wanted to send text-only emails, the file would have been called welcome_email.txt.erb, and we would have set the content type to text/plain in the mailer model. The file can look like: @@ -72,7 +77,6 @@ The file can look like:

    Welcome to example.com, <%= @user.first_name %>

    -

    You have successfully signed up to example.com, and your username is: <%= @user.login %>.
    To login to the site, just follow this link: <%= @url %>. @@ -82,10 +86,12 @@ The file can look like: ------------------------------------------------------- -=== 4. Wire it up so that the system sends the email when a user signs up -There are 3 was 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 block 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. +==== Wire it up so that the system sends the email when a user signs up +There are 3 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 block 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. -Edit #{RAILS_ROOT}/config/environment.rb +Let's see how we would go about wiring it up using an observer: + +In config/environment.rb: [source, ruby] ------------------------------------------------------- # Code that already exists @@ -99,7 +105,7 @@ Rails::Initializer.run do |config| end ------------------------------------------------------- -There was a bit of a debate on where to put observers. I put them in models, but you can create #{RAILS_ROOT}/app/observers if you like, and add that to your load path. Open #{RAILS_ROOT}/config/environment.rb and make it look like: +There was a bit of a debate on where to put observers. Some people put them in app/models, but a cleaner method may be to create an app/observers folder to store all observers, and add that to your load path. Open config/environment.rb and make it look like: [source, ruby] ------------------------------------------------------- # Code that already exists @@ -116,7 +122,7 @@ end ------------------------------------------------------- ALMOST THERE :) Now all we need is that danged observer, and we're done: -Create a file called user_observer in #{RAILS_ROOT}/app/models or #{RAILS_ROOT}/app/observers, and make it look like: +Create a file called user_observer in app/models or app/observers depending on where you stored it, and make it look like: [source, ruby] ------------------------------------------------------- class UserObserver < ActiveRecord::Observer @@ -126,8 +132,352 @@ class UserObserver < ActiveRecord::Observer end ------------------------------------------------------- -Notice how we call deliver_welcome_email? Where is that method? Well if you remember, we created a method called welcome_email in UserMailer, right? Well, as part of the "magic" of rails, we deliver the email identified by welcome_email by calling deliver_welcome_email. +Notice how we call deliver_welcome_email? Where is that method? Well if you remember, we created a method called welcome_email in UserMailer, right? Well, as part of the "magic" of rails, we deliver the email identified by welcome_email by calling deliver_welcome_email. The next section will go through this in more detail. + +That's it! Now whenever your users signup, they will be greeted with a nice welcome email. + +=== Action Mailer and dynamic deliver_ 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: + +You never instantiate your mailer class. Rather, your delivery instance +methods are automatically wrapped in class methods that start with the word +deliver_ followed by the name of the mailer method that you would +like to deliver. The signup_notification method defined above is +delivered by invoking Notifier.deliver_signup_notification. + +So, how exactly does this work? + +In ActionMailer:Base, you will find this: +[source, ruby] +------------------------------------------------------- +def method_missing(method_symbol, *parameters)#:nodoc: + case method_symbol.id2name + when /^create_([_a-z]\w*)/ then new($1, *parameters).mail + when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver! + when "new" then nil + else super + end +end +------------------------------------------------------- + +Ah, this makes things so much clearer :) so 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 parameter. The resulting object is then sent the deliver! method, which well... delivers it. + +=== Complete List of ActionMailer user-settable attributes + +[width="100%", cols="20%,80%"] +|====================================================== +|bcc| Specify the BCC addresses for the message + +|body| Define the body of the message. 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 text of the message. + +|cc| Specify the CC addresses for the message. + +|charset| Specify the charset to use for the message. This defaults to the default_charset specified for ActionMailer::Base. + +|content_type| Specify the content type for the message. This defaults to " + subject "Welcome to My Awesome Site" + sent_on Time.now + body {:user => user, :url => "http://example.com/login"} + content_type "text/html" + + # change the default from welcome_email.[html, txt].erb + template "some_other_template" # this will be in app/views/user_mailer/some_other_template.[html, txt].erb + end + +end +------------------------------------------------------- + +=== Action Mailer Layouts +Just like controller views, you can also have mailer layouts. The layout name needs to end in _mailer to be automatically recognized by your mailer as a layout. So in our UserMailer example, we need to call our layout user_mailer.[html,txt].erb. In order to use a different file just use: + +[source, ruby] +------------------------------------------------------- +class UserMailer < ActionMailer::Base + + layout 'awesome' # will use awesome.html.erb as the layout + +end +------------------------------------------------------- + +Just like with controller views, use yield to render the view inside the layout. + +=== Generating URL's in Action Mailer views +URLs can be generated in mailer views using url_for or named routes. +Unlike controllers from Action Pack, the mailer instance doesn't have any context about the incoming request, so you'll need to provide all of the details needed to generate a URL. + +When using url_for you'll need to provide the :host, :controller, and :action: + + <%= url_for(:host => "example.com", :controller => "welcome", :action => "greeting") %> + +When using named routes you only need to supply the :host: + + <%= users_url(:host => "example.com") %> + +You will want to avoid using the name_of_route_path form of named routes because it doesn't make sense to generate relative URLs in email messages. The reason that it doesn't make sense is because the email is opened on a mail client outside of your environment. Since the email is not being served by your server, a URL like "/users/show/1", will have no context. In order for the email client to properly link to a URL on your server it needs something like "http://yourserver.com/users/show/1". + +It is also possible to set a default host that will be used in all mailers by setting the :host option in +the ActionMailer::Base.default_url_options hash as follows: + + ActionMailer::Base.default_url_options[:host] = "example.com" + +This can also be set as a configuration option in config/environment.rb: + + config.action_mailer.default_url_options = { :host => "example.com" } + +If you do decide to set a default :host for your mailers you will want to use the :only_path => false option when using url_for. This will ensure that absolute URLs are generated because the url_for view helper will, by default, generate relative URLs when a :host option isn't explicitly provided. + +=== 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.txt.erb and welcome_email.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. + +To explicitly specify multipart messages, you can do something like: +[source, 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 :content_type => "text/html", + :body => "

    html content, can also be the name of an action that you call

    " + + part "text/plain" do |p| + p.body = "text content, can also be the name of an action that you call" + end + end + +end +------------------------------------------------------- + +=== Sending emails with attachments +Attachments can be added by using the attachment method: +[source, 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" + + 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 +------------------------------------------------------- + +== 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: + +1. Configure your email server to forward emails from the address(es) you would like your app to receive to /path/to/app/script/runner \'UserMailer.receive(STDIN.read)' + +2. Implement a receive method in your mailer + +Once a method called receive is defined in any mailer, Action Mailer will parse the raw incoming email into an email object, decode it, instantiate a new mailer, and pass the email object to the mailer object‘s receive method. Here's an example: + +[source, ruby] +------------------------------------------------------- +class UserMailer < ActionMailer::Base + + def receive(email) + page = Page.find_by_address(email.to.first) + page.emails.create( + :subject => email.subject, + :body => email.body + ) + + if email.has_attachments? + for attachment in email.attachments + page.attachments.create({ + :file => attachment, + :description => email.subject + }) + end + end + end + + +end +------------------------------------------------------- + + +== Using Action Mailer Helpers +Action Mailer classes have 4 helper methods available to them: +[width="100%", cols="2,8"] +|====================================================== +|add_template_helper(helper_module)|Makes all the (instance) methods in the helper module available to templates rendered through this controller. + +|helper(*args, &block)| +Declare a helper: + helper :foo +requires 'foo_helper' and includes FooHelper in the template class. + helper FooHelper +includes FooHelper in the template class. + helper { def foo() "#{bar} is the very best" end } +evaluates the block in the template class, adding method foo. + helper(:three, BlindHelper) { def mice() 'mice' end } +does all three. + +|helper_method| +Declare a controller method as a helper. For example, + helper_method :link_to + def link_to(name, options) ... end +makes the link_to controller method available in the view. + +|helper_attr| +Declare a controller attribute as a helper. For example, + helper_attr :name + attr_accessor :name +makes the name and name= controller methods available in the view. +The is a convenience wrapper for helper_method. +|====================================================== + +== Action Mailer Configuration +The following configuration options are best made in one of the environment files (environment.rb, production.rb, etc...) +[width="100%", cols="2,8a"] +|====================================================== +|template_root|Determines the base from which template references will be made. +|logger|the logger is used for generating information on the mailing run if available. + Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers. +|smtp_settings|Allows detailed configuration for :smtp delivery method: +[cols="20%,80%"] +!====================================================== +!:address !Allows you to use a remote mail server. Just change it from its default "localhost" setting. +!:port !On the off chance that your mail server doesn't run on port 25, you can change it. +!:domain !If you need to specify a HELO domain, you can do it here. +!:user_name !If your mail server requires authentication, set the username in this setting. +!:password !If your mail server requires authentication, set the password in this setting. +!:authentication !If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of :plain, :login, :cram_md5. +!====================================================== +|sendmail_settings|Allows you to override options for the :sendmail delivery method. +[cols="20%,80%"] +!====================================================== +!:location!The location of the sendmail executable. Defaults to /usr/sbin/sendmail. +!:arguments!The command line arguments. Defaults to -i -t. +!====================================================== +|raise_delivery_errors|Whether or not errors should be raised if the email fails to be delivered. +|delivery_method|Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test. +|perform_deliveries|Determines whether deliver_* methods are actually carried out. By default they are, + but this can be turned off to help functional testing. +|deliveries|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful + for unit and functional testing. +|default_charset|The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also + pick a different charset from inside a method with charset. +|default_content_type|The default content type used for the main part of the message. Defaults to "text/plain". You + can also pick a different content type from inside a method with content_type. +|default_mime_version|The default mime version used for the message. Defaults to 1.0. You + can also pick a different value from inside a method with mime_version. +|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. +|====================================================== + +=== Example Action Mailer Configuration +An example would be: +[source, ruby] +------------------------------------------------------- +ActionMailer::Base.delivery_method = :sendmail +ActionMailer::Base.sendmail_settings = { + :location => '/usr/sbin/sendmail', + :arguments => '-i -t' +} +ActionMailer::Base.perform_deliveries = true +ActionMailer::Base.raise_delivery_errors = true +ActionMailer::Base.default_charset = "iso-8859-1" +------------------------------------------------------- + +=== Action Mailer Configuration for GMail +Instructions copied from http://http://www.fromjavatoruby.com/2008/11/actionmailer-with-gmail-must-issue.html + +First you must install the action_mailer_tls plugin from http://code.openrain.com/rails/action_mailer_tls/, then all you have to do is configure action mailer. + +[source, ruby] +------------------------------------------------------- +ActionMailer::Base.smtp_settings = { + :address => "smtp.gmail.com", + :port => 587, + :domain => "domain.com", + :user_name => "user@domain.com", + :password => "password", + :authentication => :plain +} +------------------------------------------------------- + +=== Configure Action Mailer to recognize HAML templates +In environment.rb, add the following line: + +[source, ruby] +------------------------------------------------------- +ActionMailer::Base.register_template_extension('haml') +------------------------------------------------------- + +== Mailer Testing +Testing mailers involves 2 things. One is that the mail was queued and the other that the body contains what we expect it to contain. With that in mind, we could test our example mailer from above like so: + +[source, ruby] +------------------------------------------------------- +class UserMailerTest < ActionMailer::TestCase + tests UserMailer + + def test_welcome_email + user = users(:some_user_in_your_fixtures) + + # Send the email, then test that it got queued + email = UserMailer.deliver_welcome_email(user) + assert !ActionMailer::Base.deliveries.empty? + + # Test the body of the sent email contains what we expect it to + assert_equal [@user.email], email.to + assert_equal "Welcome to My Awesome Site", email.subject + assert email.body =~ /Welcome to example.com, #{user.first_name}/ + end + end +------------------------------------------------------- -That's it! Now whenever your users signup, they will be greeted with a nice welcome email. Next up, we'll talk about how to test a mailer model. +What have we done? Well, we sent the email and stored the returned object in the email variable. We then ensured that it was sent (the first assert), then, in the second batch of assertion, we ensure that the email does indeed contain the values that we expect. -== Mailer Testing \ No newline at end of file +== Epilogue +This guide presented how to create a mailer and how to test it. In reality, you may find that writing your tests before you actually write your code to be a rewarding experience. It may take some time to get used to TDD (Test Driven Development), but coding this way achieves two major benefits. Firstly, you know that the code does indeed work, because the tests fail (because there's no code), then they pass, because the code that satisfies the tests was written. Secondly, when you start with the tests, you don't have to make time AFTER you write the code, to write the tests, then never get around to it. The tests are already there and testing has now become part of your coding regimen. \ No newline at end of file diff --git a/railties/doc/guides/source/actioncontroller_basics/http_auth.txt b/railties/doc/guides/source/actioncontroller_basics/http_auth.txt index 8deb40c2c9..63e7c0f061 100644 --- a/railties/doc/guides/source/actioncontroller_basics/http_auth.txt +++ b/railties/doc/guides/source/actioncontroller_basics/http_auth.txt @@ -1,6 +1,13 @@ -== HTTP Basic Authentication == +== HTTP Authentications == -Rails comes with built-in HTTP Basic authentication. This 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`. +Rails comes with two built-in HTTP authentication mechanisms : + + * Basic Authentication + * Digest Authentication + +=== 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`. [source, ruby] ------------------------------------- @@ -10,7 +17,7 @@ class AdminController < ApplicationController before_filter :authenticate -private + private def authenticate authenticate_or_request_with_http_basic do |username, password| @@ -22,3 +29,29 @@ 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. + +=== HTTP Digest Authentication === + +HTTP Digest authentication is superior to the Basic authentication as it does not require the client to send unencrypted password over the network. Using Digest authentication with Rails is quite easy and only requires using one method, +authenticate_or_request_with_http_digest+. + +[source, ruby] +------------------------------------- +class AdminController < ApplicationController + + USERS = { "lifo" => "world" } + + before_filter :authenticate + + private + + def authenticate + authenticate_or_request_with_http_digest do |username| + USERS[username] + end + end + +end +------------------------------------- + + +As seen in the example above, +authenticate_or_request_with_http_digest+ block takes only one argument - the username. And the block returns the password. Returning +false+ or +nil+ from the +authenticate_or_request_with_http_digest+ will cause authentication failure. diff --git a/railties/doc/guides/source/active_record_basics.txt b/railties/doc/guides/source/active_record_basics.txt index 367a1bba5e..2336e162dc 100644 --- a/railties/doc/guides/source/active_record_basics.txt +++ b/railties/doc/guides/source/active_record_basics.txt @@ -1,154 +1,141 @@ Active Record Basics ==================== -Active Record is a design pattern that mitigates the mind-numbing mental gymnastics often needed to get your application to communicate with a database. This guide uses a mix of real-world examples, metaphors and detailed explanations of the actual Rails source code to help you make the most of ActiveRecord. +This guide will give you a strong grasp of the Active Record pattern and how it can be used with or without Rails. Hopefully, some of the philosophical and theoretical intentions discussed here will also make you a stronger and better developer. -After reading this guide readers should have a strong grasp of the Active Record pattern and how it can be used with or without Rails. Hopefully, some of the philosophical and theoretical intentions discussed here will also make them a stronger and better developer. +After reading this guide we hope that you'll be able to: -== ORM The Blueprint of Active Record +* Understand the way Active Record fits into the MVC model. +* Create basic Active Record models and map them with your database tables. +* Use your models to execute CRUD (Create, Read, Update and Delete) database operations. +* Follow the naming conventions used by Rails to make developing database applications easier and obvious. +* Take advantage of the way Active Record maps it's attributes with the database tables' columns to implement your application's logic. +* Use Active Record with legacy databases that do not follow the Rails naming conventions. -If Active Record is the engine of Rails then ORM is the blueprint of that engine. ORM is short for “Object Relational Mapping” and is a programming concept used to make structures within a system relational. As a thought experiment imagine the components that make up a typical car. There are doors, seats, windows, engines etc. Viewed independently they are simple parts, yet when bolted together through the aid of a blueprint, the parts become a more complex device. ORM is the blueprint that describes how the individual parts relate to one another and in some cases infers the part’s purpose through the way the associations are described. +== What's Active Record -== Active Record The Engine of Rails +Rails' ActiveRecord is an implementation of Martin Fowler's http://martinfowler.com/eaaCatalog/activeRecord.html[Active Record Design Pattern]. This pattern is based on the idea of creating relations between the database and the application in the following way: -Active Record is a design pattern used to access data within a database. The name “Active Record” was coined by Martin Fowler in his book “Patterns of Enterprise Application Architecture”. Essentially, when a record is returned from the database instead of being just the data it is wrapped in a class, which gives you methods to control that data with. The rails framework is built around the MVC (Model View Controller) design patten and the Active Record is used as the default Model. +* Each database table is mapped to a class. +* Each table column is mapped to an attribute of this class. +* Each instance of this class is mapped to a single row in the database table. -The Rails community added several useful concepts to their version of Active Record, including inheritance and associations, which are extremely useful for web applications. The associations are created by using a DSL (domain specific language) of macros, and inheritance is achieved through the use of STI (Single Table Inheritance) at the database level. +The definition of the Active Record pattern in Martin Fowler's words: -By following a few simple conventions the Rails Active Record will automatically map between: +"_An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data."_ -* Classes & Database Tables -* Class attributes & Database Table Columns +== Object Relational Mapping -=== Rails Active Record Conventions -Here are the key conventions to consider when using Active Record. +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 behaviour 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 excelent 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. -==== Naming Conventions -Database Table - Plural with underscores separating words i.e. (book_clubs) -Model Class - Singular with the first letter of each word capitalized i.e. (BookClub) -Here are some additional Examples: +== ActiveRecord as an ORM framework -[grid="all"] -`-------------`--------------- -Model / Class Table / Schema ----------------------------- -Post posts -LineItem line_items -Deer deer -Mouse mice -Person people ----------------------------- +ActiveRecord gives us several mechanisms, being the most important ones the hability to: -==== Schema Conventions +* Represent models. +* Represent associations between these models. +* Represent inheritance hierarquies through related models. +* Validate models before they get recorded to the database. +* Perform database operations in an object-oriented fashion. -To take advantage of some of the magic of Rails database tables must be modeled -to reflect the ORM decisions that Rails makes. +It's easy to see that the Rails Active Record implementation goes way beyond the basic description of the Active Record Pattern. -[grid="all"] -`-------------`--------------------------------------------------------------------------------- -Convention -------------------------------------------------------------------------------------------------- -Foreign keys These fields are named table_id i.e. (item_id, order_id) -Primary Key Rails automatically creates a primary key column named "id" unless told otherwise. -------------------------------------------------------------------------------------------------- +== Active Record inside the MVC model -==== Magic Field Names +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 responsability to deliver you the easiest possible way to recover this data from the database. -When these optional fields are used in your database table definition they give the Active Record -instance additional features. +== Convention over Configuration in ActiveRecord -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. +When writing applications using other programming languages or frameworks, it may be necessary to write a lot of configuration code. This is particulary true for ORM frameworks in general. However, if you follow the conventions adopted by Rails, you'll need to write very little configuration (in some case no configuration at all) when creating ActiveRecord models. The idea is that if you configure your applications in the very same way most of the times then this should be the default way. In this cases, explicity configuration would be needed only in those cases where you can't follow the conventions for any reason. -[grid="all"] -`------------------------`------------------------------------------------------------------------------ -Attribute Purpose ------------------------------------------------------------------------------------------------------- -created_at / created_on Rails stores the current date & time to this field when creating the record. -updated_at / updated_on Rails stores the current date & time to this field when updating the record. -lock_version Adds optimistic locking to a model link:http://api.rubyonrails.com/classes/ActiveRecord/Locking.html[more about optimistic locking]. -type Specifies that the model uses Single Table Inheritance link:http://api.rubyonrails.com/classes/ActiveRecord/Base.html[more about STI]. -id All models require an id. the default is name is "id" but can be changed using the "set_primary_key" or "primary_key" methods. -_table_name_\_count Can be used to caches the number of belonging objects on the associated class. ------------------------------------------------------------------------------------------------------- +=== Naming Conventions -By default rails assumes all tables will use “id” as their primary key to identify each record. Though fortunately you won’t have explicitly declare this, Rails will automatically create that field unless you tell it not to. +By default, ActiveRecord uses some naming conventions to find out how the mapping between models and database tables should be created. Rails will pluralize your class names to find the respective database table. So, for a class +Book+, you should have a database table called *books*. The Rails pluralization mechanisms are very powerful, being capable to pluralize (and singularize) both regular and irregular words. When using class names composed of two or more words, the model class name should follow the Ruby conventions, using the camelCase form, while the table name must contain the words separated by underscores. Examples: -For example suppose you created a database table called cars: +* Database Table - Plural with underscores separating words i.e. (book_clubs) +* Model Class - Singular with the first letter of each word capitalized i.e. (BookClub) + +[width="60%", options="header"] +|============================== +|Model / Class |Table / Schema +|Post |posts +|LineItem |line_items +|Deer |deer +|Mouse |mice +|Person |people +|============================== + +=== Schema Conventions + +ActiveRecord uses naming conventions for the columns in database tables, depending on the purpose of these columns. + +* *Foreign keys* - These fields should be named following the pattern table_id i.e. (item_id, order_id). These are the fields that ActiveRecord will look for when you create associations between your models. +* *Primary keys* - By default, ActiveRecord will use a integer column named "id" as the table's primary key. When using http://guides.rails.info/migrations.html[Rails Migrations] to create your tables, this column will be automaticaly created. + +There are also some optional column names that will create additional features to ActiveRecord instances: + +* *created_at / created_on* - ActiveRecord will store the current date and time to this field when creating the record. +* *updated_at / updated_on* - ActiveRecord will store the current date and times to this field when updating the record. +* *lock_version* - Adds http://api.rubyonrails.com/classes/ActiveRecord/Locking.html[optimistic locking] to a model. +* *type* - Specifies that the model uses http://api.rubyonrails.com/classes/ActiveRecord/Base.html[Single Table Inheritance] +* *(table_name)_count* - Used to cache the number of belonging objects on associations. For example, a +comments_count+ column in a +Post+ class that has many instances of +Comment+ will cache the number of existent comments for each post. + +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. + +== 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: + +[source, ruby] +------------------------------------------------------------------ +class Product < ActiveRecord::Base; end +------------------------------------------------------------------ + +This will create a +Product+ model, mapped to a *products* table at the database. By doing this you'll also have the hability to map the columns of each row in that table with the attributes of the instances of your model. So, suppose that the *products* table was created using a SQL sentence like: [source, sql] -------------------------------------------------------- -mysql> CREATE TABLE cars ( - id INT, - color VARCHAR(100), - doors INT, - horses INT, - model VARCHAR(100) - ); -------------------------------------------------------- - -Now you created a class named Car, which is to represent an instance of a record from your table. +------------------------------------------------------------------ +CREATE TABLE products ( + id int(11) NOT NULL auto_increment, + name varchar(255), + PRIMARY KEY (id) +); +------------------------------------------------------------------ + +Following the table schema above, you would be able to write code like the following: [source, ruby] -------------------------------------------------------- -class Car -end -------------------------------------------------------- +------------------------------------------------------------------ +p = Product.new +p.name = "Some Book" +puts p.name # "Some Book" +------------------------------------------------------------------ + +== Overriding the naming conventions -As you might expect without defining the explicit mappings between your class and the table it is impossible for Rails or any other program to correctly map those relationships. +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. +You can use the +ActiveRecord::Base.set_table_name+ method to specify the table name that should be used: [source, ruby] -------------------------------------------------------- ->> c = Car.new -=> # ->> c.doors -NoMethodError: undefined method `doors' for # - from (irb):2 -------------------------------------------------------- - -Now you could define a door methods to write and read data to and from the database. In a nutshell this is what ActiveRecord does. According to the Rails API: -“Active Record objects don‘t specify their attributes directly, but rather infer them from the table definition with which they‘re linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.” -Lets try our Car class again, this time inheriting from ActiveRecord. +------------------------------------------------------------------ +class Product < ActiveRecord::Base + set_table_name "PRODUCT" +end +------------------------------------------------------------------ +It's also possible to override the column that should be used as the table's primary key. Use the +ActiveRecord::Base.set_primary_key+ method for that: [source, ruby] -------------------------------------------------------- -class Car < ActiveRecord::Base +------------------------------------------------------------------ +class Product < ActiveRecord::Base + set_primary_key "product_id" end -------------------------------------------------------- +------------------------------------------------------------------ -Now if we try to access an attribute of the table ActiveRecord automatically handles the mappings for us, as you can see in the following example. +== Validations + +ActiveRecord gives the hability to validate the state of your models before they get recorded into the database. There are several methods that you can use to hook into the lifecycle of your models and validate that an attribute value is not empty or follow a specific format and so on. You can learn more about validations in the http://guides.rails.info/activerecord_validations_callbacks.html#_overview_of_activerecord_validation[Active Record Validations and Callbacks guide]. + +== Callbacks + +ActiveRecord callbacks allow you to attach code to certain events in the lifecycle of your models. This way you can add behaviour to your models by transparently executing code when those events occur, like when you create a new record, update it, destroy it and so on. You can learn more about callbacks in the http://guides.rails.info/activerecord_validations_callbacks.html#_callbacks[Active Record Validations and Callbacks guide]. -[source, ruby] -------------------------------------------------------- ->> c = Car.new -=> # ->> c.doors -=> nil -------------------------------------------------------- - -Rails further extends this model by giving each ActiveRecord a way of describing the variety of ways records are associated with one another. We will touch on some of these associations later in the guide but I encourage readers who are interested to read the guide to ActiveRecord associations for an in-depth explanation of the variety of ways rails can model associations. -- Associations between objects controlled by meta-programming macros. - -== Philosophical Approaches & Common Conventions -Rails has a reputation of being a zero-config framework which means that it aims to get you off the ground with as little pre-flight checking as possible. This speed benefit is achieved by following “Convention over Configuration”, which is to say that if you agree to live with the defaults then you benefit from a the inherent speed-boost. As Courtneay Gasking put it to me once “You don’t want to off-road on Rails”. ActiveRecord is no different, while it’s possible to override or subvert any of the conventions of AR, unless you have a good reason for doing so you will probably be happy with the defaults. The following is a list of the common conventions of ActiveRecord - -== ActiveRecord Magic - - timestamps - - updates - -== How ActiveRecord Maps your Database. -- sensible defaults -- overriding conventions - -== Growing Your Database Relationships Naturally - -== Attributes - - attribute accessor method. How to override them? - - attribute? - - dirty records - - -== ActiveRecord handling the CRUD of your Rails application - Understanding the life-cycle of an ActiveRecord - -== Validations & Callbacks -see the Validations & Callbacks guide for more info. \ No newline at end of file diff --git a/railties/doc/guides/source/creating_plugins/appendix.txt b/railties/doc/guides/source/creating_plugins/appendix.txt index 340c03dd4e..7cb8109372 100644 --- a/railties/doc/guides/source/creating_plugins/appendix.txt +++ b/railties/doc/guides/source/creating_plugins/appendix.txt @@ -5,7 +5,7 @@ If you prefer to use RSpec instead of Test::Unit, you may be interested in the h === References === * http://nubyonrails.com/articles/the-complete-guide-to-rails-plugins-part-i - * http://nubyonrails.com/articles/2006/05/09/the-complete-guide-to-rails-plugins-part-ii + * http://nubyonrails.com/articles/the-complete-guide-to-rails-plugins-part-ii * http://github.com/technoweenie/attachment_fu/tree/master * http://daddy.platte.name/2007/05/rails-plugins-keep-initrb-thin.html * http://www.mbleigh.com/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins diff --git a/railties/doc/guides/source/creating_plugins/creating_plugins/lib/main.rb b/railties/doc/guides/source/creating_plugins/creating_plugins/lib/main.rb new file mode 100644 index 0000000000..b0919153f1 --- /dev/null +++ b/railties/doc/guides/source/creating_plugins/creating_plugins/lib/main.rb @@ -0,0 +1,4 @@ +# To change this template, choose Tools | Templates +# and open the template in the editor. + +puts "Hello World" diff --git a/railties/doc/guides/source/creating_plugins/generator_commands.txt b/railties/doc/guides/source/creating_plugins/generator_commands.txt index f60ea3d8f1..6ded0dde59 100644 --- a/railties/doc/guides/source/creating_plugins/generator_commands.txt +++ b/railties/doc/guides/source/creating_plugins/generator_commands.txt @@ -120,7 +120,7 @@ Rails::Generator::Commands::List.send :include, Yaffle::Generator::Commands Rails::Generator::Commands::Update.send :include, Yaffle::Generator::Commands::Update ----------------------------------------------------------- -*vendor/plugins/yaffle/generators/yaffle/yaffle_route_generator.rb* +*vendor/plugins/yaffle/generators/yaffle_route/yaffle_route_generator.rb* [source, ruby] ----------------------------------------------------------- diff --git a/railties/doc/guides/source/creating_plugins/migrations.txt b/railties/doc/guides/source/creating_plugins/migrations.txt index e7d2e09069..2589169b38 100644 --- a/railties/doc/guides/source/creating_plugins/migrations.txt +++ b/railties/doc/guides/source/creating_plugins/migrations.txt @@ -28,26 +28,7 @@ Here are a few possibilities for how to allow developers to use your plugin migr === Create a custom rake task === -*vendor/plugins/yaffle/lib/db/migrate/20081116181115_create_birdhouses.rb:* - -[source, ruby] ----------------------------------------------- -class CreateBirdhouses < ActiveRecord::Migration - def self.up - create_table :birdhouses, :force => true do |t| - t.string :name - t.timestamps - end - end - - def self.down - drop_table :birdhouses - end -end ----------------------------------------------- - - -*vendor/plugins/yaffle/tasks/yaffle.rake:* +*vendor/plugins/yaffle/tasks/yaffle_tasks.rake:* [source, ruby] ---------------------------------------------- @@ -153,7 +134,7 @@ NOTE: the migration generator checks to see if a migation already exists, and it After running the test with 'rake' you can make it pass with: -*vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb* +*vendor/plugins/yaffle/generators/yaffle_migration/yaffle_migration_generator.rb* [source, ruby] ------------------------------------------------------------------ diff --git a/railties/doc/guides/source/creating_plugins/models.txt b/railties/doc/guides/source/creating_plugins/models.txt index 505ab44a71..1498b8e26c 100644 --- a/railties/doc/guides/source/creating_plugins/models.txt +++ b/railties/doc/guides/source/creating_plugins/models.txt @@ -20,7 +20,7 @@ vendor/plugins/yaffle/ As always, start with a test: -*vendor/plugins/yaffle/yaffle/woodpecker_test.rb:* +*vendor/plugins/yaffle/test/woodpecker_test.rb:* [source, ruby] ---------------------------------------------- diff --git a/railties/doc/guides/source/creating_plugins/tasks.txt b/railties/doc/guides/source/creating_plugins/tasks.txt index d848c2cfa1..edaffec194 100644 --- a/railties/doc/guides/source/creating_plugins/tasks.txt +++ b/railties/doc/guides/source/creating_plugins/tasks.txt @@ -1,10 +1,10 @@ == Rake tasks == -When you created the plugin with the built-in rails generator, it generated a rake file for you in 'vendor/plugins/yaffle/tasks/yaffle.rake'. Any rake task you add here will be available to the app. +When you created the plugin with the built-in rails generator, it generated a rake file for you in 'vendor/plugins/yaffle/tasks/yaffle_tasks.rake'. Any rake task you add here will be available to the app. Many plugin authors put all of their rake tasks into a common namespace that is the same as the plugin, like so: -*vendor/plugins/yaffle/tasks/yaffle.rake* +*vendor/plugins/yaffle/tasks/yaffle_tasks.rake* [source, ruby] --------------------------------------------------------- diff --git a/railties/doc/guides/source/creating_plugins/test_setup.txt b/railties/doc/guides/source/creating_plugins/test_setup.txt index 64236ff110..9ee98c9935 100644 --- a/railties/doc/guides/source/creating_plugins/test_setup.txt +++ b/railties/doc/guides/source/creating_plugins/test_setup.txt @@ -116,9 +116,6 @@ ActiveRecord::Schema.define(:version => 0) do t.string :last_tweet t.datetime :last_tweeted_at end - create_table :woodpeckers, :force => true do |t| - t.string :name - end end ---------------------------------------------- diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index d60ed10a39..e2afcf41ba 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -1,21 +1,22 @@ Rails form helpers ================== -Mislav Marohnić 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. In this guide you will: -* Create search forms and similar kind of generic forms not representing any specific model in your application; -* Make model-centric forms for creation and editing of specific database records; -* Generate select boxes from multiple types of data; -* Learn what makes a file upload form different; +* Create search forms and similar kind of generic forms not representing any specific model in your application +* Make model-centric forms for creation and editing of specific database records +* Generate select boxes from multiple types of data +* Understand the date and time helpers Rails provides +* Learn what makes a file upload form different +* Find out where to look for complex forms NOTE: This guide is not intended to be a complete documentation of available form helpers and their arguments. Please visit http://api.rubyonrails.org/[the Rails API documentation] for a complete reference. -Basic forms ------------ +Dealing With Basic Forms +------------------------ The most basic form helper is `form_tag`. @@ -25,7 +26,7 @@ The most basic form helper is `form_tag`. <% end %> ---------------------------------------------------------------------------- -When called without arguments like this, it creates a form element that has the current page for action attribute and "POST" as method (some line breaks added for readability): +When called without arguments like this, it creates a form element that has the current page as its action and "post" as its method (some line breaks added for readability): .Sample output from `form_tag` ---------------------------------------------------------------------------- @@ -37,12 +38,12 @@ When called without arguments like this, it creates a form element that has the ---------------------------------------------------------------------------- -If you carefully observe this output, you can see that the helper generated something you didn't specify: a `div` element with a hidden input inside. This is a security feature of Rails called *cross-site request forgery protection* and form helpers generate it for every form which action isn't "GET" (provided that this security feature is enabled). +If you carefully observe this output, you can see that the helper generated something you didn't specify: a `div` element with a hidden input inside. This is a security feature of Rails called *cross-site request forgery protection* and form helpers generate it for every form whose action is not "get" (provided that this security feature is enabled). You can read more about this in the link:./security.html#_cross_site_reference_forgery_csrf[Ruby On Rails Security Guide]. NOTE: Throughout this guide, this `div` with the hidden input will be stripped away to have clearer code samples. -Generic search form -~~~~~~~~~~~~~~~~~~~ +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: @@ -51,9 +52,9 @@ Probably the most minimal form often seen on the web is a search form with a sin 3. a text input element, and 4. a submit element. -IMPORTANT: Always use "GET" as the method for search forms. Benefits are many: users are able to bookmark a specific search and get back to it; browsers cache results of "GET" requests, but not "POST"; and others. +IMPORTANT: Always use "GET" as the method for search forms. This allows users are able to bookmark a specific search and get back to it, more generally Rails encourages you to use the right HTTP verb for an action. -To create that, you will use `form_tag`, `label_tag`, `text_field_tag` and `submit_tag`, respectively. +To create this form you will use `form_tag`, `label_tag`, `text_field_tag` and `submit_tag`, respectively. .A basic search form ---------------------------------------------------------------------------- @@ -88,10 +89,10 @@ 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. -Multiple hashes in form helper attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +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. +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. As with the `link_to` helper, the path argument doesn't have to be given a string. It can be a hash of URL parameters that Rails' routing mechanism will turn into a valid URL. Still, you cannot simply write this: @@ -113,9 +114,23 @@ 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. -Checkboxes, radio buttons and other controls -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Helpers for generating form elements +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Rails provides a series of helpers for generating form elements such as checkboxes, text fields, radio buttons and so. These basic helpers, with names ending in _tag such as `text_field_tag`, `check_box_tag` just generate a single `` 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 + +--------------------------- +<%= text_field_tag(:query) %> +--------------------------- + +then the controller code should use +--------------------------- +params[:query] +--------------------------- +to retrieve the value entered by the user. When naming inputs be aware that Rails uses certain conventions that control whether values are at the top level of the `params` hash, inside an array or a nested hash and so on. You can read more about them in the <> section. For details on the precise usage of these helpers, please refer to the http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html[API documentation]. +Checkboxes +^^^^^^^^^^ Checkboxes are form controls that give the user a set of options they can enable or disable: ---------------------------------------------------------------------------- @@ -132,6 +147,10 @@ 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. + +Radio buttons +^^^^^^^^^^^^^ Radio buttons, while similar to checkboxes, are controls that specify a set of options in which they are mutually exclusive (user can only pick one): ---------------------------------------------------------------------------- @@ -148,9 +167,13 @@ output: ---------------------------------------------------------------------------- +As with `check_box_tag` the second parameter to `radio_button_tag` is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one and `params[:age]` will contain either "child" or "adult". + IMPORTANT: Always use labels for each checkbox and radio button. They associate text with a specific option and provide a larger clickable region. -Other form controls worth mentioning are the text area, password input and hidden input: +Other helpers of interest +^^^^^^^^^^^^^^^^^^^^^^^^^ +Other form controls worth mentioning are the text area, password input and hidden input: ---------------------------------------------------------------------------- <%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %> @@ -164,55 +187,20 @@ output: ---------------------------------------------------------------------------- -Hidden inputs are not shown to the user, but they hold data same as any textual input. Values inside them can be changed with JavaScript. +Hidden inputs are not shown to the user, but they hold data like any textual input. Values inside them can be changed with JavaScript. 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. -How do forms with PUT or DELETE methods work? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PUT" and "DELETE" requests (besides "GET" and "POST"). Still, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms. How does this work, then? -Rails works around this issue by emulating other methods over POST with a hidden input named `"_method"` that is set to reflect the desired method: - ----------------------------------------------------------------------------- -form_tag(search_path, :method => "put") - -output: - -

    -
    - - -
    - ... ----------------------------------------------------------------------------- - -When parsing POSTed data, Rails will take into account the special `_method` parameter and act as if the HTTP method was the one specified inside it ("PUT" in this example). - -Different Families of helpers ------------------------------- - -Most of Rails' form helpers are available in two forms. - -Barebones helpers -~~~~~~~~~~~~~~~~~~ -These just generate the appropriate markup. These have names ending in _tag such as `text_field_tag`, `check_box_tag`. The first parameter to these is always the name of the input. This is the name under which value will appear in the `params` hash in the controller. For example if the form contains ---------------------------- -<%= text_field_tag(:query) %> ---------------------------- - -then the controller code should use ---------------------------- -params[:query] ---------------------------- -to retrieve the value entered by the user. When naming inputs be aware that Rails uses certain conventions that control whether values appear at the top level of the params hash, inside an array or a nested hash and so on. You can read more about them in the <> section. For details on the precise usage of these helpers, please refer to the http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html[API documentation]. +Dealing With Model Objects +-------------------------- Model object helpers -~~~~~~~~~~~~~~~~~~~~~ -These are designed to work with a model object (commonly an Active Record object but this need not be the case). These lack the _tag suffix, for example `text_field`, `text_area`. +~~~~~~~~~~~~~~~~~~~~~~ + +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 _tag suffix, for example `text_field`, `text_area`. -For these helpers the first arguement is the name of an instance variable and the second is the name a method (usually an attribute) to call on that object. Rails will set the value of the input control to the return value of that method for the object and set an appropriate input name. If your controller has defined `@person` and that person's name is Henry then a form containing: +For these helpers the first argument is the name of an instance variable and the second is the name of a method (usually an attribute) to call on that object. Rails will set the value of the input control to the return value of that method for the object and set an appropriate input name. If your controller has defined `@person` and that person's name is Henry then a form containing: --------------------------- <%= text_field(:person, :name) %> @@ -221,16 +209,21 @@ will produce output similar to --------------------------- --------------------------- -Upon form submission the value entered by the user will be stored in `params[:person][:name]`. The `params[:person]` hash is suitable for passing to `Person.new` or, if `@person` is an instance of Person, `@person.update_attributes`. +Upon form submission the value entered by the user will be stored in `params[:person][:name]`. The `params[:person]` hash is suitable for passing to `Person.new` or, if `@person` is an instance of Person, `@person.update_attributes`. While the name of an attribute is the most common second parameter to these helpers this is not compulsory. In the example above, as long as person objects have a `name` and a `name=` method Rails will be happy. [WARNING] ============================================================================ You must pass the name of an instance variable, i.e. `:person` or `"person"`, not an actual instance of your model object. ============================================================================ -Forms that deal with model attributes -------------------------------------- -While the helpers seen so far are handy Rails can save you some work. For example typically a form is used to edit multiple attributes of a single object, so having to repeat the name of the object being edited is clumsy. The following examples will handle an Article model. First, have the controller create one: +Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the link:./activerecord_validations_callbacks.html#_using_the_tt_errors_tt_collection_in_your_view_templates[Active Record Validations and Callbacks] guide. + +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. + +Assume we have a controller for dealing with articles: .articles_controller.rb ---------------------------------------------------------------------------- @@ -239,7 +232,7 @@ def new end ---------------------------------------------------------------------------- -Now switch to the view. The first thing to remember is to use the `form_for` helper instead of `form_tag`, and that you should pass the model name and object as arguments: +The corresponding view using `form_for` looks like this .articles/new.html.erb ---------------------------------------------------------------------------- @@ -252,9 +245,9 @@ Now switch to the view. The first thing to remember is to use the `form_for` hel There are a few things to note here: -1. `:article` is the name of the model and `@article` is the record. +1. `:article` is the name of the model and `@article` is the actual object being edited. 2. There is a single hash of options. Routing options are passed inside `:url` hash, HTML options are passed in the `:html` hash. -3. The `form_for` method yields *a form builder* object (the `f` variable). +3. The `form_for` method yields a *form builder* object (the `f` variable). 4. Methods to create form controls are called *on* the form builder object `f` The resulting HTML is: @@ -266,14 +259,34 @@ The resulting HTML is: ---------------------------------------------------------------------------- -The name passed to `form_for` controls where in the params hash the form values will appear. Here the name is `article` and so all the inputs have names of the form `article[attribute_name]`. Accordingly, in the `create` action `params[:article]` will be a hash with keys `:title` and `:body`. You can read more about the significance of input names in the <> section. +The name passed to `form_for` controls the key used in `params` to access the form's values. Here the name is `article` and so all the inputs have names of the form `article[attribute_name]`. Accordingly, in the `create` action `params[:article]` will be a hash with keys `:title` and `:body`. You can read more about the significance of input names in the <> section. The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder. +You can create a similar binding without actually creating `
    ` tags with the `fields_for` helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for creating both like so: +------------- +<% form_for :person, @person, :url => { :action => "create" } do |person_form| %> + <%= person_form.text_field :name %> + <% fields_for @person.contact_detail do |contact_details_form| %> + <%= contact_details_form.text_field :phone_number %> + <% end %> +<% end %> +------------- + +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). + Relying on record identification ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In the previous chapter you handled the Article model. This model is directly available to users of our application, so -- following the best practices for developing with Rails -- you should declare it *a resource*. +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*. When dealing with RESTful resources, calls to `form_for` can get significantly easier if you rely on *record identification*. In short, you can just pass the model instance and have Rails figure out model name and the rest: @@ -293,7 +306,7 @@ form_for(@article) Notice how the short-style `form_for` invocation is conveniently the same, regardless of the record being new or existing. Record identification is smart enough to figure out if the record is new by asking `record.new_record?`. It also selects the correct path to submit to and the name based on the class of the object. -Rails will also automatically set the class and id of the form appropriately: a form creating an article would have id and class `new_article`. If you were editing the article with id 23 the class would be set to `edit_article` and the id to `edit_article_23`. The attributes will be omitted or brevity in the rest of this guide. +Rails will also automatically set the `class` and `id` of the form appropriately: a form creating an article would have `id` and `class` `new_article`. If you were editing the article with id 23 the `class` would be set to `edit_article` and the id to `edit_article_23`. These attributes will be omitted for brevity in the rest of this guide. 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. @@ -309,49 +322,71 @@ will create a form that submits to the articles controller inside the admin name ------- form_for [:admin, :management, @article] ------- -For more information on Rails' routing system and the associated conventions, please see the link:../routing_outside_in.html[routing guide]. +For more information on Rails' routing system and the associated conventions, please see the link:./routing_outside_in.html[routing guide]. + + +How do forms with PUT or DELETE methods work? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PUT" and "DELETE" requests (besides "GET" and "POST"). Still, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms. + +Rails works around this issue by emulating other methods over POST with a hidden input named `"_method"` that is set to reflect the desired method: + +---------------------------------------------------------------------------- +form_tag(search_path, :method => "put") + +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). 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 from data stored in arrays or hashes. +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. -Here is what our wanted markup might look like: +Here is what the markup might look like: ---------------------------------------------------------------------------- ---------------------------------------------------------------------------- -Here you have a list of cities where their names are presented to the user, but internally the application only wants to handle their IDs so they are used as the options' value attributes. Let's see how Rails can help out here. +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. -The select tag and options +The select and options tag ~~~~~~~~~~~~~~~~~~~~~~~~~~ The most generic helper is `select_tag`, which -- as the name implies -- simply generates the `SELECT` tag that encapsulates an options string: ---------------------------------------------------------------------------- -<%= select_tag(:city_id, '...') %> +<%= select_tag(:city_id, '...') %> ---------------------------------------------------------------------------- -This is a start, but it doesn't dynamically create our option tags. You can generate option tags with the `options_for_select` helper: +This is a start, but it doesn't dynamically create the option tags. You can generate option tags with the `options_for_select` helper: ---------------------------------------------------------------------------- -<%= options_for_select([['Lisabon', 1], ['Madrid', 2], ...]) %> +<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...]) %> output: - + ... ---------------------------------------------------------------------------- -For input data you use a nested array where each element has two elements: option text (city name) and option value (city id). The option value is what will get submitted to your controller. It is often true that the option value is the id of a corresponding database object but this does not have to be the case. +The first argument to `options_for_select` is a nested array where each element has two elements: option text (city name) and option value (city id). The option value is what will be submitted to your controller. Often this will be the id of a corresponding database object but this does not have to be the case. Knowing this, you can combine `select_tag` and `options_for_select` to achieve the desired, complete markup: @@ -359,71 +394,68 @@ Knowing this, you can combine `select_tag` and `options_for_select` to achieve t <%= select_tag(:city_id, options_for_select(...)) %> ---------------------------------------------------------------------------- -Sometimes, depending on an application's needs, you also wish a specific option to be pre-selected. The `options_for_select` helper supports this with an optional second argument: +`options_for_select` allows you to pre-select an option by passing its value. ---------------------------------------------------------------------------- -<%= options_for_select([['Lisabon', 1], ['Madrid', 2], ...], 2) %> +<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...], 2) %> output: - + ... ---------------------------------------------------------------------------- -So whenever Rails sees that the internal value of an option being generated matches this value, it will add the `selected` attribute to that option. +Whenever Rails sees that the internal value of an option being generated matches this value, it will add the `selected` attribute to that option. [TIP] ============================================================================ -The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the internal 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. +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. ============================================================================ Select boxes for dealing with models ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Until now you've seen how to make generic select boxes, but in most cases our form controls will be tied to a specific database model. So, to continue from our previous examples, let's assume that you have a "Person" model with a `city_id` attribute. - -Consistent with other form helpers, when dealing with models you drop the `_tag` suffix from `select_tag`. +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`: ---------------------------------------------------------------------------- # controller: @person = Person.new(:city_id => 2) # view: -<%= select(:person, :city_id, [['Lisabon', 1], ['Madrid', 2], ...]) %> +<%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ...]) %> ---------------------------------------------------------------------------- Notice that the third parameter, the options array, is the same kind of argument you pass to `options_for_select`. One advantage here is that you don't have to worry about pre-selecting the correct city if the user already has one -- Rails will do this for you by reading from the `@person.city_id` attribute. -As before, if you were to use `select` helper on a form builder scoped to `@person` object, the syntax would be: +As with other helpers, if you were to use `select` helper on a form builder scoped to `@person` object, the syntax would be: ---------------------------------------------------------------------------- # select on a form builder <%= f.select(:city_id, ...) %> ---------------------------------------------------------------------------- -[WARNING] +[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 +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 -------- -ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got Fixnum(#1138750) +ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) -------- -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. +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 link:security.html#_mass_assignment[Ruby On Rails Security Guide]. ============================ + Option tags from a collection of arbitrary objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Until now you were generating option tags from nested arrays with the help of `options_for_select` method. Data in our array were raw values: - ----------------------------------------------------------------------------- -<%= options_for_select([['Lisabon', 1], ['Madrid', 2], ...]) %> ----------------------------------------------------------------------------- - -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: +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: ---------------------------------------------------------------------------- -<% cities_array = City.find(:all).map { |city| [city.name, city.id] } %> +<% cities_array = City.all.map { |city| [city.name, city.id] } %> <%= options_for_select(cities_array) %> ---------------------------------------------------------------------------- @@ -432,8 +464,7 @@ This is a perfectly valid solution, but Rails provides a less verbose alternativ ---------------------------------------------------------------------------- <%= options_from_collection_for_select(City.all, :id, :name) %> ---------------------------------------------------------------------------- - -As the name implies, this only generates option tags. To generate a working select box you would need to use it in conjunction with `select_tag`, just as you would with `options_for_select`. A method to go along with it is `collection_select`: +As the name implies, this only generates option tags. To generate a working select box you would need to use it in conjunction with `select_tag`, just as you would with `options_for_select`. When working with model objects, just as `select` combines `select_tag` and `options_for_select`, `collection_select` combines `select_tag` with `options_from_collection_for_select`. ---------------------------------------------------------------------------- <%= collection_select(:person, :city_id, City.all, :id, :name) %> @@ -441,67 +472,72 @@ As the name implies, this only generates option tags. To generate a working sele To recap, `options_from_collection_for_select` is to `collection_select` what `options_for_select` is to `select`. +[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. +============================= + Time zone and country select ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To leverage time zone support in Rails, you have to ask our 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: +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: ---------------------------------------------------------------------------- -<%= time_zone_select(:person, :city_id) %> +<%= time_zone_select(:person, :time_zone) %> ---------------------------------------------------------------------------- There is also `time_zone_options_for_select` helper for a more manual (therefore more customizable) way of doing this. Read the API documentation to learn about the possible arguments for these two methods. -Rails _used_ to have a `country_select` helper for choosing countries but this has been extracted to the http://github.com/rails/country_select/tree/master[country_select plugin]. When using this do be aware that the exclusion or inclusion of certain names from the list can be somewhat controversial (and was the reason this functionality was extracted from rails) +Rails _used_ to have a `country_select` helper for choosing countries but this has been extracted to the http://github.com/rails/country_select/tree/master[country_select plugin]. When using this do be aware that the exclusion or inclusion of certain names from the list can be somewhat controversial (and was the reason this functionality was extracted from rails). -Date and time select boxes --------------------------- +Using Date and Time Form Helpers +-------------------------------- The date and time helpers differ from all the other form helpers in two important respects: -1. Unlike other attributes you might typically have, dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc...). So in particular, there is no single value in your params hash with your date or time. -2. Other helpers use the _tag suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, `select\_date`, `select\_time` and `select_datetime` are the barebones helpers, `date_select`, `time_select` and `datetime_select` are the equivalent model object helpers +1. Dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc.) and so there is no single value in your `params` hash with your date or time. +2. Other helpers use the _tag suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, `select_date`, `select_time` and `select_datetime` are the barebones helpers, `date_select`, `time_select` and `datetime_select` are the equivalent model object helpers. -Both of these families of helpers will create a series of select boxes for the different components (year, month, day etc...). +Both of these families of helpers will create a series of select boxes for the different components (year, month, day etc.). 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 ----------- -<%= select_date Date::today, :prefix => :start_date %> +<%= select_date Date.today, :prefix => :start_date %> ----------- -outputs (with the actual option values omitted for brevity) +outputs (with actual option values omitted for brevity) ----------- ----------- -The above inputs would result in `params[:start_date]` being a hash with keys :year, :month, :day. To get an actual Time or Date object you would have to extract these values and pass them to the appropriate constructor, for example +The above inputs would result in `params[:start_date]` being a hash with keys `:year`, `:month`, `:day`. To get an actual Time or Date object you would have to extract these values and pass them to the appropriate constructor, for example ----------- -Date::civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i) +Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i) ----------- -The :prefix option controls where in the params hash the date components will be placed. Here it was set to `start_date`, if omitted it will default to `date`. +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`. 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 +`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: --------------- <%= date_select :person, :birth_date %> --------------- -outputs (with the actual option values omitted for brevity) +outputs (with actual option values omitted for brevity) -------------- -------------- -which results in a params hash like +which results in a `params` hash like -------------- {:person => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}} -------------- -When this is passed to `Person.new`, 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`. +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`. Common options ~~~~~~~~~~~~~~ @@ -511,65 +547,23 @@ 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. -Form builders -------------- - -As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying a form elements for a single object. While you can of course write helpers for your forms in the usual way you can also subclass FormBuilder and add the helpers there. For example - ----------- -<% form_for @person do |f| %> - <%= text_field_with_label f, :first_name %> -<% end %> ----------- -can be replaced with ----------- -<% form_for @person, :builder => LabellingFormBuilder do |f| %> - <%= f.text_field :first_name %> -<% end %> ----------- -by defining a LabellingFormBuilder class similar to the following: - -[source, ruby] -------- -class LabellingFormBuilder < FormBuilder - def text_field attribute, options={} - label(attribute) + text_field(attribute, options) - end -end -------- -If you reuse this frequently you could define a `labeled_form_for` helper that automatically applies the `:builder => LabellingFormBuilder` option. - -The form builder used also determines what happens when you do ------- -<%= render :partial => f %> ------- -If `f` is an instance of FormBuilder then this will render the 'form' partial, setting the partial's object to the form builder. If the form builder is of class LabellingFormBuilder then the 'labelling_form' partial would be rendered instead. +Individual components +~~~~~~~~~~~~~~~~~~~~~ -Scoping out form controls with `fields_for` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +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 a input 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. -`fields_for` creates a form builder in exactly the same way as `form_for` but doesn't create the actual `` tags. It creates a scope around a specific model object like `form_for`, which is useful for specifying additional model objects in the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for editing both like so: -------------- -<% form_for @person do |person_form| %> - <%= person_form.text_field :name %> - <% fields_for @person.contact_detail do |contact_details_form| %> - <%= contact_details_form.text_field :phone_number %> - <% end %> -<% end %> -------------- +The first parameter specifies which value should be selected and can either be an instance of a Date, Time or DateTime, in which case the relevant component will be extracted, or a numerical value. For example -which produces the following output: +--------------- +<%= select_year(2009) %> +<%= select_year(Time.now) %> +--------------- -------------- - - - -
    -------------- +will produce the same output if the current year is 2009 and the value chosen by the user can be retrieved by `params[:date][:year]`. -File Uploads +Uploading Files -------------- -A common task is uploading some sort of file, whether it's a picture of a person or a CSV file containing data to process. The most important thing to remember with file uploads is that the form's encoding *MUST* be set to multipart/form-data. If you forget to do this the file will not be uploaded. This can be done by passing `:multi_part => true` as an HTML option. This means that in the case of `form_tag` it must be passed in the second options hash and in the case of `form_for` inside the `:html` hash. +A common task is uploading some sort of file, whether it's a picture of a person or a CSV file containing data to process. The most important thing to remember with file uploads is that the form's encoding *MUST* be set to "multipart/form-data". If you forget to do this the file will not be uploaded. This can be done by passing `:multi_part => true` as an HTML option. This means that in the case of `form_tag` it must be passed in the second options hash and in the case of `form_for` inside the `:html` hash. The following two forms both upload a file. ----------- @@ -585,7 +579,7 @@ Rails provides the usual pair of helpers: the barebones `file_field_tag` and the 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). +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). [source, ruby] ----------------- @@ -603,35 +597,69 @@ NOTE: If the user has not selected a file the corresponding parameter will be an Dealing with Ajax ~~~~~~~~~~~~~~~~~ -Unlike other forms making an asynchronous file upload form is not as simple as replacing `form_for` with `remote_form_for`. With an AJAX form the serialization is done by javascript running inside the browser and since javascript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission. +Unlike other forms making an asynchronous file upload form is not as simple as replacing `form_for` with `remote_form_for`. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission. + +Customising Form Builders +------------------------- + +As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way you can also subclass FormBuilder and add the helpers there. For example + +---------- +<% form_for @person do |f| %> + <%= text_field_with_label f, :first_name %> +<% end %> +---------- +can be replaced with +---------- +<% form_for @person, :builder => LabellingFormBuilder do |f| %> + <%= f.text_field :first_name %> +<% end %> +---------- +by defining a LabellingFormBuilder class similar to the following: + +[source, ruby] +------- +class LabellingFormBuilder < FormBuilder + def text_field(attribute, options={}) + label(attribute) + text_field(attribute, options) + end +end +------- +If you reuse this frequently you could define a `labeled_form_for` helper that automatically applies the `:builder => LabellingFormBuilder` option. + +The form builder used also determines what happens when you do +------ +<%= render :partial => f %> +------ +If `f` is an instance of FormBuilder then this will render the `form` partial, setting the partial's object to the form builder. If the form builder is of class LabellingFormBuilder then the `labelling_form` partial would be rendered instead. + +Understanding Parameter Naming Conventions +----------------------------------------- -Parameter Names ---------------- [[parameter_names]] -As you've seen in the previous sections values from forms can appear either at the top level of the params hash or may appear nested in another hash. For example in a standard create -action for a Person model, `params[:model]` would usually be a hash of all the attributes for the person to create. The params hash can also contain arrays, arrays of hashes and so on. +As you've seen in the previous sections, values from forms can be at the top level of the `params` hash or nested in another hash. For example in a standard `create` +action for a Person model, `params[:model]` would usually be a hash of all the attributes for the person to create. The `params` hash can also contain arrays, arrays of hashes and so on. -Fundamentally HTML forms don't know about any sort of structured data. All they know about is name-value pairs. Rails tacks some conventions onto parameter names which it uses to express some structure. +Fundamentally HTML forms don't know about any sort of structured data, all they generate is name-value pairs, where pairs are just plain strings. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses. [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 ------------- -ActionController::RequestParser.parse_query_parameters "name=fred&phone=0123456789" -#=> {"name"=>"fred", "phone"=>"0123456789"} +ActionController::UrlEncodedPairParser.parse_query_parameters "name=fred&phone=0123456789" +# => {"name"=>"fred", "phone"=>"0123456789"} ------------- ======================== Basic structures ~~~~~~~~~~~~~~~ -The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in the params. For example if a form contains +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 ----------------- ----------------- -the params hash will contain +the `params` hash will contain -[source, ruby] ----------------- {'person' => {'name' => 'Henry'}} ----------------- @@ -641,9 +669,8 @@ Hashes can be nested as many levels as required, for example ------------------ ------------------ -will result in the params hash being +will result in the `params` hash being -[source, ruby] ----------------- {'person' => {'address' => {'city' => 'New York'}}} ----------------- @@ -664,22 +691,21 @@ We can mix and match these two concepts. For example, one element of a hash migh ----------------- -This would result in `params[:addresses]` being an array of hashes with keys `line1`, `line2` and `city`. Rails decides to start accumulating values in a new hash whenever it encounters a input name that already exists in the current hash. +This would result in `params[:addresses]` being an array of hashes with keys `line1`, `line2` and `city`. Rails decides to start accumulating values in a new hash whenever it encounters an input name that already exists in the current hash. -The one restriction is that although hashes can be nested arbitrarily deep then can be only one level of "arrayness". Frequently arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id. +There's a restriction, however, while hashes can be nested arbitrarily, only one level of "arrayness" is allowed. Arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id, an array index or some other parameter. [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. If the checkbox 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 hash. It is preferable to either use `check_box_tag` or to use hashes instead of arrays. +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. 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`/`fields_for` and the `:index` option. - -You might want to render a form with a set of edit fields for each of a person's addresses. Something a little like this will do the trick +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. +You might want to render a form with a set of edit fields for each of a person's addresses. For example: -------- <% form_for @person do |person_form| %> - <%= person_form.text_field :name%> + <%= person_form.text_field :name %> <% for address in @person.addresses %> <% person_form.fields_for address, :index => address do |address_form|%> <%= address_form.text_field :city %> @@ -687,7 +713,7 @@ You might want to render a form with a set of edit fields for each of a person's <% end %> <% end %> -------- -Assuming our person had two addresses, with ids 23 and 45 this would create output similar to this: +Assuming the person had two addresses, with ids 23 and 45 this would create output similar to this: --------
    @@ -695,14 +721,13 @@ Assuming our person had two addresses, with ids 23 and 45 this would create outp
    -------- -This will result in a params hash that looks like +This will result in a `params` hash that looks like -[source, ruby] -------- -{'person' => {'name' => 'Bob', 'address' => { '23' => {'city' => 'Paris'}, '45' => {'city' => 'London'} }}} +{'person' => {'name' => 'Bob', 'address' => {'23' => {'city' => 'Paris'}, '45' => {'city' => 'London'}}}} -------- -Rails knows that all these inputs should be part of the person hash because you called `fields_for` on the first form builder. By specifying an `:index` option you're telling rails that instead of naming the inputs `person[address][city]` it should insert that index surrounded by [] between the address and the city. If you pass an Active Record object as we did then Rails will call `to_param` on it, which by default returns the database id. This is often useful it is then easy to locate which Address record should be modified but you could pass numbers with some other significance, strings or even nil (which will result in an array parameter being created). +Rails knows that all these inputs should be part of the person hash because you called `fields_for` on the first form builder. By specifying an `:index` option you're telling rails that instead of naming the inputs `person[address][city]` it should insert that index surrounded by [] between the address and the city. If you pass an Active Record object as we did then Rails will call `to_param` on it, which by default returns the database id. This is often useful as it is then easy to locate which Address record should be modified. You can pass numbers with some other significance, strings or even `nil` (which will result in an array parameter being created). To create more intricate nestings, you can specify the first part of the input name (`person[address]` in the previous example) explicitly, for example -------- @@ -714,7 +739,7 @@ will create inputs like -------- -------- -As a general rule the final input name is the concatenation of the name given to `fields_for`/`form_for`, the index value and the name of the attribute. You can also pass an `:index` option directly to helpers such as `text_field`, but usually it is less repetitive to specify this at the form builder level rather than on individual input controls. +As a general rule the final input name is the concatenation of the name given to `fields_for`/`form_for`, the index value and the name of the attribute. You can also pass an `:index` option directly to helpers such as `text_field`, but it is usually less repetitive to specify this at the form builder level rather than on individual input controls. As a shortcut you can append [] to the name and omit the `:index` option. This is the same as specifing `:index => address` so -------- @@ -724,10 +749,10 @@ 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. -Complex forms -------------- +Building Complex forms +---------------------- -Many apps grow beyond simple forms editing a single object. For example when creating a Person instance 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: +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: * Ryan Bates' series of railscasts on http://railscasts.com/episodes/75[complex forms] * Handle Multiple Models in One Form from http://media.pragprog.com/titles/fr_arr/multiple_models_one_form.pdf[Advanced Rails Recipes] diff --git a/railties/doc/guides/source/getting_started_with_rails.txt b/railties/doc/guides/source/getting_started_with_rails.txt index 7e87c2935e..43322822fe 100644 --- a/railties/doc/guides/source/getting_started_with_rails.txt +++ b/railties/doc/guides/source/getting_started_with_rails.txt @@ -8,6 +8,8 @@ This guide covers getting up and running with Ruby on Rails. After reading it, * The basic principles of MVC (Model, View Controller) and RESTful design * How to quickly generate the starting pieces of a Rails application. +NOTE: This Guide is based on Rails 2.3. Some of the code shown here will not work in older versions of Rails. + == This Guide Assumes This guide is designed for beginners who want to get started with a Rails application from scratch. It does not assume that you have any prior experience with Rails. However, to get the most out of it, you need to have some prerequisites installed: @@ -16,7 +18,7 @@ This guide is designed for beginners who want to get started with a Rails applic * The link:http://rubyforge.org/frs/?group_id=126[RubyGems] packaging system * A working installation of link:http://www.sqlite.org/[SQLite] (preferred), link:http://www.mysql.com/[MySQL], or link:http://www.postgresql.org/[PostgreSQL] -It is highly recommended that you *familiarize yourself with Ruby before diving into Rails*. You will find it much easier to follow what's going on with a Rails application if you understand basic Ruby syntax. Rails isn't going to magically revolutionize the way you write web applications if you have no experience with the language it uses. There are some good free resources on the net for learning Ruby, including: +It is highly recommended that you *familiarize yourself with Ruby before diving into Rails*. You will find it much easier to follow what's going on with a Rails application if you understand basic Ruby syntax. Rails isn't going to magically revolutionize the way you write web applications if you have no experience with the language it uses. There are some good free resources on the internet for learning Ruby, including: * link:http://www.humblelittlerubybook.com/[Mr. Neigborly’s Humble Little Ruby Book] * link:http://www.rubycentral.com/book/[Programming Ruby] @@ -26,7 +28,7 @@ It is highly recommended that you *familiarize yourself with Ruby before diving Rails is a web development framework written in the Ruby language. It is designed to make programming web applications easier by making several assumptions about what every developer needs to get started. It allows you to write less code while accomplishing more than many other languages and frameworks. Longtime Rails developers also report that it makes web application development more fun. -Rails is _opinionated software_. That is, it assumes that there is a best way to do things, and it's designed to encourage that best way - and in some cases discourage alternatives. If you learn "The Rails Way" you'll probably discover a tremendous increase in productivity. If you persist in bringing old habits from other languages to your Rails development, and trying to use patterns you learned elsewhere, you may have a less happy experience. +Rails is _opinionated software_. That is, it assumes that there is a best way to do things, and it's designed to encourage that best way - and in some cases to discourage alternatives. If you learn "The Rails Way" you'll probably discover a tremendous increase in productivity. If you persist in bringing old habits from other languages to your Rails development, and trying to use patterns you learned elsewhere, you may have a less happy experience. The Rails philosophy includes several guiding principles: @@ -105,7 +107,7 @@ For example, to a Rails application a request such as this: +DELETE /photos/17+ -would be understood to refer to a photo resource with the ID of 17, and to indicate a desired action - deleting that resource. REST is a natural style for the architecture of web applications, and Rails makes it even more natural by using conventions to shield you from some of the RESTful complexities. +would be understood to refer to a photo resource with the ID of 17, and to indicate a desired action - deleting that resource. REST is a natural style for the architecture of web applications, and Rails makes it even more natural by using conventions to shield you from some of the RESTful complexities and browser quirks. If you’d like more details on REST as an architectural style, these resources are more approachable than Fielding’s thesis: @@ -154,6 +156,8 @@ And if you're using PostgreSQL for data storage, run this command: $ rails blog -d postgresql ------------------------------------------------------- +TIP: You can see all of the switches that the Rails application builder accepts by running +rails -h+. + After you create the blog application, switch to its folder to continue work directly in that application: [source, shell] @@ -201,13 +205,12 @@ Here's the section of the default configuration file with connection information development: adapter: sqlite3 database: db/development.sqlite3 + pool: 5 timeout: 5000 ------------------------------------------------------- If you don't have any database set up, SQLite is the easiest to get installed. If you're on OS X 10.5 or greater on a Mac, you already have it. Otherwise, you can install it using RubyGems: -If you're not running OS X 10.5 or greater, you'll need to install the SQLite gem. Similar to installing Rails you just need to run: - [source, shell] ------------------------------------------------------- $ gem install sqlite3-ruby @@ -223,6 +226,7 @@ development: adapter: mysql encoding: utf8 database: blog_development + pool: 5 username: root password: socket: /tmp/mysql.sock @@ -239,6 +243,7 @@ development: adapter: postgresql encoding: unicode database: blog_development + pool: 5 username: blog password: ------------------------------------------------------- @@ -254,6 +259,8 @@ Now that you have your database configured, it's time to have Rails create an em $ rake db:create ------------------------------------------------------- +NOTE: Rake is a general-purpose command-runner that Rails uses for many things. You can see the list of available rake commands in your application by running +rake -T+. + == Hello, Rails! One of the traditional places to start with a new language is by getting some text up on screen quickly. To do that in Rails, you need to create at minimum a controller and a view. Fortunately, you can do that in a single command. Enter this command in your terminal: @@ -281,13 +288,13 @@ You actually have a functional Rails application already - after running only tw $ script/server ------------------------------------------------------- -This will fire up the lightweight Webrick web server by default. To see your application in action, open a browser window and navigate to +http://localhost:3000+. You should see Rails' default information page: +This will fire up an instance of the Mongrel web server by default (Rails can also use several other web servers). To see your application in action, open a browser window and navigate to +http://localhost:3000+. You should see Rails' default information page: image:images/rails_welcome.png[Welcome Aboard screenshot] TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. In development mode, Rails does not generally require you to stop the server; changes you make in files will be automatically picked up by the server. -The "Welcome Aboard" page is the smoke test for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. To view the page you just created, navigate to +http://localhost:3000/home/index+. +The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. To view the page you just created, navigate to +http://localhost:3000/home/index+. === Setting the Application Home Page @@ -336,13 +343,13 @@ $ script/generate scaffold Post name:string title:string content:text NOTE: While scaffolding will get you up and running quickly, the "one size fits all" code that it generates is unlikely to be a perfect fit for your application. In most cases, you'll need to customize the generated code. Many experienced Rails developers avoid scaffolding entirely, preferring to write all or most of their source code from scratch. -The scaffold generator will build 13 files in your application, along with some folders, and edit one more. Here's a quick overview of what it creates: +The scaffold generator will build 14 files in your application, along with some folders, and edit one more. Here's a quick overview of what it creates: [options="header"] |========================================================================================================== |File |Purpose |app/models/post.rb |The Post model -|db/migrate/20081013124235_create_posts.rb |Migration to create the posts table in your database (your name will include a different timestamp) +|db/migrate/20090113124235_create_posts.rb |Migration to create the posts table in your database (your name will include a different timestamp) |app/views/posts/index.html.erb |A view to display an index of all posts |app/views/posts/show.html.erb |A view to display a single post |app/views/posts/new.html.erb |A view to create a new post @@ -355,13 +362,14 @@ The scaffold generator will build 13 files in your application, along with some |config/routes.rb |Edited to include routing information for posts |test/fixtures/posts.yml |Dummy posts for use in testing |test/unit/post_test.rb |Unit testing harness for the posts model +|test/unit/helpers/posts_helper_test.rb |Unit testing harness for the posts helper |========================================================================================================== === Running a Migration One of the products of the +script/generate scaffold+ command is a _database migration_. Migrations are Ruby classes that are designed to make it simple to create and modify database tables. Rails uses rake commands to run migrations, and it's possible to undo a migration after it's been applied to your database. Migration filenames include a timestamp to ensure that they're processed in the order that they were created. -If you look in the +db/migrate/20081013124235_create_posts.rb+ file (remember, yours will have a slightly different name), here's what you'll find: +If you look in the +db/migrate/20090113124235_create_posts.rb+ file (remember, yours will have a slightly different name), here's what you'll find: [source, ruby] ------------------------------------------------------- @@ -388,10 +396,11 @@ At this point, you can use a rake command to run the migration: [source, shell] ------------------------------------------------------- -$ rake db:create $ rake db:migrate ------------------------------------------------------- +Remember, you can't run migrations before running +rake db:create+ to create your database, as we covered earlier. + NOTE: Because you're working in the development environment by default, this command will apply to the database defined in the +development+ section of your +config/database.yml+ file. === Adding a Link @@ -472,7 +481,7 @@ title: nil, content: "A new post", created_at: nil, updated_at: nil>, This code shows creating a new +Post+ instance, attempting to save it and getting +false+ for a return value (indicating that the save failed), and inspecting the +errors+ of the post. -TIP: Unlike the development web server, the console does not automatically load your code afresh for each line. If you make changes, type +reload!+ at the console prompt to load them. +TIP: Unlike the development web server, the console does not automatically load your code afresh for each line. If you make changes to your models while the console is open, type +reload!+ at the console prompt to load them. === Listing All Posts @@ -762,7 +771,7 @@ At this point, it’s worth looking at some of the tools that Rails provides to === Using Partials to Eliminate View Duplication -As you saw earlier, the scaffold-generated views for the +new+ and +edit+ actions are largely identical. You can pull the shared code out into a +partial+ template. This requires editing the new and edit views, and adding a new template. The new +_form.html.erb+ template should be saved in the same +app/views/posts+ folder as the files from which it is being extracted: +As you saw earlier, the scaffold-generated views for the +new+ and +edit+ actions are largely identical. You can pull the shared code out into a partial template. This requires editing the new and edit views, and adding a new template. The new +_form.html.erb+ template should be saved in the same +app/views/posts+ folder as the files from which it is being extracted. Note that the name of this file begins with an underscore; that's the Rails naming convention for partial templates. +new.html.erb+: @@ -876,7 +885,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 link:actioncontroller_basics.html[Action Controller Basics] guide. +For more information on filters, see the link:../actioncontroller_basics.html[Action Controller Basics] guide. == Adding a Second Model @@ -894,7 +903,7 @@ $ script/generate model Comment commenter:string body:text post:references This command will generate four files: * +app/models/comment.rb+ - The model -* +db/migrate/20081013214407_create_comments.rb - The migration +* +db/migrate/20091013214407_create_comments.rb - The migration * +test/unit/comment_test.rb+ and +test/fixtures/comments.yml+ - The test harness. First, take a look at +comment.rb+: @@ -936,7 +945,7 @@ The +t.references+ line sets up a foreign key column for the association between $ rake db:migrate ------------------------------------------------------- -Rails is smart enough to only execute the migrations that have not already been run against this particular database. +Rails is smart enough to only execute the migrations that have not already been run against the current database. === Associating Models @@ -971,13 +980,11 @@ TIP: For more information on Active Record associations, see the link:../associa === Adding a Route -_Routes_ are entries in the +config/routes.rb+ file that tell Rails how to match incoming HTTP requests to controller actions. Open up that file and find the existing line referring to +posts+. Then edit it as follows: +_Routes_ are entries in the +config/routes.rb+ file that tell Rails how to match incoming HTTP requests to controller actions. Open up that file and find the existing line referring to +posts+ (it will be right at the top of the file). Then edit it as follows: [source, ruby] ------------------------------------------------------- -map.resources :posts do |post| - post.resources :comments -end +map.resources :posts, :has_many => :comments ------------------------------------------------------- This creates +comments+ as a _nested resource_ within +posts+. This is another part of capturing the hierarchical relationship that exists between posts and comments. @@ -1003,7 +1010,7 @@ This creates seven files: * +app/views/comments/edit.html.erb+ - The view for the edit action * +test/functional/comments_controller_test.rb+ - The functional tests for the controller -The controller will be generated with empty methods for each action that you specified in the call to +script/generate controller+: +The controller will be generated with empty methods and views for each action that you specified in the call to +script/generate controller+: [source, ruby] ------------------------------------------------------- @@ -1068,6 +1075,17 @@ class CommentsController < ApplicationController end end + def destroy + @post = Post.find(params[:post_id]) + @comment = Comment.find(params[:id]) + @comment.destroy + + respond_to do |format| + format.html { redirect_to post_comments_path(@post) } + format.xml { head :ok } + end + end + end ------------------------------------------------------- @@ -1086,7 +1104,7 @@ This creates a new +Comment+ object _and_ sets up the +post_id+ field to have th Because you skipped scaffolding, you'll need to build views for comments "by hand." Invoking +script/generate controller+ will give you skeleton views, but they'll be devoid of actual content. Here's a first pass at fleshing out the comment views. -The +index.html.erb+ view: +The +views/comments/index.html.erb+ view: [source, ruby] ------------------------------------------------------- @@ -1115,7 +1133,7 @@ The +index.html.erb+ view: <%= link_to 'Back to Post', @post %> ------------------------------------------------------- -The +new.html.erb+ view: +The +views/comments/new.html.erb+ view: [source, ruby] ------------------------------------------------------- @@ -1140,7 +1158,7 @@ The +new.html.erb+ view: <%= link_to 'Back', post_comments_path(@post) %> ------------------------------------------------------- -The +show.html.erb+ view: +The +views/comments/show.html.erb+ view: [source, ruby] ------------------------------------------------------- @@ -1160,7 +1178,7 @@ The +show.html.erb+ view: <%= link_to 'Back', post_comments_path(@post) %> ------------------------------------------------------- -The +edit.html.erb+ view: +The +views/comments/edit.html.erb+ view: [source, ruby] ------------------------------------------------------- @@ -1186,11 +1204,11 @@ The +edit.html.erb+ view: <%= link_to 'Back', post_comments_path(@post) %> ------------------------------------------------------- -Again, the added complexity here (compared to the views you saw for managing comments) comes from the necessity of juggling a post and its comments at the same time. +Again, the added complexity here (compared to the views you saw for managing posts) comes from the necessity of juggling a post and its comments at the same time. === Hooking Comments to Posts -As a final step, I'll modify the +show.html.erb+ view for a post to show the comments on that post, and to allow managing those comments: +As a next step, I'll modify the +views/posts/show.html.erb+ view to show the comments on that post, and to allow managing those comments: [source, ruby] ------------------------------------------------------- @@ -1222,13 +1240,90 @@ As a final step, I'll modify the +show.html.erb+ view for a post to show the com

    <% end %> -<%= link_to 'Edit', edit_post_path(@post) %> | -<%= link_to 'Back', posts_path %> +<%= link_to 'Edit Post', edit_post_path(@post) %> | +<%= link_to 'Back to Posts', posts_path %> | <%= link_to 'Manage Comments', post_comments_path(@post) %> ------------------------------------------------------- Note that each post has its own individual comments collection, accessible as +@post.comments+. That's a consequence of the declarative associations in the models. Path helpers such as +post_comments_path+ come from the nested route declaration in +config/routes.rb+. +== Building a Multi-Model Form + +Comments and posts are edited on two separate forms - which makes sense, given the flow of this mini-application. But what if you want to edit more than one thing on a single form? Rails 2.3 offers new support for nested forms. Let's add support for giving each post multiple tags, right in the form where you create the post. First, create a new model to hold the tags: + +[source, shell] +------------------------------------------------------- +$ script/generate model tag name:string post:references +------------------------------------------------------- + +Run the migration to create the database table: + +[source, shell] +------------------------------------------------------- +$ rake db:migrate +------------------------------------------------------- + +Next, edit the +post.rb+ file to create the other side of the association, and to tell Rails that you intend to edit tags via posts: + +[source, ruby] +------------------------------------------------------- +class Post < ActiveRecord::Base + validates_presence_of :name, :title + validates_length_of :title, :minimum => 5 + has_many :comments + has_many :tags + + accepts_nested_attributes_for :tags, :allow_destroy => :true , + :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } } +end +------------------------------------------------------- + +The +:allow_destroy+ option on the nested attribute declaration tells Rails to display a "remove" checkbox on the view that you'll build shortly. The +:reject_if+ option prevents saving new tags that do not have any attributes filled in. + +You'll also need to modify +views/posts/_form.html.erb+ to include the tags: + +[source, ruby] +------------------------------------------------------- +<% @post.tags.build if @post.tags.empty? %> +<% form_for(@post) do |post_form| %> + <%= post_form.error_messages %> + +

    + <%= post_form.label :name %>
    + <%= post_form.text_field :name %> +

    +

    + <%= post_form.label :title, "title" %>
    + <%= post_form.text_field :title %> +

    +

    + <%= post_form.label :content %>
    + <%= post_form.text_area :content %> +

    +

    Tags

    + <% post_form.fields_for :tags do |tag_form| %> +

    + <%= tag_form.label :name, 'Tag:' %> + <%= tag_form.text_field :name %> +

    + <% unless tag_form.object.nil? || tag_form.object.new_record? %> +

    + <%= tag_form.label :_delete, 'Remove:' %> + <%= tag_form.check_box :_delete %> +

    + <% end %> + <% end %> + +

    + <%= post_form.submit "Save" %> +

    +<% end %> +------------------------------------------------------- + +With these changes in place, you'll find that you can edit a post and its tags directly on the same view. + +NOTE: You may want to use javascript to dynamically add additional tags on a single form. For an example of this and other advanced techniques, see the link:http://github.com/alloy/complex-form-examples/tree/nested_attributes[nested model sample application]. + == What's Next? Now that you've seen your first Rails application, you should feel free to update it and experiment on your own. But you don't have to do everything without help. As you need assistance getting up and running with Rails, feel free to consult these support resources: @@ -1247,6 +1342,7 @@ Rails also comes with built-in help that you can generate using the rake command http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/2[Lighthouse ticket] +* * November 3, 2008: Formatting patch from Dave Rothlisberger * November 1, 2008: First approved version by link:../authors.html#mgunderloy[Mike Gunderloy] * October 16, 2008: Revised based on feedback from Pratik Naik by link:../authors.html#mgunderloy[Mike Gunderloy] (not yet approved for publication) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index e80de7adc9..4465a77289 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -1,9 +1,25 @@ The Rails Internationalization (I18n) API ========================================= -The Ruby I18n (shorthand for _internationalization_) gem which is shipped with Ruby on Rails (starting from Rails 2.2) provides an easy-to-use and extensible framework for translating your application to a single custom language other than English or providing multi-language support in your application. +The Ruby I18n (shorthand for _internationalization_) gem which is shipped with Ruby on Rails (starting from Rails 2.2) provides an easy-to-use and extensible framework for *translating your application to a single custom language* other than English or for *providing multi-language support* in your application. -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. See Rails http://rails-i18n.org/wiki[I18n Wiki] for more information. +The process of "internationalization" usually means to abstract all strings and other locale specific bits (such as date or currency formats) out of your application. The process of "localization" means to provide translations and localized formats for these bits. <<1>> + +So, in the process of _internationalizing_ your Rails application you have to: + +* Ensure you have support for i18n +* Tell Rails where to find locale dictionaries +* Tell Rails how to set, preserve and switch locale + +In the process of _localizing_ your application you'll probably want to do following three things: + +* Replace or supplement Rail's 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 +* 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. + +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 http://rails-i18n.org/wiki[I18n Wiki] for more information. == How I18n in Ruby on Rails works @@ -12,7 +28,7 @@ Internationalization is a complex problem. Natural languages differ in so many w * 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. ActiveRecord 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* -- eg. Active Record validation messages, time and date formats -- *has been internationalized*, so _localization_ of a Rails application means "over-riding" these defaults. === The overall architecture of the library @@ -74,10 +90,12 @@ en: hello: "Hello world" ------------------------------------------------------- -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 ActiveRecord validation messages in the http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml[+activerecord/lib/active_record/locale/en.yml+] file or time and date formats in the http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml[+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 _Hello world_ string. Every string inside Rails is internationalized in this way, see for instance Active Record validation messages in the http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml[+activerecord/lib/active_record/locale/en.yml+] file or time and date formats in the http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml[+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. +NOTE: The i18n library takes *pragmatic approach* to locale keys (after http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en[some discussion]), 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. + 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. @@ -112,30 +130,208 @@ I18n.default_locale = :pt === Setting and passing the locale -By default the I18n library will use :en (English) as a I18n.default_locale for looking up translations (if you do not specify a locale for a lookup). +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 http://en.wikipedia.org/wiki/Representational_State_Transfer[_RESTful_]. Read more about RESTful approach in http://www.infoq.com/articles/rest-introduction[Stefan Tilkov's articles]. 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: + +[source, ruby] +------------------------------------------------------- +before_filter :set_locale +def set_locale + # if params[:locale] is nil then I18n.default_locale will be used + I18n.locale = params[:locale] +end +------------------------------------------------------- + +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. + +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. + +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 http://github.com/svenfuchs/i18n/commit/411f8fe7[this commit] and background at http://rails-i18n.org/wiki/pages/i18n-available_locales[Rails I18n Wiki].) + +So, for having available locales easily available in Rails 2.2, we have to include this support manually in an initializer, like this: + +[source, ruby] +------------------------------------------------------- +# config/initializers/available_locales.rb +# +# Get loaded locales conveniently +# See http://rails-i18n.org/wiki/pages/i18n-available_locales +module I18n + class << self + def available_locales; backend.available_locales; end + end + module Backend + class Simple + def available_locales; translations.keys.collect { |l| l.to_s }.sort; end + end + end +end + +# You need to "force-initialize" loaded locales +I18n.backend.send(:init_translations) + +AVAILABLE_LOCALES = I18n.backend.available_locales +RAILS_DEFAULT_LOGGER.debug "* Loaded locales: #{AVAILABLE_LOCALES.inspect}" +------------------------------------------------------- + +You can then wrap the constant for easy access in ApplicationController: + +[source, ruby] +------------------------------------------------------- +class ApplicationController < ActionController::Base + def available_locales; AVAILABLE_LOCALES; end +end +------------------------------------------------------- + +=== Setting locale from the domain name -If you want to translate your Rails application to a single language other than English you can set I18n.default_locale to your locale. If you want to change the locale on a per-request basis though you can set it in a before_filter on the ApplicationController like this: +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: + +* 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 + +You can implement it like this in your ApplicationController: [source, ruby] ------------------------------------------------------- before_filter :set_locale def set_locale - # if this is nil then I18n.default_locale will be used - I18n.locale = params[:locale] + I18n.locale = extract_locale_from_uri +end +# Get locale from top-level domain or return nil if such locale is not available +# You have to put something like: +# 127.0.0.1 application.com +# 127.0.0.1 application.it +# 127.0.0.1 application.pl +# in your /etc/hosts file to try this out locally +def extract_locale_from_tld + parsed_locale = request.host.split('.').last + (available_locales.include? parsed_locale) ? parsed_locale : nil +end +------------------------------------------------------- + +We can also set the locale from the _subdomain_ in very similar way: + +[source, ruby] +------------------------------------------------------- +# Get locale code from request subdomain (like http://it.application.local:3000) +# You have to put something like: +# 127.0.0.1 gr.application.local +# in your /etc/hosts file to try this out locally +def extract_locale_from_subdomain + parsed_locale = request.subdomains.first + (available_locales.include? parsed_locale) ? parsed_locale : nil +end +------------------------------------------------------- + +If your application includes a locale switching menu, you would then have something like this in it: + +[source, ruby] +------------------------------------------------------- +link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['REQUEST_URI']}") +------------------------------------------------------- + +assuming you would set +APP_CONFIG[:deutsch_website_url]+ to some value like +http://www.application.de+. + +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). + +=== Setting 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. + +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. + +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. + +Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its http://api.rubyonrails.org/classes/ActionController/Base.html#M000515[+*ApplicationController#default_url_options*+], which is useful precisely in this scenario: it enables us to set "defaults" for http://api.rubyonrails.org/classes/ActionController/Base.html#M000503[+url_for+] and helper methods dependent on it (by implementing/overriding this method). + +We can include something like this in our ApplicationController then: + +[source, ruby] +------------------------------------------------------- +# app/controllers/application_controller.rb +def default_url_options(options={}) + logger.debug "default_url_options is passed options: #{options.inspect}\n" + { :locale => I18n.locale } +end +------------------------------------------------------- + +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+. + +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 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 http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354[+path_prefix+] option in this way: + +[source, ruby] +------------------------------------------------------- +# config/routes.rb +map.resources :books, :path_prefix => '/:locale' +------------------------------------------------------- + +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). + +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.) + +You would probably need to map URLs like these: + +[source, ruby] +------------------------------------------------------- +# config/routes.rb +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 http://github.com/svenfuchs/routing-filter/tree/master[_routing_filter_] and Raul Murciano's http://github.com/raul/translate_routes/tree/master[_translate_routes_]. See also the page http://rails-i18n.org/wiki/pages/how-to-encode-the-current-locale-in-the-url[How to encode the current locale in the URL] in the Rails i18n Wiki. + +=== Setting 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. + + +==== Using Accept-Language + +One source of client supplied information would be an +Accept-Language+ HTTP header. People may http://www.w3.org/International/questions/qa-lang-priorities[set this in their browser] or other clients (such as _curl_). + +A trivial implementation of using +Accept-Language+ header would be: + +[source, ruby] +------------------------------------------------------- +def set_locale + logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}" + I18n.locale = extract_locale_from_accept_language_header + logger.debug "* Locale set to '#{I18n.locale}'" +end +private +def extract_locale_from_accept_language_header + request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first end ------------------------------------------------------- -This will already work for URLs where you pass the locale as a query parameter as in example.com?locale=pt (which is what Google also does). +Of course, in production environment you would need much robust code, and could use a plugin such as Iaian Hecker's http://github.com/iain/http_accept_language[http_accept_language]. + +==== 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 http://www.maxmind.com/app/geolitecountry[GeoIP Lite Country]. 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. + +==== User profile -TIP: For other URL designs, see http://rails-i18n.org/wiki/pages/how-to-encode-the-current-locale-in-the-url[How to encode the current locale in the URL]. +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. -Now you've initialized I18n support for your application and told it which locale should be used. With that in place you're now ready for the really interesting stuff. +== Internationalizing your application -== Internationalize 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. -The process of "internationalization" usually means to abstract all strings and other locale specific bits out of your application. The process of "localization" means to then provide translations and localized formats for these bits. <<1>> +Let's _internationalize_ our application, ie. abstract every locale-specific parts, and that _localize_ it, ie. provide neccessary translations for these abstracts. -So, let's internationalize something. You most probably have something like this in one of your applications: +You most probably have something like this in one of your applications: [source, ruby] ------------------------------------------------------- @@ -160,7 +356,7 @@ image:images/i18n/demo_untranslated.png[rails i18n demo untranslated] === Adding Translations -Obviously there are two strings that are localized to English. In order to internationalize this code replace these strings with calls to Rails' #t helper with a key that makes sense for the translation: +Obviously there are *two strings that are localized to English*. In order to internationalize this code, *replace these strings* with calls to Rails' +#t+ helper with a key that makes sense for the translation: [source, ruby] ------------------------------------------------------- @@ -176,13 +372,13 @@ end

    <%= flash[:notice] %>

    ------------------------------------------------------- -When you now render this view it will show an error message that tells you that the translations for the keys :hello_world and :hello_flash are missing. +When you now render this view, it will show an error message which tells you that the translations for the keys +:hello_world+ and +:hello_flash+ are missing. image: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 ++. -So let's add the missing translations (i.e. do the "localization" part): +So let's add the missing translations into the dictionary files (i.e. do the "localization" part): [source, ruby] ------------------------------------------------------- @@ -197,19 +393,19 @@ pirate: hello_flash: Ahoy Flash ------------------------------------------------------- -There you go. Because you haven't changed the default_locale I18n will use English. Your application now shows: +There you go. Because you haven't changed the default_locale, I18n will use English. Your application now shows: -image:images/i18n/demo_translated_english.png[rails i18n demo translated to english] +image:images/i18n/demo_translated_en.png[rails i18n demo translated to english] -And when you change the URL to pass the pirate locale you get: +And when you change the URL to pass the pirate locale (+http://localhost:3000?locale=pirate+), you'll get: image:images/i18n/demo_translated_pirate.png[rails i18n demo translated to pirate] -NOTE You need to restart the server when you add new locale files. +NOTE: You need to restart the server when you add new locale files. === Adding Date/Time formats -Ok, 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. +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. [source, ruby] ------------------------------------------------------- @@ -234,17 +430,53 @@ So that would give you: image:images/i18n/demo_localized_pirate.png[rails i18n demo localized time to pirate] -NOTE Right now you might need to add some more date/time formats in order to make the I18n backend work as expected. See the http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale[rails-i18n repository] for starting points. +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 http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale[rails-i18n repository at Github] for an archive of various locale files. When you put such file(s) in +config/locale/+ directory, they will automatically ready for use. +=== Organization of locale files + +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. + +For example, your +config/locale+ directory could look like this: + +------------------------------------------------------- +|-defaults +|---es.rb +|---en.rb +|-models +|---book +|-----es.rb +|-----en.rb +|-views +|---defaults +|-----es.rb +|-----en.rb +|---books +|-----es.rb +|-----en.rb +|---users +|-----es.rb +|-----en.rb +|---navigation +|-----es.rb +|-----en.rb +------------------------------------------------------- + +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). + +Other stores for the i18n library could provide different means of such separation. + +Do check the http://rails-i18n.org/wiki[Rails i18n Wiki] for list of tools available for managing translations. == Overview of the I18n API features -The following purposes are covered: +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. + +Covered are features like these: -* lookup translations -* interpolate data into translations -* pluralize translations -* localize dates, numbers, currency etc. +* looking up translations +* interpolating data into translations +* pluralizing translations +* localizing dates, numbers, currency etc. === Looking up translations @@ -258,14 +490,14 @@ I18n.t :message I18n.t 'message' ------------------------------------------------------- -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: ++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: [source, ruby] ------------------------------------------------------- I18n.t :invalid, :scope => [:active_record, :error_messages] ------------------------------------------------------- -This looks up the :invalid message in the ActiveRecord error messages. +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: @@ -296,7 +528,7 @@ I18n.t :missing, :default => 'Not here' 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: [source, ruby] ------------------------------------------------------- @@ -314,7 +546,7 @@ I18n.t [:odd, :even], :scope => 'active_record.error_messages' # => ["must be odd", "must be even"] ------------------------------------------------------- -Also, a key can translate to a (potentially nested) hash as grouped translations. E.g. one can receive all ActiveRecord error messages as a Hash with: +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: [source, ruby] ------------------------------------------------------- @@ -324,9 +556,9 @@ I18n.t 'active_record.error_messages' === 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. +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. -All options besides :default and :scope that are passed to #translate will be interpolated to the translation: +All options besides +:default+ and +:scope+ that are passed to +#translate+ will be interpolated to the translation: [source, ruby] ------------------------------------------------------- @@ -335,14 +567,14 @@ I18n.translate :thanks, :name => 'Jeremy' # => 'Thanks Jeremy!' ------------------------------------------------------- -If a translation uses :default or :scope as a interpolation variable an I18n::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 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. === 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 (http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ar[Arabic], http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ja[Japanese], http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ru[Russian] and many more) have different grammars that have additional or less http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html[plural forms]. 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: +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: [source, ruby] ------------------------------------------------------- @@ -354,22 +586,22 @@ I18n.translate :inbox, :count => 2 # => '2 messages' ------------------------------------------------------- -The algorithm for pluralizations in :en is as simple as: +The algorithm for pluralizations in +:en+ is as simple as: [source, ruby] ------------------------------------------------------- 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). +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 I18n::InvalidPluralizationData exception is raised. +If the lookup for the key does not return an Hash suitable for pluralization an +18n::InvalidPluralizationData+ exception is raised. === 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. +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: [source, ruby] ------------------------------------------------------- @@ -386,7 +618,7 @@ I18n.t :foo, :locale => :de I18n.l Time.now, :locale => :de ------------------------------------------------------- -I18n.locale defaults to I18n.default_locale which defaults to :en. The default locale can be set like this: ++I18n.locale+ defaults to +I18n.default_locale+ which defaults to :+en+. The default locale can be set like this: [source, ruby] ------------------------------------------------------- @@ -419,9 +651,9 @@ pt: bar: 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". +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 ActiveSupport +en.yml+ translations YAML file: [source, ruby] ------------------------------------------------------- @@ -433,7 +665,7 @@ en: long: "%B %d, %Y" ------------------------------------------------------- -So, all of the following equivalent lookups will return the :short date format "%B %d": +So, all of the following equivalent lookups will return the +:short+ date format +"%B %d"+: [source, ruby] ------------------------------------------------------- @@ -443,11 +675,11 @@ I18n.t :short, :scope => 'date.formats' I18n.t :short, :scope => [:date, :formats] ------------------------------------------------------- -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. -=== Translations for ActiveRecord models +=== 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 lookup translations for your model and attribute names. For example when you add the following translations: @@ -463,15 +695,15 @@ en: # will translate User attribute "login" as "Handle" ------------------------------------------------------- -Then User.human_name will return "Dude" and User.human_attribute_name(:login) will return "Handle". +Then +User.human_name+ will return "Dude" and +User.human_attribute_name(:login)+ will return "Handle". ==== Error message scopes -ActiveRecord validation error messages can also be translated easily. ActiveRecord 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. -Consider a User model with a validates_presence_of validation for the name attribute like this: +Consider a User model with a +validates_presence_of+ validation for the name attribute like this: [source, ruby] ------------------------------------------------------- @@ -480,7 +712,7 @@ class User < ActiveRecord::Base end ------------------------------------------------------- -The key for the error message in this case is :blank. ActiveRecord will lookup this key in the namespaces: +The key for the error message in this case is +:blank+. Active Record will lookup this key in the namespaces: [source, ruby] ------------------------------------------------------- @@ -509,7 +741,7 @@ class Admin < User end ------------------------------------------------------- -Then ActiveRecord will look for messages in this order: +Then Active Record will look for messages in this order: [source, ruby] ------------------------------------------------------- @@ -524,11 +756,11 @@ This way you can provide special translations for various error messages at diff ==== Error message interpolation -The translated model name and translated attribute name are always available for interpolation. +The translated model name, translated attribute name, and value are always available for interpolation. -So, for example, instead of the default error message "can not be blank" you could use the attribute name like this: "Please fill in your {{attribute}}". +So, for example, instead of the default error message +"can not be blank"+ you could use the attribute name like this:+ "Please fill in your {{attribute}}"+. -count and/or value are available where applicable. Count can be used for pluralization if present: ++count+, where available, can be used for pluralization if present: |===================================================================================================== | validation | with option | message | interpolation @@ -540,25 +772,25 @@ count and/or value are available where applicable. Count can be used for plurali | validates_length_of | :is | :wrong_length | count | validates_length_of | :minimum | :too_short | count | validates_length_of | :maximum | :too_long | count -| validates_uniqueness_of | - | :taken | value -| validates_format_of | - | :invalid | value -| validates_inclusion_of | - | :inclusion | value -| validates_exclusion_of | - | :exclusion | value -| validates_associated | - | :invalid | value -| validates_numericality_of | - | :not_a_number | value -| validates_numericality_of | :greater_than | :greater_than | value -| validates_numericality_of | :greater_than_or_equal_to | :greater_than_or_equal_to | value -| validates_numericality_of | :equal_to | :equal_to | value -| validates_numericality_of | :less_than | :less_than | value -| validates_numericality_of | :less_than_or_equal_to | :less_than_or_equal_to | value -| validates_numericality_of | :odd | :odd | value -| validates_numericality_of | :even | :even | value +| validates_uniqueness_of | - | :taken | - +| validates_format_of | - | :invalid | - +| validates_inclusion_of | - | :inclusion | - +| validates_exclusion_of | - | :exclusion | - +| validates_associated | - | :invalid | - +| validates_numericality_of | - | :not_a_number | - +| validates_numericality_of | :greater_than | :greater_than | count +| validates_numericality_of | :greater_than_or_equal_to | :greater_than_or_equal_to | count +| validates_numericality_of | :equal_to | :equal_to | count +| validates_numericality_of | :less_than | :less_than | count +| validates_numericality_of | :less_than_or_equal_to | :less_than_or_equal_to | count +| validates_numericality_of | :odd | :odd | - +| validates_numericality_of | :even | :even | - |===================================================================================================== -==== Translations for the ActiveRecord error_messages_for helper +==== Translations for the Active Record error_messages_for helper -If you are using the ActiveRecord 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: @@ -575,11 +807,29 @@ en: ------------------------------------------------------- -=== Other translations and localizations +=== 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. + +==== ActionView helper methods + +* +distance_of_time_in_words+ translates and pluralizes its result and interpolates the number of seconds, minutes, hours and so on. See http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L51[datetime.distance_in_words] translations. + +* +datetime_select+ and +select_month+ use translated month names for populating the resulting select tag. See http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15[date.month_names] for translations. +datetime_select+ also looks up the order option from http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18[date.order] (unless you pass the option explicitely). All date select helpers translate the prompt using the translations in the http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L83[datetime.prompts] 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 http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2[number] scope. + +==== Active Record methods + +* +human_name+ and +human_attribute_name+ use translations for model names and attribute names if available in the http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L43[activerecord.models] 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". -Rails uses fixed strings and other localizations, such as format strings and other format information in a couple of helpers. +*+ ActiveRecord::Errors#full_messages+ prepends the attribute name to the error message using a separator that will be looked up from http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L91[activerecord.errors.format.separator] (and defaults to +' '+). -TODO list helpers and available keys +==== ActiveSupport methods + +* +Array#to_sentence+ uses format settings as given in the http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L30[support.array] scope. == Customize your I18n setup @@ -609,7 +859,7 @@ 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 ------------------------------------------------------- -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 exception’s error message string containing the missing key/scope. +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 exception’s 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. @@ -626,11 +876,11 @@ end I18n.exception_handler = :just_raise_that_exception ------------------------------------------------------- -This would re-raise all caught exceptions including MissingTranslationData. +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: [source, ruby] ------------------------------------------------------- @@ -638,8 +888,39 @@ I18n.t :foo, :raise => true # always re-raises exceptions from the backend ------------------------------------------------------- +== 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. + +If you find anything missing or wrong in this guide please file a ticket on http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview[our issue tracker]. If you want to discuss certain portions or have questions please sign up to our http://groups.google.com/group/rails-i18n[mailinglist]. + + +== 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. + +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 http://groups.google.com/group/rails-i18n[mailinglist]!) + +If you find your own locale (language) missing from our http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale[example translations data] repository for Ruby on Rails, please http://github.com/guides/fork-a-project-and-submit-your-modifications[_fork_] the repository, add your data and send a http://github.com/guides/pull-requests[pull request]. + + == Resources +* http://rails-i18n.org[rails-i18n.org] - Homepage of the rails-i18n project. You can find lots of useful resources on the http://rails-i18n.org/wiki[wiki]. +* http://groups.google.com/group/rails-i18n[rails-i18n Google group] - The project's mailing list. +* http://github.com/svenfuchs/rails-i18n/tree/master[Github: rails-i18n] - Code repository for the rails-i18n project. Most importantly you can find lots of http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale[example translations] for Rails that should work for your application in most cases. +* http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview[Lighthouse: rails-i18n] - Issue tracker for the rails-i18n project. +* http://github.com/svenfuchs/i18n/tree/master[Github: i18n] - Code repository for the i18n gem. +* http://i18n.lighthouseapp.com/projects/14947-ruby-i18n/overview[Lighthouse: i18n] - Issue tracker for the i18n gem. + + +== Authors + +* http://www.workingwithrails.com/person/9963-sven-fuchs[Sven Fuchs] (initial author) +* http://www.workingwithrails.com/person/7476-karel-mina-k[Karel Minařík] + +If you found this guide useful please consider recommending its authors on http://www.workingwithrails.com[workingwithrails]. + == Footnotes @@ -649,6 +930,7 @@ I18n.t :foo, :raise => true # always re-raises exceptions from the backend [[[3]]] 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. + == Changelog == http://rails.lighthouseapp.com/projects/16213/tickets/23[Lighthouse ticket] diff --git a/railties/doc/guides/source/images/posts_index.png b/railties/doc/guides/source/images/posts_index.png new file mode 100644 index 0000000000..50e956e8be Binary files /dev/null and b/railties/doc/guides/source/images/posts_index.png differ diff --git a/railties/doc/guides/source/images/rails_welcome.png b/railties/doc/guides/source/images/rails_welcome.png new file mode 100644 index 0000000000..7e02ce5014 Binary files /dev/null and b/railties/doc/guides/source/images/rails_welcome.png differ diff --git a/railties/doc/guides/source/index.txt b/railties/doc/guides/source/index.txt index b32d8ef7b1..bb43b0175b 100644 --- a/railties/doc/guides/source/index.txt +++ b/railties/doc/guides/source/index.txt @@ -80,17 +80,17 @@ understand how to use routing in your own Rails applications, start here. This guide covers how controllers work and how they fit into the request cycle in your application. It includes sessions, filters, and cookies, data streaming, and dealing with exceptions raised by a request, among other topics. *********************************************************** -.link:caching_with_rails.html[Rails Caching] -*********************************************************** -CAUTION: link:http://rails.lighthouseapp.com/projects/16213/tickets/10[Lighthouse Ticket] - -This guide covers the three types of caching that Rails provides by default. -*********************************************************** - ++++++++++++++++++++++++++++++++++++++

    Digging Deeper

    ++++++++++++++++++++++++++++++++++++++ +.link:action_mailer_basics.html[Action Mailer Basics] +*********************************************************** +CAUTION: link:http://rails.lighthouseapp.com/projects/16213/tickets/25[Lighthouse ticket] + +This guide describes how to use Action Mailer to send and receive emails. +*********************************************************** + .link:testing_rails_applications.html[Testing Rails Applications] *********************************************************** CAUTION: link:http://rails.lighthouseapp.com/projects/16213/tickets/8[Lighthouse Ticket] @@ -142,4 +142,4 @@ This guide covers the command line tools and rake tasks provided by Rails. Authors who have contributed to complete guides are listed link:authors.html[here]. -This work is licensed under a link:http://creativecommons.org/licenses/by-nc-sa/3.0/[Creative Commons Attribution-Noncommercial-Share Alike 3.0 License] +This work is licensed under a link:http://creativecommons.org/licenses/by-sa/3.0[Creative Commons Attribution-Share Alike 3.0 License] -- cgit v1.2.3