1 = Action Pack -- On rails from request to response
3 Action Pack splits the response to a web request into a controller part
4 (performing the logic) and a view part (rendering a template). This two-step
5 approach is known as an action, which will normally create, read, update, or
6 delete (CRUD for short) some sort of model part (often backed by a database)
7 before choosing either to render a template or redirecting to another action.
9 Action Pack implements these actions as public methods on Action Controllers
10 and uses Action Views to implement the template rendering. Action Controllers
11 are then responsible for handling all the actions relating to a certain part
12 of an application. This grouping usually consists of actions for lists and for
13 CRUDs revolving around a single (or a few) model objects. So ContactController
14 would be responsible for listing contacts, creating, deleting, and updating
15 contacts. A WeblogController could be responsible for both posts and comments.
17 Action View templates are written using embedded Ruby in tags mingled in with
18 the HTML. To avoid cluttering the templates with code, a bunch of helper
19 classes provide common behavior for forms, dates, and strings. And it's easy
20 to add specific helpers to keep the separation as the application evolves.
22 Note: Some of the features, such as scaffolding and form building, are tied to
23 ActiveRecord[http://activerecord.rubyonrails.org] (an object-relational
24 mapping package), but that doesn't mean that Action Pack depends on Active
25 Record. Action Pack is an independent package that can be used with any sort
26 of backend (Instiki[http://www.instiki.org], which is based on an older version
27 of Action Pack, used Madeleine for example). Read more about the role Action
28 Pack can play when used together with Active Record on
29 http://www.rubyonrails.org.
31 A short rundown of the major features:
33 * Actions grouped in controller as methods instead of separate command objects
34 and can therefore share helper methods.
36 BlogController < ActionController::Base
38 @customer = find_customer
42 @customer = find_customer
43 @customer.attributes = params[:customer]
45 redirect_to(:action => "display") :
46 render(:action => "edit")
50 def find_customer() Customer.find(params[:id]) end
53 {Learn more}[link:classes/ActionController/Base.html]
56 * Embedded Ruby for templates (no new "easy" template language)
58 <% for post in @posts %>
59 Title: <%= post.title %>
62 All post titles: <%= @post.collect{ |p| p.title }.join ", " %>
64 <% unless @person.is_client? %>
65 Not for clients to see...
68 {Learn more}[link:classes/ActionView.html]
71 * Builder-based templates (great for XML content, like RSS)
73 xml.rss("version" => "2.0") do
75 xml.title(@feed_title)
77 xml.description "Basecamp: Recent items"
81 for item in @recent_items
83 xml.title(item_title(item))
84 xml.description(item_description(item))
85 xml.pubDate(item_pubDate(item))
86 xml.guid(@recent_items.url(item))
87 xml.link(@recent_items.url(item))
93 {Learn more}[link:classes/ActionView/Base.html]
96 * Filters for pre and post processing of the response (as methods, procs, and classes)
98 class WeblogController < ActionController::Base
99 before_filter :authenticate, :cache, :audit
100 after_filter { |c| c.response.body = GZip::compress(c.response.body) }
101 after_filter LocalizeFilter
104 # Before this action is run, the user will be authenticated, the cache
105 # will be examined to see if a valid copy of the results already
106 # exists, and the action will be logged for auditing.
108 # After this action has run, the output will first be localized then
109 # compressed to minimize bandwidth usage
114 # Implement the filter with full access to both request and response
118 {Learn more}[link:classes/ActionController/Filters/ClassMethods.html]
121 * Helpers for forms, dates, action links, and text
123 <%= text_field "post", "title", "size" => 30 %>
124 <%= html_date_select(Date.today) %>
125 <%= link_to "New post", :controller => "post", :action => "new" %>
126 <%= truncate(post.title, 25) %>
128 {Learn more}[link:classes/ActionView/Helpers.html]
131 * Layout sharing for template reuse (think simple version of Struts
132 Tiles[http://jakarta.apache.org/struts/userGuide/dev_tiles.html])
134 class WeblogController < ActionController::Base
135 layout "weblog_layout"
141 Layout file (called weblog_layout):
142 <html><body><%= yield %></body></html>
144 Template for hello_world action:
147 Result of running hello_world action:
148 <html><body><h1>Hello world</h1></body></html>
150 {Learn more}[link:classes/ActionController/Layout/ClassMethods.html]
153 * Routing makes pretty urls incredibly easy
155 map.connect 'clients/:client_name/:project_name/:controller/:action'
157 Accessing /clients/37signals/basecamp/project/dash calls ProjectController#dash with
158 { "client_name" => "37signals", "project_name" => "basecamp" } in params[:params]
160 From that URL, you can rewrite the redirect in a number of ways:
162 redirect_to(:action => "edit") =>
163 /clients/37signals/basecamp/project/dash
165 redirect_to(:client_name => "nextangle", :project_name => "rails") =>
166 /clients/nextangle/rails/project/dash
168 {Learn more}[link:classes/ActionController/Base.html]
171 * Javascript and Ajax integration.
173 link_to_function "Greeting", "alert('Hello world!')"
174 link_to_remote "Delete this post", :update => "posts",
175 :url => { :action => "destroy", :id => post.id }
177 {Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html]
180 * Pagination for navigating lists of results.
185 paginate :people, :order => 'last_name, first_name'
189 <%= link_to "Previous page", { :page => @pages.current.previous } if @pages.current.previous %>
190 <%= link_to "Next page", { :page => @pages.current.next } if @pages.current.next %>
192 {Learn more}[link:classes/ActionController/Pagination.html]
195 * Easy testing of both controller and template result through TestRequest/Response
197 class LoginControllerTest < Test::Unit::TestCase
199 @controller = LoginController.new
200 @request = ActionController::TestRequest.new
201 @response = ActionController::TestResponse.new
204 def test_failing_authenticate
205 process :authenticate, :user_name => "nop", :password => ""
206 assert flash.has_key?(:alert)
207 assert_redirected_to :action => "index"
211 {Learn more}[link:classes/ActionController/TestRequest.html]
214 * Automated benchmarking and integrated logging
216 Processing WeblogController#index (for 127.0.0.1 at Fri May 28 00:41:55)
217 Parameters: {"action"=>"index", "controller"=>"weblog"}
218 Rendering weblog/index (200 OK)
219 Completed in 0.029281 (34 reqs/sec)
221 If Active Record is used as the model, you'll have the database debugging
224 Processing WeblogController#create (for 127.0.0.1 at Sat Jun 19 14:04:23)
225 Params: {"controller"=>"weblog", "action"=>"create",
226 "post"=>{"title"=>"this is good"} }
227 SQL (0.000627) INSERT INTO posts (title) VALUES('this is good')
228 Redirected to http://test/weblog/display/5
229 Completed in 0.221764 (4 reqs/sec) | DB: 0.059920 (27%)
231 You specify a logger through a class method, such as:
233 ActionController::Base.logger = Logger.new("Application Log")
234 ActionController::Base.logger = Log4r::Logger.new("Application Log")
237 * Caching at three levels of granularity (page, action, fragment)
239 class WeblogController < ActionController::Base
241 caches_action :account
244 # the output of the method will be cached as
245 # ActionController::Base.page_cache_directory + "/weblog/show/n.html"
246 # and the web server will pick it up without even hitting Rails
250 # the output of the method will be cached in the fragment store
251 # but Rails is hit to retrieve it, so filters are run
255 List.update(params[:list][:id], params[:list])
256 expire_page :action => "show", :id => params[:list][:id]
257 expire_action :action => "account"
258 redirect_to :action => "show", :id => params[:list][:id]
262 {Learn more}[link:classes/ActionController/Caching.html]
265 * Component requests from one controller to another
267 class WeblogController < ActionController::Base
268 # Performs a method and then lets hello_world output its render
270 do_other_stuff_before_hello_world
271 render_component :controller => "greeter", :action => "hello_world"
275 class GreeterController < ActionController::Base
277 render_text "Hello World!"
281 The same can be done in a view to do a partial rendering:
283 Let's see a greeting:
284 <%= render_component :controller => "greeter", :action => "hello_world" %>
286 {Learn more}[link:classes/ActionController/Components.html]
289 * Powerful debugging mechanism for local requests
291 All exceptions raised on actions performed on the request of a local user
292 will be presented with a tailored debugging screen that includes exception
293 message, stack trace, request parameters, session contents, and the
294 half-finished response.
296 {Learn more}[link:classes/ActionController/Rescue.html]
299 * Scaffolding for Active Record model objects
301 class AccountController < ActionController::Base
305 The AccountController now has the full CRUD range of actions and default
306 templates: list, show, destroy, new, create, edit, update
308 {Learn more}[link:classes/ActionController/Scaffolding/ClassMethods.html]
311 * Form building for Active Record model objects
313 The post object has a title (varchar), content (text), and
318 ...will generate something like (the selects will have more options, of
321 <form action="create" method="POST">
324 <input type="text" name="post[title]" value="<%= @post.title %>" />
328 <textarea name="post[content]"><%= @post.title %></textarea>
331 <b>Written on:</b><br/>
332 <select name='post[written_on(3i)]'><option>18</option></select>
333 <select name='post[written_on(2i)]'><option value='7'>July</option></select>
334 <select name='post[written_on(1i)]'><option>2004</option></select>
337 <input type="submit" value="Create">
340 This form generates a params[:post] array that can be used directly in a save action:
342 class WeblogController < ActionController::Base
344 post = Post.create(params[:post])
345 redirect_to :action => "display", :id => post.id
349 {Learn more}[link:classes/ActionView/Helpers/ActiveRecordHelper.html]
352 * Runs on top of WEBrick, Mongrel, CGI, FCGI, and mod_ruby
355 == Simple example (from outside of Rails)
357 This example will implement a simple weblog system using inline templates and
358 an Active Record model. So let's build that WeblogController with just a few
361 require 'action_controller'
364 class WeblogController < ActionController::Base
365 layout "weblog/layout"
368 @posts = Post.find(:all)
372 @post = Post.find(params[:id])
380 @post = Post.create(params[:post])
381 redirect_to :action => "display", :id => @post.id
385 WeblogController::Base.view_paths = [ File.dirname(__FILE__) ]
386 WeblogController.process_cgi if $0 == __FILE__
388 The last two lines are responsible for telling ActionController where the
389 template files are located and actually running the controller on a new
390 request from the web-server (like to be Apache).
392 And the templates look like this:
400 <% for post in @posts %>
401 <p><%= link_to(post.title, :action => "display", :id => post.id %></p>
406 <b><%= post.title %></b><br/>
407 <b><%= post.content %></b>
413 This simple setup will list all the posts in the system on the index page,
414 which is called by accessing /weblog/. It uses the form builder for the Active
415 Record model to make the new screen, which in turn hands everything over to
416 the create action (that's the default target for the form builder when given a
417 new model). After creating the post, it'll redirect to the display page using
418 an URL such as /weblog/display/5 (where 5 is the id of the post).
423 Action Pack ships with three examples that all demonstrate an increasingly
424 detailed view of the possibilities. First is blog_controller that is just a
425 single file for the whole MVC (but still split into separate parts). Second is
426 the debate_controller that uses separate template files and multiple screens.
427 Third is the address_book_controller that uses the layout feature to separate
428 template casing from content.
430 Please note that you might need to change the "shebang" line to
431 #!/usr/local/env ruby, if your Ruby is not placed in /usr/local/bin/ruby
433 Also note that these examples are all for demonstrating using Action Pack on
434 its own. Not for when it's used inside of Rails.
438 The latest version of Action Pack can be found at
440 * http://rubyforge.org/project/showfiles.php?group_id=249
442 Documentation can be found at
444 * http://api.rubyonrails.com
449 You can install Action Pack with the following command.
451 % [sudo] ruby install.rb
453 from its distribution directory.
458 Action Pack is released under the MIT license.
463 The Action Pack homepage is http://www.rubyonrails.org. You can find
464 the Action Pack RubyForge page at http://rubyforge.org/projects/actionpack.
465 And as Jim from Rake says:
467 Feel free to submit commits or feature requests. If you send a patch,
468 remember to update the corresponding unit tests. If fact, I prefer
469 new feature to be submitted in the form of new unit tests.