From e4efcfd43e60c4a6eb1def4ecb976e2a3fc8702f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 24 Feb 2005 01:29:43 +0000 Subject: [PATCH] Updated documentation git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@780 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionpack/README | 154 +++++++++++------- actionpack/lib/action_controller/base.rb | 50 +++--- .../lib/action_controller/components.rb | 28 +++- .../lib/action_controller/dependencies.rb | 3 + actionpack/lib/action_controller/helpers.rb | 3 +- actionpack/lib/action_controller/routing.rb | 4 +- 6 files changed, 147 insertions(+), 95 deletions(-) diff --git a/actionpack/README b/actionpack/README index da48752a69..a79cdef953 100755 --- a/actionpack/README +++ b/actionpack/README @@ -50,7 +50,7 @@ A short rundown of the major features: def find_customer() Customer.find(@params["id"]) end end - Learn more in link:classes/ActionController/Base.html + {Learn more}[link:classes/ActionController/Base.html] * Embedded Ruby for templates (no new "easy" template language) @@ -65,30 +65,32 @@ A short rundown of the major features: Not for clients to see... <% end %> - Learn more in link:classes/ActionView.html + {Learn more}[link:classes/ActionView.html] * Builder-based templates (great for XML content, like RSS) - xml.rss("version" => "2.0") do - xml.channel do - xml.title(@feed_title) - xml.link(@url) - xml.description "Basecamp: Recent items" - xml.language "en-us" - xml.ttl "40" + xml.rss("version" => "2.0") do + xml.channel do + xml.title(@feed_title) + xml.link(@url) + xml.description "Basecamp: Recent items" + xml.language "en-us" + xml.ttl "40" - for item in @recent_items - xml.item do - xml.title(item_title(item)) - xml.description(item_description(item)) - xml.pubDate(item_pubDate(item)) - xml.guid(@recent_items.url(item)) - xml.link(@recent_items.url(item)) + for item in @recent_items + xml.item do + xml.title(item_title(item)) + xml.description(item_description(item)) + xml.pubDate(item_pubDate(item)) + xml.guid(@recent_items.url(item)) + xml.link(@recent_items.url(item)) + end end end end - end + + {Learn more}[link:classes/ActionView/Base.html] * Filters for pre and post processing of the response (as methods, procs, and classes) @@ -113,7 +115,7 @@ A short rundown of the major features: end end - Learn more in link:classes/ActionController/Filters/ClassMethods.html + {Learn more}[link:classes/ActionController/Filters/ClassMethods.html] * Helpers for forms, dates, action links, and text @@ -123,7 +125,7 @@ A short rundown of the major features: <%= link_to "New post", :controller => "post", :action => "new" %> <%= truncate(post.title, 25) %> - Learn more in link:classes/ActionView/Helpers.html + {Learn more}[link:classes/ActionView/Helpers.html] * Layout sharing for template reuse (think simple version of Struts @@ -145,28 +147,25 @@ A short rundown of the major features: Result of running hello_world action:

Hello world

- Learn more in link:classes/ActionController/Layout/ClassMethods.html + {Learn more}[link:classes/ActionController/Layout/ClassMethods.html] -* Advanced redirection that makes pretty urls easy +* Routing makes pretty urls incredibly easy - RewriteRule ^/library/books/([A-Z]+)([0-9]+)/([-_a-zA-Z0-9]+)$ \ - /books_controller.cgi?action=$3&type=$1&code=$2 [QSA] [L] + map.connect 'clients/:client_name/:project_name/:controller/:action' - Accessing /library/books/ISBN/0743536703/show calls BooksController#show + Accessing /clients/37signals/basecamp/project/dash calls ProjectController#dash with + { "client_name" => "37signals", "project_name" => "basecamp" } in @params["params"] From that URL, you can rewrite the redirect in a number of ways: redirect_to(:action => "edit") => - /library/books/ISBN/0743536703/edit + /clients/37signals/basecamp/project/dash - redirect_to(:path_params => { "type" => "XTC", "code" => "12354345" }) => - /library/books/XTC/12354345/show + redirect_to(:client_name => "nextangle", :project_name => "rails") => + /clients/nextangle/rails/project/dash - redirect_to(:controller_prefix => "admin", :controller => "accounts") => - /admin/accounts/ - - Learn more in link:classes/ActionController/Base.html + {Learn more}[link:classes/ActionController/Base.html] * Easy testing of both controller and template result through TestRequest/Response @@ -185,7 +184,7 @@ A short rundown of the major features: end end - Learn more in link:classes/ActionController/TestRequest.html + {Learn more}[link:classes/ActionController/TestRequest.html] * Automated benchmarking and integrated logging @@ -211,6 +210,58 @@ A short rundown of the major features: ActionController::Base.logger = Log4r::Logger.new("Application Log") +* Caching at three levels of granularity (page, action, fragment) + + class WeblogController < ActionController::Base + caches_page :show + caches_action :account + + def show + # the output of the method will be cached as + # ActionController::Base.page_cache_directory + "/weblog/show/n.html" + # and the web server will pick it up without even hitting Rails + end + + def account + # the output of the method will be cached in the fragment store + # but Rails is hit to retrieve it, so filters are run + end + + def update + List.update(@params["list"]["id"], @params["list"]) + expire_page :action => "show", :id => @params["list"]["id"] + expire_action :action => "account" + redirect_to :action => "show", :id => @params["list"]["id"] + end + end + + {Learn more}[link:classes/ActionController/Caching.html] + + +* Component requests from one controller to another + + class WeblogController < ActionController::Base + # Performs a method and then lets hello_world output its render + def delegate_action + do_other_stuff_before_hello_world + render_component :controller => "greeter", :action => "hello_world" + end + end + + class GreeterController < ActionController::Base + def hello_world + render_text "Hello World!" + end + end + + The same can be done in a view to do a partial rendering: + + Let's see a greeting: + <%= render_component :controller => "greeter", :action => "hello_world" %> + + {Learn more}[link:classes/ActionController/Components.html] + + * Powerful debugging mechanism for local requests All exceptions raised on actions performed on the request of a local user @@ -218,7 +269,7 @@ A short rundown of the major features: message, stack trace, request parameters, session contents, and the half-finished response. - Learn more in link:classes/ActionController/Rescue.html + {Learn more}[link:classes/ActionController/Rescue.html] * Scaffolding for Action Record model objects @@ -231,7 +282,7 @@ A short rundown of the major features: The AccountController now has the full CRUD range of actions and default templates: list, show, destroy, new, create, edit, update - Learn more in link:classes/ActionController/Scaffolding/ClassMethods.html + {Learn more}link:classes/ActionController/Scaffolding/ClassMethods.html * Form building for Active Record model objects @@ -272,42 +323,21 @@ A short rundown of the major features: end end - Learn more in link:classes/ActionView/Helpers/ActiveRecordHelper.html + {Learn more}[link:classes/ActionView/Helpers/ActiveRecordHelper.html] -* Automated mapping of URLs to controller/action pairs through Apache's - mod_rewrite - - Requesting /blog/display/5 will call BlogController#display and - make 5 available as an instance variable through @params["id"] - - -* Runs on top of CGI, FCGI, and mod_ruby - - See the address_book_controller example for all three forms +* Runs on top of WEBrick, CGI, FCGI, and mod_ruby == Simple example This example will implement a simple weblog system using inline templates and -an Active Record model. The first thing we need to do is setup an .htaccess to -interpret pretty URLs into something the controller can use. Let's use the -simplest form for starters: - - RewriteRule ^weblog/([-_a-zA-Z0-9]+)/([0-9]+)$ \ - /weblog_controller.cgi?action=$2&id=$3 [QSA] - RewriteRule ^weblog/([-_a-zA-Z0-9]+)$ \ - /weblog_controller.cgi?action=$2 [QSA] - RewriteRule ^weblog/$ \ - /weblog_controller.cgi?action=index [QSA] - -Now we'll be able to access URLs like weblog/display/5 and have -WeblogController#display called with { "id" => 5 } in the @params array -available for the action. So let's build that WeblogController with just a few +an Active Record model. So let's build that WeblogController with just a few methods: require 'action_controller' require 'post' + class WeblogController < ActionController::Base layout "weblog/layout" @@ -362,7 +392,7 @@ which is called by accessing /weblog/. It uses the form builder for the Active Record model to make the new screen, which in turns hand everything over to the create action (that's the default target for the form builder when given a new model). After creating the post, it'll redirect to the display page using -an URL such as /weblog/display/5 (where 5 is the id of the post. +an URL such as /weblog/display/5 (where 5 is the id of the post). == Examples @@ -386,7 +416,7 @@ The latest version of Action Pack can be found at Documentation can be found at -* http://actionpack.rubyonrails.org +* http://ap.rubyonrails.com == Installation @@ -405,7 +435,7 @@ Action Pack is released under the same license as Ruby. == Support -The Action Pack homepage is http://actionpack.rubyonrails.org. You can find +The Action Pack homepage is http://www.rubyonrails.com. You can find the Action Pack RubyForge page at http://rubyforge.org/projects/actionpack. And as Jim from Rake says: diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 8b02731510..7c4c226c34 100755 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -14,7 +14,7 @@ module ActionController #:nodoc: end class MissingTemplate < ActionControllerError #:nodoc: end - class RoutingError < ActionControllerError + class RoutingError < ActionControllerError#:nodoc: attr_reader :failures def initialize(message, failures=[]) super(message) @@ -86,9 +86,9 @@ module ActionController #:nodoc: # # # - # A request stemming from a form holding these inputs will include { "post" # => { "name" => "david", "address" => "hyacintvej" } }. + # A request stemming from a form holding these inputs will include { "post" => { "name" => "david", "address" => "hyacintvej" } }. # If the address input had been named "post[address][street]", the @params would have included - # { "post" => { "address" => { "street" => "hyacintvej" } } }. There's no limit to the depth of the nesting. + # { "post" => { "address" => { "street" => "hyacintvej" } } }. There's no limit to the depth of the nesting. # # == Sessions # @@ -156,22 +156,20 @@ module ActionController #:nodoc: # # Redirects work by rewriting the URL of the current action. So if the show action was called by "/library/books/ISBN/0743536703/show", # we can redirect to an edit action simply by doing redirect_to(:action => "edit"), which could throw the user to - # "/library/books/ISBN/0743536703/edit". Naturally, you'll need to setup the .htaccess (or other means of URL rewriting for the web server) - # to point to the proper controller and action in the first place, but once you have, it can be rewritten with ease. + # "/library/books/ISBN/0743536703/edit". Naturally, you'll need to setup the routes configuration file to point to the proper controller + # and action in the first place, but once you have, it can be rewritten with ease. # - # Let's consider a bunch of examples on how to go from "/library/books/ISBN/0743536703/edit" to somewhere else: + # Let's consider a bunch of examples on how to go from "/clients/37signals/basecamp/project/dash" to somewhere else: # - # redirect_to(:action => "show", :action_prefix => "XTC/123") => - # "http://www.singlefile.com/library/books/XTC/123/show" + # redirect_to(:action => "edit") => + # /clients/37signals/basecamp/project/dash + # + # redirect_to(:client_name => "nextangle", :project_name => "rails") => + # /clients/nextangle/rails/project/dash # - # redirect_to(:path_params => {"type" => "EXBC"}) => - # "http://www.singlefile.com/library/books/EXBC/0743536703/show" + # Those redirects happen under the configuration of: # - # redirect_to(:controller => "settings") => - # "http://www.singlefile.com/library/settings/" - # - # For more examples of redirecting options, have a look at the unit test in test/controller/url_test.rb. It's very readable and will give - # you an excellent understanding of the different options and what they do. + # map.connect 'clients/:client_name/:project_name/:controller/:action' # # == Calling multiple redirects or renders # @@ -337,20 +335,20 @@ module ActionController #:nodoc: # * :anchor -- specifies the anchor name to be appended to the path. For example, # url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments' # will produce "/posts/show/10#comments". - # * :only-path -- if true, returns the absolute URL (omitting the protocol, host name, and port) + # * :only_path -- if true, returns the absolute URL (omitting the protocol, host name, and port) # * :host -- overrides the default (current) host if provided # * :protocol -- overrides the default (current) protocol if provided - #   + # # The URL is generated from the remaining keys in the hash. A URL contains two key parts: the and a query string. # Routes composes a query string as the key/value pairs not included in the . - #   + # # The default Routes setup supports a typical Rails path of "controller/action/id" where action and id are optional, with # action defaulting to 'index' when not given. Here are some typical url_for statements and their corresponding URLs: #   # url_for :controller => 'posts', :action => 'recent' # => 'proto://host.com/posts/recent' # url_for :controller => 'posts', :action => 'index' # => 'proto://host.com/posts' # url_for :controller => 'posts', :action => 'show', :id => 10 # => 'proto://host.com/posts/show/10' - #   + # # When generating a new URL, missing values may be filled in from the current request's parameters. For example, # url_for :action => 'some_action' will retain the current controller, as expected. This behavior extends to # other parameters, including :controller, :id, and any other parameters that are placed into a Route's @@ -359,21 +357,21 @@ module ActionController #:nodoc: # The URL helpers such as url_for have a limited form of memory: when generating a new URL, they can look for # missing values in the current request's parameters. Routes attempts to guess when a value should and should not be # taken from the defaults. There are a few simple rules on how this is performed: - #   + # # * If the controller name begins with a slash, no defaults are used: url_for :controller => '/home' # * If the controller changes, the action will default to index unless provided - #   + # # The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the # route given by map.connect 'people/:last/:first/:action', :action => 'bio', :controller => 'people'. - #   + # # Suppose that the current URL is "people/hh/david/contacts". Let's consider a few different cases URLs which are generated # from this page. - #   + # # * url_for :action => 'bio' -- During the generation of this URL, default values will be used for the first and # last components, and the action shall change. The generated URL will be, "people/david/hh/bio". # * url_for :first => 'davids-little-brother' This generates the URL 'people/hh/davids-little-brother' -- note # that this URL leaves out the assumed action of 'bio'. - #   + # # However, you might ask why the action from the current request, 'contacts', isn't carried over into the new URL. The # answer has to do with the order in which the parameters appear in the generated path. In a nutshell, since the # value that appears in the slot for :first is not equal to default value for :first we stop using @@ -472,12 +470,12 @@ module ActionController #:nodoc: # Renders an empty response that can be used when the request is only interested in triggering an effect. Do note that good # HTTP manners mandate that you don't use GET requests to trigger data changes. - def render_nothing(status = nil) + def render_nothing(status = nil) #:doc: render_text "", status end # Returns the result of the render as a string. - def render_to_string(template_name = default_template_name) + def render_to_string(template_name = default_template_name) #:doc: add_variables_to_assigns @template.render_file(template_name) end diff --git a/actionpack/lib/action_controller/components.rb b/actionpack/lib/action_controller/components.rb index 0374a56c10..f4fa05504b 100644 --- a/actionpack/lib/action_controller/components.rb +++ b/actionpack/lib/action_controller/components.rb @@ -1,7 +1,27 @@ module ActionController #:nodoc: - # TODO: Cookies and session variables set in render_component that happens inside a view should be copied over. - module Components #:nodoc: - def self.append_features(base) + # Components allows you to call other actions for their rendered response while execution another action. You can either delegate + # the entire response rendering or you can mix a partial response in with your other content. + # + # class WeblogController < ActionController::Base + # # Performs a method and then lets hello_world output its render + # def delegate_action + # do_other_stuff_before_hello_world + # render_component :controller => "greeter", :action => "hello_world" + # end + # end + # + # class GreeterController < ActionController::Base + # def hello_world + # render_text "Hello World!" + # end + # end + # + # The same can be done in a view to do a partial rendering: + # + # Let's see a greeting: + # <%= render_component :controller => "greeter", :action => "hello_world" %> + module Components + def self.append_features(base) #:nodoc: super base.helper do def render_component(options) @@ -11,10 +31,12 @@ module ActionController #:nodoc: end protected + # Renders the component specified as the response for the current method def render_component(options = {}) #:doc: component_logging(options) { render_text(component_response(options).body, response.headers["Status"]) } end + # Returns the component response as a string def render_component_as_string(options) #:doc: component_logging(options) { component_response(options, false).body } end diff --git a/actionpack/lib/action_controller/dependencies.rb b/actionpack/lib/action_controller/dependencies.rb index 1f8ae7c22d..1deaf0ce4b 100644 --- a/actionpack/lib/action_controller/dependencies.rb +++ b/actionpack/lib/action_controller/dependencies.rb @@ -26,6 +26,9 @@ module ActionController #:nodoc: # # model :post (already required) # # helper :post (already required) # end + # + # Also note, that if the models follow the pattern of just 1 class per file in the form of MyClass => my_class.rb, then these + # classes doesn't have to be required as Active Support will auto-require them. module ClassMethods # Specifies a variable number of models that this controller depends on. Models are normally Active Record classes or a similar # backend for modelling entity classes. diff --git a/actionpack/lib/action_controller/helpers.rb b/actionpack/lib/action_controller/helpers.rb index a97fd93410..fcfd45a507 100644 --- a/actionpack/lib/action_controller/helpers.rb +++ b/actionpack/lib/action_controller/helpers.rb @@ -35,8 +35,7 @@ module ActionController #:nodoc: template_class.class_eval "include #{helper_module}" end - # Declare a helper. If you use this method in your controller, you don't - # have to do the +self.append_features+ incantation in your helper class. + # Declare a helper: # helper :foo # requires 'foo_helper' and includes FooHelper in the template class. # helper FooHelper diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb index f97956d040..b800f423c6 100644 --- a/actionpack/lib/action_controller/routing.rb +++ b/actionpack/lib/action_controller/routing.rb @@ -2,7 +2,7 @@ module ActionController module Routing ROUTE_FILE = defined?(RAILS_ROOT) ? File.expand_path(File.join(RAILS_ROOT, 'config', 'routes')) : nil - class Route + class Route #:nodoc: attr_reader :defaults # The defaults hash def initialize(path, hash={}) @@ -185,7 +185,7 @@ module ActionController end end - class RouteSet + class RouteSet#:nodoc: def initialize @routes = [] end