From 741bf96a424256648f2624959f0da7602e81bf4c Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 22 Jan 2009 16:07:57 +0000 Subject: Regenerate guides --- railties/doc/guides/html/action_mailer_basics.html | 30 +++ railties/doc/guides/html/creating_plugins.html | 32 +-- railties/doc/guides/html/i18n.html | 223 +++++++++++++-------- 3 files changed, 179 insertions(+), 106 deletions(-) (limited to 'railties') diff --git a/railties/doc/guides/html/action_mailer_basics.html b/railties/doc/guides/html/action_mailer_basics.html index 56451818eb..c59012ec22 100644 --- a/railties/doc/guides/html/action_mailer_basics.html +++ b/railties/doc/guides/html/action_mailer_basics.html @@ -50,6 +50,9 @@
  • Mailer Testing
  • +
  • + Epilogue +
  • @@ -189,6 +192,33 @@ http://www.gnu.org/software/src-highlite -->

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

    +
    +

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

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

    +

    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.

    @@ -324,7 +340,7 @@ http://www.gnu.org/software/src-highlite --> def available_locales; AVAILABLE_LOCALES;endend

    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:

    +

    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:

    • @@ -381,28 +397,63 @@ http://www.gnu.org/software/src-highlite --> 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 versus www.example.com/nl/books. This is achievable with the over-riding default_url_options strategy: 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 so on.

    +

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

    - +
    -Tip +Important For setting locale from URL see How to encode the current locale in the URL in the Rails i18n Wiki.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

    +

    # TODO: Accept-Language, GeoIP, etc. Explain why it is not such a good idea in most cases.

    +

    OK! Now you’ve initialized I18n support for your 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.

    3. Internationalize your application

    @@ -432,7 +483,7 @@ ActionController::Routing

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

    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 Active Record 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 '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

    +

    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.

    +

    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

    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. Active Record 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:

    • -distance_of_time_in_words translates and pluralizes its result and interpolates the number of seconds, minutes, hours and so on. See datetime.distance_in_words translations. +distance_of_time_in_words translates and pluralizes its result and interpolates the number of seconds, minutes, hours and so on. See datetime.distance_in_words translations.

    • -datetime_select and select_month use translated month names for populating the resulting select tag. See date.month_names for translations. datetime_select also looks up the order option from date.order (unless you pass the option explicitely). All date select helpers translate the prompt using the translations in the datetime.prompts scope if applicable. +datetime_select and select_month use translated month names for populating the resulting select tag. See date.month_names for translations. datetime_select also looks up the order option from date.order (unless you pass the option explicitely). All date select helpers translate the prompt using the translations in the 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 number scope. +The number_to_currency, number_with_precision, number_to_percentage, number_with_delimiter and humber_to_human_size helpers use the number format settings located in the number scope.

    @@ -976,25 +1041,21 @@ The number_to_currency, number_with_precision, number_to_percentage, number_with
    • -human_name and human_attribute_name use translations for model names and attribute names if available in the 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". +human_name and human_attribute_name use translations for model names and attribute names if available in the 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#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 ' '). +ActiveRecord::Errors#generate_message (which is used by Active Record validations but may also be used manually) uses human_name and human_attribute_name (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes".

    +

    * ActiveRecord::Errors#full_messages prepends the attribute name to the error message using a separator that will be looked up from activerecord.errors.format.separator (and defaults to ' ').

    5.2.3. ActiveSupport methods

    • -Array#to_sentence uses format settings as given in the support.array scope. +Array#to_sentence uses format settings as given in the support.array scope.

    @@ -1023,7 +1084,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:

    @@ -1038,9 +1099,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 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

    +

    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

    @@ -1069,7 +1130,7 @@ http://www.gnu.org/software/src-highlite -->
  • -rails-i18n Google group - The project’s mailinglist. +rails-i18n Google group - The project’s mailing list.

  • @@ -1099,12 +1160,12 @@ http://www.gnu.org/software/src-highlite -->
    • -Sven Fuchs[http://www.workingwithrails.com/person/9963-sven-fuchs] (initial author) +Sven Fuchs (initial author)

    • -Karel Minarik[http://www.workingwithrails.com/person/7476-karel-mina-k] +Karel Minařík

    -- cgit v1.2.3