提交 703d0e05 编写于 作者: X Xavier Noria

Merge branch 'master' of git://github.com/lifo/docrails

...@@ -101,7 +101,7 @@ def validates_with(*args, &block) ...@@ -101,7 +101,7 @@ def validates_with(*args, &block)
# class Person # class Person
# include ActiveModel::Validations # include ActiveModel::Validations
# #
# validates :instance_validations # validate :instance_validations
# #
# def instance_validations # def instance_validations
# validates_with MyValidator # validates_with MyValidator
...@@ -116,7 +116,7 @@ def validates_with(*args, &block) ...@@ -116,7 +116,7 @@ def validates_with(*args, &block)
# class Person # class Person
# include ActiveModel::Validations # include ActiveModel::Validations
# #
# validates :instance_validations, :on => :create # validate :instance_validations, :on => :create
# #
# def instance_validations # def instance_validations
# validates_with MyValidator, MyOtherValidator # validates_with MyValidator, MyOtherValidator
......
...@@ -287,7 +287,7 @@ def touch(name = nil) ...@@ -287,7 +287,7 @@ def touch(name = nil)
private private
# A hook to be overriden by association modules. # A hook to be overridden by association modules.
def destroy_associations def destroy_associations
end end
......
...@@ -204,7 +204,7 @@ def textile(body, lite_mode=false) ...@@ -204,7 +204,7 @@ def textile(body, lite_mode=false)
t = RedCloth.new(body) t = RedCloth.new(body)
t.hard_breaks = false t.hard_breaks = false
t.lite_mode = lite_mode t.lite_mode = lite_mode
t.to_html(:notestuff, :plusplus, :code, :tip) t.to_html(:notestuff, :plusplus, :code)
end end
end end
......
...@@ -3,23 +3,24 @@ ...@@ -3,23 +3,24 @@
module RailsGuides module RailsGuides
module TextileExtensions module TextileExtensions
def notestuff(body) def notestuff(body)
body.gsub!(/^(IMPORTANT|CAUTION|WARNING|NOTE|INFO)[.:](.*)$/) do |m| # The following regexp detects special labels followed by a
css_class = $1.downcase # paragraph, perhaps at the end of the document.
css_class = 'warning' if css_class.in?(['caution', 'important']) #
# It is important that we do not eat more than one newline
result = "<div class='#{css_class}'><p>" # because formatting may be wrong otherwise. For example,
result << $2.strip # if a bulleted list follows the first item is not rendered
result << '</p></div>' # as a list item, but as a paragraph starting with a plain
result # asterisk.
end body.gsub!(/^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO)[.:](.*?)(\n(?=\n)|\Z)/m) do |m|
end css_class = case $1
when 'CAUTION', 'IMPORTANT'
def tip(body) 'warning'
body.gsub!(/^TIP[.:](.*)$/) do |m| when 'TIP'
result = "<div class='info'><p>" 'info'
result << $1.strip else
result << '</p></div>' $1.downcase
result end
%Q(<div class="#{css_class}"><p>#{$2.strip}</p></div>)
end end
end end
......
...@@ -78,14 +78,14 @@ The result of expressions follow them and are introduced by "# => ", vertically ...@@ -78,14 +78,14 @@ The result of expressions follow them and are introduced by "# => ", vertically
If a line is too long, the comment may be placed on the next line: If a line is too long, the comment may be placed on the next line:
<ruby> <ruby>
# label(:post, :title) # label(:post, :title)
# # => <label for="post_title">Title</label> # # => <label for="post_title">Title</label>
# #
# label(:post, :title, "A short title") # label(:post, :title, "A short title")
# # => <label for="post_title">A short title</label> # # => <label for="post_title">A short title</label>
# #
# label(:post, :title, "A short title", :class => "title_label") # label(:post, :title, "A short title", :class => "title_label")
# # => <label for="post_title" class="title_label">A short title</label> # # => <label for="post_title" class="title_label">A short title</label>
</ruby> </ruby>
Avoid using any printing methods like +puts+ or +p+ for that purpose. Avoid using any printing methods like +puts+ or +p+ for that purpose.
......
...@@ -27,9 +27,7 @@ The most basic form helper is +form_tag+. ...@@ -27,9 +27,7 @@ The most basic form helper is +form_tag+.
<% end %> <% end %>
</erb> </erb>
When called without arguments like this, it creates a form element that has the current page as its action and "post" as its method (some line breaks added for readability): When called without arguments like this, it creates a +&lt;form&gt;+ tag which, when submitted, will POST to the current page. For instance, assuming the current page is +/home/index+, the generated HTML will look like this (some line breaks added for readability):
Sample output from +form_tag+:
<html> <html>
<form accept-charset="UTF-8" action="/home/index" method="post"> <form accept-charset="UTF-8" action="/home/index" method="post">
...@@ -41,36 +39,30 @@ Sample output from +form_tag+: ...@@ -41,36 +39,30 @@ Sample output from +form_tag+:
</form> </form>
</html> </html>
If you carefully observe this output, you can see that the helper generated something you didn't specify: a +div+ element with two hidden input elements inside. The first input element with name +utf8+ enforces browsers to properly respect your form's character encoding and is generated for all forms whether action is "get" or "post". Second input element with name +authenticity_token+ is a security feature of Rails called *cross-site request forgery protection* and form helpers generate it for every form whose action is not "get" (provided that this security feature is enabled). You can read more about this in the "Ruby On Rails Security Guide":./security.html#_cross_site_reference_forgery_csrf. Now, you'll notice that the HTML contains something extra: a +div+ element with two hidden input elements inside. This div is important, because the form cannot be successfully submitted without it. The first input element with name +utf8+ enforces browsers to properly respect your form's character encoding and is generated for all forms whether their actions are "GET" or "POST". The second input element with name +authenticity_token+ is a security feature of Rails called *cross-site request forgery protection*, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the "Security Guide":./security.html#_cross_site_reference_forgery_csrf.
NOTE: Throughout this guide, this +div+ with the hidden input elements will be stripped away to have clearer code samples. NOTE: Throughout this guide, the +div+ with the hidden input elements will be excluded from code samples for brevity.
h4. A Generic Search Form h4. A Generic Search Form
Probably the most minimal form often seen on the web is a search form with a single text input for search terms. This form consists of: One of the most basic forms you see on the web is a search form. This form contains:
# a form element with "GET" method, # a form element with "GET" method,
# a label for the input, # a label for the input,
# a text input element, and # a text input element, and
# a submit element. # a submit element.
IMPORTANT: Always use "GET" as the method for search forms. This allows users to bookmark a specific search and get back to it. More generally Rails encourages you to use the right HTTP verb for an action. To create this form you will use +form_tag+, +label_tag+, +text_field_tag+, and +submit_tag+, respectively. Like this:
To create this form you will use +form_tag+, +label_tag+, +text_field_tag+, and +submit_tag+, respectively.
A basic search form
<erb> <erb>
<%= form_tag(search_path, :method => "get") do %> <%= form_tag("/search", :method => "get") do %>
<%= label_tag(:q, "Search for:") %> <%= label_tag(:q, "Search for:") %>
<%= text_field_tag(:q) %> <%= text_field_tag(:q) %>
<%= submit_tag("Search") %> <%= submit_tag("Search") %>
<% end %> <% end %>
</erb> </erb>
TIP: +search_path+ can be a named route specified in "routes.rb" as: <br /><code>match "search" => "search"</code> This declares that path "/search" will be handled by action "search" belonging to controller "search". This will generate the following HTML:
The above view code will result in the following markup:
<html> <html>
<form accept-charset="UTF-8" action="/search" method="get"> <form accept-charset="UTF-8" action="/search" method="get">
...@@ -80,47 +72,35 @@ The above view code will result in the following markup: ...@@ -80,47 +72,35 @@ The above view code will result in the following markup:
</form> </form>
</html> </html>
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.
Besides +text_field_tag+ and +submit_tag+, there is a similar helper for _every_ form control in HTML. 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 the example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript. IMPORTANT: Always use "GET" as the method for search forms. This allows users to bookmark a specific search and get back to it. More generally Rails encourages you to use the right HTTP verb for an action.
h4. Multiple Hashes in Form Helper Calls h4. Multiple Hashes in Form Helper Calls
By now you've seen that the +form_tag+ helper accepts 2 arguments: the path for the action and an options hash. This hash specifies the method of form submission and HTML options such as the form element's class. The +form_tag+ helper accepts 2 arguments: the path for the action and an options hash. This hash specifies the method of form submission and HTML options such as the form element's class.
As with the +link_to+ helper, the path argument doesn't have to be given a string. It can be a hash of URL parameters that Rails' routing mechanism will turn into a valid URL. However, this is a bad way to pass multiple hashes as method arguments: 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 recognizable by Rails' routing mechanism, which will turn the hash into a valid URL. However, since both arguments to +form_tag+ are hashes, you can easily run into a problem if you would like to specify both. For instance, let's say you write this:
<ruby> <ruby>
form_tag(:controller => "people", :action => "search", :method => "get", :class => "nifty_form") form_tag(:controller => "people", :action => "search", :method => "get", :class => "nifty_form")
# => <form accept-charset="UTF-8" action="/people/search?method=get&class=nifty_form" method="post"> # => '<form accept-charset="UTF-8" action="/people/search?method=get&class=nifty_form" method="post">'
</ruby> </ruby>
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 correct way of passing multiple hashes as arguments is to delimit the first hash (or both hashes) with curly brackets: Here, +method+ and +class+ are appended to the query string of the generated URL because you even though you mean to write two hashes, you really only specified one. So you need to tell Ruby which is which by delimiting the first hash (or both) with curly brackets. This will generate the HTML you expect:
<ruby> <ruby>
form_tag({:controller => "people", :action => "search"}, :method => "get", :class => "nifty_form") form_tag({:controller => "people", :action => "search"}, :method => "get", :class => "nifty_form")
# => <form accept-charset="UTF-8" action="/people/search" method="get" class="nifty_form"> # => '<form accept-charset="UTF-8" action="/people/search" method="get" class="nifty_form">'
</ruby> </ruby>
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.
WARNING: Do not delimit the second hash without doing so with the first hash, otherwise your method invocation will result in an +expecting tASSOC+ syntax error.
h4. Helpers for Generating Form Elements h4. Helpers for Generating Form Elements
Rails provides a series of helpers for generating form elements such as checkboxes, text fields and radio buttons. These basic helpers, with names ending in <notextile>_tag</notextile> such as +text_field_tag+ and +check_box_tag+ generate just a single +&lt;input&gt;+ element. The first parameter to these is always the name of the input. In the controller this name will be the key in the +params+ hash used to get the value entered by the user. For example, if the form contains Rails provides a series of helpers for generating form elements such as checkboxes, text fields, and radio buttons. These basic helpers, with names ending in "_tag" (such as +text_field_tag+ and +check_box_tag+), generate just a single +&lt;input&gt;+ element. The first parameter to these is always the name of the input. When the form is submitted, the name will be passed along with the form data, and will make its way to the +params+ hash in the controller with the value entered by the user for that field. For example, if the form contains +<%= text_field_tag(:query) %>+, then you would be able to get the value of this field in the controller with +params[:query]+.
<erb> When naming inputs, Rails uses certain conventions that make it possible to submit parameters with non-scalar values such as arrays or hashes, which will also be accessible in +params+. You can read more about them in "chapter 7 of this guide":#understanding-parameter-naming-conventions. For details on the precise usage of these helpers, please refer to the "API documentation":http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html.
<%= text_field_tag(:query) %>
</erb>
then the controller code should use
<ruby>
params[:query]
</ruby>
to retrieve the value entered by the user. When naming inputs, be aware that Rails uses certain conventions that control whether values are at the top level of the +params+ hash, inside an array or a nested hash and so on. You can read more about them in the parameter_names section. For details on the precise usage of these helpers, please refer to the "API documentation":http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html.
h5. Checkboxes h5. Checkboxes
...@@ -133,7 +113,7 @@ Checkboxes are form controls that give the user a set of options they can enable ...@@ -133,7 +113,7 @@ Checkboxes are form controls that give the user a set of options they can enable
<%= label_tag(:pet_cat, "I own a cat") %> <%= label_tag(:pet_cat, "I own a cat") %>
</erb> </erb>
output: This generates the following:
<html> <html>
<input id="pet_dog" name="pet_dog" type="checkbox" value="1" /> <input id="pet_dog" name="pet_dog" type="checkbox" value="1" />
...@@ -142,11 +122,11 @@ output: ...@@ -142,11 +122,11 @@ output:
<label for="pet_cat">I own a cat</label> <label for="pet_cat">I own a cat</label>
</html> </html>
The second parameter to +check_box_tag+ is the value of the input. This is the value that will be submitted by the browser if the checkbox is ticked (i.e. the value that will be present in the +params+ hash). With the above form you would check the value of +params[:pet_dog]+ and +params[:pet_cat]+ to see which pets the user owns. The first parameter to +check_box_tag+, of course, is the name of the input. The second parameter, naturally, is the value of the input. This value will be included in the form data (and be present in +params+) when the checkbox is checked.
h5. Radio Buttons h5. Radio Buttons
Radio buttons, while similar to checkboxes, are controls that specify a set of options in which they are mutually exclusive (i.e. the user can only pick one): Radio buttons, while similar to checkboxes, are controls that specify a set of options in which they are mutually exclusive (i.e., the user can only pick one):
<erb> <erb>
<%= radio_button_tag(:age, "child") %> <%= radio_button_tag(:age, "child") %>
...@@ -155,7 +135,7 @@ Radio buttons, while similar to checkboxes, are controls that specify a set of o ...@@ -155,7 +135,7 @@ Radio buttons, while similar to checkboxes, are controls that specify a set of o
<%= label_tag(:age_adult, "I'm over 21") %> <%= label_tag(:age_adult, "I'm over 21") %>
</erb> </erb>
output: Output:
<html> <html>
<input id="age_child" name="age" type="radio" value="child" /> <input id="age_child" name="age" type="radio" value="child" />
...@@ -164,13 +144,13 @@ output: ...@@ -164,13 +144,13 @@ output:
<label for="age_adult">I'm over 21</label> <label for="age_adult">I'm over 21</label>
</html> </html>
As with +check_box_tag+ the second parameter to +radio_button_tag+ is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one and +params[:age]+ will contain either "child" or "adult". As with +check_box_tag+, the second parameter to +radio_button_tag+ is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one, and +params[:age]+ will contain either "child" or "adult".
IMPORTANT: Always use labels for each checkbox and radio button. They associate text with a specific option and provide a larger clickable region. NOTE: Always use labels for checkbox and radio buttons. They associate text with a specific option and make it easier for users to click the inputs by expanding the clickable region.
h4. Other Helpers of Interest h4. Other Helpers of Interest
Other form controls worth mentioning are the text area, password input, hidden input, search input, tel input, url input and email input: Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, URL fields and email fields:
<erb> <erb>
<%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %> <%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %>
...@@ -194,9 +174,11 @@ Output: ...@@ -194,9 +174,11 @@ Output:
<input id="user_address" size="30" name="user[address]" type="email" /> <input id="user_address" size="30" name="user[address]" type="email" />
</html> </html>
Hidden inputs are not shown to the user, but they hold data like any textual input. Values inside them can be changed with JavaScript. The search, tel, url and email inputs are specified in HTML5 and may receive special handling and/or formatting in some user-agents. Hidden inputs are not shown to the user but instead hold data like any textual input. Values inside them can be changed with JavaScript.
IMPORTANT: The search, telephone, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the "Security Guide":security.html#logging.
h3. Dealing with Model Objects h3. Dealing with Model Objects
......
...@@ -477,7 +477,7 @@ Several methods are provided that allow you to control all this: ...@@ -477,7 +477,7 @@ Several methods are provided that allow you to control all this:
For example, this migration For example, this migration
<ruby> <pre>
class CreateProducts < ActiveRecord::Migration class CreateProducts < ActiveRecord::Migration
def change def change
suppress_messages do suppress_messages do
...@@ -496,7 +496,7 @@ class CreateProducts < ActiveRecord::Migration ...@@ -496,7 +496,7 @@ class CreateProducts < ActiveRecord::Migration
end end
end end
end end
</ruby> </pre>
generates the following output generates the following output
...@@ -514,40 +514,107 @@ If you just want Active Record to shut up then running +rake db:migrate VERBOSE= ...@@ -514,40 +514,107 @@ If you just want Active Record to shut up then running +rake db:migrate VERBOSE=
h3. Using Models in Your Migrations h3. Using Models in Your Migrations
When creating or updating data in a migration it is often tempting to use one of your models. After all they exist to provide easy access to the underlying data. This can be done but some caution should be observed. When creating or updating data in a migration it is often tempting to use one of your models. After all they exist to provide easy access to the underlying data. This can be done, but some caution should be observed.
Consider for example a migration that uses the +Product+ model to update a row in the corresponding table. Alice later updates the +Product+ model, adding a new column and a validation on it. Bob comes back from holiday, updates the source and runs outstanding migrations with +rake db:migrate+, including the one that used the +Product+ model. When the migration runs the source is up to date and so the +Product+ model has the validation added by Alice. The database however is still old and so does not have that column and an error ensues because that validation is on a column that does not yet exist. For example, problems occur when the model uses database columns which are (1) not currently in the database and (2) will be created by this or a subsequent migration.
Frequently I just want to update rows in the database without writing out the SQL by hand: I'm not using anything specific to the model. One pattern for this is to define a copy of the model inside the migration itself, for example: Consider this example, where Alice and Bob are working on the same code base which contains a +Product+ model:
<ruby> Bob goes on vacation.
class AddPartNumberToProducts < ActiveRecord::Migration
class Product < ActiveRecord::Base Alice creates a migration for the +products+ table which adds a new column and initializes it.
She also adds a validation to the Product model for the new column.
<pre>
# db/migrate/20100513121110_add_flag_to_product.rb
class AddFlagToProduct < ActiveRecord::Migration
def change
add_column :products, :flag, :int
Product.all.each { |f| f.update_attributes!(:flag => 'false') }
end end
end
</pre>
<pre>
# app/model/product.rb
class Product < ActiveRecord::Base
validates_presence_of :flag
end
</pre>
Alice adds a second migration which adds and initializes another column to the +products+ table and also adds a validation to the Product model for the new column.
<pre>
# db/migrate/20100515121110_add_fuzz_to_product.rb
class AddFuzzToProduct < ActiveRecord::Migration
def change def change
... add_column :products, :fuzz, :string
Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
end end
end end
</ruby> </pre>
The migration has its own minimal copy of the +Product+ model and no longer cares about the +Product+ model defined in the application.
h4. Dealing with Changing Models <pre>
# app/model/product.rb
For performance reasons information about the columns a model has is cached. For example if you add a column to a table and then try and use the corresponding model to insert a new row it may try to use the old column information. You can force Active Record to re-read the column information with the +reset_column_information+ method, for example class Product < ActiveRecord::Base
validates_presence_of :flag
validates_presence_of :fuzz
end
</pre>
<ruby> Both migrations work for Alice.
class AddPartNumberToProducts < ActiveRecord::Migration
Bob comes back from vacation and:
# updates the source - which contains both migrations and the latests version of the Product model.
# runs outstanding migrations with +rake db:migrate+, which includes the one that updates the +Product+ model.
The migration crashes because when the model attempts to save, it tries to validate the second added column, which is not in the database when the _first_ migration runs.
<pre>
rake aborted!
An error has occurred, this and all later migrations canceled:
undefined method `fuzz' for #<Product:0x000001049b14a0>
</pre>
A fix for this is to create a local model within the migration. This keeps rails from running the validations, so that the migrations run to completion.
When using a faux model, it's a good idea to call +Product.reset_column_information+ to refresh the ActiveRecord cache for the Product model prior to updating data in the database.
If Alice had done this instead, there would have been no problem:
<pre>
# db/migrate/20100513121110_add_flag_to_product.rb
class AddFlagToProduct < ActiveRecord::Migration
class Product < ActiveRecord::Base class Product < ActiveRecord::Base
end end
def change
add_column :products, :flag, :int
Product.reset_column_information
Product.all.each { |f| f.update_attributes!(:flag => false) }
end
end
</pre>
<pre>
# db/migrate/20100515121110_add_fuzz_to_product.rb
class AddFuzzToProduct < ActiveRecord::Migration
class Product < ActiveRecord::Base
end
def change def change
add_column :product, :part_number, :string add_column :products, :fuzz, :string
Product.reset_column_information Product.reset_column_information
... Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
end end
end end
</ruby> </pre>
h3. Schema Dumping and You h3. Schema Dumping and You
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册