diff options
Diffstat (limited to 'railties/doc/guides/source')
34 files changed, 2835 insertions, 1954 deletions
diff --git a/railties/doc/guides/source/action_mailer_basics.txt b/railties/doc/guides/source/action_mailer_basics.txt new file mode 100644 index 0000000000..c6cd16f10b --- /dev/null +++ b/railties/doc/guides/source/action_mailer_basics.txt @@ -0,0 +1,133 @@ +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. + +== What is Action Mailer? +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. + +== Quick walkthrough to creating a Mailer +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: +[source, shell] +------------------------------------------------------- +./script/generate mailer UserMailer +exists app/models/ +create app/views/user_mailer +exists test/unit/ +create test/fixtures/user_mailer +create app/models/user_mailer.rb +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: +[source, ruby] +------------------------------------------------------- +class UserMailer < ActionMailer::Base + +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 + + 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" + 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 + +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 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: +[source, html] +------------------------------------------------------- +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <head> + <meta content='text/html; charset=iso-8859-1' http-equiv='Content-Type' /> + </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 %>. + </p> + <p>Thanks for joining and have a great day!</p> + </body> +</html> +------------------------------------------------------- + +=== 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. + +Edit #{RAILS_ROOT}/config/environment.rb +[source, ruby] +------------------------------------------------------- +# Code that already exists + +Rails::Initializer.run do |config| + + # Code that already exists + + config.active_record.observers = :user_observer + +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: +[source, ruby] +------------------------------------------------------- +# Code that already exists + +Rails::Initializer.run do |config| + + # Code that already exists + + config.load_paths += %W(#{RAILS_ROOT}/app/observers) + + config.active_record.observers = :user_observer + +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: +[source, ruby] +------------------------------------------------------- +class UserObserver < ActiveRecord::Observer + def after_create(user) + 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. + +== Mailer Testing
\ No newline at end of file diff --git a/railties/doc/guides/source/actioncontroller_basics/session.txt b/railties/doc/guides/source/actioncontroller_basics/session.txt index ae5f876777..24818fcb2d 100644 --- a/railties/doc/guides/source/actioncontroller_basics/session.txt +++ b/railties/doc/guides/source/actioncontroller_basics/session.txt @@ -21,42 +21,11 @@ If you need a different session storage mechanism, you can change it in the `con config.action_controller.session_store = :active_record_store ------------------------------------------ -=== Disabling the Session === - -Sometimes you don't need a session. In this case, you can turn it off to avoid the unnecessary overhead. To do this, use the `session` class method in your controller: - -[source, ruby] ------------------------------------------- -class ApplicationController < ActionController::Base - session :off -end ------------------------------------------- - -You can also turn the session on or off for a single controller: - -[source, ruby] ------------------------------------------- -# The session is turned off by default in ApplicationController, but we -# want to turn it on for log in/out. -class LoginsController < ActionController::Base - session :on -end ------------------------------------------- - -Or even for specified actions: - -[source, ruby] ------------------------------------------- -class ProductsController < ActionController::Base - session :on, :only => [:create, :update] -end ------------------------------------------- - === Accessing the Session === In your controller you can access the session through the `session` instance method. -NOTE: There are two `session` methods, the class and the instance method. The class method which is described above is used to turn the session on and off while the instance method described below is used to access session values. +NOTE: Sessions are lazily loaded. If you don't access sessions in your action's code, they will not be loaded. Hence you will never need to disable sessions, just not accessing them will do the job. Session values are stored using key/value pairs like a hash: diff --git a/railties/doc/guides/source/active_record_basics.txt b/railties/doc/guides/source/active_record_basics.txt index 892adb2d43..367a1bba5e 100644 --- a/railties/doc/guides/source/active_record_basics.txt +++ b/railties/doc/guides/source/active_record_basics.txt @@ -151,31 +151,4 @@ Rails has a reputation of being a zero-config framework which means that it aim == ActiveRecord handling the CRUD of your Rails application - Understanding the life-cycle of an ActiveRecord == Validations & Callbacks -- Validations - * create! - * validates_acceptance_of - * validates_associated - * validates_confirmation_of - * validates_each - * validates_exclusion_of - * validates_format_of - * validates_inclusion_of - * validates_length_of - * validates_numericality_of - * validates_presence_of - * validates_size_of - * validates_uniqueness_of - - Callback - * (-) save - * (-) valid - * (1) before_validation - * (2) before_validation_on_create - * (-) validate - * (-) validate_on_create - * (3) after_validation - * (4) after_validation_on_create - * (5) before_save - * (6) before_create - * (-) create - * (7) after_create - * (8) after_save +see the Validations & Callbacks guide for more info.
\ No newline at end of file diff --git a/railties/doc/guides/source/finders.txt b/railties/doc/guides/source/active_record_querying.txt index d2bd55ada7..c0aa5482d5 100644 --- a/railties/doc/guides/source/finders.txt +++ b/railties/doc/guides/source/active_record_querying.txt @@ -1,23 +1,19 @@ -Rails Finders -============= +Active Record Query Interface +============================= -This guide covers the +find+ method defined in +ActiveRecord::Base+, as well as other ways of finding particular instances of your models. By using this guide, you will be able to: +This guide covers different ways to retrieve data from the database using Active Record. By referring to this guide, you will be able to: * Find records using a variety of methods and conditions * Specify the order, retrieved attributes, grouping, and other properties of the found records -* Use eager loading to cut down on the number of database queries in your application -* Use dynamic finders +* Use eager loading to reduce the number of database queries needed for data retrieval +* Use dynamic finders methods * Create named scopes to add custom finding behavior to your models * Check for the existence of particular records -* Perform aggregate calculations on Active Record models +* Perform various calculations on Active Record models -If you're used to using raw SQL to find database records, you'll find that there are generally better ways to carry out the same operations in Rails. Active Record insulates you from the need to use SQL in most cases. +If you're used to using raw SQL to find database records then, generally, you will find that there are better ways to carry out the same operations in Rails. Active Record insulates you from the need to use SQL in most cases. -The SQL in your log may have some quoting, and that quoting depends on the backend (MySQL, for example, puts backticks around field and table names). Attempting to copy the raw SQL contained within this guide may not work in your database system. Please consult the database systems manual before attempting to execute any SQL. - -== The Sample Models - -This guide demonstrates finding using the following models: +Code examples throughout this guide will refer to one or more of the following models: [source,ruby] ------------------------------------------------------- @@ -26,35 +22,47 @@ class Client < ActiveRecord::Base has_one :mailing_address has_many :orders has_and_belongs_to_many :roles -end +end +------------------------------------------------------- +[source,ruby] +------------------------------------------------------- class Address < ActiveRecord::Base belongs_to :client end +------------------------------------------------------- +[source,ruby] +------------------------------------------------------- class MailingAddress < Address end +------------------------------------------------------- +[source,ruby] +------------------------------------------------------- class Order < ActiveRecord::Base belongs_to :client, :counter_cache => true end +------------------------------------------------------- +[source,ruby] +------------------------------------------------------- class Role < ActiveRecord::Base has_and_belongs_to_many :clients end ------------------------------------------------------- -== Database Agnostic - -Active Record will perform queries on the database for you and is compatible with most database systems (MySQL, PostgreSQL and SQLite to name a few). Regardless of which database system you're using, the Active Record method format will always be the same. +**** +Active Record will perform queries on the database for you and is compatible with most database systems (MySQL, PostgreSQL and SQLite to name a few). Regardless of which database system you're using, the Active Record method format will always be the same. +**** -== IDs, First, Last and All +== Retrieving objects -+ActiveRecord::Base+ has methods defined on it to make interacting with your database and the tables within it much, much easier. For finding records, the key method is +find+. This method allows you to pass arguments into it to perform certain queries on your database without the need of SQL. If you wanted to find the record with the id of 1, you could type +Client.find(1)+ which would execute this query on your database: +To retrieve objects from the database, Active Record provides a primary method called +find+. This method allows you to pass arguments into it to perform certain queries on your database without the need of SQL. If you wanted to find the record with the id of 1, you could type +Client.find(1)+ which would execute this query on your database: [source, sql] ------------------------------------------------------- -SELECT * FROM clients WHERE (clients.id = 1) +SELECT * FROM clients WHERE (clients.id = 1) ------------------------------------------------------- NOTE: Because this is a standard table created from a migration in Rails, the primary key is defaulted to 'id'. If you have specified a different primary key in your migrations, this is what Rails will find on when you call the find method, not the id column. @@ -63,26 +71,26 @@ If you wanted to find clients with id 1 or 2, you call +Client.find([1,2])+ or + [source, sql] ------------------------------------------------------- -SELECT * FROM clients WHERE (clients.id IN (1,2)) +SELECT * FROM clients WHERE (clients.id IN (1,2)) ------------------------------------------------------- ------------------------------------------------------- >> Client.find(1,2) -=> [#<Client id: 1, name: => "Ryan", locked: false, orders_count: 2, - created_at: "2008-09-28 15:38:50", updated_at: "2008-09-28 15:38:50">, - #<Client id: 2, name: => "Michael", locked: false, orders_count: 3, +=> [#<Client id: 1, name: => "Ryan", locked: false, orders_count: 2, + created_at: "2008-09-28 15:38:50", updated_at: "2008-09-28 15:38:50">, + #<Client id: 2, name: => "Michael", locked: false, orders_count: 3, created_at: "2008-09-28 13:12:40", updated_at: "2008-09-28 13:12:40">] ------------------------------------------------------- -Note that if you pass in a list of numbers that the result will be returned as an array, not as a single +Client+ object. +Note that if you pass in a list of numbers that the result will be returned as an array, not as a single Client object. -NOTE: If +find(id)+ or +find([id1, id2])+ fails to find any records, it will raise a +RecordNotFound+ exception. +NOTE: If +find(id)+ or +find([id1, id2])+ fails to find any records, it will raise a RecordNotFound exception. If you wanted to find the first Client object you would simply type +Client.first+ and that would find the first client in your clients table: ------------------------------------------------------- >> Client.first -=> #<Client id: 1, name: => "Ryan", locked: false, orders_count: 2, +=> #<Client id: 1, name: => "Ryan", locked: false, orders_count: 2, created_at: "2008-09-28 15:38:50", updated_at: "2008-09-28 15:38:50"> ------------------------------------------------------- @@ -93,13 +101,13 @@ If you were reading your log file (the default is log/development.log) you may s SELECT * FROM clients LIMIT 1 ------------------------------------------------------- -Indicating the query that Rails has performed on your database. +Indicating the query that Rails has performed on your database. To find the last Client object you would simply type +Client.last+ and that would find the last client created in your clients table: ------------------------------------------------------- >> Client.last -=> #<Client id: 2, name: => "Michael", locked: false, orders_count: 3, +=> #<Client id: 2, name: => "Michael", locked: false, orders_count: 3, created_at: "2008-09-28 13:12:40", updated_at: "2008-09-28 13:12:40"> ------------------------------------------------------- @@ -121,11 +129,11 @@ To find all the Client objects you would simply type +Client.all+ and that would ------------------------------------------------------- >> Client.all -=> [#<Client id: 1, name: => "Ryan", locked: false, orders_count: 2, - created_at: "2008-09-28 15:38:50", updated_at: "2008-09-28 15:38:50">, - #<Client id: 2, name: => "Michael", locked: false, orders_count: 3, +=> [#<Client id: 1, name: => "Ryan", locked: false, orders_count: 2, + created_at: "2008-09-28 15:38:50", updated_at: "2008-09-28 15:38:50">, + #<Client id: 2, name: => "Michael", locked: false, orders_count: 3, created_at: "2008-09-28 13:12:40", updated_at: "2008-09-28 13:12:40">] -------------------------------------------------------- +------------------------------------------------------- You may see in Rails code that there are calls to methods such as +Client.find(:all)+, +Client.find(:first)+ and +Client.find(:last)+. These methods are just alternatives to +Client.all+, +Client.first+ and +Client.last+ respectively. @@ -143,7 +151,7 @@ WARNING: Building your own conditions as pure strings can leave you vulnerable t === Array Conditions === -Now what if that number could vary, say as a parameter from somewhere, or perhaps from the user's level status somewhere? The find then becomes something like +Client.first(:conditions => ["orders_count = ?", params[:orders]])+. Active Record will go through the first element in the conditions value and any additional elements will replace the question marks (?) in the first element. If you want to specify two conditions, you can do it like +Client.first(:conditions => ["orders_count = ? AND locked = ?", params[:orders], false])+. In this example, the first question mark will be replaced with the value in +params[:orders]+ and the second will be replaced with +false+ and this will find the first record in the table that has '2' as its value for the +orders_count+ field and +false+ for its locked field. +Now what if that number could vary, say as a argument from somewhere, or perhaps from the user's level status somewhere? The find then becomes something like +Client.first(:conditions => ["orders_count = ?", params[:orders]])+. Active Record will go through the first element in the conditions value and any additional elements will replace the question marks (?) in the first element. If you want to specify two conditions, you can do it like +Client.first(:conditions => ["orders_count = ? AND locked = ?", params[:orders], false])+. In this example, the first question mark will be replaced with the value in +params[:orders]+ and the second will be replaced with the SQL representation of +false+, which depends on the adapter. The reason for doing code like: @@ -159,7 +167,7 @@ instead of: Client.first(:conditions => "orders_count = #{params[:orders]}") ------------------------------------------------------- -is because of parameter safety. Putting the variable directly into the conditions string will pass the variable to the database *as-is*. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your parameters directly inside the conditions string. +is because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database *as-is*. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string. TIP: For more information on the dangers of SQL injection, see the link:../security.html#_sql_injection[Ruby on Rails Security Guide]. @@ -175,28 +183,28 @@ This would generate the proper query which is great for small ranges but not so [source, sql] ------------------------------------------------------- -SELECT * FROM users WHERE (created_at IN +SELECT * FROM users WHERE (created_at IN ('2007-12-31','2008-01-01','2008-01-02','2008-01-03','2008-01-04','2008-01-05', '2008-01-06','2008-01-07','2008-01-08','2008-01-09','2008-01-10','2008-01-11', '2008-01-12','2008-01-13','2008-01-14','2008-01-15','2008-01-16','2008-01-17', '2008-01-18','2008-01-19','2008-01-20','2008-01-21','2008-01-22','2008-01-23',... ‘2008-12-15','2008-12-16','2008-12-17','2008-12-18','2008-12-19','2008-12-20', '2008-12-21','2008-12-22','2008-12-23','2008-12-24','2008-12-25','2008-12-26', - '2008-12-27','2008-12-28','2008-12-29','2008-12-30','2008-12-31')) + '2008-12-27','2008-12-28','2008-12-29','2008-12-30','2008-12-31')) ------------------------------------------------------- Things can get *really* messy if you pass in Time objects as it will attempt to compare your field to *every second* in that range: [source, ruby] ------------------------------------------------------- -Client.all(:conditions => ["created_at IN (?)", +Client.all(:conditions => ["created_at IN (?)", (params[:start_date].to_date.to_time)..(params[:end_date].to_date.to_time)]) ------------------------------------------------------- [source, sql] ------------------------------------------------------- -SELECT * FROM users WHERE (created_at IN - ('2007-12-01 00:00:00', '2007-12-01 00:00:01' ... +SELECT * FROM users WHERE (created_at IN + ('2007-12-01 00:00:00', '2007-12-01 00:00:01' ... '2007-12-01 23:59:59', '2007-12-02 00:00:00')) ------------------------------------------------------- @@ -212,7 +220,7 @@ In this example it would be better to use greater-than and less-than operators i [source, ruby] ------------------------------------------------------- -Client.all(:conditions => +Client.all(:conditions => ["created_at > ? AND created_at < ?", params[:start_date], params[:end_date]]) ------------------------------------------------------- @@ -220,7 +228,7 @@ You can also use the greater-than-or-equal-to and less-than-or-equal-to like thi [source, ruby] ------------------------------------------------------- -Client.all(:conditions => +Client.all(:conditions => ["created_at >= ? AND created_at <= ?", params[:start_date], params[:end_date]]) ------------------------------------------------------- @@ -232,7 +240,7 @@ Similar to the array style of params you can also specify keys in your condition [source, ruby] ------------------------------------------------------- -Client.all(:conditions => +Client.all(:conditions => ["created_at >= :start_date AND created_at <= :end_date", { :start_date => params[:start_date], :end_date => params[:end_date] }]) ------------------------------------------------------- @@ -240,7 +248,7 @@ This makes for clearer readability if you have a large number of variable condit === Hash Conditions -Rails also allows you to pass in a hash conditions too which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them: +Rails also allows you to pass in a hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them: [source, ruby] ------------------------------------------------------- @@ -258,27 +266,63 @@ The good thing about this is that we can pass in a range for our fields without [source, ruby] ------------------------------------------------------- -Client.all(:conditions => { :created_at => ((Time.now.midnight - 1.day)..Time.now.midnight}) +Client.all(:conditions => { :created_at => (Time.now.midnight - 1.day)..Time.now.midnight}) +------------------------------------------------------- + +This will find all clients created yesterday by using a BETWEEN sql statement: + +[source, sql] +------------------------------------------------------- +SELECT * FROM `clients` WHERE (`clients`.`created_at` BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00') ------------------------------------------------------- -This will find all clients created yesterday. This shows the shorter syntax for the examples in <<_array_conditions, Array Conditions>> +This demonstrates a shorter syntax for the examples in <<_array_conditions, Array Conditions>> You can also join in tables and specify their columns in the hash: [source, ruby] ------------------------------------------------------- -Client.all(:include => "orders", :conditions => { 'orders.created_at; => ((Time.now.midnight - 1.day)..Time.now.midnight}) +Client.all(:include => "orders", :conditions => { 'orders.created_at' => (Time.now.midnight - 1.day)..Time.now.midnight }) +------------------------------------------------------- + +An alternative and cleaner syntax to this is: + +[source, ruby] +------------------------------------------------------- +Client.all(:include => "orders", :conditions => { :orders => { :created_at => (Time.now.midnight - 1.day)..Time.now.midnight } }) ------------------------------------------------------- -This will find all clients who have orders that were created yesterday. +This will find all clients who have orders that were created yesterday, again using a BETWEEN expression. + +If you want to find records using the IN expression you can pass an array to the conditions hash: + +[source, ruby] +------------------------------------------------------- +Client.all(:include => "orders", :conditions => { :orders_count => [1,3,5] } +------------------------------------------------------- + +This code will generate SQL like this: + +[source, sql] +------------------------------------------------------- +SELECT * FROM `clients` WHERE (`clients`.`orders_count` IN (1,2,3)) +------------------------------------------------------- == Ordering -If you're getting a set of records and want to force an order, you can use +Client.all(:order => "created_at")+ which by default will sort the records by ascending order. If you'd like to order it in descending order, just tell it to do that using +Client.all(:order => "created_at desc")+ +If you're getting a set of records and want to order them in ascending order by the +created_at+ field in your table, you can use +Client.all(:order => "created_at")+. If you'd like to order it in descending order, just tell it to do that using +Client.all(:order => "created_at desc")+. The value for this option is passed in as sanitized SQL and allows you to sort via multiple fields: +Client.all(:order => "created_at desc, orders_count asc")+. == Selecting Certain Fields -To select certain fields, you can use the select option like this: +Client.first(:select => "viewable_by, locked")+. This select option does not use an array of fields, but rather requires you to type SQL-like code. The above code will execute +SELECT viewable_by, locked FROM clients LIMIT 0,1+ on your database. +To select certain fields, you can use the select option like this: +Client.first(:select => "viewable_by, locked")+. This select option does not use an array of fields, but rather requires you to type SQL-like code. The above code will execute +SELECT viewable_by, locked FROM clients LIMIT 1+ on your database. + +Be careful because this also means you're initializing a model object with only the fields that you've selected. If you attempt to access a field that is not in the initialized record you'll receive: + +------------------------------------------------------- +ActiveRecord::MissingAttributeError: missing attribute: <attribute> +------------------------------------------------------- + +Where <attribute> is the atrribute you asked for. The +id+ method will not raise the +ActiveRecord::MissingAttributeError+, so just be careful when working with associations because they need the +id+ method to function properly. You can also call SQL functions within the select option. For example, if you would like to only grab a single record per unique value in a certain field by using the +DISTINCT+ function you can do it like this: +Client.all(:select => "DISTINCT(name)")+. @@ -319,7 +363,7 @@ The group option for find is useful, for example, if you want to find a collecti Order.all(:group => "date(created_at)", :order => "created_at") ------------------------------------------------------- -And this will give you a single +Order+ object for each date where there are orders in the database. +And this will give you a single +Order+ object for each date where there are orders in the database. The SQL that would be executed would be something like this: @@ -328,16 +372,29 @@ The SQL that would be executed would be something like this: SELECT * FROM orders GROUP BY date(created_at) ------------------------------------------------------- +== Having + +The +:having+ option allows you to specify SQL and acts as a kind of a filter on the group option. +:having+ can only be specified when +:group+ is specified. + +An example of using it would be: + +[source, ruby] +------------------------------------------------------- +Order.all(:group => "date(created_at)", :having => ["created_at > ?", 1.month.ago]) +------------------------------------------------------- + +This will return single order objects for each day, but only for the last month. + == Read Only -Readonly is a find option that you can set in order to make that instance of the record read-only. Any attempt to alter or destroy the record will not succeed, raising an +Active Record::ReadOnlyRecord+ exception. To set this option, specify it like this: ++readonly+ is a +find+ option that you can set in order to make that instance of the record read-only. Any attempt to alter or destroy the record will not succeed, raising an ActiveRecord::ReadOnlyRecord exception. To set this option, specify it like this: [source, ruby] ------------------------------------------------------- Client.first(:readonly => true) ------------------------------------------------------- -If you assign this record to a variable +client+, calling the following code will raise an +ActiveRecord::ReadOnlyRecord+ exception: +If you assign this record to a variable client, calling the following code will raise an ActiveRecord::ReadOnlyRecord exception: [source, ruby] ------------------------------------------------------- @@ -358,31 +415,42 @@ Topic.transaction do end ------------------------------------------------------- +You can also pass SQL to this option to allow different types of locks. For example, MySQL has an expression called LOCK IN SHARE MODE where you can lock a record but still allow other queries to read it. To specify this expression just pass it in as the lock option: + +[source, ruby] +------------------------------------------------------- +Topic.transaction do + t = Topic.find(params[:id], :lock => "LOCK IN SHARE MODE") + t.increment!(:views) +end +------------------------------------------------------- + == Making It All Work Together -You can chain these options together in no particular order as Active Record will write the correct SQL for you. If you specify two instances of the same options inside the find statement Active Record will use the latter. +You can chain these options together in no particular order as Active Record will write the correct SQL for you. If you specify two instances of the same options inside the +find+ method Active Record will use the last one you specified. This is because the options passed to find are a hash and defining the same key twice in a hash will result in the last definition being used. == Eager Loading -Eager loading is loading associated records along with any number of records in as few queries as possible. For example, if you wanted to load all the addresses associated with all the clients in a single query you could use +Client.all(:include => :address)+. If you wanted to include both the address and mailing address for the client you would use +Client.find(:all, :include => [:address, :mailing_address]). Include will first find the client records and then load the associated address records. Running script/server in one window, and executing the code through script/console in another window, the output should look similar to this: +Eager loading is loading associated records along with any number of records in as few queries as possible. For example, if you wanted to load all the addresses associated with all the clients in a single query you could use +Client.all(:include => :address)+. If you wanted to include both the address and mailing address for the client you would use +Client.find(:all, :include => [:address, :mailing_address])+. Include will first find the client records and then load the associated address records. Running script/server in one window, and executing the code through script/console in another window, the output should look similar to this: [source, sql] ------------------------------------------------------- -Client Load (0.000383) SELECT * FROM clients -Address Load (0.119770) SELECT addresses.* FROM addresses - WHERE (addresses.client_id IN (13,14)) -MailingAddress Load (0.001985) SELECT mailing_addresses.* FROM +Client Load (0.000383) SELECT * FROM clients +Address Load (0.119770) SELECT addresses.* FROM addresses + WHERE (addresses.client_id IN (13,14)) +MailingAddress Load (0.001985) SELECT mailing_addresses.* FROM mailing_addresses WHERE (mailing_addresses.client_id IN (13,14)) ------------------------------------------------------- -The numbers +13+ and +14+ in the above SQL are the ids of the clients gathered from the +Client.all+ query. Rails will then run a query to gather all the addresses and mailing addresses that have a client_id of 13 or 14. Although this is done in 3 queries, this is more efficient than not eager loading because without eager loading it would run a query for every time you called +address+ or +mailing_address+ on one of the objects in the clients array, which may lead to performance issues if you're loading a large number of records at once. +The numbers +13+ and +14+ in the above SQL are the ids of the clients gathered from the +Client.all+ query. Rails will then run a query to gather all the addresses and mailing addresses that have a client_id of 13 or 14. Although this is done in 3 queries, this is more efficient than not eager loading because without eager loading it would run a query for every time you called +address+ or +mailing_address+ on one of the objects in the clients array, which may lead to performance issues if you're loading a large number of records at once and is often called the "N+1 query problem". The problem is that the more queries your server has to execute, the slower it will run. -If you wanted to get all the addresses for a client in the same query you would do +Client.all(:joins => :address)+ and you wanted to find the address and mailing address for that client you would do +Client.all(:joins => [:address, :mailing_address])+. This is more efficient because it does all the SQL in one query, as shown by this example: +If you wanted to get all the addresses for a client in the same query you would do +Client.all(:joins => :address)+. +If you wanted to find the address and mailing address for that client you would do +Client.all(:joins => [:address, :mailing_address])+. This is more efficient because it does all the SQL in one query, as shown by this example: [source, sql] ------------------------------------------------------- -+Client Load (0.000455) SELECT clients.* FROM clients INNER JOIN addresses - ON addresses.client_id = client.id INNER JOIN mailing_addresses ON ++Client Load (0.000455) SELECT clients.* FROM clients INNER JOIN addresses + ON addresses.client_id = client.id INNER JOIN mailing_addresses ON mailing_addresses.client_id = client.id ------------------------------------------------------- @@ -399,45 +467,45 @@ When using eager loading you can specify conditions for the columns of the table [source, ruby] ------------------------------------------------------- -Client.first(:include => "orders", :conditions => - ["orders.created_at >= ? AND orders.created_at <= ?", Time.now - 2.weeks, Time.now]) +Client.first(:include => "orders", :conditions => + ["orders.created_at >= ? AND orders.created_at <= ?", 2.weeks.ago, Time.now]) ------------------------------------------------------- == Dynamic finders -For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +name+ on your Client model for example, you get +find_by_name+ and +find_all_by_name+ for free from Active Record. If you have also have a +locked+ field on the client model, you also get +find_by_locked+ and +find_all_by_locked+. +For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +name+ on your Client model for example, you get +find_by_name+ and +find_all_by_name+ for free from Active Record. If you have also have a +locked+ field on the Client model, you also get +find_by_locked+ and +find_all_by_locked+. -You can do +find_last_by_*+ methods too which will find the last record matching your parameter. +You can do +find_last_by_*+ methods too which will find the last record matching your argument. -You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an +ActiveRecord::RecordNotFound+ error if they do not return any records, like +Client.find_by_name!('Ryan')+ +You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an ActiveRecord::RecordNotFound error if they do not return any records, like +Client.find_by_name!("Ryan")+ -If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields for example +Client.find_by_name_and_locked('Ryan', true)+. +If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields for example +Client.find_by_name_and_locked("Ryan", true)+. -There's another set of dynamic finders that let you find or create/initialize objects if they aren't find. These work in a similar fashion to the other finders and can be used like +find_or_create_by_name(params[:name])+. Using this will firstly perform a find and then create if the find returns nil. The SQL looks like this for +Client.find_or_create_by_name('Ryan')+: +There's another set of dynamic finders that let you find or create/initialize objects if they aren't found. These work in a similar fashion to the other finders and can be used like +find_or_create_by_name(params[:name])+. Using this will firstly perform a find and then create if the find returns nil. The SQL looks like this for +Client.find_or_create_by_name("Ryan")+: [source,sql] ------------------------------------------------------- SELECT * FROM clients WHERE (clients.name = 'Ryan') LIMIT 1 BEGIN -INSERT INTO clients (name, updated_at, created_at, orders_count, locked) - VALUES('Ryan', '2008-09-28 15:39:12', '2008-09-28 15:39:12', '0', '0') +INSERT INTO clients (name, updated_at, created_at, orders_count, locked) + VALUES('Ryan', '2008-09-28 15:39:12', '2008-09-28 15:39:12', 0, '0') COMMIT ------------------------------------------------------- -+find_or_create+'s sibling, +find_or_initialize+, will find an object and if it does not exist will act similar to calling +new+ with the parameters you passed in. For example: ++find_or_create+'s sibling, +find_or_initialize+, will find an object and if it does not exist will act similar to calling +new+ with the arguments you passed in. For example: [source, ruby] ------------------------------------------------------- client = Client.find_or_initialize_by_name('Ryan') ------------------------------------------------------- -will either assign an existing client object with the name 'Ryan' to the client local variable, or initialize new object similar to calling +Client.new(:name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it. +will either assign an existing client object with the name 'Ryan' to the client local variable, or initialize a new object similar to calling +Client.new(:name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it. == Finding By SQL -If you'd like to use your own SQL to find records a table you can use +find_by_sql+. The +find_by_sql+ method will return an array of objects even if it only returns a single record in it's call to the database. For example you could run this query: +If you'd like to use your own SQL to find records in a table you can use +find_by_sql+. The +find_by_sql+ method will return an array of objects even the underlying query returns just a single record. For example you could run this query: [source, ruby] ------------------------------------------------------- @@ -457,7 +525,7 @@ Client.connection.select_all("SELECT * FROM `clients` WHERE `id` = '1'") == Working with Associations -When you define a has_many association on a model you get the find method and dynamic finders also on that association. This is helpful for finding associated records within the scope of an existing record, for example finding all the orders for a client that have been sent and not received by doing something like +Client.find(params[:id]).orders.find_by_sent_and_received(true, false)+. Having this find method available on associations is extremely helpful when using nested controllers. +When you define a has_many association on a model you get the +find+ method and dynamic finders also on that association. This is helpful for finding associated records within the scope of an existing record, for example finding all the orders for a client that have been sent and not received by doing something like +Client.find(params[:id]).orders.find_by_sent_and_received(true, false)+. Having this find method available on associations is extremely helpful when using nested resources. == Named Scopes @@ -465,7 +533,7 @@ Named scopes are another way to add custom finding behavior to the models in the === Simple Named Scopes -Suppose want to find all clients who are male. You could use this code: +Suppose we want to find all clients who are male. You could use this code: [source, ruby] ------------------------------------------------------- @@ -485,7 +553,7 @@ class Client < ActiveRecord::Base end ------------------------------------------------------- -You can call this new named_scope with +Client.active.all+ and this will do the same query as if we just used +Client.all(:conditions => ["active = ?", true])+. Please be aware that the conditions syntax in named_scope and find is different and the two are not interchangeable. If you want to find the first client within this named scope you could do +Client.active.first+. +You can call this new named_scope with +Client.active.all+ and this will do the same query as if we just used +Client.all(:conditions => ["active = ?", true])+. If you want to find the first client within this named scope you could do +Client.active.first+. === Combining Named Scopes @@ -514,25 +582,25 @@ class Client < ActiveRecord::Base end ------------------------------------------------------- -This looks like a standard named scope that defines a method called recent which gathers all records created any time between now and 2 weeks ago. That's correct for the first time the model is loaded but for any time after that, +2.weeks.ago+ is set to that same value, so you will consistently get records from a certain date until your model is reloaded by something like your application restarting. The way to fix this is to put the code in a lambda block: +This looks like a standard named scope that defines a method called +recent+ which gathers all records created any time between now and 2 weeks ago. That's correct for the first time the model is loaded but for any time after that, +2.weeks.ago+ is set to that same value, so you will consistently get records from a certain date until your model is reloaded by something like your application restarting. The way to fix this is to put the code in a lambda block: [source, ruby] ------------------------------------------------------- class Client < ActiveRecord::Base - named_scope :recent, lambda { { :conditions => ["created_at > ?", 2.weeks.ago] } } + named_scope :recent, lambda { { :conditions => ["created_at > ?", 2.weeks.ago] } } end ------------------------------------------------------- -And now every time the recent named scope is called, the code in the lambda block will be parsed, so you'll get actually 2 weeks ago from the code execution, not 2 weeks ago from the time the model was loaded. +And now every time the +recent+ named scope is called, the code in the lambda block will be executed, so you'll get actually 2 weeks ago from the code execution, not 2 weeks ago from the time the model was loaded. === Named Scopes with Multiple Models -In a named scope you can use +:include+ and +:joins+ options just like in find. +In a named scope you can use +:include+ and +:joins+ options just like in +find+. [source, ruby] ------------------------------------------------------- class Client < ActiveRecord::Base - named_scope :active_within_2_weeks, :joins => :order, + named_scope :active_within_2_weeks, :joins => :order, lambda { { :conditions => ["orders.created_at > ?", 2.weeks.ago] } } end ------------------------------------------------------- @@ -541,16 +609,16 @@ This method, called as +Client.active_within_2_weeks.all+, will return all clien === Arguments to Named Scopes -If you want to pass a named scope a compulsory argument, just specify it as a block parameter like this: +If you want to pass to a named scope a required arugment, just specify it as a block argument like this: [source, ruby] ------------------------------------------------------- class Client < ActiveRecord::Base - named_scope :recent, lambda { |time| { :conditions => ["created_at > ?", time] } } + named_scope :recent, lambda { |time| { :conditions => ["created_at > ?", time] } } end ------------------------------------------------------- -This will work if you call +Client.recent(2.weeks.ago).all+ but not if you call +Client.recent+. If you want to add an optional argument for this, you have to use the splat operator as the block's parameter. +This will work if you call +Client.recent(2.weeks.ago).all+ but not if you call +Client.recent+. If you want to add an optional argument for this, you have to use prefix the arugment with an *. [source, ruby] ------------------------------------------------------- @@ -587,14 +655,14 @@ Just like named scopes, anonymous scopes can be stacked, either with other anony == Existence of Objects -If you simply want to check for the existence of the object there's a method called +exists?+. This method will query the database using the same query as find, but instead of returning an object or collection of objects it will return either true or false. +If you simply want to check for the existence of the object there's a method called +exists?+. This method will query the database using the same query as +find+, but instead of returning an object or collection of objects it will return either +true+ or false+. [source, ruby] ------------------------------------------------------- Client.exists?(1) ------------------------------------------------------- -The above code will check for the existence of a clients table record with the id of 1 and return true if it exists. +The +exists?+ method also takes multiple ids, but the catch is that it will return true if any one of those records exists. [source, ruby] ------------------------------------------------------- @@ -603,8 +671,6 @@ Client.exists?(1,2,3) Client.exists?([1,2,3]) ------------------------------------------------------- -The +exists?+ method also takes multiple ids, as shown by the above code, but the catch is that it will return true if any one of those records exists. - Further more, +exists+ takes a +conditions+ option much like find: [source, ruby] @@ -627,10 +693,10 @@ Which will execute: [source, sql] ------------------------------------------------------- -SELECT count(*) AS count_all FROM clients WHERE (first_name = 1) +SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan') ------------------------------------------------------- -You can also use +include+ or +joins+ for this to do something a little more complex: +You can also use +:include+ or +:joins+ for this to do something a little more complex: [source, ruby] ------------------------------------------------------- @@ -641,9 +707,9 @@ Which will execute: [source, sql] ------------------------------------------------------- -SELECT count(DISTINCT clients.id) AS count_all FROM clients - LEFT OUTER JOIN orders ON orders.client_id = client.id WHERE - (clients.first_name = 'name' AND orders.status = 'received') +SELECT count(DISTINCT clients.id) AS count_all FROM clients + LEFT OUTER JOIN orders ON orders.client_id = client.id WHERE + (clients.first_name = 'Ryan' AND orders.status = 'received') ------------------------------------------------------- This code specifies +clients.first_name+ just in case one of the join tables has a field also called +first_name+ and it uses +orders.status+ because that's the name of our join table. @@ -701,30 +767,8 @@ Client.sum("orders_count") For options, please see the parent section, <<_calculations, Calculations>> -== Credits - -Thanks to Ryan Bates for his awesome screencast on named scope #108. The information within the named scope section is intentionally similar to it, and without the cast may have not been possible. - -Thanks to Mike Gunderloy for his tips on creating this guide. - == Changelog http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/16[Lighthouse ticket] -* December 17 2008: Fixed up syntax errors. -* December 16 2008: Covered hash conditions that were introduced in Rails 2.2.2. -* December 1 2008: Added using an SQL function example to Selecting Certain Fields section as per http://rails.lighthouseapp.com/projects/16213/tickets/36-adding-an-example-for-using-distinct-to-ar-finders[this ticket] -* November 23 2008: Added documentation for +find_by_last+ and +find_by_bang!+ -* November 21 2008: Fixed all points specified in http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/16-activerecord-finders#ticket-16-13[this comment] and http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/16-activerecord-finders#ticket-16-14[this comment] -* November 18 2008: Fixed all points specified in http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/16-activerecord-finders#ticket-16-11[this comment] -* November 8, 2008: Editing pass by link:../authors.html#mgunderloy[Mike Gunderloy] . First release version. -* October 27, 2008: Added scoped section, added named params for conditions and added sub-section headers for conditions section by Ryan Bigg -* October 27, 2008: Fixed up all points specified in http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/16-activerecord-finders#ticket-16-6[this comment] with an exception of the final point by Ryan Bigg -* October 26, 2008: Editing pass by link:../authors.html#mgunderloy[Mike Gunderloy] . First release version. -* October 22, 2008: Calculations complete, first complete draft by Ryan Bigg -* October 21, 2008: Extended named scope section by Ryan Bigg -* October 9, 2008: Lock, count, cleanup by Ryan Bigg -* October 6, 2008: Eager loading by Ryan Bigg -* October 5, 2008: Covered conditions by Ryan Bigg -* October 1, 2008: Covered limit/offset, formatting changes by Ryan Bigg -* September 28, 2008: Covered first/last/all by Ryan Bigg +* December 29 2008: Initial version by Ryan Bigg
\ No newline at end of file diff --git a/railties/doc/guides/source/activerecord_validations_callbacks.txt b/railties/doc/guides/source/activerecord_validations_callbacks.txt index e0bb534d0b..8bfb69ea99 100644 --- a/railties/doc/guides/source/activerecord_validations_callbacks.txt +++ b/railties/doc/guides/source/activerecord_validations_callbacks.txt @@ -1,32 +1,34 @@ Active Record Validations and Callbacks ======================================= -This guide teaches you how to work with the lifecycle of your Active Record objects. More precisely, you will learn how to validate the state of your objects before they go into the database and also how to teach them to perform custom operations at certain points of their lifecycles. +This guide teaches you how to hook into the lifecycle of your Active Record objects. More precisely, you will learn how to validate the state of your objects before they go into the database as well as how to perform custom operations at certain points in the object lifecycle. After reading this guide and trying out the presented concepts, we hope that you'll be able to: -* Correctly use all the built-in Active Record validation helpers +* Use the built-in Active Record validation helpers * Create your own custom validation methods * Work with the error messages generated by the validation process -* Register callback methods that will execute custom operations during your objects lifecycle, for example before/after they are saved. -* Create special classes that encapsulate common behaviour for your callbacks -* Create Observers - classes with callback methods specific for each of your models, keeping the callback code outside your models' declarations. +* Create callback methods to respond to events in the object lifecycle. +* Create special classes that encapsulate common behavior for your callbacks +* Create Rails Observers -== Motivations to validate your Active Record objects +== Overview of ActiveRecord Validation -The main reason for validating your objects before they get into the database is to ensure that only valid data is recorded. It's important to be sure that an email address column only contains valid email addresses, or that the customer's name column will never be empty. Constraints like that keep your database organized and helps your application to work properly. +Before you dive into the detail of validations in Rails, you should understand a bit about how validations fit into the big picture. Why should you use validations? When do these validations take place? + +=== Why Use ActiveRecord Validations? -There are several ways to validate the data that goes to the database, like using database native constraints, implementing validations only at the client side or implementing them directly into your models. Each one has pros and cons: +The main reason for validating your objects before they get into the database is to ensure that only valid data is recorded. It's important to be sure that an email address column only contains valid email addresses, or that the customer's name column will never be empty. Constraints like that keep your database organized and helps your application to work properly. -* Using database constraints and/or stored procedures makes the validation mechanisms database-dependent and may turn your application into a hard to test and mantain beast. However, if your database is used by other applications, it may be a good idea to use some constraints also at the database level. -* Implementing validations only at the client side can be problematic, specially with web-based applications. Usually this kind of validation is done using javascript, which may be turned off in the user's browser, leading to invalid data getting inside your database. However, if combined with server side validation, client side validation may be useful, since the user can have a faster feedback from the application when trying to save invalid data. -* Using validation directly into your Active Record classes ensures that only valid data gets recorded, while still keeping the validation code in the right place, avoiding breaking the MVC pattern. Since the validation happens on the server side, the user cannot disable it, so it's also safer. It may be a hard and tedious work to implement some of the logic involved in your models' validations, but fear not: Active Record gives you the hability to easily create validations, using several built-in helpers while still allowing you to create your own validation methods. +There are several ways that you could validate the data that goes to the database, including native database constraints, client-side validations, and model-level validations. Each of these has pros and cons: -== How it works +* Using database constraints and/or stored procedures makes the validation mechanisms database-dependent and may turn your application into a hard to test and maintain beast. However, if your database is used by other applications, it may be a good idea to use some constraints also at the database level. Additionally, database-level validations can safely handle some things (such as uniqueness in heavily-used tables) that are problematic to implement from the application level. +* Implementing validations only at the client side can be difficult in web-based applications. Usually this kind of validation is done using javascript, which may be turned off in the user's browser, leading to invalid data getting inside your database. However, if combined with server side validation, client side validation may be useful, since the user can have a faster feedback from the application when trying to save invalid data. +* Using validation directly in your Active Record classes ensures that only valid data gets recorded, while still keeping the validation code in the right place, avoiding breaking the MVC pattern. Since the validation happens on the server side, the user cannot disable it, so it's also safer. It may be a hard and tedious work to implement some of the logic involved in your models' validations, but fear not: Active Record gives you the ability to easily create validations, providing built-in helpers for common validations while still allowing you to create your own validation methods. -=== When does validation happens? +=== When Does Validation Happen? -There are two kinds of Active Record objects: those that correspond to a row inside your database and those who do not. When you create a fresh object, using the +new+ method, that object does not belong to the database yet. Once you call +save+ upon that object it'll be recorded to it's table. Active Record uses the +new_record?+ instance method to discover if an object is already in the database or not. Consider the following simple and very creative Active Record class: +There are two kinds of Active Record objects: those that correspond to a row inside your database and those that do not. When you create a fresh object, using the +new+ method, that object does not belong to the database yet. Once you call +save+ upon that object it will be saved into the appropriate database table. Active Record uses the +new_record?+ instance method to determine whether an object is already in the database or not. Consider the following simple Active Record class: [source, ruby] ------------------------------------------------------------------ @@ -34,7 +36,7 @@ class Person < ActiveRecord::Base end ------------------------------------------------------------------ -We can see how it works by looking at the following script/console output: +We can see how it works by looking at some script/console output: ------------------------------------------------------------------ >> p = Person.new(:name => "John Doe", :birthdate => Date.parse("09/03/1979")) @@ -47,25 +49,25 @@ We can see how it works by looking at the following script/console output: => false ------------------------------------------------------------------ -Saving new records means sending an SQL insert operation to the database, while saving existing records (by calling either +save+ or +update_attributes+) will result in a SQL update operation. Active Record will use these facts to perform validations upon your objects, avoiding then to be recorded to the database if their inner state is invalid in some way. You can specify validations that will be beformed every time a object is saved, just when you're creating a new record or when you're updating an existing one. +Saving new records means sending an SQL +INSERT+ operation to the database, while saving existing records (by calling either +save+ or +update_attributes+) will result in a SQL +UPDATE+ operation. Active Record will use these facts to perform validations upon your objects, keeping them out of the database if their inner state is invalid in some way. You can specify validations that will be beformed every time a object is saved, just when you're creating a new record or when you're updating an existing one. -CAUTION: There are four methods that when called will trigger validation: +save+, +save!+, +update_attributes+ and +update_attributes!+. There is one method left, which is +update_attribute+. This method will update the value of an attribute without triggering any validation, so be careful when using +update_attribute+, since it can let you save your objects in an invalid state. +CAUTION: There are four methods that when called will trigger validation: +save+, +save!+, +update_attributes+ and +update_attributes!+. There is one update method for Active Record objects left, which is +update_attribute+. This method will update the value of an attribute _without_ triggering any validation. Be careful when using +update_attribute+, because it can let you save your objects in an invalid state. -=== The meaning of 'valid' +=== The Meaning of +valid+ -For verifying if an object is valid, Active Record uses the +valid?+ method, which basically looks inside the object to see if it has any validation errors. These errors live in a collection that can be accessed through the +errors+ instance method. The proccess is really simple: If the +errors+ method returns an empty collection, the object is valid and can be saved. Each time a validation fails, an error message is added to the +errors+ collection. +To verify whether an object is valid, Active Record uses the +valid?+ method, which basically looks inside the object to see if it has any validation errors. These errors live in a collection that can be accessed through the +errors+ instance method. The process is really simple: If the +errors+ method returns an empty collection, the object is valid and can be saved. Each time a validation fails, an error message is added to the +errors+ collection. -== The declarative validation helpers +== The Declarative Validation Helpers -Active Record offers many pre-defined validation helpers that you can use directly inside your class definitions. These helpers create validations rules that are commonly used in most of the applications that you'll write, so you don't need to recreate it everytime, avoiding code duplication, keeping everything organized and boosting your productivity. Everytime a validation fails, an error message is added to the object's +errors+ collection, this message being associated with the field being validated. +Active Record offers many pre-defined validation helpers that you can use directly inside your class definitions. These helpers create validation rules that are commonly used. Every time a validation fails, an error message is added to the object's +errors+ collection, and this message is associated with the field being validated. -Each helper accepts an arbitrary number of attributes, received as symbols, so with a single line of code you can add the same kind of validation to several attributes. +Each helper accepts an arbitrary number of attributes identified by symbols, so with a single line of code you can add the same kind of validation to several attributes. -All these helpers accept the +:on+ and +:message+ options, which define when the validation should be applied and what message should be added to the +errors+ collection when it fails, respectively. The +:on+ option takes one the values +:save+ (it's the default), +:create+ or +:update+. There is a default error message for each one of the validation helpers. These messages are used when the +:message+ option isn't used. Let's take a look at each one of the available helpers, listed in alphabetic order. +All these helpers accept the +:on+ and +:message+ options, which define when the validation should be applied and what message should be added to the +errors+ collection when it fails, respectively. The +:on+ option takes one of the values +:save+ (the default), +:create+ or +:update+. There is a default error message for each one of the validation helpers. These messages are used when the +:message+ option isn't used. Let's take a look at each one of the available helpers. === The +validates_acceptance_of+ helper -Validates that a checkbox has been checked for agreement purposes. It's normally used when the user needs to agree with your application's terms of service, confirm reading some clauses or any similar concept. This validation is very specific to web applications and actually this 'acceptance' does not need to be recorded anywhere in your database (if you don't have a field for it, the helper will just create a virtual attribute). +Validates that a checkbox on the user interface was checked when a form was submitted. This is normally used when the user needs to agree to your application's terms of service, confirm reading some text, or any similar concept. This validation is very specific to web applications and actually this 'acceptance' does not need to be recorded anywhere in your database (if you don't have a field for it, the helper will just create a virtual attribute). [source, ruby] ------------------------------------------------------------------ @@ -76,7 +78,7 @@ end The default error message for +validates_acceptance_of+ is "_must be accepted_" -+validates_acceptance_of+ can receive an +:accept+ option, which determines the value that will be considered acceptance. It defaults to "1", but you can change it. ++validates_acceptance_of+ can receive an +:accept+ option, which determines the value that will be considered acceptance. It defaults to "1", but you can change this. [source, ruby] ------------------------------------------------------------------ @@ -85,7 +87,6 @@ class Person < ActiveRecord::Base end ------------------------------------------------------------------ - === The +validates_associated+ helper You should use this helper when your model has associations with other models and they also need to be validated. When you try to save your object, +valid?+ will be called upon each one of the associated objects. @@ -100,13 +101,13 @@ end This validation will work with all the association types. -CAUTION: Pay attention not to use +validates_associated+ on both ends of your associations, because this will lead to several recursive calls and blow up the method calls' stack. +CAUTION: Don't use +validates_associated+ on both ends of your associations, because this will lead to several recursive calls and blow up the method calls' stack. -The default error message for +validates_associated+ is "_is invalid_". Note that the errors for each failed validation in the associated objects will be set there and not in this model. +The default error message for +validates_associated+ is "_is invalid_". Note that each associated object will contain its own +errors+ collection; errors do not bubble up to the calling model. === The +validates_confirmation_of+ helper -You should use this helper when you have two text fields that should receive exactly the same content, like when you want to confirm an email address or password. This validation creates a virtual attribute, using the name of the field that has to be confirmed with '_confirmation' appended. +You should use this helper when you have two text fields that should receive exactly the same content. For example, you may want to confirm an email address or a password. This validation creates a virtual attribute, using the name of the field that has to be confirmed with '_confirmation' appended. [source, ruby] ------------------------------------------------------------------ @@ -116,6 +117,7 @@ end ------------------------------------------------------------------ In your view template you could use something like +[source, html] ------------------------------------------------------------------ <%= text_field :person, :email %> <%= text_field :person, :email_confirmation %> @@ -133,21 +135,6 @@ end The default error message for +validates_confirmation_of+ is "_doesn't match confirmation_" -=== The +validates_each+ helper - -This helper validates attributes against a block. It doesn't have a predefined validation function. You should create one using a block, and every attribute passed to +validates_each+ will be tested against it. In the following example, we don't want names and surnames to begin with lower case. - -[source, ruby] ------------------------------------------------------------------- -class Person < ActiveRecord::Base - validates_each :name, :surname do |model, attr, value| - model.errors.add(attr, 'Must start with upper case') if value =~ /^[a-z]/ - end -end ------------------------------------------------------------------- - -The block receives the model, the attribute's name and the attribute's value. If your validation fails, you can add an error message to the model, therefore making it invalid. - === The +validates_exclusion_of+ helper This helper validates that the attributes' values are not included in a given set. In fact, this set can be any enumerable object. @@ -160,13 +147,13 @@ class MovieFile < ActiveRecord::Base end ------------------------------------------------------------------ -The +validates_exclusion_of+ helper has an option +:in+ that receives the set of values that will not be accepted for the validated attributes. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. In the previous example we used the +:message+ option to show how we can personalize it with the current attribute's value, through the +%s+ format mask. +The +validates_exclusion_of+ helper has an option +:in+ that receives the set of values that will not be accepted for the validated attributes. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. This example uses the +:message+ option to show how you can personalize it with the current attribute's value, through the +%s+ format mask. The default error message for +validates_exclusion_of+ is "_is not included in the list_". === The +validates_format_of+ helper -This helper validates the attributes's values by testing if they match a given pattern. This pattern must be specified using a Ruby regular expression, which must be passed through the +:with+ option. +This helper validates the attributes' values by testing whether they match a given pattern. This pattern must be specified using a Ruby regular expression, which is specified using the +:with+ option. [source, ruby] ------------------------------------------------------------------ @@ -190,13 +177,13 @@ class Coffee < ActiveRecord::Base end ------------------------------------------------------------------ -The +validates_inclusion_of+ helper has an option +:in+ that receives the set of values that will be accepted. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. In the previous example we used the +:message+ option to show how we can personalize it with the current attribute's value, through the +%s+ format mask. +The +validates_inclusion_of+ helper has an option +:in+ that receives the set of values that will be accepted. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. The previous example uses the +:message+ option to show how you can personalize it with the current attribute's value, through the +%s+ format mask. The default error message for +validates_inclusion_of+ is "_is not included in the list_". === The +validates_length_of+ helper -This helper validates the length of your attribute's value. It can receive a variety of different options, so you can specify length contraints in different ways. +This helper validates the length of your attribute's value. It includes a variety of different options, so you can specify length constraints in different ways: [source, ruby] ------------------------------------------------------------------ @@ -215,7 +202,7 @@ The possible length constraint options are: * +:in+ (or +:within+) - The attribute length must be included in a given interval. The value for this option must be a Ruby range. * +:is+ - The attribute length must be equal to a given value. -The default error messages depend on the type of length validation being performed. You can personalize these messages, using the +:wrong_length+, +:too_long+ and +:too_short+ options and the +%d+ format mask as a placeholder for the number corresponding to the length contraint being used. You can still use the +:message+ option to specify an error message. +The default error messages depend on the type of length validation being performed. You can personalize these messages, using the +:wrong_length+, +:too_long+ and +:too_short+ options and the +%d+ format mask as a placeholder for the number corresponding to the length constraint being used. You can still use the +:message+ option to specify an error message. [source, ruby] ------------------------------------------------------------------ @@ -224,27 +211,38 @@ class Person < ActiveRecord::Base end ------------------------------------------------------------------ -This helper has an alias called +validates_size_of+, it's the same helper with a different name. You can use it if you'd like to. +The +validates_size_of+ helper is an alias for +validates_length_of+. === The +validates_numericality_of+ helper This helper validates that your attributes have only numeric values. By default, it will match an optional sign followed by a integral or floating point number. Using the +:integer_only+ option set to true, you can specify that only integral numbers are allowed. -If you use +:integer_only+ set to +true+, then it will use the +$$/\A[+\-]?\d+\Z/$$+ regular expression to validate the attribute's value. Otherwise, it will try to convert the value using +Kernel.Float+. +If you set +:integer_only+ to +true+, then it will use the +$$/\A[+\-]?\d+\Z/+ regular expression to validate the attribute's value. Otherwise, it will try to convert the value to a number using +Kernel.Float+. [source, ruby] ------------------------------------------------------------------ class Player < ActiveRecord::Base validates_numericality_of :points - validates_numericality_of :games_played, :integer_only => true + validates_numericality_of :games_played, :only_integer => true end ------------------------------------------------------------------ +Besides +:only_integer+, the +validates_numericality_of+ helper also accepts the following options to add constraints to acceptable values: + +* +:greater_than+ - Specifies the value must be greater than the supplied value. The default error message for this option is "_must be greater than (value)_" +* +:greater_than_or_equal_to+ - Specifies the value must be greater than or equal the supplied value. The default error message for this option is "_must be greater than or equal to (value)_" +* +:equal_to+ - Specifies the value must be equal to the supplied value. The default error message for this option is "_must be equal to (value)_" +* +:less_than+ - Specifies the value must be less than the supplied value. The default error message for this option is "_must e less than (value)_" +* +:less_than_or_equal_to+ - Specifies the value must be less than or equal the supplied value. The default error message for this option is "_must be less or equal to (value)_" +* +:odd+ - Specifies the value must be an odd number if set to true. The default error message for this option is "_must be odd_" +* +:even+ - Specifies the value must be an even number if set to true. The default error message for this option is "_must be even_" + + The default error message for +validates_numericality_of+ is "_is not a number_". === The +validates_presence_of+ helper -This helper validates that the attributes are not empty. It uses the +blank?+ method to check if the value is either +nil+ or an empty string (if the string has only spaces, it will still be considered empty). +This helper validates that the specified attributes are not empty. It uses the +blank?+ method to check if the value is either +nil+ or an empty string (if the string has only spaces, it will still be considered empty). [source, ruby] ------------------------------------------------------------------ @@ -253,7 +251,7 @@ class Person < ActiveRecord::Base end ------------------------------------------------------------------ -NOTE: If you want to be sure that an association is present, you'll need to test if the foreign key used to map the association is present, and not the associated object itself. +NOTE: If you want to be sure that an association is present, you'll need to test whether the foreign key used to map the association is present, and not the associated object itself. [source, ruby] ------------------------------------------------------------------ @@ -263,13 +261,13 @@ class LineItem < ActiveRecord::Base end ------------------------------------------------------------------ -NOTE: If you want to validate the presence of a boolean field (where the real values are true and false), you will want to use validates_inclusion_of :field_name, :in => [true, false] This is due to the way Object#blank? handles boolean values. false.blank? # => true +NOTE: If you want to validate the presence of a boolean field (where the real values are true and false), you should use validates_inclusion_of :field_name, :in => [true, false] This is due to the way Object#blank? handles boolean values. false.blank? # => true The default error message for +validates_presence_of+ is "_can't be empty_". === The +validates_uniqueness_of+ helper -This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint directly into your database, so it may happen that two different database connections create two records with the same value for a column that you wish were unique. To avoid that, you must create an unique index in your database. +This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint directly into your database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create an unique index in your database. [source, ruby] ------------------------------------------------------------------ @@ -280,7 +278,7 @@ end The validation happens by performing a SQL query into the model's table, searching for a record where the attribute that must be validated is equal to the value in the object being validated. -There is a +:scope+ option that you can use to specify other attributes that must be used to define uniqueness: +There is a +:scope+ option that you can use to specify other attributes that are used to limit the uniqueness check: [source, ruby] ------------------------------------------------------------------ @@ -290,7 +288,7 @@ class Holiday < ActiveRecord::Base end ------------------------------------------------------------------ -There is also a +:case_sensitive+ option that you can use to define if the uniqueness contraint will be case sensitive or not. This option defaults to true. +There is also a +:case_sensitive+ option that you can use to define whether the uniqueness constraint will be case sensitive or not. This option defaults to true. [source, ruby] ------------------------------------------------------------------ @@ -301,13 +299,28 @@ end The default error message for +validates_uniqueness_of+ is "_has already been taken_". -== Common validation options +=== The +validates_each+ helper + +This helper validates attributes against a block. It doesn't have a predefined validation function. You should create one using a block, and every attribute passed to +validates_each+ will be tested against it. In the following example, we don't want names and surnames to begin with lower case. + +[source, ruby] +------------------------------------------------------------------ +class Person < ActiveRecord::Base + validates_each :name, :surname do |model, attr, value| + model.errors.add(attr, 'Must start with upper case') if value =~ /^[a-z]/ + end +end +------------------------------------------------------------------ + +The block receives the model, the attribute's name and the attribute's value. You can do anything you like to check for valid data within the block. If your validation fails, you can add an error message to the model, therefore making it invalid. + +== Common Validation Options -There are some common options that all the validation helpers can use. Here they are, except for the +:if+ and +:unless+ options, which we'll cover right at the next topic. +There are some common options that all the validation helpers can use. Here they are, except for the +:if+ and +:unless+ options, which are discussed later in the conditional validation topic. === The +:allow_nil+ option -You may use the +:allow_nil+ option everytime you want to trigger a validation only if the value being validated is not +nil+. You may be asking yourself if it makes any sense to use +:allow_nil+ and +validates_presence_of+ together. Well, it does. Remember, validation will be skipped only for +nil+ attributes, but empty strings are not considered +nil+. +The +:allow_nil+ option skips the validation when the value being validated is +nil+. You may be asking yourself if it makes any sense to use +:allow_nil+ and +validates_presence_of+ together. Well, it does. Remember, the validation will be skipped only for +nil+ attributes, but empty strings are not considered +nil+. [source, ruby] ------------------------------------------------------------------ @@ -317,13 +330,27 @@ class Coffee < ActiveRecord::Base end ------------------------------------------------------------------ +=== The +:allow_blank+ option + +The +:allow_blank: option is similar to the +:allow_nil+ option. This option will let validation pass if the attribute's value is +nil+ or an empty string, i.e., any value that returns +true+ for +blank?+. + +[source, ruby] +------------------------------------------------------------------ +class Topic < ActiveRecord::Base + validates_length_of :title, :is => 5, :allow_blank => true +end + +Topic.create("title" => "").valid? # => true +Topic.create("title" => nil).valid? # => true +------------------------------------------------------------------ + === The +:message+ option -As stated before, the +:message+ option lets you specify the message that will be added to the +errors+ collection when validation fails. When this option is not used, Active Record will use the respective default error message for each validation helper. +As you've already seen, the +:message+ option lets you specify the message that will be added to the +errors+ collection when validation fails. When this option is not used, Active Record will use the respective default error message for each validation helper, together with the attribute name. === The +:on+ option -As stated before, the +:on+ option lets you specify when the validation should happen. The default behaviour for all the built-in validation helpers is to be ran on save (both when you're creating a new record and when you're updating it). If you want to change it, you can use +:on =$$>$$ :create+ to run the validation only when a new record is created or +:on =$$>$$ :update+ to run the validation only when a record is updated. +The +:on+ option lets you specify when the validation should happen. The default behavior for all the built-in validation helpers is to be ran on save (both when you're creating a new record and when you're updating it). If you want to change it, you can use +:on =$$>$$ :create+ to run the validation only when a new record is created or +:on =$$>$$ :update+ to run the validation only when a record is updated. [source, ruby] ------------------------------------------------------------------ @@ -334,7 +361,7 @@ class Person < ActiveRecord::Base # => it will be possible to create the record with a 'non-numerical age' validates_numericality_of :age, :on => :update - # => the default + # => the default (validates on both create and update) validates_presence_of :name, :on => :save end ------------------------------------------------------------------ @@ -345,7 +372,7 @@ Sometimes it will make sense to validate an object just when a given predicate i === Using a symbol with the +:if+ and +:unless+ options -You can associated the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option. +You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option. [source, ruby] ------------------------------------------------------------------ @@ -383,19 +410,7 @@ end == Writing your own validation methods -When the built-in validation helpers are not enough for your needs, you can write your own validation methods, by implementing one or more of the +validate+, +validate_on_create+ or +validate_on_update+ methods. As the names of the methods states, the right method to implement depends on when you want the validations to be ran. The meaning of valid is still the same: to make an object invalid you just need to add a message to it's +errors+ collection. - -[source, ruby] ------------------------------------------------------------------- -class Invoice < ActiveRecord::Base - def validate_on_create - errors.add(:expiration_date, "can't be in the past") if - !expiration_date.blank? and expiration_date < Date.today - end -end ------------------------------------------------------------------- - -If your validation rules are too complicated and you want to break them in small methods, you can implement all of them and call one of +validate+, +validate_on_create+ or +validate_on_update+ methods, passing it the symbols for the methods' names. +When the built-in validation helpers are not enough for your needs, you can write your own validation methods. You can do that by implementing methods that verify the state of your models and add messages to their +errors+ collection when they are invalid. You must then register those methods by using one or more of the +validate+, +validate_on_create+ or +validate_on_update+ class methods, passing in the symbols for the validation methods' names. You can pass more than one symbol for each class method and the respective validations will be ran in the same order as they were registered. [source, ruby] ------------------------------------------------------------------ @@ -415,7 +430,34 @@ class Invoice < ActiveRecord::Base end ------------------------------------------------------------------ -== Using the +errors+ collection +You can even create your own validation helpers and reuse them in several different models. Here is an example where we create a custom validation helper to validate the format of fields that represent email addresses: + +[source, ruby] +------------------------------------------------------------------ +module ActiveRecord + module Validations + module ClassMethods + def validates_email_format_of(value) + validates_format_of value, + :with => /\A[\w\._%-]+@[\w\.-]+\.[a-zA-Z]{2,4}\z/, + :if => Proc.new { |u| !u.email.blank? }, + :message => "Invalid format for email address" + end + end + end +end +------------------------------------------------------------------ + +The recipe is simple: just create a new validation method inside the +ActiveRecord::Validations::ClassMethods+ module. You can put this code in a file inside your application's *lib* folder, and then requiring it from your *environment.rb* or any other file inside *config/initializers*. You can use this helper like this: + +[source, ruby] +------------------------------------------------------------------ +class Person < ActiveRecord::Base + validates_email_format_of :email_address +end +------------------------------------------------------------------ + +== Manipulating the +errors+ collection You can do more than just call +valid?+ upon your objects based on the existance of the +errors+ collection. Here is a list of the other available methods that you can use to manipulate errors or ask for an object's state. @@ -498,35 +540,100 @@ p.errors.on(:name) # => ["can't be blank", "is too short (minimum is 3 characters)"] ------------------------------------------------------------------ -== Callbacks +== Using the +errors+ collection in your view templates -Callbacks are methods that get called at certain moments of an object's lifecycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted or loaded from the database. +Rails provides built-in helpers to display the error messages of your models in your view templates. When creating a form with the form_for helper, you can use the error_messages method on the form builder to render all failed validation messages for the current model instance. -=== Callbacks registration +[source, ruby] +------------------------------------------------------------------ +class Product < ActiveRecord::Base + validates_presence_of :description, :value + validates_numericality_of :value, :allow_nil => true +end +------------------------------------------------------------------ + +------------------------------------------------------------------ +<% form_for(@product) do |f| %> + <%= f.error_messages %> + <p> + <%= f.label :description %><br /> + <%= f.text_field :description %> + </p> + <p> + <%= f.label :value %><br /> + <%= f.text_field :value %> + </p> + <p> + <%= f.submit "Create" %> + </p> +<% end %> +------------------------------------------------------------------ + +image::images/error_messages.png[Error messages] + +You can also use the +error_messages_for+ helper to display the error messages of a model assigned to a view template. It's very similar to the previous example and will achieve exactly the same result. + +------------------------------------------------------------------ +<%= error_messages_for :product %> +------------------------------------------------------------------ + +The displayed text for each error message will always be formed by the capitalized name of the attribute that holds the error, followed by the error message itself. + +Both the +form.error_messages+ and the +error_messages_for+ helpers accept options that let you customize the +div+ element that holds the messages, changing the header text, the message below the header text and the tag used for the element that defines the header. + +------------------------------------------------------------------ +<%= f.error_messages :header_message => "Invalid product!", + :message => "You'll need to fix the following fields:", + :header_tag => :h3 %> +------------------------------------------------------------------ + +Which results in the following content + +image::images/customized_error_messages.png[Customized error messages] + +If you pass +nil+ to any of these options, it will get rid of the respective section of the +div+. + +It's also possible to change the CSS classes used by the +error_messages+ helper. These classes are automatically defined at the *scaffold.css* file, generated by the scaffold script. If you're not using scaffolding, you can still define those CSS classes at your CSS files. Here is a list of the default CSS classes. -In order to use the available callbacks, you need to registrate them. There are two ways of doing that. +* +.fieldWithErrors+ - Style for the form fields with errors. +* +#errorExplanation+ - Style for the +div+ element with the error messages. +* +#errorExplanation h2+ - Style for the header of the +div+ element. +* +#errorExplanation p+ - Style for the paragraph that holds the message that appears right below the header of the +div+ element. +* +#errorExplanation ul li+ - Style for the list of error messages. -=== Registering callbacks by overriding the callback methods +=== Changing the way form fields with errors are displayed -You can specify the callback method directly, by overriding it. Let's see how it works using the +before_validation+ callback, which will surprisingly run right before any validation is done. +By default, form fields with errors are displayed enclosed by a +div+ element with the +fieldWithErrors+ CSS class. However, we can write some Ruby code to override the way Rails treats those fields by default. Here is a simple example where we change the Rails behaviour to always display the error messages in front of each of the form fields with errors. The error messages will be enclosed by a +span+ element with a +validation-error+ CSS class. There will be no +div+ element enclosing the +input+ element, so we get rid of that red border around the text field. You can use the +validation-error+ CSS class to style it anyway you want. [source, ruby] ------------------------------------------------------------------ -class User < ActiveRecord::Base - validates_presence_of :login, :email - - protected - def before_validation - if self.login.nil? - self.login = email unless email.blank? - end +ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| + if instance.error_message.kind_of?(Array) + %(#{html_tag}<span class='validation-error'> + #{instance.error_message.join(',')}</span>) + else + %(#{html_tag}<span class='validation-error'> + #{instance.error_message}</span>) end end ------------------------------------------------------------------ -=== Registering callbacks by using macro-style class methods +This will result in something like the following content: + +image::images/validation_error_messages.png[Validation error messages] + +The way form fields with errors are treated is defined by the +ActionView::Base.field_error_proc+ Ruby Proc. This Proc receives two parameters: -The other way you can register a callback method is by implementing it as an ordinary method, and then using a macro-style class method to register it as a callback. The last example could be written like that: +* A string with the HTML tag +* An object of the +ActionView::Helpers::InstanceTag+ class. + +== Callbacks + +Callbacks are methods that get called at certain moments of an object's lifecycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted or loaded from the database. + +=== Callbacks registration + +In order to use the available callbacks, you need to registrate them. You can do that by implementing them as an ordinary methods, and then using a macro-style class method to register then as callbacks. [source, ruby] ------------------------------------------------------------------ @@ -555,12 +662,57 @@ class User < ActiveRecord::Base end ------------------------------------------------------------------ -In Rails, the preferred way of registering callbacks is by using macro-style class methods. The main advantages of using macro-style class methods are: +CAUTION: Remember to always declare the callback methods as being protected or private. These methods should never be public, otherwise it will be possible to call them from code outside the model, violating object encapsulation and exposing implementation details. -* You can add more than one method for each type of callback. Those methods will be queued for execution at the same order they were registered. -* Readability, since your callback declarations will live at the beggining of your models' files. +== Conditional callbacks -CAUTION: Remember to always declare the callback methods as being protected or private. These methods should never be public, otherwise it will be possible to call them from code outside the model, violating object encapsulation and exposing implementation details. +Like in validations, we can also make our callbacks conditional, calling then only when a given predicate is satisfied. You can do that by using the +:if+ and +:unless+ options, which can take a symbol, a string or a Ruby Proc. You may use the +:if+ option when you want to specify when the callback *should* get called. If you want to specify when the callback *should not* be called, then you may use the +:unless+ option. + +=== Using a symbol with the +:if+ and +:unless+ options + +You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns +false+ the callback won't be executed. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check the if the callback should be executed. + +[source, ruby] +------------------------------------------------------------------ +class Order < ActiveRecord::Base + before_save :normalize_card_number, :if => :paid_with_card? +end +------------------------------------------------------------------ + +=== Using a string with the +:if+ and +:unless+ options + +You can also use a string that will be evaluated using +:eval+ and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition. + +[source, ruby] +------------------------------------------------------------------ +class Order < ActiveRecord::Base + before_save :normalize_card_number, :if => "paid_with_card?" +end +------------------------------------------------------------------ + +=== Using a Proc object with the +:if+ and :+unless+ options + +Finally, it's possible to associate +:if+ and +:unless+ with a Ruby Proc object. This option is best suited when writing short validation methods, usually one-liners. + +[source, ruby] +------------------------------------------------------------------ +class Order < ActiveRecord::Base + before_save :normalize_card_number, + :if => Proc.new { |order| order.paid_with_card? } +end +------------------------------------------------------------------ + +=== Multiple Conditions for Callbacks + +When writing conditional callbacks, it's possible to mix both +:if+ and +:unless+ in the same callback declaration. + +[source, ruby] +------------------------------------------------------------------ +class Comment < ActiveRecord::Base + after_create :send_email_to_author, :if => :author_wants_emails?, + :unless => Proc.new { |comment| comment.post.ignore_comments? } +end +------------------------------------------------------------------ == Available callbacks @@ -608,7 +760,7 @@ The +after_initialize+ and +after_find+ callbacks are a bit different from the o == Halting Execution -As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks and the database operation to be executed. However, if at any moment one of the +before_create+, +before_save+, +before_update+ or +before_destroy+ callback methods returns a boolean +false+ (not +nil+) value, this execution chain will be halted and the desired operation will not complete: your model will not get persisted in the database, or your records will not get deleted and so on. +As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks and the database operation to be executed. However, if at any moment one of the +before_create+, +before_save+, +before_update+ or +before_destroy+ callback methods returns a boolean +false+ (not +nil+) value or raise and exception, this execution chain will be halted and the desired operation will not complete: your model will not get persisted in the database, or your records will not get deleted and so on. It's because the whole callback chain is wrapped in a transaction, so raising an exception or returning +false+ fires a database ROLLBACK. == Callback classes @@ -658,13 +810,13 @@ You can declare as many callbacks as you want inside your callback classes. == Observers -Active Record callbacks are a powerful feature, but they can pollute your model implementation with code that's not directly related to the model's purpose. In object-oriented software, it's always a good idea to design your classes with a single responsability in the whole system. For example, it wouldn't make much sense to have a +User+ model with a method that writes data about a login attempt to a log file. Whenever you're using callbacks to write code that's not directly related to your model class purposes, it may be a good moment to create an Observer. +Active Record callbacks are a powerful feature, but they can pollute your model implementation with code that's not directly related to the model's purpose. In object-oriented software, it's always a good idea to design your classes with a single responsibility in the whole system. For example, it wouldn't make much sense to have a +User+ model with a method that writes data about a login attempt to a log file. Whenever you're using callbacks to write code that's not directly related to your model class purposes, it may be a good moment to create an Observer. -An Active Record Observer is an object that links itself to a model and register it's methods for callbacks. Your model's implementation remain clean, while you can reuse the code in the Observer to add behaviuor to more than one model class. Ok, you may say that we can also do that using callback classes, but it would still force us to add code to our model's implementation. +An Active Record Observer is an object that links itself to a model and registers its methods for callbacks. Your model's implementation remains clean, while you can reuse the code in the Observer to add behaviour to more than one model class. OK, you may say that we can also do that using callback classes, but it would still force us to add code to our model's implementation. -Observer classes are subclasses of the +ActiveRecord::Observer+ class. When this class is subclassed, Active Record will look at the name of the new class and then strip the 'Observer' part to find the name of the Active Record class to observe. +Observer classes are subclasses of the ActiveRecord::Observer class. When this class is subclassed, Active Record will look at the name of the new class and then strip the 'Observer' part to find the name of the Active Record class to observe. -Consider a +Registration+ model, where we want to send an email everytime a new registration is created. Since sending emails is not directly related to our model's purpose, we could create an Observer to do just that: +Consider a Registration model, where we want to send an email every time a new registration is created. Since sending emails is not directly related to our model's purpose, we could create an Observer to do just that: [source, ruby] ------------------------------------------------------------------ @@ -688,7 +840,7 @@ end === Registering observers -If you payed attention, you may be wondering where Active Record Observers are referenced in our applications, so they get instantiate and begin to interact with our models. For observers to work we need to register them somewhere. The usual place to do that is in our application's *config/environment.rb* file. In this file there is a commented out line where we can define the observers that our application should load at start-up. +If you paid attention, you may be wondering where Active Record Observers are referenced in our applications, so they get instantiated and begin to interact with our models. For observers to work we need to register them somewhere. The usual place to do that is in our application's *config/environment.rb* file. In this file there is a commented-out line where we can define the observers that our application should load at start-up. [source, ruby] ------------------------------------------------------------------ @@ -706,4 +858,6 @@ By convention, you should always save your observers' source files inside *app/m == Changelog -http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks +http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks[Lighthouse ticket] + +January 9, 2009: Initial version by http://guides.rails.info/authors.html#cmarques[Cássio Marques] diff --git a/railties/doc/guides/source/authors.txt b/railties/doc/guides/source/authors.txt index 987238eb4c..d4862fe4b9 100644 --- a/railties/doc/guides/source/authors.txt +++ b/railties/doc/guides/source/authors.txt @@ -43,3 +43,15 @@ and unobtrusive JavaScript. His home on the internet is his blog http://tore.dar *********************************************************** Jeff Dean is a software engineer with http://pivotallabs.com/[Pivotal Labs]. *********************************************************** + +.Cássio Marques +[[cmarques]] +*********************************************************** +Cássio Marques is a Brazilian software developer working with different programming languages such as Ruby, JavaScript, C++ and Java, as an independent consultant. He blogs at http://cassiomarques.wordpress.com, which is mainly written in portuguese, but will soon get a new section for posts with english translation. +*********************************************************** + +.Pratik Naik +[[lifo]] +*********************************************************** +Pratik Naik is an independent Ruby on Rails consultant and also a member of the http://rubyonrails.com/core[Rails core team]. He blogs semi-regularly at http://m.onkey.org[has_many :bugs, :through => :rails] and has an active http://twitter.com/lifo[twitter account] . +*********************************************************** diff --git a/railties/doc/guides/source/benchmarking_and_profiling/appendix.txt b/railties/doc/guides/source/benchmarking_and_profiling/appendix.txt deleted file mode 100644 index 8e2e383ff3..0000000000 --- a/railties/doc/guides/source/benchmarking_and_profiling/appendix.txt +++ /dev/null @@ -1,95 +0,0 @@ -== Other Profiling Tools == - -There are a lot of great profiling tools out there. Some free, some not so free. This is a sort list detailing some of them. - -=== httperf === -http://www.hpl.hp.com/research/linux/httperf/[http://www.hpl.hp.com/research/linux/httperf/] - -A necessary tool in your arsenal. Very useful for load testing your website. - -#TODO write and link to a short article on how to use httperf. Anybody have a good tutorial availble. - - -=== Rails Analyzer === - -The Rails Analyzer project contains a collection of tools for Rails. It's open source and pretty speedy. It's not being actively worked on but is still contains some very useful tools. - -* The Production Log Analyzer examines Rails log files and gives back a report. It also includes action_grep which will give you all log results for a particular action. - -* The Action Profiler similar to Ruby-Prof profiler. - -* rails_stat which gives a live counter of requests per second of a running Rails app. - -* The SQL Dependency Grapher allows you to visualize the frequency of table dependencies in a Rails application. - -Their project homepage can be found at http://rails-analyzer.rubyforge.org/[http://rails-analyzer.rubyforge.org/] - -The one major caveat is that it needs your log to be in a different format from how rails sets it up specifically SyslogLogger. - - -==== SyslogLogger ==== - -SyslogLogger is a Logger work-alike that logs via syslog instead of to a file. You can add SyslogLogger to your Rails production environment to aggregate logs between multiple machines. - -More information can be found out at http://rails-analyzer.rubyforge.org/hacks/classes/SyslogLogger.html[http://rails-analyzer.rubyforge.org/hacks/classes/SyslogLogger.html] - -If you don't have access to your machines root system or just want something a bit easier to implement there is also a module developed by Geoffrey Grosenbach - -==== A Hodel 3000 Compliant Logger for the Rest of Us ==== - -Directions taken from -http://topfunky.net/svn/plugins/hodel_3000_compliant_logger/lib/hodel_3000_compliant_logger.rb[link to module file] - -Just put the module in your lib directory and add this to your environment.rb in it's config portion. - ------------------------------------------------------------- -require 'hodel_3000_compliant_logger' -config.logger = Hodel3000CompliantLogger.new(config.log_path) -------------------------------------------------------------- - -It's that simple. Your log output on restart should look like this. - -.Hodel 3000 Example ----------------------------------------------------------------------------- -Jul 15 11:45:43 matthew-bergmans-macbook-pro-15 rails[16207]: -Parameters: {"action"=>"shipping", "controller"=>"checkout"} -Jul 15 11:45:43 matthew-bergmans-macbook-pro-15 rails[16207]: -[4;36;1mBook Columns (0.003155)[0m [0;1mSHOW FIELDS FROM `books`[0m -Jul 15 11:45:43 matthew-bergmans-macbook-pro-15 rails[16207]: -[4;35;1mBook Load (0.000881)[0m [0mSELECT * FROM `books` WHERE (`books`.`id` = 1 AND (`books`.`sold` = 1)) [0m -Jul 15 11:45:43 matthew-bergmans-macbook-pro-15 rails[16207]: -[4;36;1mShippingAddress Columns (0.002683)[0m [0;1mSHOW FIELDS FROM `shipping_addresses`[0m -Jul 15 11:45:43 matthew-bergmans-macbook-pro-15 rails[16207]: -[4;35;1mBook Load (0.000362)[0m [0mSELECT ounces FROM `books` WHERE (`books`.`id` = 1) [0m -Jul 15 11:45:43 matthew-bergmans-macbook-pro-15 rails[16207]: -Rendering template within layouts/application -Jul 15 11:45:43 matthew-bergmans-macbook-pro-15 rails[16207]: -Rendering checkout/shipping -Jul 15 11:45:43 matthew-bergmans-macbook-pro-15 rails[16207]: -[4;36;1mBook Load (0.000548)[0m [0;1mSELECT * FROM `books` -WHERE (sold = 0) LIMIT 3[0m -Jul 15 11:45:43 matthew-bergmans-macbook-pro-15 rails[16207]: -[4;35;1mAuthor Columns (0.002571)[0m [0mSHOW FIELDS FROM `authors`[0m -Jul 15 11:45:43 matthew-bergmans-macbook-pro-15 rails[16207]: -[4;36;1mAuthor Load (0.000811)[0m [0;1mSELECT * FROM `authors` WHERE (`authors`.`id` = 1) [0m -Jul 15 11:45:43 matthew-bergmans-macbook-pro-15 rails[16207]: -Rendered store/_new_books (0.01358) -Jul 15 11:45:43 matthew-bergmans-macbook-pro-15 rails[16207]: -Completed in 0.37297 (2 reqs/sec) | Rendering: 0.02971 (7%) | DB: 0.01697 (4%) | 200 OK [https://secure.jeffbooks/checkout/shipping] ----------------------------------------------------------------------------- - -=== Palmist === -An open source mysql query analyzer. Full featured and easy to work with. Also requires Hodel 3000 -http://www.flyingmachinestudios.com/projects/[http://www.flyingmachinestudios.com/projects/] - -=== New Relic === -http://www.newrelic.com/[http://www.newrelic.com/] - -Pretty nifty performance tools, pricey though. They do have a basic free -service both for when in development and when you put your application into production. Very simple installation and signup. - -#TODO more in-depth without being like an advertisement. - -==== Manage ==== - -Like new relic a production monitoring tool. diff --git a/railties/doc/guides/source/benchmarking_and_profiling/digging_deeper.txt b/railties/doc/guides/source/benchmarking_and_profiling/digging_deeper.txt deleted file mode 100644 index fe22fba078..0000000000 --- a/railties/doc/guides/source/benchmarking_and_profiling/digging_deeper.txt +++ /dev/null @@ -1,105 +0,0 @@ -== Real Life Example == -=== The setup === - -So I have been building this application for the last month and feel pretty good about the ruby code. I'm readying it for beta testers when I discover to my shock that with less then twenty people it starts to crash. It's a pretty simple Ecommerce site so I'm very confused by what I'm seeing. On running looking through my log files I find to my shock that the lowest time for a page run is running around 240 ms. My database finds aren't the problems so I'm lost as to what is happening to cause all this. Lets run a benchmark. - - -[source, ruby] ----------------------------------------------------------------------------- -class HomepageTest < ActionController::PerformanceTest - # Replace this with your real tests. - def test_homepage - get '/' - end -end ----------------------------------------------------------------------------- - -.Output ----------------------------------------------------------------------------- -HomepageTest#test_homepage (115 ms warmup) - process_time: 591 ms - memory: 3052.90 KB - objects: 59471 ----------------------------------------------------------------------------- - - - -Obviously something is very very wrong here. 3052.90 Kb to load my minimal homepage. For Comparison for another site running well I get this for my homepage test. - -.Default ----------------------------------------------------------------------------- -HomepageTest#test_homepage (19 ms warmup) - process_time: 26 ms - memory: 298.79 KB - objects: 1917 ----------------------------------------------------------------------------- - -that over a factor of ten difference. Lets look at our flat process time file to see if anything pops out at us. - -.Process time ----------------------------------------------------------------------------- -20.73 0.39 0.12 0.00 0.27 420 Pathname#cleanpath_aggressive -17.07 0.14 0.10 0.00 0.04 3186 Pathname#chop_basename - 6.47 0.06 0.04 0.00 0.02 6571 Kernel#=== - 5.04 0.06 0.03 0.00 0.03 840 Pathname#initialize - 5.03 0.05 0.03 0.00 0.02 4 ERB::Compiler::ExplicitScanner#scan - 4.51 0.03 0.03 0.00 0.00 9504 String#== - 2.94 0.46 0.02 0.00 0.44 1393 String#gsub - 2.66 0.09 0.02 0.00 0.07 480 Array#each - 2.46 0.01 0.01 0.00 0.00 3606 Regexp#to_s ----------------------------------------------------------------------------- - -Yes indeed we seem to have found the problem. Pathname#cleanpath_aggressive is taking nearly a quarter our process time and Pathname#chop_basename another 17%. From here I do a few more benchmarks to make sure that these processes are slowing down the other pages. They are so now I know what I must do. *If we can get rid of or shorten these processes we can make our pages run much quicker*. - -Now both of these are main ruby processes so are goal right now is to find out what other process is calling them. Glancing at our Graph file I see that #cleanpath is calling #cleanpath_aggressive. #cleanpath is being called by String#gsub and from there some html template errors. But my page seems to be rendering fine. why would it be calling template errors. I'm decide to check my object flat file to see if I can find any more information. - -.Objects Created ----------------------------------------------------------------------------- -20.74 34800.00 12324.00 0.00 22476.00 420 Pathname#cleanpath_aggressive -16.79 18696.00 9978.00 0.00 8718.00 3186 Pathname#chop_basename -11.47 13197.00 6813.00 0.00 6384.00 480 Array#each - 8.51 41964.00 5059.00 0.00 36905.00 1386 String#gsub - 6.07 3606.00 3606.00 0.00 0.00 3606 Regexp#to_s ----------------------------------------------------------------------------- - -nope nothing new here. Lets look at memory usage - -.Memory Consuption ----------------------------------------------------------------------------- - 40.17 1706.80 1223.70 0.00 483.10 3186 Pathname#chop_basename - 14.92 454.47 454.47 0.00 0.00 3606 Regexp#to_s - 7.09 2381.36 215.99 0.00 2165.37 1386 String#gsub - 5.08 231.19 154.73 0.00 76.46 420 Pathname#prepend_prefix - 2.34 71.35 71.35 0.00 0.00 1265 String#initialize_copy ----------------------------------------------------------------------------- - -Ok so it seems Regexp#to_s is the second costliest process. At this point I try to figure out what could be calling a regular expression cause I very rarely use them. Going over my standard layout I discover at the top. - - -[source, html] ----------------------------------------------------------------------------- -<%if request.env["HTTP_USER_AGENT"].match(/Opera/)%> -<%= stylesheet_link_tag "opera" %> -<% end %> ----------------------------------------------------------------------------- - -That's wrong. I mistakenly am using a search function for a simple compare function. Lets fix that. - - -[source, html] ----------------------------------------------------------------------------- -<%if request.env["HTTP_USER_AGENT"] =~ /Opera/%> -<%= stylesheet_link_tag "opera" %> -<% end %> ----------------------------------------------------------------------------- - -I'll now try my test again. - ----------------------------------------------------------------------------- -process_time: 75 ms - memory: 519.95 KB - objects: 6537 ----------------------------------------------------------------------------- - -Much better. The problem has been solved. Now I should have realized earlier due to the String#gsub that my problem had to be with reqexp serch function but such knowledge comes with time. Looking through the mass output data is a skill. - diff --git a/railties/doc/guides/source/benchmarking_and_profiling/edge_rails_features.txt b/railties/doc/guides/source/benchmarking_and_profiling/edge_rails_features.txt deleted file mode 100644 index 765a1e2120..0000000000 --- a/railties/doc/guides/source/benchmarking_and_profiling/edge_rails_features.txt +++ /dev/null @@ -1,185 +0,0 @@ -== Performance Testing Built into Rails == - -As of June 20, 2008 edge rails has had a new type of Unit test geared towards profiling. Of course like most great things, getting it working takes bit of work. The test relies on statistics gathered from the Garbage Collection that isn't readily available from standard compiled ruby. There is a patch located at http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch[http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch] - -Also the test requires a new version of Ruby-Prof version of 0.6.1. It is not readily available at the moment and can most easily be found as a tarball on github. It's repository is located at git://github.com/jeremy/ruby-prof.git. - -What follows is a description of how to set up an alternative ruby install to use these features - -=== Compiling the Interpreter === - - -[source, shell] ----------------------------------------------------------------------------- -[User ~]$ mkdir rubygc -[User ~]$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.6-p111.tar.gz -[User ~]$ tar -xzvf ruby-1.8.6-p111.tar.gz -[User ~]$ cd ruby-1.8.6-p111 -[User ruby-1.8.6-p111]$ curl http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch | patch -p0 - -#I like putting my alternative ruby builds in an opt directory, set the prefix to where ever you feel is most comfortable. - -[User ruby-1.8.6-p111]$ ./configure --prefix=/opt/rubygc -[User ruby-1.8.6-p111]$ sudo make && make install ----------------------------------------------------------------------------- - -Add the following lines in your \~/.profile or \~/.bash\_login for convenience. - ----------------------------------------------------------------------------- -alias gcruby='/opt/rubygc/rubygc/bin/ruby' -alias gcrake='/opt/rubygc/rubygc/bin/rake' -alias gcgem='/opt/rubygc/rubygc/bin/gem' -alias gcirb=/opt/rubygc/rubygc/bin/irb' -alias gcrails='/opt/rubygc/rubygc/bin/rails' ----------------------------------------------------------------------------- - -=== Installing RubyGems === - -Next we need to install rubygems and rails so that we can use the interpreter properly. - - -[source, shell] ----------------------------------------------------------------------------- -[User ~]$ wget http://rubyforge.org/frs/download.php/38646/rubygems-1.2.0.tgz -[User ~]$ tar -xzvf rubygems-1.2.0.tgz -[User ~]$ cd rubygems-1.2.0 -[User rubygems-1.2.0]$ gcruby setup.rb -[User rubygems-1.2.0]$ cd ~ -[User ~]$ gcgem install rake -[User ~]$ gcgem install mysql -[User ~]$ gcgem install rails ----------------------------------------------------------------------------- - -If installing mysql gem fails ( like it did for me ), you will have to manually install it : - -[source, shell] ----------------------------------------------------------------------------- -[User ~]$ cd /Users/lifo/rubygc/lib/ruby/gems/1.8/gems/mysql-2.7/ -[User mysql-2.7]$ gcruby extconf.rb --with-mysql-config -[User mysql-2.7]$ make && make install ----------------------------------------------------------------------------- - -=== Installing Jeremy Kemper's ruby-prof === - -We are in the home stretch. All we need now is ruby-proff 0.6.1 - - -[source, shell] ----------------------------------------------------------------------------- -[User ~]$ git clone git://github.com/jeremy/ruby-prof.git -[User ~]$ cd ruby-prof/ -[User ruby-prof (master)]$ gcrake gem -[User ruby-prof (master)]$ gcgem install pkg/ruby-prof-0.6.1.gem ----------------------------------------------------------------------------- - -Finished, go get yourself a power drink! - -=== Ok so I lied, a few more things we need to do === - -You have everything we need to start profiling through rails Unit Testing. Unfortunately we are still missing a few files. I'm going to do the next step on a fresh Rails app, but it will work just as well on developmental 2.1 rails application. - -==== The Rails App ==== - -First I need to generate a rail app - -[source, shell] ----------------------------------------------------------------------------- -[User ~]$ gcrails profiling_tester -d mysql -[User ~]$ cd profiling_tester -[User profiling_tester]$ script/generate scaffold item name:string -[User profiling_tester]$ gcrake db:create:all -[User profiling_tester]$ gcrake db:migrate -[User profiling_tester (master)]$ rm public/index.html ----------------------------------------------------------------------------- - -Now I'm going to init it as a git repository and add edge rails as a submodule to it. - -[source, shell] ----------------------------------------------------------------------------- -[User profiling_tester]$ git init -[User profiling_tester (master)]$ git submodule add git://github.com/rails/rails.git vendor/rails ----------------------------------------------------------------------------- - -Finally we want to change config.cache_classes to true in our environment.rb - ----------------------------------------------------------------------------- -config.cache_classes = true ----------------------------------------------------------------------------- - -If we don't cache classes, then the time Rails spends reloading and compiling our models and controllers will confound our results. Obviously we will try to make our test setup as similar as possible to our production environment. - -=== Generating and Fixing the tests === - -Ok next we need to generate the test script. - -[source, shell] ----------------------------------------------------------------------------- -[User profiling_tester (master)]$ script/generate performance_test homepage ----------------------------------------------------------------------------- - -This will generate _test/performance/homepage_test.rb_ for you. However, as I have generated the project using Rails 2.1 gem, we'll need to manually generate one more file before we can go ahead. - -We need to put the following inside _test/performance/test_helper.rb - - -[source, ruby] ----------------------------------------------------------------------------- -require 'test_helper' -require 'performance_test_help' ----------------------------------------------------------------------------- - -Though this depends where you run your tests from and your system config. I myself run my tests from the Application root directory - -so instead of - -[source, ruby] ----------------------------------------------------------------------------- -require 'test_helper' - -#I have - -require 'test/test_helper' ----------------------------------------------------------------------------- - -Also I needed to change homepage_test.rb to reflect this also - -[source, ruby] ----------------------------------------------------------------------------- -require 'test/performance/test_helper.rb' ----------------------------------------------------------------------------- - -=== Testing === - -#TODO is there some way to compare multiple request at once like ruby_analyze - -Now, if we look at the generated performance test ( one we generated using _script/generate performance_test_ ), it'll look something like : - -[source, ruby] ----------------------------------------------------------------------------- -.require 'performance/test_helper' - -class HomepageTest < ActionController::PerformanceTest - # Replace this with your real tests. - def test_homepage - get '/' - end -end ----------------------------------------------------------------------------- - - -The format looks very similar to that of an integration test. And guess what, that's what it is. But that doesn't stop you from testing your Model methods. You could very well write something like : - -[source, ruby] ----------------------------------------------------------------------------- -require 'performance/test_helper' - -class UserModelTest < ActionController::PerformanceTest - # Replace this with your real tests. - def test_slow_find - User.this_takes_shlong_to_run - end -end ----------------------------------------------------------------------------- - - -Which is very useful way to profile individual processes. diff --git a/railties/doc/guides/source/benchmarking_and_profiling/gameplan.txt b/railties/doc/guides/source/benchmarking_and_profiling/gameplan.txt deleted file mode 100644 index 1f1d365eff..0000000000 --- a/railties/doc/guides/source/benchmarking_and_profiling/gameplan.txt +++ /dev/null @@ -1,27 +0,0 @@ -== Get Yourself a Game Plan == - -You end up dealing with a large amount of data whenever you profile an application. It's crucial to use a rigorous approach to analyzing your application's performance else fail miserably in a vortex of numbers. This leads us to - - -=== The Analysis Process === - -I’m going to give an example methodology for conducting your benchmarking and profiling on an application. It is based on your typical scientific method. - -For something as complex as Benchmarking you need to take any methodology with a grain of salt but there are some basic strictures that you can depend on. - -Formulate a question you need to answer which is simple, tests the smallest measurable thing possible, and is exact. This is typically the hardest part of the experiment. From there some steps that you should follow are. - -* Develop a set of variables and processes to measure in order to answer this question! -* Profile based on the question and variables. Key problems to avoid when designing this experiment are: - - Confounding: Test one thing at a time, keep everything the same so you don't poison the data with uncontrolled processes. - - Cross Contamination: Make sure that runs from one test do not harm the other tests. - - Steady States: If you’re testing long running process. You must take the ramp up time and performance hit into your initial measurements. - - Sampling Error: Data should perform have a steady variance or range. If you get wild swings or sudden spikes, etc. then you must either account for the reason why or you have a sampling error. - - Measurement Error: Aka Human error, always go through your calculations at least twice to make sure there are no mathematical errors. . -* Do a small run of the experiment to verify the design. -* Use the small run to determine a proper sample size. -* Run the test. -* Perform the analysis on the results and determine where to go from there. - -Note: Even though we are using the typical scientific method; developing a hypothesis is not always useful in terms of profiling. - - diff --git a/railties/doc/guides/source/benchmarking_and_profiling/index.txt b/railties/doc/guides/source/benchmarking_and_profiling/index.txt deleted file mode 100644 index ef45ff62c6..0000000000 --- a/railties/doc/guides/source/benchmarking_and_profiling/index.txt +++ /dev/null @@ -1,242 +0,0 @@ -Benchmarking and Profiling Rails -================================ - -This guide covers the benchmarking and profiling tactics/tools of Rails and Ruby in general. By referring to this guide, you will be able to: - -* Understand the various types of benchmarking and profiling metrics -* Generate performance/benchmarking tests -* Use GC patched Ruby binary to measure memory usage and object allocation -* Understand the information provided by Rails inside the log files -* Learn about various tools facilitating benchmarking and profiling - -== Why Benchmark and Profile ? - -Benchmarking and Profiling is an integral part of the development cycle. It is very important that you don't make your end users wait for too long before the page is completely loaded. Ensuring a plesant browsing experience to the end users and cutting cost of unnecessary hardwares is important for any web application. - -=== What is the difference between benchmarking and profiling ? === - -Benchmarking is the process of finding out if a piece of code is slow or not. Whereas profiling is the process of finding out what exactly is slowing down that piece of code. - -== Using and understanding the log files == - -Rails logs files containt basic but very useful information about the time taken to serve every request. A typical log entry looks something like : - -[source, ruby] ----------------------------------------------------------------------------- -Processing ItemsController#index (for 127.0.0.1 at 2008-10-17 00:08:18) [GET] - Session ID: BAh7BiIKZmxhc2hJQzonQWN0aHsABjoKQHVzZWR7AA==--83cff4fe0a897074a65335 - Parameters: {"action"=>"index", "controller"=>"items"} -Rendering template within layouts/items -Rendering items/index -Completed in 5ms (View: 2, DB: 0) | 200 OK [http://localhost/items] ----------------------------------------------------------------------------- - -For this section, we're only interested in the last line from that log entry: - -[source, ruby] ----------------------------------------------------------------------------- -Completed in 5ms (View: 2, DB: 0) | 200 OK [http://localhost/items] ----------------------------------------------------------------------------- - -This data is fairly straight forward to understand. Rails uses millisecond(ms) as the metric to measures the time taken. The complete request spent 5 ms inside Rails, out of which 2 ms were spent rendering views and none was spent communication with the database. It's safe to assume that the remaining 3 ms were spent inside the controller. - -== Helper methods == - -Rails provides various helper methods inside Active Record, Action Controller and Action View to measure the time taken by a specific code. The method is called +benchmark()+ in all three components. - -[source, ruby] ----------------------------------------------------------------------------- -Project.benchmark("Creating project") do - project = Project.create("name" => "stuff") - project.create_manager("name" => "David") - project.milestones << Milestone.find(:all) -end ----------------------------------------------------------------------------- - -The above code benchmarks the multiple statments enclosed inside +Project.benchmark("Creating project") do..end+ block and prints the results inside log files. The statement inside log files will look like: - -[source, ruby] ----------------------------------------------------------------------------- -Creating projectem (185.3ms) ----------------------------------------------------------------------------- - -Please refer to http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001336[API docs] for optional options to +benchmark()+ - -Similarly, you could use this helper method inside http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715[controllers] ( Note that it's a class method here ): - -[source, ruby] ----------------------------------------------------------------------------- -def process_projects - self.class.benchmark("Processing projects") do - Project.process(params[:project_ids]) - Project.update_cached_projects - end -end ----------------------------------------------------------------------------- - -and http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715[views]: - -[source, ruby] ----------------------------------------------------------------------------- -<% benchmark("Showing projects partial") do %> - <%= render :partial => @projects %> -<% end %> ----------------------------------------------------------------------------- - -== Performance Test Cases == - -Rails provides a very easy to write performance test cases, which look just like the regular integration tests. - -If you have a look at +test/performance/browsing_test.rb+ in a newly created Rails application: - -[source, ruby] ----------------------------------------------------------------------------- -require 'test_helper' -require 'performance_test_help' - -# Profiling results for each test method are written to tmp/performance. -class BrowsingTest < ActionController::PerformanceTest - def test_homepage - get '/' - end -end ----------------------------------------------------------------------------- - -This is an automatically generated example performance test file, for testing performance of homepage('/') of the application. - -=== Modes === - -==== Benchmarking ==== -==== Profiling ==== - -=== Metrics === - -==== Process Time ==== - -CPU Cycles. - -==== Memory ==== - -Memory taken. - -==== Objects ==== - -Objects allocated. - -==== GC Runs ==== - -Number of times the Ruby GC was run. - -==== GC Time ==== - -Time spent running the Ruby GC. - -=== Preparing Ruby and Ruby-prof === - -Before we go ahead, Rails performance testing requires you to build a special Ruby binary with some super powers - GC patch for measuring GC Runs/Time. This process is very straight forward. If you've never compiled a Ruby binary before, you can follow the following steps to build a ruby binary inside your home directory: - -==== Compile ==== - -[source, shell] ----------------------------------------------------------------------------- -[lifo@null ~]$ mkdir rubygc -[lifo@null ~]$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.6-p111.tar.gz -[lifo@null ~]$ tar -xzvf ruby-1.8.6-p111.tar.gz -[lifo@null ~]$ cd ruby-1.8.6-p111 -[lifo@null ruby-1.8.6-p111]$ curl http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch | patch -p0 -[lifo@null ruby-1.8.6-p111]$ ./configure --prefix=/Users/lifo/rubygc -[lifo@null ruby-1.8.6-p111]$ make && make install ----------------------------------------------------------------------------- - -==== Prepare aliases ==== - -Add the following lines in your ~/.profile for convenience: - ----------------------------------------------------------------------------- -alias gcruby='/Users/lifo/rubygc/bin/ruby' -alias gcrake='/Users/lifo/rubygc/bin/rake' -alias gcgem='/Users/lifo/rubygc/bin/gem' -alias gcirb='/Users/lifo/rubygc/bin/irb' -alias gcrails='/Users/lifo/rubygc/bin/rails' ----------------------------------------------------------------------------- - -==== Install rubygems and some basic gems ==== - ----------------------------------------------------------------------------- -[lifo@null ~]$ wget http://rubyforge.org/frs/download.php/38646/rubygems-1.2.0.tgz -[lifo@null ~]$ tar -xzvf rubygems-1.2.0.tgz -[lifo@null ~]$ cd rubygems-1.2.0 -[lifo@null rubygems-1.2.0]$ gcruby setup.rb -[lifo@null rubygems-1.2.0]$ cd ~ -[lifo@null ~]$ gcgem install rake -[lifo@null ~]$ gcgem install rails ----------------------------------------------------------------------------- - -==== Install MySQL gem ==== - ----------------------------------------------------------------------------- -[lifo@null ~]$ gcgem install mysql ----------------------------------------------------------------------------- - -If this fails, you can try to install it manually: - ----------------------------------------------------------------------------- -[lifo@null ~]$ cd /Users/lifo/rubygc/lib/ruby/gems/1.8/gems/mysql-2.7/ -[lifo@null mysql-2.7]$ gcruby extconf.rb --with-mysql-config -[lifo@null mysql-2.7]$ make && make install ----------------------------------------------------------------------------- - -=== Installing Jeremy Kemper's ruby-prof === - -We also need to install Jeremy's ruby-prof gem using our newly built ruby: - -[source, shell] ----------------------------------------------------------------------------- -[lifo@null ~]$ git clone git://github.com/jeremy/ruby-prof.git -[lifo@null ~]$ cd ruby-prof/ -[lifo@null ruby-prof (master)]$ gcrake gem -[lifo@null ruby-prof (master)]$ gcgem install pkg/ruby-prof-0.6.1.gem ----------------------------------------------------------------------------- - -=== Generating performance test === - -Rails provides a simple generator for creating new performance tests: - -[source, shell] ----------------------------------------------------------------------------- -[lifo@null application (master)]$ script/generate performance_test homepage ----------------------------------------------------------------------------- - -This will generate +test/performance/homepage_test.rb+: - -[source, ruby] ----------------------------------------------------------------------------- -require 'test_helper' -require 'performance_test_help' - -class HomepageTest < ActionController::PerformanceTest - # Replace this with your real tests. - def test_homepage - get '/' - end -end ----------------------------------------------------------------------------- - -Which you can modify to suit your needs. - -=== Running tests === - -include::rubyprof.txt[] - -include::digging_deeper.txt[] - -include::gameplan.txt[] - -include::appendix.txt[] - -== Changelog == - -http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/4[Lighthouse ticket] - -* October 17, 2008: First revision by Pratik -* September 6, 2008: Initial version by Matthew Bergman <MzbPhoto@gmail.com>
\ No newline at end of file diff --git a/railties/doc/guides/source/benchmarking_and_profiling/rubyprof.txt b/railties/doc/guides/source/benchmarking_and_profiling/rubyprof.txt deleted file mode 100644 index fa01d413a1..0000000000 --- a/railties/doc/guides/source/benchmarking_and_profiling/rubyprof.txt +++ /dev/null @@ -1,179 +0,0 @@ -== Understanding Performance Tests Outputs == - -=== Our First Performance Test === - -So how do we profile a request. - -One of the things that is important to us is how long it takes to render the home page - so let's make a request to the home page. Once the request is complete, the results will be outputted in the terminal. - -In the terminal run - -[source, ruby] ----------------------------------------------------------------------------- -[User profiling_tester]$ gcruby tests/performance/homepage.rb ----------------------------------------------------------------------------- - -After the tests runs for a few seconds you should see something like this. - ----------------------------------------------------------------------------- -HomepageTest#test_homepage (19 ms warmup) - process_time: 26 ms - memory: 298.79 KB - objects: 1917 - -Finished in 2.207428 seconds. ----------------------------------------------------------------------------- - -Simple but efficient. - -* Process Time refers to amount of time necessary to complete the action. -* memory is the amount of information loaded into memory -* object ??? #TODO find a good definition. Is it the amount of objects put into a ruby heap for this process? - -In addition we also gain three types of itemized log files for each of these outputs. They can be found in your tmp directory of your application. - -*The Three types are* - -* Flat File - A simple text file with the data laid out in a grid -* Graphical File - A html colored coded version of the simple text file with hyperlinks between the various methods. Most useful is the bolding of the main processes for each portion of the action. -* Tree File - A file output that can be use in conjunction with KCachegrind to visualize the process - -NOTE: KCachegrind is Linux only. For Mac this means you have to do a full KDE install to have it working in your OS. Which is over 3 gigs in size. For windows there is clone called wincachegrind but it is no longer actively being developed. - -Below are examples for Flat Files and Graphical Files - -=== Flat Files === - -.Flat File Output Processing Time -============================================================================ -Thread ID: 2279160 -Total: 0.026097 - - %self total self wait child calls name - 6.41 0.06 0.04 0.00 0.02 571 Kernel#=== - 3.17 0.00 0.00 0.00 0.00 172 Hash#[] - 2.42 0.00 0.00 0.00 0.00 13 MonitorMixin#mon_exit - 2.05 0.00 0.00 0.00 0.00 15 Array#each - 1.56 0.00 0.00 0.00 0.00 6 Logger#add - 1.55 0.00 0.00 0.00 0.00 13 MonitorMixin#mon_enter - 1.36 0.03 0.00 0.00 0.03 1 ActionController::Integration::Session#process - 1.31 0.00 0.00 0.00 0.00 13 MonitorMixin#mon_release - 1.15 0.00 0.00 0.00 0.00 8 MonitorMixin#synchronize-1 - 1.09 0.00 0.00 0.00 0.00 23 Class#new - 1.03 0.01 0.00 0.00 0.01 5 MonitorMixin#synchronize - 0.89 0.00 0.00 0.00 0.00 74 Hash#default - 0.89 0.00 0.00 0.00 0.00 6 Hodel3000CompliantLogger#format_message - 0.80 0.00 0.00 0.00 0.00 9 c - 0.80 0.00 0.00 0.00 0.00 11 ActiveRecord::ConnectionAdapters::ConnectionHandler#retrieve_connection_pool - 0.79 0.01 0.00 0.00 0.01 1 ActionController::Benchmarking#perform_action_without_rescue - 0.18 0.00 0.00 0.00 0.00 17 <Class::Object>#allocate -============================================================================ - -So what do these columns tell us: - - * %self - The percentage of time spent processing the method. This is derived from self_time/total_time - * total - The time spent in this method and its children. - * self - The time spent in this method. - * wait - Time processed was queued - * child - The time spent in this method's children. - * calls - The number of times this method was called. - * name - The name of the method. - -Name can be displayed three seperate ways: - * #toplevel - The root method that calls all other methods - * MyObject#method - Example Hash#each, The class Hash is calling the method each - * <Object:MyObject>#test - The <> characters indicate a singleton method on a singleton class. Example <Class::Object>#allocate - -Methods are sorted based on %self. Hence the ones taking the most time and resources will be at the top. - -So for Array#each which is calling each on the class array. We find that it processing time is 2% of the total and was called 15 times. The rest of the information is 0.00 because the process is so fast it isn't recording times less then 100 ms. - - -.Flat File Memory Output -============================================================================ -Thread ID: 2279160 -Total: 509.724609 - - %self total self wait child calls name - 4.62 23.57 23.57 0.00 0.00 34 String#split - 3.95 57.66 20.13 0.00 37.53 3 <Module::YAML>#quick_emit - 2.82 23.70 14.35 0.00 9.34 2 <Module::YAML>#quick_emit-1 - 1.37 35.87 6.96 0.00 28.91 1 ActionView::Helpers::FormTagHelper#form_tag - 1.35 7.69 6.88 0.00 0.81 1 ActionController::HttpAuthentication::Basic::ControllerMethods#authenticate_with_http_basic - 1.06 6.09 5.42 0.00 0.67 90 String#gsub - 1.01 5.13 5.13 0.00 0.00 27 Array#- -============================================================================ - -Very similar to the processing time format. The main difference here is that instead of calculating time we are now concerned with the amount of KB put into memory *(or is it strictly into the heap) can I get clarification on this minor point?* - -So for <Module::YAML>#quick_emit which is singleton method on the class YAML it uses 57.66 KB in total, 23.57 through its own actions, 6.69 from actions it calls itself and that it was called twice. - -.Flat File Objects -============================================================================ -Thread ID: 2279160 -Total: 6537.000000 - - %self total self wait child calls name - 15.16 1096.00 991.00 0.00 105.00 66 Hash#each - 5.25 343.00 343.00 0.00 0.00 4 Mysql::Result#each_hash - 4.74 2203.00 310.00 0.00 1893.00 42 Array#each - 3.75 4529.00 245.00 0.00 4284.00 1 ActionView::Base::CompiledTemplates#_run_erb_47app47views47layouts47application46html46erb - 2.00 136.00 131.00 0.00 5.00 90 String#gsub - 1.73 113.00 113.00 0.00 0.00 34 String#split - 1.44 111.00 94.00 0.00 17.00 31 Array#each-1 -============================================================================ - - - #TODO Find correct terminology for how to describe what this is exactly profiling as in are there really 2203 array objects or 2203 pointers to array objects?. - -=== Graph Files === - -While the information gleamed from flat files is very useful we still don't know which processes each method is calling. We only know how many. This is not true for a graph file. Below is a text representation of a graph file. The actual graph file is an html entity and an example of which can be found link:examples/graph.html[Here] - -#TODO (Handily the graph file has links both between it many processes and to the files that actually contain them for debugging. - ) - -.Graph File -============================================================================ -Thread ID: 21277412 - - %total %self total self children calls Name -/____________________________________________________________________________/ -100.00% 0.00% 8.77 0.00 8.77 1 #toplevel* - 8.77 0.00 8.77 1/1 Object#run_primes -/____________________________________________________________________________/ - 8.77 0.00 8.77 1/1 #toplevel -100.00% 0.00% 8.77 0.00 8.77 1 Object#run_primes* - 0.02 0.00 0.02 1/1 Object#make_random_array - 2.09 0.00 2.09 1/1 Object#find_largest - 6.66 0.00 6.66 1/1 Object#find_primes -/____________________________________________________________________________/ - 0.02 0.02 0.00 1/1 Object#make_random_array -0.18% 0.18% 0.02 0.02 0.00 1 Array#each_index - 0.00 0.00 0.00 500/500 Kernel.rand - 0.00 0.00 0.00 500/501 Array#[]= -/____________________________________________________________________________/ -============================================================================ - -As you can see the calls have been separated into slices, no longer is the order determined by process time but instead from hierarchy. Each slice profiles a primary entry, with the primary entry's parents being shown above itself and it's children found below. A primary entry can be ascertained by it having values in the %total and %self columns. Here the main entry here have been bolded for connivence. - -So if we look at the last slice. The primary entry would be Array#each_index. It takes 0.18% of the total process time and it is only called once. It is called from Object#make_random_array which is only called once. It's children are Kernal.rand which is called by it all 500 its times that it was call in this action and Arry#[]= which was called 500 times by Array#each_index and once by some other entry. - -=== Tree Files === - -It's pointless trying to represent a tree file textually so here's a few pretty pictures of it's usefulness - -.KCachegrind Graph -[caption="KCachegrind graph"] -image:images/kgraph.png[Graph created by KCachegrind] - -.KCachegrind List -[caption="KCachegrind List"] -image:images/klist.png[List created by KCachegrind] - -#TODO Add a bit more information to this. - -== Getting to the Point of all of this == - -Now I know all of this is a bit dry and academic. But it's a very powerful tool when you know how to leverage it properly. Which we are going to take a look at in our next section - diff --git a/railties/doc/guides/source/benchmarking_and_profiling/statistics.txt b/railties/doc/guides/source/benchmarking_and_profiling/statistics.txt deleted file mode 100644 index 9fca979dec..0000000000 --- a/railties/doc/guides/source/benchmarking_and_profiling/statistics.txt +++ /dev/null @@ -1,57 +0,0 @@ -== A Lession In Statistics == - -#TODO COMPRESS DOWN INTO A PARAGRAPH AND A HALF -maybe I'll just combine with the methodology portion as an appendix. - -Adapted from a blog Article by Zed Shaw. His rant is funnier but will take longer to read. <br /> http://www.zedshaw.com/rants/programmer_stats.html[Programmers Need To Learn Statistics Or I Will Kill Them All] - -=== Why Learn Statistics === - -Statistics is a hard discipline. One can study it for years without fully grasping all the complexities. But its a necessary evil for coders of every level to at least know the basics. You can't optimize without it, and if you use it wrong, you'll just waste your time and the rest of your team's. - -=== Power-of-Ten Syndrome === - -If you done any benchmarking you have probably heard -“All you need to do is run that test [insert power-of-ten] times and then do an average.” - -For new developers this whole power of ten comes about because we need enough data to minimize the results being contaminated by outliers. If you loaded a page five times with three of those times being around 75ms and twice 250ms you have no way of knowing the real average processing time for you page. But if we take a 1000 times and 950 are 75ms and 50 are 250ms we have a much clearer picture of the situation. - -But this still begs the question of how you determine that 1000 is the correct number of iterations to improve the power of the experiment? (Power in this context basically means the chance that your experiment is right.) - -The first thing that needs to be determined is how you are performing the samplings? 1000 iterations run in a massive sequential row? A set of 10 runs with 100 each? The statistics are different depending on which you do, but the 10 runs of 100 each would be a better approach. This lets you compare sample means and figure out if your repeated runs have any bias. More simply put, this allows you to see if you have a many or few outliers that might be poisoning your averages. - -Another consideration is if a 1000 transactions is enough to get the process into a steady state after the ramp-up period? If you are benchmarking a long running process that stabilizes only after a warm-up time you must take that into consideration. - -Also remember getting an average is not an end goal in itself. In fact in some cases they tell you almost nothing. - -=== Don't Just Use Averages! === - -One cannot simply say my website “[insert power-of-ten] requests per second”. This is due to it being an Average. Without some form of range or variance error analysis it's a useless number. Two averages can be the same, but hide massive differences in behavior. Without a standard deviation it’s not possible to figure out if the two might even be close. - -Two averages can be the same say 30 requests a second and yet have a completely different standard deviation. Say the first sample has +-3 and the second is +-30 - -Stability is vastly different for these two samples If this were a web server performance run I’d say the second server has a major reliability problem. No, it’s not going to crash, but it’s performance response is so erratic that you’d never know how long a request would take. Even though the two servers perform the same on average, users will think the second one is slower because of how it seems to randomly perform. - -Another big thing to take into consideration when benchmarking and profiling is Confounding - -=== Confounding === - -The idea of confounding is pretty simple: If you want to measure something, then don’t measure anything else. - -#TODO add more information in how to avoid confounding. - -* Your testing system and your production system must be separate. You can't profile on the same system because you are using resources to run the test that your server should be using to serve the requests. - -And one more thing. - -=== Define what you are Measuring === - -Before you can measure something you really need to lay down a very concrete definition of what you’re measuring. You should also try to measure the simplest thing you can and try to avoid confounding. - -The most important thing to determine though is how much data you can actually send to your application through it's pipe. - -=== Back to Business === - -Now I know this was all a bit boring, but these fundamentals a necessary for understanding what we are actually doing here. Now onto the actual code and rails processes. - - diff --git a/railties/doc/guides/source/caching_with_rails.txt b/railties/doc/guides/source/caching_with_rails.txt index 16dac19e08..6da67ed481 100644 --- a/railties/doc/guides/source/caching_with_rails.txt +++ b/railties/doc/guides/source/caching_with_rails.txt @@ -214,19 +214,19 @@ sweeper such as the following: [source, ruby] ----------------------------------------------------- class StoreSweeper < ActionController::Caching::Sweeper - observe Product # This sweeper is going to keep an eye on the Post model + observe Product # This sweeper is going to keep an eye on the Product model - # If our sweeper detects that a Post was created call this + # If our sweeper detects that a Product was created call this def after_create(product) expire_cache_for(product) end - # If our sweeper detects that a Post was updated call this + # If our sweeper detects that a Product was updated call this def after_update(product) expire_cache_for(product) end - # If our sweeper detects that a Post was deleted call this + # If our sweeper detects that a Product was deleted call this def after_destroy(product) expire_cache_for(product) end diff --git a/railties/doc/guides/source/command_line.txt b/railties/doc/guides/source/command_line.txt index 1ad2e75c51..8a887bd001 100644 --- a/railties/doc/guides/source/command_line.txt +++ b/railties/doc/guides/source/command_line.txt @@ -52,7 +52,7 @@ NOTE: This output will seem very familiar when we get to the `generate` command. === server === -Let's try it! The `server` command launches a small web server written in Ruby named WEBrick which was also installed when you installed Rails. You'll use this any time you want to view your work through a web browser. +Let's try it! The `server` command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to view your work through a web browser. NOTE: WEBrick isn't your only option for serving Rails. We'll get to that in a later section. [XXX: which section] @@ -99,7 +99,7 @@ Using generators will save you a large amount of time by writing *boilerplate co Let's make our own controller with the controller generator. But what command should we use? Let's ask the generator: -NOTE: All Rails console utilities have help text. For commands that require a lot of input to run correctly, you can try the command without any parameters (like `rails` or `./script/generate`). For others, you can try adding `--help` or `-h` to the end, as in `./script/server --help`. +NOTE: All Rails console utilities have help text. As with most *NIX utilities, you can try adding `--help` or `-h` to the end, for example `./script/server --help`. [source,shell] ------------------------------------------------------ @@ -200,24 +200,47 @@ Examples: creates a Post model with a string title, text body, and published flag. ------------------------------------------------------ -Let's set up a simple model called "HighScore" that will keep track of our highest score on video games we play. Then we'll wire up our controller and view to modify and list our scores. +But instead of generating a model directly (which we'll be doing later), let's set up a scaffold. A *scaffold* in Rails is a full set of model, database migration for that model, controller to manipulate it, views to view and manipulate the data, and a test suite for each of the above. + +Let's set up a simple resource called "HighScore" that will keep track of our highest score on video games we play. [source,shell] ------------------------------------------------------ -$ ./script/generate model HighScore id:integer game:string score:integer - exists app/models/ - exists test/unit/ - exists test/fixtures/ - create app/models/high_score.rb - create test/unit/high_score_test.rb - create test/fixtures/high_scores.yml - create db/migrate - create db/migrate/20081126032945_create_high_scores.rb +$ ./script/generate scaffold HighScore game:string score:integer + exists app/models/ + exists app/controllers/ + exists app/helpers/ + create app/views/high_scores + create app/views/layouts/ + exists test/functional/ + create test/unit/ + create public/stylesheets/ + create app/views/high_scores/index.html.erb + create app/views/high_scores/show.html.erb + create app/views/high_scores/new.html.erb + create app/views/high_scores/edit.html.erb + create app/views/layouts/high_scores.html.erb + create public/stylesheets/scaffold.css + create app/controllers/high_scores_controller.rb + create test/functional/high_scores_controller_test.rb + create app/helpers/high_scores_helper.rb + route map.resources :high_scores +dependency model + exists app/models/ + exists test/unit/ + create test/fixtures/ + create app/models/high_score.rb + create test/unit/high_score_test.rb + create test/fixtures/high_scores.yml + exists db/migrate + create db/migrate/20081217071914_create_high_scores.rb ------------------------------------------------------ -Taking it from the top, we have the *models* directory, where all of your data models live. *test/unit*, where all the unit tests live (gasp! -- unit tests!), fixtures for those tests, a test, the *migrate* directory, where the database-modifying migrations live, and a migration to create the `high_scores` table with the right fields. +Taking it from the top - the generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the *resource*, and new tests for everything. + +The migration requires that we *migrate*, that is, run some Ruby code (living in that `20081217071914_create_high_scores.rb`) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while. -The migration requires that we *migrate*, that is, run some Ruby code (living in that `20081126032945_create_high_scores.rb`) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while. +NOTE: Hey. Install the sqlite3-ruby gem while you're at it. `gem install sqlite3-ruby` [source,shell] ------------------------------------------------------ @@ -231,23 +254,87 @@ $ rake db:migrate NOTE: Let's talk about unit tests. Unit tests are code that tests and makes assertions about code. In unit testing, we take a little part of code, say a method of a model, and test its inputs and outputs. Unit tests are your friend. The sooner you make peace with the fact that your quality of life will drastically increase when you unit test your code, the better. Seriously. We'll make one in a moment. -Yo! Let's shove a small table into our greeting controller and view, listing our sweet scores. +Let's see the interface Rails created for us. ./script/server; http://localhost:3000/high_scores -[source,ruby] +We can create new high scores (55,160 on Space Invaders!) + +=== console === +The `console` command lets you interact with your Rails application from the command line. On the underside, `script/console` uses IRB, so if you've ever used it, you'll be right at home. This is useful for testing out quick ideas with code and changing data server-side without touching the website. + +=== dbconsole === +`dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3. + +=== plugin === +The `plugin` command simplifies plugin management; think a miniature version of the Gem utility. Let's walk through installing a plugin. You can call the sub-command *discover*, which sifts through repositories looking for plugins, or call *source* to add a specific repository of plugins, or you can specify the plugin location directly. + +Let's say you're creating a website for a client who wants a small accounting system. Every event having to do with money must be logged, and must never be deleted. Wouldn't it be great if we could override the behavior of a model to never actually take its record out of the database, but *instead*, just set a field? + +There is such a thing! The plugin we're installing is called "acts_as_paranoid", and it lets models implement a "deleted_at" column that gets set when you call destroy. Later, when calling find, the plugin will tack on a database check to filter out "deleted" things. + +[source,shell] +------------------------------------------------------ +$ ./script/plugin install http://svn.techno-weenie.net/projects/plugins/acts_as_paranoid ++ ./CHANGELOG ++ ./MIT-LICENSE +... +... ------------------------------------------------------ -class GreetingController < ApplicationController - def hello - if request.post? - score = HighScore.new(params[:high_score]) - if score.save - flash[:notice] = "New score posted!" - end - end - - @scores = HighScore.find(:all) - end -end +=== runner === +`runner` runs Ruby code in the context of Rails non-interactively. For instance: + +[source,shell] ------------------------------------------------------ +$ ./script/runner "Model.long_running_method" +------------------------------------------------------ + +=== destroy === +Think of `destroy` as the opposite of `generate`. It'll figure out what generate did, and undo it. Believe you-me, the creation of this tutorial used this command many times! -XXX: Go with scaffolding instead, modifying greeting controller for high scores seems dumb. +[source,shell] +------------------------------------------------------ +$ ./script/generate model Oops + exists app/models/ + exists test/unit/ + exists test/fixtures/ + create app/models/oops.rb + create test/unit/oops_test.rb + create test/fixtures/oops.yml + exists db/migrate + create db/migrate/20081221040817_create_oops.rb +$ ./script/destroy model Oops + notempty db/migrate + notempty db + rm db/migrate/20081221040817_create_oops.rb + rm test/fixtures/oops.yml + rm test/unit/oops_test.rb + rm app/models/oops.rb + notempty test/fixtures + notempty test + notempty test/unit + notempty test + notempty app/models + notempty app +------------------------------------------------------ + +=== about === +Check it: Version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version! `about` is useful when you need to ask help, check if a security patch might affect you, or when you need some stats for an existing Rails installation. + +[source,shell] +------------------------------------------------------ +$ ./script/about +About your application's environment +Ruby version 1.8.6 (i486-linux) +RubyGems version 1.3.1 +Rails version 2.2.0 +Active Record version 2.2.0 +Action Pack version 2.2.0 +Active Resource version 2.2.0 +Action Mailer version 2.2.0 +Active Support version 2.2.0 +Edge Rails revision unknown +Application root /home/commandsapp +Environment development +Database adapter sqlite3 +Database schema version 20081217073400 +------------------------------------------------------
\ No newline at end of file diff --git a/railties/doc/guides/source/configuring.txt b/railties/doc/guides/source/configuring.txt index 945e48cd45..489e205eb1 100644 --- a/railties/doc/guides/source/configuring.txt +++ b/railties/doc/guides/source/configuring.txt @@ -6,23 +6,43 @@ This guide covers the configuration and initialization features available to Rai * Adjust the behavior of your Rails applications * Add additional code to be run at application start time +NOTE: The first edition of this Guide was written from the Rails 2.3 source code. While the information it contains is broadly applicable to Rails 2.2, backwards compatibility is not guaranteed. + == Locations for Initialization Code -preinitializers -environment.rb first -env-specific files -initializers (load_application_initializers) -after-initializer +Rails offers (at least) five good spots to place initialization code: + +* Preinitializers +* environment.rb +* Environment-specific Configuration Files +* Initializers (load_application_initializers) +* After-Initializers == Using a Preinitializer -== Initialization Process Settings +Rails allows you to use a preinitializer to run code before the framework itself is loaded. If you save code in +RAILS_ROOT/config/preinitializer.rb+, that code will be the first thing loaded, before any of the framework components (Active Record, Action Pack, and so on.) If you want to change the behavior of one of the classes that is used in the initialization process, you can do so in this file. == Configuring Rails Components +In general, the work of configuring Rails means configuring the components of Rails, as well as configuring Rails itself. The +environment.rb+ and environment-specific configuration files (such as +config/environments/production.rb+) allow you to specify the various settings that you want to pass down to all of the components. For example, the default Rails 2.3 +environment.rb+ file includes one setting: + +[source, ruby] +------------------------------------------------------- +config.time_zone = 'UTC' +------------------------------------------------------- + +This is a setting for Rails itself. If you want to pass settings to individual Rails components, you can do so via the same +config+ object: + +[source, ruby] +------------------------------------------------------- +config.active_record.colorize_logging = false +------------------------------------------------------- + +Rails will use that particular setting to configure Active Record. + === Configuring Active Record -+ActiveRecord::Base+ includej a variety of configuration options: ++ActiveRecord::Base+ includes a variety of configuration options: +logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed on to any new database connections made. You can retrieve this logger by calling +logger+ on either an ActiveRecord model class or an ActiveRecord model instance. Set to nil to disable logging. @@ -125,21 +145,21 @@ There are a number of settings available on +ActionMailer::Base+: +smtp_settings+ allows detailed configuration for the +:smtp+ delivery method. It accepts a hash of options, which can include any of these options: -* <tt>:address</tt> - Allows you to use a remote mail server. Just change it from its default "localhost" setting. -* <tt>:port</tt> - On the off chance that your mail server doesn't run on port 25, you can change it. -* <tt>:domain</tt> - If you need to specify a HELO domain, you can do it here. -* <tt>:user_name</tt> - If your mail server requires authentication, set the username in this setting. -* <tt>:password</tt> - If your mail server requires authentication, set the password in this setting. -* <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of <tt>:plain</tt>, <tt>:login</tt>, <tt>:cram_md5</tt>. +* +: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 detailed configuration for the +sendmail+ delivery method. It accepts a hash of options, which can include any of these options: -* <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>. -* <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt>. +* +:location+ - The location of the sendmail executable. Defaults to +/usr/sbin/sendmail+. +* +:arguments+ - The command line arguments. Defaults to +-i -t+. +raise_delivery_errors+ specifies whether to raise an error if email delivery cannot be completed. It defaults to +true+. -+delivery_method+ defines the delivery method. The allowed values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, and <tt>:test</tt>. ++delivery_method+ defines the delivery method. The allowed values are +:smtp+ (default), +:sendmail+, and +:test+. +perform_deliveries+ specifies whether mail will actually be delivered. By default this is +true+; it can be convenient to set it to +false+ for testing. @@ -151,7 +171,7 @@ There are a number of settings available on +ActionMailer::Base+: +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 -<tt>["text/html", "text/enriched", "text/plain"]</tt>. Items that appear first in the array have higher priority in the mail client ++["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. === Configuring Active Resource @@ -174,21 +194,46 @@ There are a few configuration options available in Active Support: Active Model currently has a single configuration setting: -+ActiveModel::Errors.default_error_messages is an array containing all of the validation error messages. ++ActiveModel::Errors.default_error_messages+ is an array containing all of the validation error messages. == Using Initializers - organization, controlling load order + +After it loads the framework plus any gems and plugins in your application, Rails turns to loading initializers. An initializer is any file of ruby code stored under +/config/initializers+ in your application. You can use initializers to hold configuration settings that should be made after all of the frameworks and plugins are loaded. + +NOTE: You can use subfolders to organize your initializers if you like, because Rails will look into the whole file hierarchy from the +initializers+ folder on down. + +TIP: If you have any ordering dependency in your initializers, you can control the load order by naming. For example, +01_critical.rb+ will be loaded before +02_normal.rb+. == Using an After-Initializer +After-initializers are run (as you might guess) after any initializers are loaded. You can supply an +after_initialize+ block (or an array of such blocks) by setting up +config.after_initialize+ in any of the Rails configuration files: + +[source, ruby] +------------------------------------------------------------------ +config.after_initialize do + SomeClass.init +end +------------------------------------------------------------------ + +WARNING: Some parts of your application, notably observers and routing, are not yet set up at the point where the +after_initialize+ block is called. + == Rails Environment Settings -ENV +Some parts of Rails can also be configured externally by supplying environment variables. The following environment variables are recognized by various parts of Rails: + ++ENV['RAILS_ENV']+ defines the Rails environment (production, development, test, and so on) that Rails will run under. + ++ENV['RAILS_RELATIVE_URL_ROOT']+ is used by the routing code to recognize URLs when you deploy your application to a subdirectory. + ++ENV["RAILS_ASSET_ID"]+ will override the default cache-busting timestamps that Rails generates for downloadable assets. + ++ENV["RAILS_CACHE_ID"]+ and +ENV["RAILS_APP_VERSION"]+ are used to generate expanded cache keys in Rails' caching code. This allows you to have multiple separate caches from the same application. + ++ENV['RAILS_GEM_VERSION']+ defines the version of the Rails gems to use, if +RAILS_GEM_VERSION+ is not defined in your +environment.rb+ file. == Changelog == http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/28[Lighthouse ticket] +* January 3, 2009: First reasonably complete draft by link:../authors.html#mgunderloy[Mike Gunderloy] * November 5, 2008: Rough outline by link:../authors.html#mgunderloy[Mike Gunderloy] - -need to look for def self. ??? diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index 88ca74a557..d60ed10a39 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -4,13 +4,12 @@ Mislav Marohnić <mislav.marohnic@gmail.com> 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 we will: +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; -* Build complex, multi-model 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. @@ -28,7 +27,7 @@ The most basic form helper is `form_tag`. 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): -.Sample rendering of `form_tag` +.Sample output from `form_tag` ---------------------------------------------------------------------------- <form action="/home/index" method="post"> <div style="margin:0;padding:0"> @@ -38,7 +37,7 @@ When called without arguments like this, it creates a form element that has the </form> ---------------------------------------------------------------------------- -If you carefully observe this output, you can see that the helper generated something we 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 which action isn't "GET" (provided that this security feature is enabled). NOTE: Throughout this guide, this `div` with the hidden input will be stripped away to have clearer code samples. @@ -52,9 +51,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 other. +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. -To create that, we will use `form_tag`, `label_tag`, `text_field_tag` and `submit_tag`, respectively. +To create that, you will use `form_tag`, `label_tag`, `text_field_tag` and `submit_tag`, respectively. .A basic search form ---------------------------------------------------------------------------- @@ -87,27 +86,27 @@ The above view code will result in the following markup: Besides `text_field_tag` and `submit_tag`, there is a similar helper for _every_ form control in HTML. -TIP: For every form input, an ID attribute is generated from its name ("q" in our example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript. +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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -By now we've seen that the `form_tag` helper accepts 2 arguments: the path for the action attribute and an options hash for parameters (like `:method`). +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. -Identical to the `link_to` helper, the path argument doesn't have to be given as string or a named route. It can be a hash of URL parameters that Rails' routing mechanism will turn into a valid URL. Still, we cannot simply write this: +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: .A bad way to pass multiple hashes as method arguments ---------------------------------------------------------------------------- -form_tag(:controller => "people", :action => "search", :method => "get") -# => <form action="/people/search?method=get" method="post"> +form_tag(:controller => "people", :action => "search", :method => "get", :class => "nifty_form") +# => <form action="/people/search?method=get&class=nifty_form" method="post"> ---------------------------------------------------------------------------- -Here we wanted to pass two hashes, but the Ruby interpreter sees only one hash, so Rails will construct a URL that we didn't want. The solution is to delimit the first hash (or both hashes) with curly brackets: +Here you wanted to pass two hashes, but the Ruby interpreter sees only one hash, so Rails will construct a URL with extraneous parameters. The solution is to delimit the first hash (or both hashes) with curly brackets: .The correct way of passing multiple hashes as arguments ---------------------------------------------------------------------------- -form_tag({:controller => "people", :action => "search"}, :method => "get") -# => <form action="/people/search" method="get"> +form_tag({:controller => "people", :action => "search"}, :method => "get", :class => "nifty_form") +# => <form action="/people/search" method="get" class="nifty_form"> ---------------------------------------------------------------------------- This is a common pitfall when using form helpers, since many of them accept multiple hashes. So in future, if a helper produces unexpected output, make sure that you have delimited the hash parameters properly. @@ -151,7 +150,7 @@ output: 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 we might mention are the text area, password input and hidden input: +Other form controls worth mentioning are the text area, password input and hidden input: ---------------------------------------------------------------------------- <%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %> @@ -174,7 +173,7 @@ 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 wanted method: +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") @@ -189,13 +188,49 @@ 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). +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 <<parameter_names,parameter names>> 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]. + +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`. + +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: + +--------------------------- +<%= text_field(:person, :name) %> +--------------------------- +will produce output similar to +--------------------------- +<input id="person_name" name="person[name]" type="text" value="Henry"/> +--------------------------- +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`. + +[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 ------------------------------------- -When we're dealing with an actual model, we will use a different set of form helpers and have Rails take care of some details in the background. In the following examples we will handle an Article model. First, let us have the controller create one: +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: .articles_controller.rb ---------------------------------------------------------------------------- @@ -204,11 +239,11 @@ def new end ---------------------------------------------------------------------------- -Now we switch to the view. The first thing to remember is that we should use `form_for` helper instead of `form_tag`, and that we should pass the model name and object as arguments: +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: .articles/new.html.erb ---------------------------------------------------------------------------- -<% form_for :article, @article, :url => { :action => "create" } do |f| %> +<% form_for :article, @article, :url => { :action => "create" }, :html => {:class => "nifty_form"} do |f| %> <%= f.text_field :title %> <%= f.text_area :body, :size => "60x12" %> <%= submit_tag "Create" %> @@ -217,39 +252,30 @@ Now we switch to the view. The first thing to remember is that we should use `fo There are a few things to note here: -1. `:article` is the name of the model and `@article` is our record. -2. The URL for the action attribute is passed as a parameter named `:url`. +1. `:article` is the name of the model and `@article` is the record. +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). -4. Methods to create form controls are called *on* the form builder object `f` and *without* the `"_tag"` suffix (so `text_field_tag` becomes `f.text_field`). +4. Methods to create form controls are called *on* the form builder object `f` The resulting HTML is: ---------------------------------------------------------------------------- -<form action="/articles/create" method="post"> +<form action="/articles/create" method="post" class="nifty_form"> <input id="article_title" name="article[title]" size="30" type="text" /> <textarea id="article_body" name="article[body]" cols="60" rows="12"></textarea> <input name="commit" type="submit" value="Create" /> </form> ---------------------------------------------------------------------------- +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 <<parameter_names,parameter names>> section. -A nice thing about `f.text_field` and other helper methods is that they will pre-fill the form control with the value read from the corresponding attribute in the model. For example, if we created the article instance by supplying an initial value for the title in the controller: - ----------------------------------------------------------------------------- -@article = Article.new(:title => "Rails makes forms easy") ----------------------------------------------------------------------------- - -... the corresponding input will be rendered with a value: - ----------------------------------------------------------------------------- -<input id="post_title" name="post[title]" size="30" type="text" value="Rails makes forms easy" /> ----------------------------------------------------------------------------- +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. Relying on record identification ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In the previous chapter we handled the Article model. This model is directly available to users of our application and, following the best practices for developing with Rails, we should declare it *a resource*. +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*. -When dealing with RESTful resources, our calls to `form_for` can get significantly easier if we rely on *record identification*. In short, we can just pass the model instance and have Rails figure out model name and the rest: +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: ---------------------------------------------------------------------------- ## Creating a new article @@ -265,10 +291,26 @@ form_for(:article, @article, :url => article_path(@article), :method => "put") 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?`. +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. 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. +Dealing with namespaces +^^^^^^^^^^^^^^^^^^^^^^^ + +If you have created namespaced routes `form_for` has a nifty shorthand for that too. If your application has an admin namespace then +------- +form_for [:admin, @article] +------- +will create a form that submits to the articles controller inside the admin namespace (submitting to `admin_article_path(@article)` in the case of an update). If you have several levels of namespacing then the syntax is similar: + +------- +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]. + Making select boxes with ease ----------------------------- @@ -286,20 +328,18 @@ Here is what our wanted markup might look like: </select> ---------------------------------------------------------------------------- -Here we have a list of cities where their names are presented to the user, but internally we want to handle just their IDs so we keep them in value attributes. Let's see how Rails can help out here. +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. The select tag and options ~~~~~~~~~~~~~~~~~~~~~~~~~~ -The most generic helper is `select_tag`, which -- as the name implies -- simply generates the `SELECT` tag that encapsulates the options: +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, '<option value="1">Lisabon</option>...') %> ---------------------------------------------------------------------------- -This is a start, but it doesn't dynamically create our option tags. We had to pass them in as a string. - -We can generate option tags with the `options_for_select` helper: +This is a start, but it doesn't dynamically create our option tags. You can generate option tags with the `options_for_select` helper: ---------------------------------------------------------------------------- <%= options_for_select([['Lisabon', 1], ['Madrid', 2], ...]) %> @@ -311,18 +351,18 @@ output: ... ---------------------------------------------------------------------------- -For input data we used a nested array where each element has two elements: visible value (name) and internal value (ID). +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. -Now you can combine `select_tag` and `options_for_select` to achieve the desired, complete markup: +Knowing this, you can combine `select_tag` and `options_for_select` to achieve the desired, complete markup: ---------------------------------------------------------------------------- <%= select_tag(:city_id, options_for_select(...)) %> ---------------------------------------------------------------------------- -Sometimes, depending on our application's needs, we also wish a specific option to be pre-selected. The `options_for_select` helper supports this with an optional second argument: +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(cities_array, 2) %> +<%= options_for_select([['Lisabon', 1], ['Madrid', 2], ...], 2) %> output: @@ -333,13 +373,372 @@ 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. +[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. + +============================================================================ + Select boxes for dealing with models ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Until now we've covered 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 we have a "Person" model with a `city_id` attribute. +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`. ---------------------------------------------------------------------------- -... +# controller: +@person = Person.new(:city_id => 2) + +# view: +<%= select(:person, :city_id, [['Lisabon', 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: + +---------------------------------------------------------------------------- +# select on a form builder +<%= f.select(:city_id, ...) %> +---------------------------------------------------------------------------- + +[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 +-------- +ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got Fixnum(#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. +============================ +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: + +---------------------------------------------------------------------------- +<% cities_array = City.find(:all).map { |city| [city.name, city.id] } %> +<%= options_for_select(cities_array) %> +---------------------------------------------------------------------------- + +This is a perfectly valid solution, but Rails provides a less verbose alternative: `options_from_collection_for_select`. This helper expects a collection of arbitrary objects and two additional arguments: the names of the methods to read the option *value* and *text* from, respectively: + +---------------------------------------------------------------------------- +<%= options_from_collection_for_select(City.all, :id, :name) %> ---------------------------------------------------------------------------- -...
\ No newline at end of file +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`: + +---------------------------------------------------------------------------- +<%= collection_select(:person, :city_id, City.all, :id, :name) %> +---------------------------------------------------------------------------- + +To recap, `options_from_collection_for_select` is to `collection_select` what `options_for_select` is to `select`. + +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: + +---------------------------------------------------------------------------- +<%= time_zone_select(:person, :city_id) %> +---------------------------------------------------------------------------- + +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) + +Date and time select boxes +-------------------------- + +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 + +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 %> +----------- +outputs (with the actual option values omitted for brevity) +----------- +<select id="start_date_year" name="start_date[year]"> ... </select> +<select id="start_date_month" name="start_date[month]"> ... </select> +<select id="start_date_day" name="start_date[day]"> ... </select> +----------- +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) +----------- +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`. + +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 +--------------- +<%= date_select :person, :birth_date %> +--------------- +outputs (with the actual option values omitted for brevity) +-------------- +<select id="person_birth_date_1i" name="person[birth_date(1i)]"> ... </select> +<select id="person_birth_date_2i" name="person[birth_date(2i)]"> ... </select> +<select id="person_birth_date_3i" name="person[birth_date(3i)]"> ... </select> +-------------- +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`. + +Common options +~~~~~~~~~~~~~~ +Both families of helpers use the same core set of functions to generate the individual select tags and so both accept largely the same options. In particular, by default Rails will generate year options 5 years either side of the current year. If this is not an appropriate range, the `:start_year` and `:end_year` options override this. For an exhaustive list of the available options, refer to the http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html[API documentation]. + +As a rule of thumb you should be using `date_select` when working with model objects and `select_date` in others cases, such as a search form which filters results by date. + +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. + +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> +------------- + +File Uploads +-------------- +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. +----------- +<% form_tag({:action => :upload}, :multipart => true) do %> + <%= file_field_tag 'picture' %> +<% end %> + +<% form_for @person, :html => {:multipart => true} do |f| %> + <%= f.file_field :picture %> +<% 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]`. + +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). + +[source, ruby] +----------------- +def upload + uploaded_io = params[:person][:picture] + File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'w') do |file| + file.write(uploaded_io.read) + end +end +---------------- + +Once a file has been uploaded there are a multitude of potential tasks, ranging from where to store the files (on disk, Amazon S3, etc) and associating them with models to resizing image files and generating thumbnails. The intricacies of this are beyond the scope of this guide, but there are several plugins designed to assist with these. Two of the better known ones are http://github.com/technoweenie/attachment_fu[Attachment-Fu] and http://www.thoughtbot.com/projects/paperclip[Paperclip]. + +NOTE: If the user has not selected a file the corresponding parameter will be an empty string. + +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. + +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. + +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. + +[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"} +------------- +======================== + +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 +----------------- +<input id="person_name" name="person[name]" type="text" value="Henry"/> +----------------- +the params hash will contain + +[source, ruby] +----------------- +{'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 + +[source, ruby] +----------------- +{'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: +----------------- +<input name="person[phone_number][]" type="text"/> +<input name="person[phone_number][]" type="text"/> +<input name="person[phone_number][]" type="text"/> +----------------- +This would result in `params[:person][:phone_number]` being an array. + +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 +----------------- +<input name="addresses[][line1]" type="text"/> +<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. + +[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. + +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 + +-------- +<% form_for @person do |person_form| %> + <%= 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 %> + <% end %> + <% end %> +<% end %> +-------- +Assuming our 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"> + <input id="person_name" name="person[name]" size="30" type="text" /> + <input id="person_address_23_city" name="person[address][23][city]" size="30" type="text" /> + <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 + +[source, ruby] +-------- +{'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). + +To create more intricate nestings, you can specify the first part of the input name (`person[address]` in the previous example) explicitly, for example +-------- +<% fields_for 'person[address][primary]', address, :index => address do |address_form| %> + <%= address_form.text_field :city %> +<% end %> +-------- +will create inputs like +-------- +<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 shortcut you can append [] to the name and omit the `:index` option. This is the same as specifing `:index => address` so +-------- +<% fields_for 'person[address][primary][]', address do |address_form| %> + <%= address_form.text_field :city %> +<% end %> +-------- +produces exactly the same output as the previous example. + +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: + +* 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] +* Eloy Duran's http://github.com/alloy/complex-form-examples/tree/alloy-nested_params[nested_params] plugin +* Lance Ivy's http://github.com/cainlevy/nested_assignment/tree/master[nested_assignment] plugin and http://github.com/cainlevy/complex-form-examples/tree/cainlevy[sample application] +* James Golick's http://github.com/giraffesoft/attribute_fu/tree[attribute_fu] plugin + +== Changelog == + +http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/1[Lighthouse ticket] + +.Authors +* Mislav Marohnić <mislav.marohnic@gmail.com> +* link:../authors.html#fcheung[Frederick Cheung] diff --git a/railties/doc/guides/source/getting_started_with_rails.txt b/railties/doc/guides/source/getting_started_with_rails.txt index 58eff9fd3d..7e87c2935e 100644 --- a/railties/doc/guides/source/getting_started_with_rails.txt +++ b/railties/doc/guides/source/getting_started_with_rails.txt @@ -163,24 +163,23 @@ $ cd blog In any case, Rails will create a folder in your working directory called +blog+. Open up that folder and explore its contents. Most of the work in this tutorial will happen in the +app/+ folder, but here's a basic rundown on the function of each folder that Rails creates in a new application by default: -[grid="all"] -`-----------`----------------------------------------------------------------------------------------------------------------------------- -File/Folder Purpose ------------------------------------------------------------------------------------------------------------------------------------------- -+README+ This is a brief instruction manual for your application. Use it to tell others what your application does, how to set it up, and so on. -+Rakefile+ This file contains batch jobs that can be run from the terminal. -+app/+ Contains the controllers, models, and views for your application. You'll focus on this folder for the remainder of this guide. -+config/+ Configure your application's runtime rules, routes, database, and more. -+db/+ Shows your current database schema, as well as the database migrations. You'll learn about migrations shortly. -+doc/+ In-depth documentation for your application. -+lib/+ Extended modules for your application (not covered in this guide). -+log/+ Application log files. -+public/+ The only folder seen to the world as-is. This is where your images, javascript, stylesheets (CSS), and other static files go. -+script/+ Scripts provided by Rails to do recurring tasks, such as benchmarking, plugin installation, and starting the console or the web server. -+test/+ Unit tests, fixtures, and other test apparatus. These are covered in link:../testing_rails_applications.html[Testing Rails Applications] -+tmp/+ Temporary files -+vendor/+ A place for third-party code. In a typical Rails application, this includes Ruby Gems, the Rails source code (if you install it into your project) and plugins containing additional prepackaged functionality. -------------------------------------------------------------------------------------------------------------------------------------------- +[options="header"] +|========================================================================================================== +|File/Folder |Purpose +|+README+ |This is a brief instruction manual for your application. Use it to tell others what your application does, how to set it up, and so on. +|+Rakefile+ |This file contains batch jobs that can be run from the terminal. +|+app/+ |Contains the controllers, models, and views for your application. You'll focus on this folder for the remainder of this guide. +|+config/+ |Configure your application's runtime rules, routes, database, and more. +|+db/+ |Shows your current database schema, as well as the database migrations. You'll learn about migrations shortly. +|+doc/+ |In-depth documentation for your application. +|+lib/+ |Extended modules for your application (not covered in this guide). +|+log/+ |Application log files. +|+public/+ |The only folder seen to the world as-is. This is where your images, javascript, stylesheets (CSS), and other static files go. +|+script/+ |Scripts provided by Rails to do recurring tasks, such as benchmarking, plugin installation, and starting the console or the web server. +|+test/+ |Unit tests, fixtures, and other test apparatus. These are covered in link:../testing_rails_applications.html[Testing Rails Applications] +|+tmp/+ |Temporary files +|+vendor/+ |A place for third-party code. In a typical Rails application, this includes Ruby Gems, the Rails source code (if you install it into your project) and plugins containing additional prepackaged functionality. +|========================================================================================================== === Configuring a Database @@ -339,25 +338,24 @@ NOTE: While scaffolding will get you up and running quickly, the "one size fits 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: -[grid="all"] -`---------------------------------------------`-------------------------------------------------------------------------------------------- -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) -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 -app/views/posts/edit.html.erb A view to edit an existing post -app/views/layouts/posts.html.erb A view to control the overall look and feel of the other posts views -public/stylesheets/scaffold.css Cascading style sheet to make the scaffolded views look better -app/controllers/posts_controller.rb The Posts controller -test/functional/posts_controller_test.rb Functional testing harness for the posts controller -app/helpers/posts_helper.rb Helper functions to be used from the posts views -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 -------------------------------------------------------------------------------------------------------------------------------------------- +[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) +|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 +|app/views/posts/edit.html.erb |A view to edit an existing post +|app/views/layouts/posts.html.erb |A view to control the overall look and feel of the other posts views +|public/stylesheets/scaffold.css |Cascading style sheet to make the scaffolded views look better +|app/controllers/posts_controller.rb |The Posts controller +|test/functional/posts_controller_test.rb |Functional testing harness for the posts controller +|app/helpers/posts_helper.rb |Helper functions to be used from the posts views +|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 +|========================================================================================================== === Running a Migration diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index ba3cc42a5b..e80de7adc9 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -1,32 +1,38 @@ -The Rails Internationalization API -================================== +The Rails Internationalization (I18n) API +========================================= -The Ruby I18n 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 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. == How I18n in Ruby on Rails works -Internationalization is a complex problem. Natural languages differ in so many ways that it is hard to provide tools for solving all problems at once. For that reason the Rails I18n API focusses on: +Internationalization is a complex problem. Natural languages differ in so many ways (eg. in pluralization rules) that it is hard to provide tools for solving all problems at once. For that reason the Rails I18n API focuses on: * 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. + === The overall architecture of the library Thus, the Ruby I18n gem is split into two parts: -* The public API which is just a Ruby module with a bunch of public methods and definitions how the library works. -* A shipped backend (which is intentionally named the Simple backend) that implements these methods. +* The public API of the i18n framework -- a Ruby module with public methods and definitions how the library works +* A default backend (which is intentionally named _Simple_ backend) that implements these methods -As a user you should always only access the public methods on the I18n module but it is useful to know about the capabilities of the backend you use and maybe exchange the shipped Simple backend with a more powerful one. +As a user you should always only access the public methods on the I18n module, but it is useful to know about the capabilities of the backend. + +NOTE: It is possible (or even desirable) to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section <<_using_different_backends,Using different backends>> below. === The public I18n API -We will go into more detail about the public methods later but here's a quick overview. The most important methods are: +The most important methods of the I18n API are: [source, ruby] ------------------------------------------------------- -translate # lookup translations -localize # localize Date and Time objects to local formats +translate # Lookup text translations +localize # Localize Date and Time objects to local formats ------------------------------------------------------- These have the aliases #t and #l so you can use them like this: @@ -41,28 +47,42 @@ There are also attribute readers and writers for the following attributes: [source, ruby] ------------------------------------------------------- -load_path # announce your custom translation files -locale # get and set the current locale -default_locale # get and set the default locale -exception_handler # use a different exception_handler -backend # use a different backend +load_path # Announce your custom translation files +locale # Get and set the current locale +default_locale # Get and set the default locale +exception_handler # Use a different exception_handler +backend # Use a different backend ------------------------------------------------------- -== Walkthrough: setup a simple I18n'ed Rails application +So, let's internationalize a simple Rails application from the ground up in the next chapters! + +== Setup the Rails application for internationalization -There are just a few, simple steps to get up and running with a I18n support for your application. +There are just a few, simple steps to get up and running with I18n support for your application. === Configure the I18n module -Rails will wire up all required settings for you with sane defaults. If you need different settings you can overwrite them easily. +Following the _convention over configuration_ philosophy, Rails will set-up your application with reasonable defaults. If you need different settings, you can overwrite them easily. + +Rails adds all +.rb+ and +.yml+ files from +config/locales+ directory to your *translations load path*, automatically. + +See the default +en.yml+ locale in this directory, containing a sample pair of translation strings: + +[source, ruby] +------------------------------------------------------- +en: + hello: "Hello world" +------------------------------------------------------- -The I18n library will use English (:en) as a *default locale* by default. I.e if you don't set a different locale, :en will be used for looking up translations. Also, Rails adds all files from config/locales/*.rb,yml to your translations load path. +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. -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. +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. -(Hint: The backend will lazy-load these translations when a translation is looked up for the first time. This makes it possible to just swap the backend with something else even after translations have already been announced.) +The *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. -The default environment.rb says: +NOTE: The backend will lazy-load these translations when a translation is looked up for the first time. This makes it possible to just swap the backend with something else even after translations have already been announced. + +The default +environment.rb+ files has instruction how to add locales from another directory and how to set different default locale. Just uncomment and edit the specific lines. [source, ruby] ------------------------------------------------------- @@ -75,22 +95,22 @@ The default environment.rb says: === Optional: custom I18n configuration setup -For the sake of completeness let's mention that if you do not want to use the environment for some reason you can always wire up things manually, too. +For the sake of completeness, let's mention that if you do not want to use the +environment.rb+ file for some reason, you can always wire up things manually, too. -To tell the I18n library where it can find your custom translation files you can specify the load path anywhere in your application - just make sure it gets run before any translations are actually looked up. You might also want to change the default locale. The simplest thing possible is to put the following into an initializer: +To tell the I18n library where it can find your custom translation files you can specify the load path anywhere in your application - just make sure it gets run before any translations are actually looked up. You might also want to change the default locale. The simplest thing possible is to put the following into an *initializer*: [source, ruby] ------------------------------------------------------- # in config/initializer/locale.rb # tell the I18n library where to find your translations -I18n.load_path += Dir[ File.join(RAILS_ROOT, 'lib', 'locale', '*.{rb,yml}') ] +I18n.load_path << Dir[ File.join(RAILS_ROOT, 'lib', 'locale', '*.{rb,yml}') ] -# you can omit this if you're happy with English as a default locale +# set default locale to something else then :en I18n.default_locale = :pt ------------------------------------------------------- -=== Set the locale in each request +=== 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). @@ -510,25 +530,30 @@ So, for example, instead of the default error message "can not be blank" you cou count and/or value are available where applicable. Count can be used for pluralization if present: -|================================================================================== -| validation | with option | message | interpolation -| validates_confirmation_of | - | :confirmation | - -| validates_acceptance_of | - | :accepted | - -| validates_presence_of | - | :blank | - -| validates_length_of | :within, :in | :too_short | count -| validates_length_of | :within, :in | :too_long | count -| 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 | :odd | :odd | value -| validates_numericality_of | :even | :even | value -|================================================================================== +|===================================================================================================== +| validation | with option | message | interpolation +| validates_confirmation_of | - | :confirmation | - +| validates_acceptance_of | - | :accepted | - +| validates_presence_of | - | :blank | - +| validates_length_of | :within, :in | :too_short | count +| validates_length_of | :within, :in | :too_long | count +| 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 +|===================================================================================================== ==== Translations for the ActiveRecord error_messages_for helper @@ -624,9 +649,6 @@ 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. -== Credits - -== NOTES - -How to contribute? +== Changelog == +http://rails.lighthouseapp.com/projects/16213/tickets/23[Lighthouse ticket] diff --git a/railties/doc/guides/source/images/customized_error_messages.png b/railties/doc/guides/source/images/customized_error_messages.png Binary files differnew file mode 100644 index 0000000000..fa676991e3 --- /dev/null +++ b/railties/doc/guides/source/images/customized_error_messages.png diff --git a/railties/doc/guides/source/images/error_messages.png b/railties/doc/guides/source/images/error_messages.png Binary files differnew file mode 100644 index 0000000000..32de1cac21 --- /dev/null +++ b/railties/doc/guides/source/images/error_messages.png diff --git a/railties/doc/guides/source/images/validation_error_messages.png b/railties/doc/guides/source/images/validation_error_messages.png Binary files differnew file mode 100644 index 0000000000..622d35da5d --- /dev/null +++ b/railties/doc/guides/source/images/validation_error_messages.png diff --git a/railties/doc/guides/source/index.txt b/railties/doc/guides/source/index.txt index a5648fb757..b32d8ef7b1 100644 --- a/railties/doc/guides/source/index.txt +++ b/railties/doc/guides/source/index.txt @@ -1,6 +1,11 @@ -Ruby on Rails guides +Ruby on Rails Guides ==================== +These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together. There are two different versions of the Guides site, and you should be sure to use the one that applies to your situation: + +* http://guides.rubyonrails.org/[Current Release version] - based on Rails 2.2 +* http://guides.rails.info/[Edge Rails version] - based on the Rails 2.3 branch + WARNING: This page is the result of ongoing http://hackfest.rubyonrails.org/guide[Rails Guides hackfest] and a work in progress. CAUTION: Guides marked with this icon are currently being worked on. While they might still be useful to you, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections at the respective Lighthouse ticket. @@ -23,16 +28,23 @@ Everything you need to know to install Rails and create your first application. This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner. *********************************************************** +.link:activerecord_validations_callbacks.html[Active Record Validations and Callbacks] +*********************************************************** +CAUTION: link:http://rails.lighthouseapp.com/projects/16213/tickets/26[Lighthouse Ticket] + +This guide covers how you can use Active Record validations and callbacks. +*********************************************************** + .link:association_basics.html[Active Record Associations] *********************************************************** This guide covers all the associations provided by Active Record. *********************************************************** -.link:finders.html[Active Record Finders] +.link:active_record_querying.html[Active Record Query Interface] *********************************************************** CAUTION: link:http://rails.lighthouseapp.com/projects/16213/tickets/16[Lighthouse Ticket] -This guide covers the find method defined in ActiveRecord::Base, as well as named scopes. +This guide covers the database query interface provided by Active Record. *********************************************************** ++++++++++++++++++++++++++++++++++++++ @@ -101,11 +113,9 @@ ways of achieving this and how to understand what is happening "behind the scene of your code. *********************************************************** -.link:benchmarking_and_profiling.html[Benchmarking and Profiling Rails Applications] +.link:performance_testing.html[Performance Testing Rails Applications] *********************************************************** -CAUTION: link:http://rails.lighthouseapp.com/projects/16213/tickets/4[Lighthouse Ticket] - -This guide covers ways to analyze and optimize your running Rails code. +This guide covers ways to benchmark and profile your Rails application. *********************************************************** .link:creating_plugins.html[The Basics of Creating Rails Plugins] @@ -115,13 +125,20 @@ This guide covers how to build a plugin to extend the functionality of Rails. .link:i18n.html[The Rails Internationalization API] *********************************************************** -CAUTION: still a basic draft +CAUTION: link:http://rails.lighthouseapp.com/projects/16213/tickets/23[Lighthouse ticket] This guide introduces you to the basic concepts and features of the Rails I18n API and shows you how to localize your application. *********************************************************** +.link:configuring.html[Configuring Rails Applications] +*********************************************************** +This guide covers the basic configuration settings for a Rails application. +*********************************************************** - +.link:command_line.html[Rails Command Line Tools and Rake tasks] +*********************************************************** +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]. diff --git a/railties/doc/guides/source/layouts_and_rendering.txt b/railties/doc/guides/source/layouts_and_rendering.txt index 8f1fae5007..23cb83c512 100644 --- a/railties/doc/guides/source/layouts_and_rendering.txt +++ b/railties/doc/guides/source/layouts_and_rendering.txt @@ -6,6 +6,7 @@ This guide covers the basic layout features of Action Controller and Action View * Use the various rendering methods built in to Rails * Create layouts with multiple content sections * Use partials to DRY up your views +* Use nested layouts (sub-templates) == Overview: How the Pieces Fit Together @@ -34,9 +35,9 @@ def show end ------------------------------------------------------- -Rails will automatically render +app/views/books/show.html.erb+ after running the method. In fact, if you have the default catch-all route in place (+map.connect ':controller/:action/:id'+), Rails will even render views that don't have any code at all in the controller. For example, if you have the default route in place and a request comes in for +/books/sale_list+, Rails will render +app/views/books/sale_list.html.erb+ in response. +Rails will automatically render +app/views/books/show.html.erb+ after running the method. In fact, if you have the default catch-all route in place (+map.connect \':controller/:action/:id'+), Rails will even render views that don't have any code at all in the controller. For example, if you have the default route in place and a request comes in for +/books/sale_list+, Rails will render +app/views/books/sale_list.html.erb+ in response. -NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), +.rjs+ for RJS (javascript with embedded ruby) and +.builder+ for Builder (XML generator). You'll also find +.rhtml+ used for ERB templates and .rxml for Builder templates, but those extensions are now formally deprecated and will be removed from a future version of Rails. +NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), +.rjs+ for RJS (javascript with embedded ruby) and +.builder+ for Builder (XML generator). You'll also find +.rhtml+ used for ERB templates and +.rxml+ for Builder templates, but those extensions are now formally deprecated and will be removed from a future version of Rails. === Using +render+ @@ -57,9 +58,9 @@ This will send an empty response to the browser (though it will include any stat TIP: You should probably be using the +head+ method, discussed later in this guide, instead of +render :nothing+. This provides additional flexibility and makes it explicit that you're only generating HTTP headers. -==== Using +render+ with +:action+ +==== Rendering an Action's View -If you want to render the view that corresponds to a different action within the same template, you can use +render+ with the +:action+ option: +If you want to render the view that corresponds to a different action within the same template, you can use +render+ with the name of the view: [source, ruby] ------------------------------------------------------- @@ -68,28 +69,71 @@ def update if @book.update_attributes(params[:book]) redirect_to(@book) else - render :action => "edit" + render "edit" end end end ------------------------------------------------------- +If the call to +update_attributes+ fails, calling the +update+ action in this controller will render the +edit.html.erb+ template belonging to the same controller. -If the call to +update_attributes_ fails, calling the +update+ action in this controller will render the +edit.html.erb+ template belonging to the same controller. +If you prefer, you can use a symbol instead of a string to specify the action to render: + +[source, ruby] +------------------------------------------------------- +def update + @book = Book.find(params[:id]) + if @book.update_attributes(params[:book]) + redirect_to(@book) + else + render :edit + end + end +end +------------------------------------------------------- + +To be explicit, you can use +render+ with the +:action+ option (though this is no longer necessary as of Rails 2.3): + +[source, ruby] +------------------------------------------------------- +def update + @book = Book.find(params[:id]) + if @book.update_attributes(params[:book]) + redirect_to(@book) + else + render :action => "edit" + end + end +end +------------------------------------------------------- WARNING: Using +render+ with +:action+ is a frequent source of confusion for Rails newcomers. The specified action is used to determine which view to render, but Rails does _not_ run any of the code for that action in the controller. Any instance variables that you require in the view must be set up in the current action before calling +render+. -==== Using +render+ with +:template+ +==== Rendering an Action's Template from Another Controller -What if you want to render a template from an entirely different controller from the one that contains the action code? You can do that with the +:template+ option to +render+, which accepts the full path (relative to +app/views+) of the template to render. For example, if you're running code in an +AdminProductsController+ that lives in +app/controllers/admin+, you can render the results of an action to a template in +app/views/products+ this way: +What if you want to render a template from an entirely different controller from the one that contains the action code? You can also do that with +render+, which accepts the full path (relative to +app/views+) of the template to render. For example, if you're running code in an +AdminProductsController+ that lives in +app/controllers/admin+, you can render the results of an action to a template in +app/views/products+ this way: + +[source, ruby] +------------------------------------------------------- +render 'products/show' +------------------------------------------------------- + +Rails knows that this view belongs to a different controller because of the embedded slash character in the string. If you want to be explicit, you can use the +:template+ option (which was required on Rails 2.2 and earlier): [source, ruby] ------------------------------------------------------- render :template => 'products/show' ------------------------------------------------------- -==== Using +render+ with +:file+ +==== Rendering an Arbitrary File + +The +render+ method can also use a view that's entirely outside of your application (perhaps you're sharing views between two Rails applications): + +[source, ruby] +------------------------------------------------------- +render "/u/apps/warehouse_app/current/app/views/products/show" +------------------------------------------------------- -If you want to use a view that's entirely outside of your application (perhaps you're sharing views between two Rails applications), you can use the +:file+ option to +render+: +Rails determines that this is a file render because of the leading slash character. To be explicit, you can use the +:file+ option (which was required on Rails 2.2 and earlier): [source, ruby] ------------------------------------------------------- @@ -98,7 +142,9 @@ render :file => "/u/apps/warehouse_app/current/app/views/products/show" The +:file+ option takes an absolute file-system path. Of course, you need to have rights to the view that you're using to render the content. -NOTE: By default, if you use the +:file+ option, the file is rendered without using the current layout. If you want Rails to put the file into the current layout, you need to add the +:layout => true+ option +NOTE: By default, the file is rendered without using the current layout. If you want Rails to put the file into the current layout, you need to add the +:layout => true+ option. + +TIP: If you're running on Microsoft Windows, you should use the +:file+ option to render a file, because Windows filenames do not have the same format as Unix filenames. ==== Using +render+ with +:inline+ @@ -931,10 +977,59 @@ Rails determines the name of the partial to use by looking at the model name in In this case, Rails will use the customer or employee partials as appropriate for each member of the collection. +=== Using Nested Layouts + +You may find that your application requires a layout that differs slightly from your regular application layout to support one particular controller. Rather than repeating the main layout and editing it, you can accomplish this by using nested layouts (sometimes called sub-templates). Here's an example: + +Suppose you have the follow ApplicationController layout: + ++app/views/layouts/application.erb+ + +[source, html] +------------------------------------------------------- +<html> +<head> + <title><%= @page_title %><title> + <% stylesheet_tag 'layout' %> + <style type="text/css"><%= yield :stylesheets %></style> +<head> +<body> + <div id="top_menu">Top menu items here</div> + <div id="menu">Menu items here</div> + <div id="main"><%= yield %></div> +</body> +</html> +------------------------------------------------------- + +On pages generated by NewsController, you want to hide the top menu and add a right menu: + ++app/views/layouts/news.erb+ + +[source, html] +------------------------------------------------------- +<% content_for :stylesheets do %> + #top_menu {display: none} + #right_menu {float: right; background-color: yellow; color: black} +<% end -%> +<% content_for :main %> + <div id="right_menu">Right menu items here</div> + <%= yield %> + <% end -%> +<% render :file => 'layouts/application' %> +------------------------------------------------------- + +NOTE: In versions of Rails before Rails 2.3, you should use +render \'layouts/applications\'+ instead of +render :file => \'layouts/applications\'+ + +That's it. The News views will use the new layout, hiding the top menu and adding a new right menu inside the "content" div. + +There are several ways of getting similar results with differents sub-templating schemes using this technique. Note that there is no limit in nesting levels. One can use the +ActionView::render+ method via +render \'layouts/news\'+ to base a new layout on the News layout. + == Changelog == http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/15[Lighthouse ticket] +* December 27, 2008: Merge patch from Rodrigo Rosenfeld Rosas covering subtemplates +* December 27, 2008: Information on new rendering defaults by link:../authors.html#mgunderloy[Mike Gunderloy] * November 9, 2008: Added partial collection counter by link:../authors.html#mgunderloy[Mike Gunderloy] * November 1, 2008: Added +:js+ option for +render+ by link:../authors.html#mgunderloy[Mike Gunderloy] * October 16, 2008: Ready for publication by link:../authors.html#mgunderloy[Mike Gunderloy] diff --git a/railties/doc/guides/source/performance_testing.txt b/railties/doc/guides/source/performance_testing.txt new file mode 100644 index 0000000000..250cf290a2 --- /dev/null +++ b/railties/doc/guides/source/performance_testing.txt @@ -0,0 +1,560 @@ +Performance Testing Rails Applications +====================================== + +This guide covers the various ways of performance testing a Ruby on Rails application. By referring to this guide, you will be able to: + +* Understand the various types of benchmarking and profiling metrics +* Generate performance and benchmarking tests +* Use a GC-patched Ruby binary to measure memory usage and object allocation +* Understand the benchmarking information provided by Rails inside the log files +* Learn about various tools facilitating benchmarking and profiling + +Performance testing is an integral part of the development cycle. It is very important that you don't make your end users wait for too long before the page is completely loaded. Ensuring a pleasant browsing experience for end users and cutting the cost of unnecessary hardware is important for any non-trivial web application. + +== Performance Test Cases == + +Rails performance tests are a special type of integration tests, designed for benchmarking and profiling the test code. With performance tests, you can determine where your application's memory or speed problems are coming from, and get a more in-depth picture of those problems. + +In a freshly generated Rails application, +test/performance/browsing_test.rb+ contains an example of a performance test: + +[source, ruby] +---------------------------------------------------------------------------- +require 'test_helper' +require 'performance_test_help' + +# Profiling results for each test method are written to tmp/performance. +class BrowsingTest < ActionController::PerformanceTest + def test_homepage + get '/' + end +end +---------------------------------------------------------------------------- + +This example is a simple performance test case for profiling a GET request to the application's homepage. + +=== Generating performance tests === + +Rails provides a generator called +performance_test+ for creating new performance tests: + +[source, shell] +---------------------------------------------------------------------------- +script/generate performance_test homepage +---------------------------------------------------------------------------- + +This generates +homepage_test.rb+ in the +test/performance+ directory: + +[source, ruby] +---------------------------------------------------------------------------- +require 'test_helper' +require 'performance_test_help' + +class HomepageTest < ActionController::PerformanceTest + # Replace this with your real tests. + def test_homepage + get '/' + end +end +---------------------------------------------------------------------------- + +=== Examples === + +Let's assume your application has the following controller and model: + +[source, ruby] +---------------------------------------------------------------------------- +# routes.rb +map.root :controller => 'home' +map.resources :posts + +# home_controller.rb +class HomeController < ApplicationController + def dashboard + @users = User.last_ten(:include => :avatars) + @posts = Post.all_today + end +end + +# posts_controller.rb +class PostsController < ApplicationController + def create + @post = Post.create(params[:post]) + redirect_to(@post) + end +end + +# post.rb +class Post < ActiveRecord::Base + before_save :recalculate_costly_stats + + def slow_method + # I fire gallzilion queries sleeping all around + end + + private + + def recalculate_costly_stats + # CPU heavy calculations + end +end +---------------------------------------------------------------------------- + +==== Controller Example ==== + +Because performance tests are a special kind of integration test, you can use the +get+ and +post+ methods in them. + +Here's the performance test for +HomeController#dashboard+ and +PostsController#create+: + +[source, ruby] +---------------------------------------------------------------------------- +require 'test_helper' +require 'performance_test_help' + +class PostPerformanceTest < ActionController::PerformanceTest + def setup + # Application requires logged-in user + login_as(:lifo) + end + + def test_homepage + get '/dashboard' + end + + def test_creating_new_post + post '/posts', :post => { :body => 'lifo is fooling you' } + end +end +---------------------------------------------------------------------------- + +You can find more details about the +get+ and +post+ methods in the link:../testing_rails_applications.html#mgunderloy[Testing Rails Applications] guide. + +==== Model Example ==== + +Even though the performance tests are integration tests and hence closer to the request/response cycle by nature, you can still performance test pure model code. + +Performance test for +Post+ model: + +[source, ruby] +---------------------------------------------------------------------------- +require 'test_helper' +require 'performance_test_help' + +class PostModelTest < ActionController::PerformanceTest + def test_creation + Post.create :body => 'still fooling you', :cost => '100' + end + + def test_slow_method + # Using posts(:awesome) fixture + posts(:awesome).slow_method + end +end +---------------------------------------------------------------------------- + +=== Modes === + +Performance tests can be run in two modes : Benchmarking and Profiling. + +==== Benchmarking ==== + +Benchmarking helps find out how fast each performance test runs. Each test case is run +4 times+ in benchmarking mode. + +To run performance tests in benchmarking mode: + +[source, shell] +---------------------------------------------------------------------------- +$ rake test:benchmark +---------------------------------------------------------------------------- + +==== Profiling ==== + +Profiling helps you see the details of a performance test and provide an in-depth picture of the slow and memory hungry parts. Each test case is run +1 time+ in profiling mode. + +To run performance tests in profiling mode: + +[source, shell] +---------------------------------------------------------------------------- +$ rake test:profile +---------------------------------------------------------------------------- + +=== Metrics === + +Benchmarking and profiling run performance tests in various modes described below. + +==== Wall Time ==== + +Wall time measures the real world time elapsed during the test run. It is affected by any other processes concurrently running on the system. + +Mode : Benchmarking + +==== Process Time ==== + +Process time measures the time taken by the process. It is unaffected by any other processes running concurrently on the same system. Hence, process time is likely to be constant for any given performance test, irrespective of the machine load. + +Mode : Profiling + +==== Memory ==== + +Memory measures the amount of memory used for the performance test case. + +Mode : Benchmarking, Profiling [xref:gc[Requires GC-Patched Ruby]] + +==== Objects ==== + +Objects measures the number of objects allocated for the performance test case. + +Mode : Benchmarking, Profiling [xref:gc[Requires GC-Patched Ruby]] + +==== GC Runs ==== + +GC Runs measures the number of times GC was invoked for the performance test case. + +Mode : Benchmarking [xref:gc[Requires GC-Patched Ruby]] + +==== GC Time ==== + +GC Time measures the amount of time spent in GC for the performance test case. + +Mode : Benchmarking [xref:gc[Requires GC-Patched Ruby]] + +=== Understanding the output === + +Performance tests generate different outputs inside +tmp/performance+ directory depending on their mode and metric. + +==== Benchmarking ==== + +In benchmarking mode, performance tests generate two types of outputs : + +===== Command line ===== + +This is the primary form of output in benchmarking mode. Example : + +[source, shell] +---------------------------------------------------------------------------- +BrowsingTest#test_homepage (31 ms warmup) + wall_time: 6 ms + memory: 437.27 KB + objects: 5514 + gc_runs: 0 + gc_time: 19 ms +---------------------------------------------------------------------------- + +===== CSV files ===== + +Performance test results are also appended to +.csv+ files inside +tmp/performance+. For example, running the default +BrowsingTest#test_homepage+ will generate following five files : + + - BrowsingTest#test_homepage_gc_runs.csv + - BrowsingTest#test_homepage_gc_time.csv + - BrowsingTest#test_homepage_memory.csv + - BrowsingTest#test_homepage_objects.csv + - BrowsingTest#test_homepage_wall_time.csv + +As the results are appended to these files each time the performance tests are run in benchmarking mode, you can collect data over a period of time. This can be very helpful in analyzing the effects of code changes. + +Sample output of +BrowsingTest#test_homepage_wall_time.csv+: + +[source, shell] +---------------------------------------------------------------------------- +measurement,created_at,app,rails,ruby,platform +0.00738224999999992,2009-01-08T03:40:29Z,,2.3.0.master.0744148,ruby-1.8.6.110,i686-darwin9.0.0 +0.00755874999999984,2009-01-08T03:46:18Z,,2.3.0.master.0744148,ruby-1.8.6.110,i686-darwin9.0.0 +0.00762099999999993,2009-01-08T03:49:25Z,,2.3.0.master.0744148,ruby-1.8.6.110,i686-darwin9.0.0 +0.00603075000000008,2009-01-08T04:03:29Z,,2.3.0.master.0744148,ruby-1.8.6.111,i686-darwin9.1.0 +0.00619899999999995,2009-01-08T04:03:53Z,,2.3.0.master.0744148,ruby-1.8.6.111,i686-darwin9.1.0 +0.00755449999999991,2009-01-08T04:04:55Z,,2.3.0.master.0744148,ruby-1.8.6.110,i686-darwin9.0.0 +0.00595999999999997,2009-01-08T04:05:06Z,,2.3.0.master.0744148,ruby-1.8.6.111,i686-darwin9.1.0 +0.00740450000000004,2009-01-09T03:54:47Z,,2.3.0.master.859e150,ruby-1.8.6.110,i686-darwin9.0.0 +0.00603150000000008,2009-01-09T03:54:57Z,,2.3.0.master.859e150,ruby-1.8.6.111,i686-darwin9.1.0 +0.00771250000000012,2009-01-09T15:46:03Z,,2.3.0.master.859e150,ruby-1.8.6.110,i686-darwin9.0.0 +---------------------------------------------------------------------------- + +==== Profiling ==== + +In profiling mode, you can choose from four types of output. + +===== Command line ===== + +This is a very basic form of output in profiling mode: + +[source, shell] +---------------------------------------------------------------------------- +BrowsingTest#test_homepage (58 ms warmup) + process_time: 63 ms + memory: 832.13 KB + objects: 7882 +---------------------------------------------------------------------------- + +===== Flat ===== + +Flat output shows the total amount of time spent in each method. http://ruby-prof.rubyforge.org/files/examples/flat_txt.html[Check ruby prof documentation for a better explanation]. + +===== Graph ===== + +Graph output shows how long each method takes to run, which methods call it and which methods it calls. http://ruby-prof.rubyforge.org/files/examples/graph_txt.html[Check ruby prof documentation for a better explanation]. + +===== Tree ===== + +Tree output is profiling information in calltree format for use by http://kcachegrind.sourceforge.net/html/Home.html[kcachegrind] and similar tools. + +=== Tuning Test Runs === + +By default, each performance test is run +4 times+ in benchmarking mode and +1 time+ in profiling. However, test runs can easily be configured. + +CAUTION: Performance test configurability is not yet enabled in Rails. But it will be soon. + +=== Performance Test Environment === + +Performance tests are run in the +development+ environment. But running performance tests will set the following configuration parameters: + +[source, shell] +---------------------------------------------------------------------------- +ActionController::Base.perform_caching = true +ActiveSupport::Dependencies.mechanism = :require +Rails.logger.level = ActiveSupport::BufferedLogger::INFO +---------------------------------------------------------------------------- + +As +ActionController::Base.perform_caching+ is set to +true+, performance tests will behave much as they do in the +production+ environment. + +[[gc]] +=== Installing GC-Patched Ruby === + +To get the best from Rails performance tests, you need to build a special Ruby binary with some super powers - http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch[GC patch] for measuring GC Runs/Time and memory/object allocation. + +The process is fairly straight forward. If you've never compiled a Ruby binary before, follow these steps to build a ruby binary inside your home directory: + +==== Installation ==== + +Compile Ruby and apply this http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch[GC Patch]: + +==== Download and Extract ==== + +[source, shell] +---------------------------------------------------------------------------- +[lifo@null ~]$ mkdir rubygc +[lifo@null ~]$ wget <download the latest stable ruby from ftp://ftp.ruby-lang.org/pub/ruby> +[lifo@null ~]$ tar -xzvf <ruby-version.tar.gz> +[lifo@null ~]$ cd <ruby-version> +---------------------------------------------------------------------------- + +==== Apply the patch ==== + +[source, shell] +---------------------------------------------------------------------------- +[lifo@null ruby-version]$ curl http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch | patch -p0 +---------------------------------------------------------------------------- + +==== Configure and Install ==== + +The following will install ruby in your home directory's +/rubygc+ directory. Make sure to replace +<homedir>+ with a full patch to your actual home directory. + +[source, shell] +---------------------------------------------------------------------------- +[lifo@null ruby-version]$ ./configure --prefix=/<homedir>/rubygc +[lifo@null ruby-version]$ make && make install +---------------------------------------------------------------------------- + +==== Prepare aliases ==== + +For convenience, add the following lines in your +~/.profile+: + +---------------------------------------------------------------------------- +alias gcruby='~/rubygc/bin/ruby' +alias gcrake='~/rubygc/bin/rake' +alias gcgem='~/rubygc/bin/gem' +alias gcirb='~/rubygc/bin/irb' +alias gcrails='~/rubygc/bin/rails' +---------------------------------------------------------------------------- + +==== Install rubygems and dependency gems ==== + +Download http://rubyforge.org/projects/rubygems[Rubygems] and install it from source. Rubygem's README file should have necessary installation instructions. + +Additionally, install the following gems : + + * +rake+ + * +rails+ + * +ruby-prof+ + * +rack+ + * +mysql+ + +If installing +mysql+ fails, you can try to install it manually: + +[source, shell] +---------------------------------------------------------------------------- +[lifo@null mysql]$ gcruby extconf.rb --with-mysql-config +[lifo@null mysql]$ make && make install +---------------------------------------------------------------------------- + +And you're ready to go. Don't forget to use +gcruby+ and +gcrake+ aliases when running the performance tests. + +== Command Line Tools == + +Writing performance test cases could be an overkill when you are looking for one time tests. Rails ships with two command line tools that enable quick and dirty performance testing: + +=== benchmarker === + ++benchmarker+ is a wrapper around Ruby's http://ruby-doc.org/core/classes/Benchmark.html[Benchmark] module. + +Usage: + +[source, shell] +---------------------------------------------------------------------------- +$ script/performance/benchmarker [times] 'Person.expensive_way' 'Person.another_expensive_way' ... +---------------------------------------------------------------------------- + +Examples: + +[source, shell] +---------------------------------------------------------------------------- +$ script/performance/benchmarker 10 'Item.all' 'CouchItem.all' +---------------------------------------------------------------------------- + +If the +[times]+ argument is omitted, supplied methods are run just once: + +[source, shell] +---------------------------------------------------------------------------- +$ script/performance/benchmarker 'Item.first' 'Item.last' +---------------------------------------------------------------------------- + +=== profiler === + ++profiler+ is a wrapper around http://ruby-prof.rubyforge.org/[ruby-prof] gem. + +Usage: + +[source, shell] +---------------------------------------------------------------------------- +$ script/performance/profiler 'Person.expensive_method(10)' [times] [flat|graph|graph_html] +---------------------------------------------------------------------------- + +Examples: + +[source, shell] +---------------------------------------------------------------------------- +$ script/performance/profiler 'Item.all' +---------------------------------------------------------------------------- + +This will profile +Item.all+ in +RubyProf::WALL_TIME+ measure mode. By default, it prints flat output to the shell. + +[source, shell] +---------------------------------------------------------------------------- +$ script/performance/profiler 'Item.all' 10 graph +---------------------------------------------------------------------------- + +This will profile +10.times { Item.all }+ with +RubyProf::WALL_TIME+ measure mode and print graph output to the shell. + +If you want to store the output in a file: + +[source, shell] +---------------------------------------------------------------------------- +$ script/performance/profiler 'Item.all' 10 graph 2> graph.txt +---------------------------------------------------------------------------- + +== Helper methods == + +Rails provides various helper methods inside Active Record, Action Controller and Action View to measure the time taken by a given piece of code. The method is called +benchmark()+ in all the three components. + +=== Model === + +[source, ruby] +---------------------------------------------------------------------------- +Project.benchmark("Creating project") do + project = Project.create("name" => "stuff") + project.create_manager("name" => "David") + project.milestones << Milestone.find(:all) +end +---------------------------------------------------------------------------- + +This benchmarks the code enclosed in the +Project.benchmark("Creating project") do..end+ block and prints the result to the log file: + +[source, ruby] +---------------------------------------------------------------------------- +Creating project (185.3ms) +---------------------------------------------------------------------------- + +Please refer to the http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001336[API docs] for additional options to +benchmark()+ + +=== Controller === + +Similarly, you could use this helper method inside http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715[controllers] + +NOTE: +benchmark+ is a class method inside controllers + +[source, ruby] +---------------------------------------------------------------------------- +def process_projects + self.class.benchmark("Processing projects") do + Project.process(params[:project_ids]) + Project.update_cached_projects + end +end +---------------------------------------------------------------------------- + +=== View === + +And in http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715[views]: + +[source, ruby] +---------------------------------------------------------------------------- +<% benchmark("Showing projects partial") do %> + <%= render :partial => @projects %> +<% end %> +---------------------------------------------------------------------------- + +== Request Logging == + +Rails log files contain very useful information about the time taken to serve each request. Here's a typical log file entry: + +[source, ruby] +---------------------------------------------------------------------------- +Processing ItemsController#index (for 127.0.0.1 at 2009-01-08 03:06:39) [GET] +Rendering template within layouts/items +Rendering items/index +Completed in 5ms (View: 2, DB: 0) | 200 OK [http://0.0.0.0/items] +---------------------------------------------------------------------------- + +For this section, we're only interested in the last line: + +[source, ruby] +---------------------------------------------------------------------------- +Completed in 5ms (View: 2, DB: 0) | 200 OK [http://0.0.0.0/items] +---------------------------------------------------------------------------- + +This data is fairly straightforward to understand. Rails uses millisecond(ms) as the metric to measures the time taken. The complete request spent 5 ms inside Rails, out of which 2 ms were spent rendering views and none was spent communication with the database. It's safe to assume that the remaining 3 ms were spent inside the controller. + +Michael Koziarski has an http://www.therailsway.com/2009/1/6/requests-per-second[interesting blog post] explaining the importance of using milliseconds as the metric. + +== Useful Links == + +=== Rails Plugins and Gems === + +* http://rails-analyzer.rubyforge.org/[Rails Analyzer] +* http://www.flyingmachinestudios.com/projects/[Palmist] +* http://github.com/josevalim/rails-footnotes/tree/master[Rails Footnotes] +* http://github.com/dsboulder/query_reviewer/tree/master[Query Reviewer] + +=== Generic Tools === + +* http://www.hpl.hp.com/research/linux/httperf[httperf] +* http://httpd.apache.org/docs/2.2/programs/ab.html[ab] +* http://jakarta.apache.org/jmeter[JMeter] +* http://kcachegrind.sourceforge.net/html/Home.html[kcachegrind] + +=== Tutorials and Documentation === + +* http://ruby-prof.rubyforge.org[ruby-prof API Documentation] +* http://railscasts.com/episodes/98-request-profiling[Request Profiling Railscast] - Outdated, but useful for understanding call graphs + +== Commercial Products == + +Rails has been lucky to have three startups dedicated to Rails specific performance tools: + +* http://www.newrelic.com[New Relic] +* http://www.fiveruns.com[Fiveruns] +* http://scoutapp.com[Scout] + +== Changelog == + +http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/4[Lighthouse ticket] + +* January 9, 2009: Complete rewrite by link:../authors.html#lifo[Pratik] +* September 6, 2008: Initial version by Matthew Bergman diff --git a/railties/doc/guides/source/rails_on_rack.txt b/railties/doc/guides/source/rails_on_rack.txt new file mode 100644 index 0000000000..6526117c8f --- /dev/null +++ b/railties/doc/guides/source/rails_on_rack.txt @@ -0,0 +1,256 @@ +Rails on Rack +============= + +This guide covers Rails integration with Rack and interfacing with other Rack components. By referring to this guide, you will be able to: + + * Create Rails Metal applications + * Use Rack Middlewares in your Rails applications + * Understand Action Pack's internal Middleware stack + * Define custom internal Middleware stack + * Understand the best practices for developing a middleware aimed at Rails applications + +NOTE: This guide assumes a working knowledge of Rack protocol and Rack concepts such as middlewares, url maps and Rack::Builder. + +== Introduction to Rack == + +**** +Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call. + +- http://rack.rubyforge.org/doc[Rack API Documentation] +**** + +Explaining Rack is not really in the scope of this guide. In case you are not familiar with Rack's basics, you should check out the following links: + +* http://rack.github.com[Official Rack Website] +* http://chneukirchen.org/blog/archive/2007/02/introducing-rack.html[Introducing Rack] +* http://m.onkey.org/2008/11/17/ruby-on-rack-1[Ruby on Rack #1 - Hello Rack!] +* http://m.onkey.org/2008/11/18/ruby-on-rack-2-rack-builder[Ruby on Rack #2 - The Builder] + +== Rails on Rack == + +=== ActionController::Dispatcher.new === + ++ActionController::Dispatcher.new+ is the primary Rack application object of a Rails application. It responds to +call+ method with a single +env+ argument and returns a Rack response. Any Rack compliant web server should be using +ActionController::Dispatcher.new+ object to serve a Rails application. + +=== script/server === + ++script/server+ does the basic job of creating a +Rack::Builder+ object and starting the webserver. This is Rails equivalent of Rack's +rackup+ script. + +Here's how +script/server+ creates an instance of +Rack::Builder+ + +[source, ruby] +---------------------------------------------------------------------------- +app = Rack::Builder.new { + use Rails::Rack::LogTailer unless options[:detach] + use Rails::Rack::Static + use Rails::Rack::Debugger if options[:debugger] + run ActionController::Dispatcher.new +}.to_app +---------------------------------------------------------------------------- + +Middlewares used in the code above are most useful in development envrionment. The following table explains their usage: + +[options="header"] +|========================================================================================================== +|Middleware |Purpose +|Rails::Rack::LogTailer | Appends log file output to console +|Rails::Rack::Static | Serves static files inside +RAILS_ROOT/public+ directory +|Rails::Rack::Debugger | Starts Debugger +|========================================================================================================== + +=== rackup === + +To use +rackup+ instead of Rails' +script/server+, you can put the following inside +config.ru+ of your Rails application's root directory: + +[source, ruby] +---------------------------------------------------------------------------- +# RAILS_ROOT/config.ru +require "config/environment" + +use Rails::Rack::LogTailer +use Rails::Rack::Static +run ActionController::Dispatcher.new +---------------------------------------------------------------------------- + +And start the server: + +[source, shell] +---------------------------------------------------------------------------- +[lifo@null application]$ rackup +---------------------------------------------------------------------------- + +To find out more about different +rackup+ options: + +[source, shell] +---------------------------------------------------------------------------- +[lifo@null application]$ rackup --help +---------------------------------------------------------------------------- + +== Action Controller Middleware Stack == + +Many of Action Controller's internal components are implemented as Rack middlewares. +ActionController::Dispatcher+ uses +ActionController::MiddlewareStack+ to combine various internal and external middlewares to form a complete Rails Rack application. + +.What is ActionController::MiddlewareStack ? +NOTE: +ActionController::MiddlewareStack+ is Rails equivalent of +Rack::Builder+, but built for better flexibility and more features to meet Rails' requirements. + +=== Inspecting Middleware Stack === + +Rails has a handy rake task for inspecting the middleware stack in use: + +[source, shell] +---------------------------------------------------------------------------- +$ rake middleware +---------------------------------------------------------------------------- + +For a freshly generated Rails application, this will produce: + +[source, ruby] +---------------------------------------------------------------------------- +use ActionController::Lock +use ActionController::Failsafe +use ActiveRecord::QueryCache +use ActionController::Session::CookieStore, {:secret=>"<secret>", :session_key=>"_<app>_session"} +use Rails::Rack::Metal +use ActionController::VerbPiggybacking +run ActionController::Dispatcher.new +---------------------------------------------------------------------------- + +=== Adding Middlewares === + +Rails provides a very simple configuration interface for adding generic Rack middlewares to a Rails applications. + +Here's how you can add middlewares via +environment.rb+ + +[source, ruby] +---------------------------------------------------------------------------- +# environment.rb + +config.middleware.use Rack::BounceFavicon +---------------------------------------------------------------------------- + +=== Internal Middleware Stack === + +[source, ruby] +---------------------------------------------------------------------------- +use "ActionController::Lock", :if => lambda { + !ActionController::Base.allow_concurrency +} + +use "ActionController::Failsafe" + +use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) } + +["ActionController::Session::CookieStore", + "ActionController::Session::MemCacheStore", + "ActiveRecord::SessionStore"].each do |store| + use(store, ActionController::Base.session_options, + :if => lambda { + if session_store = ActionController::Base.session_store + session_store.name == store + end + } + ) +end + +use ActionController::VerbPiggybacking +---------------------------------------------------------------------------- + +[options="header"] +|========================================================================================================== +|Middleware |Purpose +|ActionController::Lock | Sets +env["rack.multithread"]+ flag to +true+ and wraps the application within a Mutex. +|ActionController::Failsafe | Returns HTTP Status +500+ to the client if an exception gets raised while dispatching. +|ActiveRecord::QueryCache | Enable the Active Record query cache. +|ActionController::Session::CookieStore | Uses the cookie based session store. +|ActionController::Session::MemCacheStore | Uses the memcached based session store. +|ActiveRecord::SessionStore | Uses the database based session store. +|ActionController::VerbPiggybacking | Sets HTTP method based on +_method+ parameter or +env["HTTP_X_HTTP_METHOD_OVERRIDE"]+. +|========================================================================================================== + +=== Customizing Internal Middleware Stack === + +VERIFY THIS WORKS. Just a code dump at the moment. + +Put the following in an initializer. +[source, ruby] +---------------------------------------------------------------------------- +ActionController::Dispatcher.middleware = ActionController::MiddlewareStack.new do |m| + m.use ActionController::Lock + m.use ActionController::Failsafe + m.use ActiveRecord::QueryCache + m.use ActionController::Session::CookieStore + m.use ActionController::VerbPiggybacking +end +---------------------------------------------------------------------------- + +Josh says : + +**** +3.3: I wouldn't recommend this: custom internal stack +i'd recommend using config.middleware.use api +we still need a better api for swapping out existing middleware, etc +config.middleware.swap AC::Sessions, My::Sessoins +or something like that +**** + +== Rails Metal Applications == + +Rails Metal applications are minimal Rack applications specially designed for integrating with a typical Rails application. As Rails Metal Applications skip all of the Action Controller stack, serving a request has no overhead from the Rails framework itself. This is especially useful for infrequent cases where the performance of the full stack Rails framework is an issue. + +=== Generating a Metal Application === + +Rails provides a generator called +performance_test+ for creating new performance tests: + +[source, shell] +---------------------------------------------------------------------------- +script/generate metal poller +---------------------------------------------------------------------------- + +This generates +poller.rb+ in the +app/metal+ directory: + +[source, ruby] +---------------------------------------------------------------------------- +# Allow the metal piece to run in isolation +require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails) + +class Poller + def self.call(env) + if env["PATH_INFO"] =~ /^\/poller/ + [200, {"Content-Type" => "text/html"}, ["Hello, World!"]] + else + [404, {"Content-Type" => "text/html"}, ["Not Found"]] + end + end +end +---------------------------------------------------------------------------- + +Metal applications are an optimization. You should make sure to http://weblog.rubyonrails.org/2008/12/20/performance-of-rails-metal[understand the related performance implications] before using it. + +=== Execution Order === + +All Metal Applications are executed by +Rails::Rack::Metal+ middleware, which is a part of the +ActionController::MiddlewareStack+ chain. + +Here's the primary method responsible for running the Metal applications: + +[source, ruby] +---------------------------------------------------------------------------- +def call(env) + @metals.keys.each do |app| + result = app.call(env) + return result unless result[0].to_i == 404 + end + @app.call(env) +end +---------------------------------------------------------------------------- + +In the code above, +@metals+ is an ordered ( alphabetical ) hash of metal applications. Due to the alphabetical ordering, +aaa.rb+ will come before +bbb.rb+ in the metal chain. + +IMPORTANT: Metal applications cannot return the HTTP Status +404+ to a client, as it is used for continuing the Metal chain execution. Please use normal Rails controllers or a custom middleware if returning +404+ is a requirement. + +== Middlewares and Rails == + +== Changelog == + +http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/4[Lighthouse ticket] + +* January 11, 2009: First version by link:../authors.html#lifo[Pratik]
\ No newline at end of file diff --git a/railties/doc/guides/source/routing_outside_in.txt b/railties/doc/guides/source/routing_outside_in.txt index 3f6c80de5c..a182948bb9 100644 --- a/railties/doc/guides/source/routing_outside_in.txt +++ b/railties/doc/guides/source/routing_outside_in.txt @@ -132,18 +132,17 @@ map.resources :photos creates seven different routes in your application: -[grid="all"] -`----------`---------------`-----------`--------`------------------------------------------- -HTTP verb URL controller action used for --------------------------------------------------------------------------------------------- -GET /photos Photos index display a list of all photos -GET /photos/new Photos new return an HTML form for creating a new photo -POST /photos Photos create create a new photo -GET /photos/1 Photos show display a specific photo -GET /photos/1/edit Photos edit return an HTML form for editing a photo -PUT /photos/1 Photos update update a specific photo -DELETE /photos/1 Photos destroy delete a specific photo --------------------------------------------------------------------------------------------- +[options="header"] +|========================================================================================================== +|HTTP verb |URL |controller |action |used for +|GET |/photos |Photos |index |display a list of all photos +|GET |/photos/new |Photos |new |return an HTML form for creating a new photo +|POST |/photos |Photos |create |create a new photo +|GET |/photos/1 |Photos |show |display a specific photo +|GET |/photos/1/edit |Photos |edit |return an HTML form for editing a photo +|PUT |/photos/1 |Photos |update |update a specific photo +|DELETE |/photos/1 |Photos |destroy |delete a specific photo +|========================================================================================================== For the specific routes (those that reference just a single resource), the identifier for the resource will be available within the corresponding controller action as +params[:id]+. @@ -197,17 +196,16 @@ map.resource :geocoder creates six different routes in your application: -[grid="all"] -`----------`---------------`-----------`--------`------------------------------------------- -HTTP verb URL controller action used for --------------------------------------------------------------------------------------------- -GET /geocoder/new Geocoders new return an HTML form for creating the new geocoder -POST /geocoder Geocoders create create the new geocoder -GET /geocoder Geocoders show display the one and only geocoder resource -GET /geocoder/edit Geocoders edit return an HTML form for editing the geocoder -PUT /geocoder Geocoders update update the one and only geocoder resource -DELETE /geocoder Geocoders destroy delete the geocoder resource --------------------------------------------------------------------------------------------- +[options="header"] +|========================================================================================================== +|HTTP verb |URL |controller |action |used for +|GET |/geocoder/new |Geocoders |new |return an HTML form for creating the new geocoder +|POST |/geocoder |Geocoders |create |create the new geocoder +|GET |/geocoder |Geocoders |show |display the one and only geocoder resource +|GET |/geocoder/edit |Geocoders |edit |return an HTML form for editing the geocoder +|PUT |/geocoder |Geocoders |update |update the one and only geocoder resource +|DELETE |/geocoder |Geocoders |destroy |delete the geocoder resource +|========================================================================================================== NOTE: Even though the name of the resource is singular in +routes.rb+, the matching controller is still plural. @@ -245,18 +243,17 @@ map.resources :photos, :controller => "images" will recognize incoming URLs containing +photo+ but route the requests to the Images controller: -[grid="all"] -`----------`---------------`-----------`--------`------------------------------------------- -HTTP verb URL controller action used for --------------------------------------------------------------------------------------------- -GET /photos Images index display a list of all images -GET /photos/new Images new return an HTML form for creating a new image -POST /photos Images create create a new image -GET /photos/1 Images show display a specific image -GET /photos/1/edit Images edit return an HTML form for editing a image -PUT /photos/1 Images update update a specific image -DELETE /photos/1 Images destroy delete a specific image --------------------------------------------------------------------------------------------- +[options="header"] +|========================================================================================================== +|HTTP verb |URL |controller |action |used for +|GET |/photos |Images |index |display a list of all images +|GET |/photos/new |Images |new |return an HTML form for creating a new image +|POST |/photos |Images |create |create a new image +|GET |/photos/1 |Images |show |display a specific image +|GET |/photos/1/edit |Images |edit |return an HTML form for editing a image +|PUT |/photos/1 |Images |update |update a specific image +|DELETE |/photos/1 |Images |destroy |delete a specific image +|========================================================================================================== NOTE: The helpers will be generated with the name of the resource, not the name of the controller. So in this case, you'd still get +photos_path+, +new_photo_path+, and so on. @@ -328,18 +325,17 @@ map.resources :photos, :as => "images" will recognize incoming URLs containing +image+ but route the requests to the Photos controller: -[grid="all"] -`----------`---------------`-----------`--------`------------------------------------------- -HTTP verb URL controller action used for --------------------------------------------------------------------------------------------- -GET /images Photos index display a list of all photos -GET /images/new Photos new return an HTML form for creating a new photo -POST /images Photos create create a new photo -GET /images/1 Photos show display a specific photo -GET /images/1/edit Photos edit return an HTML form for editing a photo -PUT /images/1 Photos update update a specific photo -DELETE /images/1 Photos destroy delete a specific photo --------------------------------------------------------------------------------------------- +[options="header"] +|========================================================================================================== +|HTTP verb |URL |controller |action |used for +|GET |/images |Photos |index |display a list of all photos +|GET |/images/new |Photos |new |return an HTML form for creating a new photo +|POST |/images |Photos |create |create a new photo +|GET |/images/1 |Photos |show |display a specific photo +|GET |/images/1/edit |Photos |edit |return an HTML form for editing a photo +|PUT |/images/1 |Photos |update |update a specific photo +|DELETE |/images/1 |Photos |destroy |delete a specific photo +|========================================================================================================== NOTE: The helpers will be generated with the name of the resource, not the path name. So in this case, you'd still get +photos_path+, +new_photo_path+, and so on. @@ -452,18 +448,17 @@ end In addition to the routes for magazines, this declaration will also create routes for ads, each of which requires the specification of a magazine in the URL: -[grid="all"] -`----------`-----------------------`-----------`--------`------------------------------------------- -HTTP verb URL controller action used for --------------------------------------------------------------------------------------------- -GET /magazines/1/ads Ads index display a list of all ads for a specific magazine -GET /magazines/1/ads/new Ads new return an HTML form for creating a new ad belonging to a specific magazine -POST /magazines/1/ads Ads create create a new ad belonging to a specific magazine -GET /magazines/1/ads/1 Ads show display a specific ad belonging to a specific magazine -GET /magazines/1/ads/1/edit Ads edit return an HTML form for editing an ad belonging to a specific magazine -PUT /magazines/1/ads/1 Ads update update a specific ad belonging to a specific magazine -DELETE /magazines/1/ads/1 Ads destroy delete a specific ad belonging to a specific magazine --------------------------------------------------------------------------------------------- +[options="header"] +|========================================================================================================== +|HTTP verb |URL |controller |action |used for +|GET |/magazines/1/ads |Ads |index |display a list of all ads for a specific magazine +|GET |/magazines/1/ads/new |Ads |new |return an HTML form for creating a new ad belonging to a specific magazine +|POST |/magazines/1/ads |Ads |create |create a new ad belonging to a specific magazine +|GET |/magazines/1/ads/1 |Ads |show |display a specific ad belonging to a specific magazine +|GET |/magazines/1/ads/1/edit |Ads |edit |return an HTML form for editing an ad belonging to a specific magazine +|PUT |/magazines/1/ads/1 |Ads |update |update a specific ad belonging to a specific magazine +|DELETE |/magazines/1/ads/1 |Ads |destroy |delete a specific ad belonging to a specific magazine +|========================================================================================================== This will also create routing helpers such as +magazine_ads_url+ and +edit_magazine_ad_path+. @@ -795,7 +790,7 @@ As with conditions in RESTful routes, you can specify +:get+, +:post+, +:put+, + === Route Globbing -Route globbing is a way to specify that a particular parameter (which must be the last parameter in the route) should be matched to all the remaining parts of a route. For example +Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example [source, ruby] ------------------------------------------------------- diff --git a/railties/doc/guides/source/security.txt b/railties/doc/guides/source/security.txt index 9b3f47932e..b4e8bb4b41 100644 --- a/railties/doc/guides/source/security.txt +++ b/railties/doc/guides/source/security.txt @@ -93,7 +93,7 @@ That means the security of this storage depends on this secret (and of the diges .................................... config.action_controller.session = { - :session_key => ‘_app_session’, + :key => ‘_app_session’, :secret => ‘0x0dkfj3927dkc7djdh36rkckdfzsg...’ } .................................... diff --git a/railties/doc/guides/source/stylesheets/base.css b/railties/doc/guides/source/stylesheets/base.css index 1cf0a3de98..2bf0b93c40 100644 --- a/railties/doc/guides/source/stylesheets/base.css +++ b/railties/doc/guides/source/stylesheets/base.css @@ -6,95 +6,95 @@ 1. General HTML elements 2. General classes 3. General structure - 1. header - 2. Content - 3. Sidebar - 4. Sitewide elements - 1. Introduction boxes - 2. Navigation + 1. header + 2. Content + 3. Sidebar + 4. Sitewide elements + 1. Introduction boxes + 2. Navigation 5. Elements for specific areas - 1. Weblog - + 1. Weblog + ---------------------------------------------------------------------------- */ * { - margin: 0; - padding: 0; + margin: 0; + padding: 0; } -body { - color: #333333; +body { + color: #333333; + + background-color: #FFFFFF; + background-image: url(../images/header_backdrop.png); + background-repeat: repeat-x; + background-position: 0 -25px; - background-color: #FFFFFF; - background-image: url(../images/header_backdrop.png); - background-repeat: repeat-x; - background-position: 0 -25px; + font-size: 80%; + font-family: verdana, helvetica, arial, sans-serif; + line-height: 1.7em; - font-size: 80%; - font-family: verdana, helvetica, arial, sans-serif; - line-height: 1.7em; - - /* Center in IE5.5 */ - text-align: center; + /* Center in IE5.5 */ + text-align: center; } h1 { - font-size: 2em; - font-weight: normal; - letter-spacing: -0.04em; + font-size: 2em; + font-weight: normal; + letter-spacing: -0.04em; } h2 { - font-size: 1.5em; - font-weight: normal; - letter-spacing: -0.04em; + font-size: 1.5em; + font-weight: normal; + letter-spacing: -0.04em; } h1,h2,h3,h4,h5,h6 { - margin-top: 1em; - margin-bottom: 0.5em; + margin-top: 1em; + margin-bottom: 0.5em; } .pageheader a:link, .pageheader a:visited{ - color: #333; + color: #333; text-decoration: none; } img { - border: none; + border: none; } p { - margin-bottom: 1em; + margin-bottom: 1em; } a:link { - color: #BB2233; + color: #BB2233; } a:visited { - color: #991122; + color: #991122; } a:hover { - color: #CC2233; - background-color: #EEEEEE; + color: #CC2233; + background-color: #EEEEEE; } a:active { } ul { - margin-top: 1em; - list-style-type: none; + margin-top: 1em; + list-style-type: none; } ul li { - margin-left: 0.5em; - padding-left: 1em; - - background-image: url(../images/bullet.gif); - background-repeat: no-repeat; - background-position: 0 0.55em; + margin-left: 0.5em; + padding-left: 1em; + + background-image: url(../images/bullet.gif); + background-repeat: no-repeat; + background-position: 0 0.55em; } ul li p { @@ -102,98 +102,98 @@ ul li p { } /* ---------------------------------------------------------------------------- - Structure + Structure ---------------------------------------------------------------------------- */ div#container { - width: 90%; - max-width: 790px; + width: 90%; + max-width: 790px; - margin-top: 10px; - margin-left: auto; - margin-right: auto; + margin-top: 10px; + margin-left: auto; + margin-right: auto; - font-size: 1em; + font-size: 1em; - /* Don't center text, only div#container */ - text-align: left; + /* Don't center text, only div#container */ + text-align: left; } div#header { - /* This height controls the vertical position of #content and #sidebar */ - height: 160px; - overflow: hidden; + /* This height controls the vertical position of #content and #sidebar */ + height: 160px; + overflow: hidden; } div#header h1 { - height: 30px; - - margin: 0; - margin-top: 10px; - margin-left: 100px; - padding: 0; - font-weight: bold; - font-size: 24pt; + height: 30px; + + margin: 0; + margin-top: 10px; + margin-left: 100px; + padding: 0; + font-weight: bold; + font-size: 24pt; } div#header p { - height: 30px; - margin: 0; - margin-left: 160px; - padding: 0; - font-weight: bold; - font-size: 14pt; - color: #999; + height: 30px; + margin: 0; + margin-left: 160px; + padding: 0; + font-weight: bold; + font-size: 14pt; + color: #999; } /* div#logo { - float: left; - width: 110px; - height: 140px; - margin-right: 31px; + float: left; + width: 110px; + height: 140px; + margin-right: 31px; } */ div#content { - margin-left: 170px; + margin-left: 170px; } /* Fix the IE only 3pixel jog - documented at http://www.positioniseverything.net/articles/hollyhack.html#haslayout \*/ * html #content { - height: 1px; + height: 1px; } /* End hide from IE5-mac */ div#sidebar { - float: left; - width: 170px; - margin-top: -4px; - font-size: 0.8em; + float: left; + width: 170px; + margin-top: -4px; + font-size: 0.8em; } div#sidebar h2 { - margin: 0; - font-size: 1.1em; - font-weight: bold; + margin: 0; + font-size: 1.1em; + font-weight: bold; } div#sidebar ul { - margin-top: 0; - margin-bottom: 1em; - padding: 0; + margin-top: 0; + margin-bottom: 1em; + padding: 0; } div#sidebar ol li { - margin: 0 0 2px 0px; - padding: 0; - line-height: 1.3em; - background-image: none; + margin: 0 0 2px 0px; + padding: 0; + line-height: 1.3em; + background-image: none; } div#sidebar ol li a { - display: block; - width: 150px; - padding: 0.2em 0; + display: block; + width: 150px; + padding: 0.2em 0; } div#sidebar ul li { @@ -203,160 +203,160 @@ div#sidebar ul li { div#sidebar ol>ol { padding-left: 5px; padding-right: 5px; - list-style-type: none; - + list-style-type: none; + } div#sidebar ol>ol li a { - display: block; - width: 140px; - padding: 0.2em 0; - margin-left: 10px; - + display: block; + width: 140px; + padding: 0.2em 0; + margin-left: 10px; + } div#sidebar ol li a:hover { } /* ---------------------------------------------------------------------------- - Specific site-wide elements + Specific site-wide elements ---------------------------------------------------------------------------- */ /* Introduction boxes */ .introduction { - margin-bottom: 1em; - padding: 1em; - background-color: #D6DFE8; + margin-bottom: 1em; + padding: 1em; + background-color: #D6DFE8; } .introduction p { - margin-bottom: 0; + margin-bottom: 0; } /* Navigation */ ul#navMain { - height: 22px; - margin: 0; - margin-left: 140px; - padding: 16px 0; + height: 22px; + margin: 0; + margin-left: 140px; + padding: 16px 0; - list-style-type: none; + list-style-type: none; } ul#navMain li { - display: inline; - background-image: none; - margin: 0; - padding: 0; + display: inline; + background-image: none; + margin: 0; + padding: 0; } ul#navMain li { - border-left: 1px solid #FFFFFF; + border-left: 1px solid #FFFFFF; } ul#navMain li.first-child { - /* Wouldn't it be nice if IE was up-to-date with the rest of the world so we could skip - superfluous classes? */ - border-left: none; + /* Wouldn't it be nice if IE was up-to-date with the rest of the world so we could skip + superfluous classes? */ + border-left: none; } -ul#navMain li a { - padding: 0.2em 1em; - - color: #FFFFFF; - text-decoration: none; +ul#navMain li a { + padding: 0.2em 1em; + + color: #FFFFFF; + text-decoration: none; } ul#navMain li.first-child a { - /* Wouldn't it be nice if IE was up-to-date with the rest of the world? */ - padding-left: 0; + /* Wouldn't it be nice if IE was up-to-date with the rest of the world? */ + padding-left: 0; } -ul#navMain li a:hover { - text-decoration: underline; - background-color: transparent; +ul#navMain li a:hover { + text-decoration: underline; + background-color: transparent; } /* Mark the current page */ ul#navMain li.current a { - font-weight: bold; + font-weight: bold; } /* ---------------------------------------------------------------------------- - Elements for specific areas + Elements for specific areas ---------------------------------------------------------------------------- */ /* Weblog */ .blogEntry { - margin-bottom: 2em; + margin-bottom: 2em; } .blogEntry h2 { - margin-top: 0; - margin-bottom: 0; + margin-top: 0; + margin-bottom: 0; } p.metaData { - color: #999999; - font-size: 0.9em; + color: #999999; + font-size: 0.9em; } /* Reference documentation */ #reference #sidebar { - display: none; - width: 0; + display: none; + width: 0; } #reference #content { - margin-left: 0; + margin-left: 0; } #reference #content #api { - width: 100%; - height: 800px; + width: 100%; + height: 800px; } #reference #logo { - width: 80px; - height: 86px; + width: 80px; + height: 86px; - margin-right: 0; + margin-right: 0; } #reference #logo img { - height: 84px; + height: 84px; } #reference { - /* The header is smaller on the reference page, move the background up so the menu is in the - proper place still */ - background-position: 0 -70px; + /* The header is smaller on the reference page, move the background up so the menu is in the + proper place still */ + background-position: 0 -70px; } #reference #header { - height: 90px; + height: 90px; } #reference #header h1 { - height: 24px; + height: 24px; + + margin-top: 2px; + margin-left: 0; + + background-image: none; - margin-top: 2px; - margin-left: 0; - - background-image: none; + text-indent: 0; + font-size: 1.5em; + font-weight: bold; - text-indent: 0; - font-size: 1.5em; - font-weight: bold; - } #reference #container { - max-width: 100%; + max-width: 100%; }
\ No newline at end of file diff --git a/railties/doc/guides/source/stylesheets/forms.css b/railties/doc/guides/source/stylesheets/forms.css index a3fce205b7..1da34dc999 100644 --- a/railties/doc/guides/source/stylesheets/forms.css +++ b/railties/doc/guides/source/stylesheets/forms.css @@ -1,7 +1,7 @@ label, input { - display: block; - float: left; - margin-bottom: 10px; + display: block; + float: left; + margin-bottom: 10px; } label { @@ -25,7 +25,7 @@ form>h1{ } td { - vertical-align: top; + vertical-align: top; } #livepreview { diff --git a/railties/doc/guides/source/stylesheets/more.css b/railties/doc/guides/source/stylesheets/more.css index 9446b439d4..756ae06d0e 100644 --- a/railties/doc/guides/source/stylesheets/more.css +++ b/railties/doc/guides/source/stylesheets/more.css @@ -12,8 +12,8 @@ border: 1px solid #ccc; } -pre { - overflow: auto; +pre { + overflow: auto; } #content pre, #content ul { @@ -25,13 +25,13 @@ pre { } div#header h1 a{ - color: #333333; - text-decoration: none; + color: #333333; + text-decoration: none; } div#header p a{ text-decoration: none; - color: #999; + color: #999; } .left-floaty { @@ -80,3 +80,174 @@ div#header p a{ font-size: small; padding-right: 1em; } + +div#container { + max-width: 900px; + padding-bottom: 3em; +} + +div#content { + margin-left: 200px; +} + +div#container.notoc { + max-width: 600px; +} + +.notoc div#content { + margin-left: 0; +} + +pre { + line-height: 1.4em; +} + +#content p tt { + background: #eeeeee; + border: solid 1px #cccccc; + padding: 3px; +} + +dt { + font-weight: bold; +} + +#content dt tt { + font-size: 10pt; +} + +dd { + margin-left: 3em; +} + +#content dt tt, #content pre tt { + background: none; + padding: 0; + border: 0; +} + +#content .olist ol { + margin-left: 2em; +} + +#header { + position: relative; + max-width: 840px; + margin-left: auto; + margin-right: auto; +} + +#header.notoc { + max-width: 580px; +} + +#logo { + position: absolute; + left: 10px; + top: 10px; + width: 110px; + height: 140px; +} + +div#header h1#site_title { + background: url('../images/ruby_on_rails_by_mike_rundle2.gif') top left no-repeat; + position: absolute; + width: 392px; + height: 55px; + left: 145px; + top: 20px; + margin: 0; + padding: 0; +} + +#site_title span { + display: none; +} + +#site_title_tagline { + display: none; +} + +ul#navMain { + position: absolute; + margin: 0; + padding: 0; + top: 97px; + left: 145px; +} + +.left-floaty, .right-floaty { + padding: 15px; +} + +.admonitionblock, +.tableblock { + margin-left: 1em; + margin-right: 1em; + margin-top: 0.25em; + margin-bottom: 1em; +} + +.admonitionblock .icon { + padding-right: 8px; +} + +.admonitionblock .content { + border: solid 1px #ffda78; + background: #fffebd; + padding: 10px; + padding-top: 8px; + padding-bottom: 8px; +} + +.admonitionblock .title { + font-size: 140%; + margin-bottom: 0.5em; +} + +.tableblock table { + border: solid 1px #aaaaff; + background: #f0f0ff; +} + +.tableblock th { + background: #e0e0e0; +} + +.tableblock th, +.tableblock td { + padding: 3px; + padding-left: 5px; + padding-right: 5px; +} + +.sidebarblock { + margin-top: 0.25em; + margin: 1em; + border: solid 1px #ccccbb; + padding: 8px; + background: #ffffe0; +} + +.sidebarblock .sidebar-title { + font-size: 140%; + font-weight: 600; + margin-bottom: 0.3em; +} + +.sidebarblock .sidebar-content > .para:last-child > p { + margin-bottom: 0; +} + +.sidebarblock .sidebar-title a { + text-decoration: none; +} + +.sidebarblock .sidebar-title a:hover { + text-decoration: underline; +} + + + + + diff --git a/railties/doc/guides/source/templates/guides.html.erb b/railties/doc/guides/source/templates/guides.html.erb index d69cf5e08a..ae1145c1ae 100644 --- a/railties/doc/guides/source/templates/guides.html.erb +++ b/railties/doc/guides/source/templates/guides.html.erb @@ -1,97 +1,94 @@ <%- - manuals_index_url = ENV['MANUALSONRAILS_INDEX_URL'] || "index.html" - show_toc = ENV['MANUALSONRAILS_TOC'] != 'no' + manuals_index_url = ENV['MANUALSONRAILS_INDEX_URL'] || "index.html" + show_toc = ENV['MANUALSONRAILS_TOC'] != 'no' -%> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> - <title><%- if multi_page? && !is_preamble? -%><%=h current_chapter.plain_title %> :: <% end %><%=h title %></title> - <!--[if lt IE 8]> - <script src="http://ie7-js.googlecode.com/svn/version/2.0(beta3)/IE8.js" type="text/javascript"></script> - <![endif]--> - <link href="stylesheets/base.css" media="screen" rel="Stylesheet" type="text/css" /> - <link href="stylesheets/forms.css" media="screen" rel="Stylesheet" type="text/css" /> - <link href="stylesheets/more.css" media="screen" rel="Stylesheet" type="text/css" /> - <style type="text/css"> - <%= include_file('inline.css') %> - </style> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title><%- if multi_page? && !is_preamble? -%><%=h current_chapter.plain_title %> :: <% end %><%=h title %></title> + <!--[if lt IE 8]> + <script src="http://ie7-js.googlecode.com/svn/version/2.0(beta3)/IE8.js" type="text/javascript"></script> + <![endif]--> + <link href="stylesheets/base.css" media="screen" rel="Stylesheet" type="text/css" /> + <link href="stylesheets/forms.css" media="screen" rel="Stylesheet" type="text/css" /> + <link href="stylesheets/more.css" media="screen" rel="Stylesheet" type="text/css" /> </head> <body> - <div id="header" <% if !show_toc %> class="notoc"<% end %>> - <div id="logo"> - <a href="index.html" title="Ruby on Rails"><img src="images/rails_logo_remix.gif" alt="Rails" height="140" width="110" /></a> - </div> - - <h1 id="site_title"><span>Ruby on Rails</span></h1> - <h2 id="site_title_tagline">Sustainable productivity for web-application development</h2> + <div id="header" <% if !show_toc %> class="notoc"<% end %>> + <div id="logo"> + <a href="index.html" title="Ruby on Rails"><img src="images/rails_logo_remix.gif" alt="Rails" height="140" width="110" /></a> + </div> - <ul id="navMain"> - <li class="first-child"><a href="http://www.rubyonrails.org/" title="Ruby on Rails" class="ruby_on_rails">Ruby on Rails</a></li> - <li><a class="manuals" href="index.html" title="Manuals Index">Guides Index</a></li> - </ul> - </div> + <h1 id="site_title"><span>Ruby on Rails</span></h1> + <h2 id="site_title_tagline">Sustainable productivity for web-application development</h2> - <div id="container"<% if !show_toc %> class="notoc"<% end %>> - <% if show_toc %> - <div id="sidebar"> - <h2>Chapters</h2> - <%- if multi_page? -%> - <a href="<%=h chapters.first.basename %>">Preamble</a> - <%- end -%> - <ol> - <%- if multi_page? -%> - <%- for heading in table_of_contents -%> - <li> - <a href="<%=h heading.basename %>"><%= heading.title_without_numbers %></a> - <%- if !heading.children.empty? -%> - <ul> - <% for h in heading.children %> - <li><a href="<%=h h.basename %><%=h h.anchor %>"><%= h.title_without_numbers %></a></li> - <% end %> - </ul> - <%- end -%> - </li> - <%- end -%> - <%- else -%> - <%- for heading in table_of_contents -%> - <li> - <a href="<%=h heading.anchor %>"><%= heading.title_without_numbers %></a> - <%- if !heading.children.empty? -%> - <ul> - <% for h in heading.children %> - <li><a href="<%=h h.anchor %>"><%= h.title_without_numbers %></a></li> - <% end %> - </ul> - <%- end -%> - </li> - <%- end -%> - <%- end -%> - </ol> - </div> - <% end %> - <div id="content"> - <%- if multi_page? && !is_preamble? -%> - <h2 id="<%=h current_chapter.anchor_id %>"><%= current_chapter.title %></h2> - <%- else -%> - <h1><%=h title %></h1> - <%- end -%> - <%= contents %> - <%- if multi_page? -%> - <div id="chapter_navigation"> - <%- if prev_chapter -%> - <div class="left-floaty"> - <a href="<%=h prev_chapter.basename %>">« <%= prev_chapter.title %></a> - </div> - <%- end -%> - <%- if next_chapter -%> - <div class="right-floaty"> - <a href="<%=h next_chapter.basename %>"><%= next_chapter.title %> »</a> - </div> - <%- end -%> - </div> - <%- end -%> - </div> - </div> + <ul id="navMain"> + <li class="first-child"><a href="http://www.rubyonrails.org/" title="Ruby on Rails" class="ruby_on_rails">Ruby on Rails</a></li> + <li><a class="manuals" href="index.html" title="Manuals Index">Guides Index</a></li> + </ul> + </div> + + <div id="container"<% if !show_toc %> class="notoc"<% end %>> + <% if show_toc %> + <div id="sidebar"> + <h2>Chapters</h2> + <%- if multi_page? -%> + <a href="<%=h chapters.first.basename %>">Preamble</a> + <%- end -%> + <ol> + <%- if multi_page? -%> + <%- for heading in table_of_contents -%> + <li> + <a href="<%=h heading.basename %>"><%= heading.title_without_numbers %></a> + <%- if !heading.children.empty? -%> + <ul> + <% for h in heading.children %> + <li><a href="<%=h h.basename %><%=h h.anchor %>"><%= h.title_without_numbers %></a></li> + <% end %> + </ul> + <%- end -%> + </li> + <%- end -%> + <%- else -%> + <%- for heading in table_of_contents -%> + <li> + <a href="<%=h heading.anchor %>"><%= heading.title_without_numbers %></a> + <%- if !heading.children.empty? -%> + <ul> + <% for h in heading.children %> + <li><a href="<%=h h.anchor %>"><%= h.title_without_numbers %></a></li> + <% end %> + </ul> + <%- end -%> + </li> + <%- end -%> + <%- end -%> + </ol> + </div> + <% end %> + <div id="content"> + <%- if multi_page? && !is_preamble? -%> + <h2 id="<%=h current_chapter.anchor_id %>"><%= current_chapter.title %></h2> + <%- else -%> + <h1><%=h title %></h1> + <%- end -%> + <%= contents %> + <%- if multi_page? -%> + <div id="chapter_navigation"> + <%- if prev_chapter -%> + <div class="left-floaty"> + <a href="<%=h prev_chapter.basename %>">« <%= prev_chapter.title %></a> + </div> + <%- end -%> + <%- if next_chapter -%> + <div class="right-floaty"> + <a href="<%=h next_chapter.basename %>"><%= next_chapter.title %> »</a> + </div> + <%- end -%> + </div> + <%- end -%> + </div> + </div> </body> </html> diff --git a/railties/doc/guides/source/templates/inline.css b/railties/doc/guides/source/templates/inline.css deleted file mode 100644 index 1b406733de..0000000000 --- a/railties/doc/guides/source/templates/inline.css +++ /dev/null @@ -1,165 +0,0 @@ -div#container { - max-width: 900px; - padding-bottom: 3em; -} - -div#content { - margin-left: 200px; -} - -div#container.notoc { - max-width: 600px; -} - -.notoc div#content { - margin-left: 0; -} - -pre { - line-height: 1.4em; -} - -#content p tt { - background: #eeeeee; - border: solid 1px #cccccc; - padding: 3px; -} - -dt { - font-weight: bold; -} - -#content dt tt { - font-size: 10pt; -} - -dd { - margin-left: 3em; -} - -#content dt tt, #content pre tt { - background: none; - padding: 0; - border: 0; -} - -#content .olist ol { - margin-left: 2em; -} - -#header { - position: relative; - max-width: 840px; - margin-left: auto; - margin-right: auto; -} - -#header.notoc { - max-width: 580px; -} - -#logo { - position: absolute; - left: 10px; - top: 10px; - width: 110px; - height: 140px; -} - -div#header h1#site_title { - background: url('images/ruby_on_rails_by_mike_rundle2.gif') top left no-repeat; - position: absolute; - width: 392px; - height: 55px; - left: 145px; - top: 20px; - margin: 0; - padding: 0; -} - -#site_title span { - display: none; -} - -#site_title_tagline { - display: none; -} - -ul#navMain { - position: absolute; - margin: 0; - padding: 0; - top: 97px; - left: 145px; -} - -.left-floaty, .right-floaty { - padding: 15px; -} - -.admonitionblock, -.tableblock { - margin-left: 1em; - margin-right: 1em; - margin-top: 0.25em; - margin-bottom: 1em; -} - -.admonitionblock .icon { - padding-right: 8px; -} - -.admonitionblock .content { - border: solid 1px #ffda78; - background: #fffebd; - padding: 10px; - padding-top: 8px; - padding-bottom: 8px; -} - -.admonitionblock .title { - font-size: 140%; - margin-bottom: 0.5em; -} - -.tableblock table { - border: solid 1px #aaaaff; - background: #f0f0ff; -} - -.tableblock th { - background: #e0e0e0; -} - -.tableblock th, -.tableblock td { - padding: 3px; - padding-left: 5px; - padding-right: 5px; -} - -.sidebarblock { - margin-top: 0.25em; - margin: 1em; - border: solid 1px #ccccbb; - padding: 8px; - background: #ffffe0; -} - -.sidebarblock .sidebar-title { - font-size: 140%; - font-weight: 600; - margin-bottom: 0.3em; -} - -.sidebarblock .sidebar-content > .para:last-child > p { - margin-bottom: 0; -} - -.sidebarblock .sidebar-title a { - text-decoration: none; -} - -.sidebarblock .sidebar-title a:hover { - text-decoration: underline; -} diff --git a/railties/doc/guides/source/testing_rails_applications.txt b/railties/doc/guides/source/testing_rails_applications.txt index cb77829fc1..054260e276 100644 --- a/railties/doc/guides/source/testing_rails_applications.txt +++ b/railties/doc/guides/source/testing_rails_applications.txt @@ -228,16 +228,15 @@ NOTE: +db:test:prepare+ will fail with an error if db/schema.rb doesn't exists. ==== Rake Tasks for Preparing your Application for Testing ==== -[grid="all"] -|------------------------------------------------------------------------------------ -|Tasks Description -|------------------------------------------------------------------------------------ -|+rake db:test:clone+ Recreate the test database from the current environment's database schema -|+rake db:test:clone_structure+ Recreate the test databases from the development structure -|+rake db:test:load+ Recreate the test database from the current +schema.rb+ -|+rake db:test:prepare+ Check for pending migrations and load the test schema -|+rake db:test:purge+ Empty the test database. -|------------------------------------------------------------------------------------ +[options="header"] +|========================================================================================================== +|Tasks |Description +|+rake db:test:clone+ |Recreate the test database from the current environment's database schema +|+rake db:test:clone_structure+ |Recreate the test databases from the development structure +|+rake db:test:load+ |Recreate the test database from the current +schema.rb+ +|+rake db:test:prepare+ |Check for pending migrations and load the test schema +|+rake db:test:purge+ |Empty the test database. +|========================================================================================================== TIP: You can see all these rake tasks and their descriptions by running +rake \-\-tasks \-\-describe+ @@ -404,30 +403,29 @@ By now you've caught a glimpse of some of the assertions that are available. Ass There are a bunch of different types of assertions you can use. Here's the complete list of assertions that ship with +test/unit+, the testing library used by Rails. The +[msg]+ parameter is an optional string message you can specify to make your test failure messages clearer. It's not required. -[grid="all"] -`-----------------------------------------------------------------`------------------------------------------------------------------------ -Assertion Purpose ------------------------------------------------------------------------------------------------------------------------------------------- -+assert( boolean, [msg] )+ Ensures that the object/expression is true. -+assert_equal( obj1, obj2, [msg] )+ Ensures that +obj1 == obj2+ is true. -+assert_not_equal( obj1, obj2, [msg] )+ Ensures that +obj1 == obj2+ is false. -+assert_same( obj1, obj2, [msg] )+ Ensures that +obj1.equal?(obj2)+ is true. -+assert_not_same( obj1, obj2, [msg] )+ Ensures that +obj1.equal?(obj2)+ is false. -+assert_nil( obj, [msg] )+ Ensures that +obj.nil?+ is true. -+assert_not_nil( obj, [msg] )+ Ensures that +obj.nil?+ is false. -+assert_match( regexp, string, [msg] )+ Ensures that a string matches the regular expression. -+assert_no_match( regexp, string, [msg] )+ Ensures that a string doesn't matches the regular expression. -+assert_in_delta( expecting, actual, delta, [msg] )+ Ensures that the numbers `expecting` and `actual` are within `delta` of each other. -+assert_throws( symbol, [msg] ) { block }+ Ensures that the given block throws the symbol. -+assert_raises( exception1, exception2, ... ) { block }+ Ensures that the given block raises one of the given exceptions. -+assert_nothing_raised( exception1, exception2, ... ) { block }+ Ensures that the given block doesn't raise one of the given exceptions. -+assert_instance_of( class, obj, [msg] )+ Ensures that +obj+ is of the +class+ type. -+assert_kind_of( class, obj, [msg] )+ Ensures that +obj+ is or descends from +class+. -+assert_respond_to( obj, symbol, [msg] )+ Ensures that +obj+ has a method called +symbol+. -+assert_operator( obj1, operator, obj2, [msg] )+ Ensures that +obj1.operator(obj2)+ is true. -+assert_send( array, [msg] )+ Ensures that executing the method listed in +array[1]+ on the object in +array[0]+ with the parameters of +array[2 and up]+ is true. This one is weird eh? -+flunk( [msg] )+ Ensures failure. This is useful to explicitly mark a test that isn't finished yet. ------------------------------------------------------------------------------------------------------------------------------------------- +[options="header"] +|========================================================================================================================================== +|Assertion |Purpose +|+assert( boolean, [msg] )+ |Ensures that the object/expression is true. +|+assert_equal( obj1, obj2, [msg] )+ |Ensures that +obj1 == obj2+ is true. +|+assert_not_equal( obj1, obj2, [msg] )+ |Ensures that +obj1 == obj2+ is false. +|+assert_same( obj1, obj2, [msg] )+ |Ensures that +obj1.equal?(obj2)+ is true. +|+assert_not_same( obj1, obj2, [msg] )+ |Ensures that +obj1.equal?(obj2)+ is false. +|+assert_nil( obj, [msg] )+ |Ensures that +obj.nil?+ is true. +|+assert_not_nil( obj, [msg] )+ |Ensures that +obj.nil?+ is false. +|+assert_match( regexp, string, [msg] )+ |Ensures that a string matches the regular expression. +|+assert_no_match( regexp, string, [msg] )+ |Ensures that a string doesn't matches the regular expression. +|+assert_in_delta( expecting, actual, delta, [msg] )+ |Ensures that the numbers `expecting` and `actual` are within `delta` of each other. +|+assert_throws( symbol, [msg] ) { block }+ |Ensures that the given block throws the symbol. +|+assert_raises( exception1, exception2, ... ) { block }+ |Ensures that the given block raises one of the given exceptions. +|+assert_nothing_raised( exception1, exception2, ... ) { block }+ |Ensures that the given block doesn't raise one of the given exceptions. +|+assert_instance_of( class, obj, [msg] )+ |Ensures that +obj+ is of the +class+ type. +|+assert_kind_of( class, obj, [msg] )+ |Ensures that +obj+ is or descends from +class+. +|+assert_respond_to( obj, symbol, [msg] )+ |Ensures that +obj+ has a method called +symbol+. +|+assert_operator( obj1, operator, obj2, [msg] )+ |Ensures that +obj1.operator(obj2)+ is true. +|+assert_send( array, [msg] )+ |Ensures that executing the method listed in +array[1]+ on the object in +array[0]+ with the parameters of +array[2 and up]+ is true. This one is weird eh? +|+flunk( [msg] )+ |Ensures failure. This is useful to explicitly mark a test that isn't finished yet. +|========================================================================================================================================= Because of the modular nature of the testing framework, it is possible to create your own assertions. In fact, that's exactly what Rails does. It includes some specialized assertions to make your life easier. @@ -437,19 +435,18 @@ NOTE: Creating your own assertions is an advanced topic that we won't cover in t Rails adds some custom assertions of its own to the +test/unit+ framework: -[grid="all"] -`----------------------------------------------------------------------------------`------------------------------------------------------- -Assertion Purpose ------------------------------------------------------------------------------------------------------------------------------------------- -+assert_valid(record)+ Ensures that the passed record is valid by Active Record standards and returns any error messages if it is not. -+assert_difference(expressions, difference = 1, message = nil) {|| ...}+ Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block. -+assert_no_difference(expressions, message = nil, &block)+ Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block. -+assert_recognizes(expected_options, path, extras={}, message=nil)+ Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options. -+assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)+ Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures. -+assert_response(type, message = nil)+ Asserts that the response comes with a specific status code. You can specify +:success+ to indicate 200, +:redirect+ to indicate 300-399, +:missing+ to indicate 404, or +:error+ to match the 500-599 range -+assert_redirected_to(options = {}, message=nil)+ Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that +assert_redirected_to(:controller => "weblog")+ will also match the redirection of +redirect_to(:controller => "weblog", :action => "show")+ and so on. -+assert_template(expected = nil, message=nil)+ Asserts that the request was rendered with the appropriate template file. ------------------------------------------------------------------------------------------------------------------------------------------- +[options="header"] +|=========================================================================================================================================================== +|Assertion |Purpose +|+assert_valid(record)+ |Ensures that the passed record is valid by Active Record standards and returns any error messages if it is not. +|+assert_difference(expressions, difference = 1, message = nil) {...}+ |Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block. +|+assert_no_difference(expressions, message = nil, &block)+ |Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block. +|+assert_recognizes(expected_options, path, extras={}, message=nil)+ |Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options. +|+assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)+ |Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures. +|+assert_response(type, message = nil)+ |Asserts that the response comes with a specific status code. You can specify +:success+ to indicate 200, +:redirect+ to indicate 300-399, +:missing+ to indicate 404, or +:error+ to match the 500-599 range +|+assert_redirected_to(options = {}, message=nil)+ |Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that +assert_redirected_to(:controller => "weblog")+ will also match the redirection of +redirect_to(:controller => "weblog", :action => "show")+ and so on. +|+assert_template(expected = nil, message=nil)+ |Asserts that the request was rendered with the appropriate template file. +|=========================================================================================================================================================== You'll see the usage of some of these assertions in the next chapter. @@ -595,7 +592,7 @@ For example, you could verify the contents on the title element in your response assert_select 'title', "Welcome to Rails Testing Guide" -------------------------------------------------- -You can also use nested +assert_select+ blocks. In this case the inner +assert_select+ will run the assertion on each element selected by the outer `assert_select` block: +You can also use nested +assert_select+ blocks. In this case the inner +assert_select+ runs the assertion on the complete collection of elements selected by the outer `assert_select` block: [source,ruby] -------------------------------------------------- @@ -604,21 +601,35 @@ assert_select 'ul.navigation' do end -------------------------------------------------- -The +assert_select+ assertion is quite powerful. For more advanced usage, refer to its link:http://api.rubyonrails.com/classes/ActionController/Assertions/SelectorAssertions.html#M000749[documentation]. +Alternatively the collection of elements selected by the outer +assert_select+ may be iterated through so that +assert_select+ may be called separately for each element. Suppose for example that the response contains two ordered lists, each with four list elements then the following tests will both pass. + +[source,ruby] +-------------------------------------------------- +assert_select "ol" do |elements| + elements.each do |element| + assert_select element, "li", 4 + end +end + +assert_select "ol" do + assert_select "li", 8 +end +-------------------------------------------------- + +The +assert_select+ assertion is quite powerful. For more advanced usage, refer to its link:http://api.rubyonrails.com/classes/ActionController/Assertions/SelectorAssertions.html[documentation]. ==== Additional View-based Assertions ==== There are more assertions that are primarily used in testing views: -[grid="all"] -`----------------------------------------------------------------------------------`------------------------------------------------------- -Assertion Purpose ------------------------------------------------------------------------------------------------------------------------------------------- -+assert_select_email+ Allows you to make assertions on the body of an e-mail. -+assert_select_rjs+ Allows you to make assertions on RJS response. +assert_select_rjs+ has variants which allow you to narrow down on the updated element or even a particular operation on an element. -+assert_select_encoded+ Allows you to make assertions on encoded HTML. It does this by un-encoding the contents of each element and then calling the block with all the un-encoded elements. -+css_select(selector)+ or +css_select(element, selector)+ Returns an array of all the elements selected by the _selector_. In the second variant it first matches the base _element_ and tries to match the _selector_ expression on any of its children. If there are no matches both variants return an empty array. ------------------------------------------------------------------------------------------------------------------------------------------- +[options="header"] +|========================================================================================================================================= +|Assertion |Purpose +|+assert_select_email+ |Allows you to make assertions on the body of an e-mail. +|+assert_select_rjs+ |Allows you to make assertions on RJS response. +assert_select_rjs+ has variants which allow you to narrow down on the updated element or even a particular operation on an element. +|+assert_select_encoded+ |Allows you to make assertions on encoded HTML. It does this by un-encoding the contents of each element and then calling the block with all the un-encoded elements. +|+css_select(selector)+ or +css_select(element, selector)+ |Returns an array of all the elements selected by the _selector_. In the second variant it first matches the base _element_ and tries to match the _selector_ expression on any of its children. If there are no matches both variants return an empty array. +|========================================================================================================================================= Here's an example of using +assert_select_email+: @@ -664,22 +675,21 @@ Integration tests inherit from +ActionController::IntegrationTest+. This makes a In addition to the standard testing helpers, there are some additional helpers available to integration tests: -[grid="all"] -`----------------------------------------------------------------------------------`------------------------------------------------------- -Helper Purpose ------------------------------------------------------------------------------------------------------------------------------------------- -+https?+ Returns +true+ if the session is mimicking a secure HTTPS request. -+https!+ Allows you to mimic a secure HTTPS request. -+host!+ Allows you to set the host name to use in the next request. -+redirect?+ Returns +true+ if the last request was a redirect. -+follow_redirect!+ Follows a single redirect response. -+request_via_redirect(http_method, path, [parameters], [headers])+ Allows you to make an HTTP request and follow any subsequent redirects. -+post_via_redirect(path, [parameters], [headers])+ Allows you to make an HTTP POST request and follow any subsequent redirects. -+get_via_redirect(path, [parameters], [headers])+ Allows you to make an HTTP GET request and follow any subsequent redirects. -+put_via_redirect(path, [parameters], [headers])+ Allows you to make an HTTP PUT request and follow any subsequent redirects. -+delete_via_redirect(path, [parameters], [headers])+ Allows you to make an HTTP DELETE request and follow any subsequent redirects. -+open_session+ Opens a new session instance. ------------------------------------------------------------------------------------------------------------------------------------------- +[options="header"] +|========================================================================================================================================= +|Helper |Purpose +|+https?+ |Returns +true+ if the session is mimicking a secure HTTPS request. +|+https!+ |Allows you to mimic a secure HTTPS request. +|+host!+ |Allows you to set the host name to use in the next request. +|+redirect?+ |Returns +true+ if the last request was a redirect. +|+follow_redirect!+ |Follows a single redirect response. +|+request_via_redirect(http_method, path, [parameters], [headers])+ |Allows you to make an HTTP request and follow any subsequent redirects. +|+post_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP POST request and follow any subsequent redirects. +|+get_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP GET request and follow any subsequent redirects. +|+put_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP PUT request and follow any subsequent redirects. +|+delete_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP DELETE request and follow any subsequent redirects. +|+open_session+ |Opens a new session instance. +|========================================================================================================================================= === Integration Testing Examples === @@ -767,18 +777,17 @@ end You don't need to set up and run your tests by hand on a test-by-test basis. Rails comes with a number of rake tasks to help in testing. The table below lists all rake tasks that come along in the default Rakefile when you initiate a Rail project. -[grid="all"] ------------------------------------------------------------------------------------- -Tasks Description ------------------------------------------------------------------------------------- -+rake test+ Runs all unit, functional and integration tests. You can also simply run +rake+ as the _test_ target is the default. -+rake test:units+ Runs all the unit tests from +test/unit+ -+rake test:functionals+ Runs all the functional tests from +test/functional+ -+rake test:integration+ Runs all the integration tests from +test/integration+ -+rake test:recent+ Tests recent changes -+rake test:uncommitted+ Runs all the tests which are uncommitted. Only supports Subversion -+rake test:plugins+ Run all the plugin tests from +vendor/plugins/*/**/test+ (or specify with +PLUGIN=_name_+) ------------------------------------------------------------------------------------- +[options="header"] +|=================================================================================== +|Tasks |Description +|+rake test+ |Runs all unit, functional and integration tests. You can also simply run +rake+ as the _test_ target is the default. +|+rake test:units+ |Runs all the unit tests from +test/unit+ +|+rake test:functionals+ |Runs all the functional tests from +test/functional+ +|+rake test:integration+ |Runs all the integration tests from +test/integration+ +|+rake test:recent+ |Tests recent changes +|+rake test:uncommitted+ |Runs all the tests which are uncommitted. Only supports Subversion +|+rake test:plugins+ |Run all the plugin tests from +vendor/plugins/*/**/test+ (or specify with +PLUGIN=_name_+) +|=================================================================================== == Brief Note About Test::Unit == @@ -983,7 +992,7 @@ The built-in +test/unit+ based testing is not the only way to test Rails applica * link:http://avdi.org/projects/nulldb/[NullDB], a way to speed up testing by avoiding database use. * link:http://github.com/thoughtbot/factory_girl/tree/master[Factory Girl], as replacement for fixtures. * link:http://www.thoughtbot.com/projects/shoulda[Shoulda], an extension to +test/unit+ with additional helpers, macros, and assertions. -* link: http://rspec.info/[RSpec], a behavior-driven development framework +* link:http://rspec.info/[RSpec], a behavior-driven development framework == Changelog == |