5.5. Analyzing the Controller

Controllers are stored in appcontrollers, so you can find your Articles controller in appcontrollersarticles_controller.rb.

Note how the singular resource (that is, article) passed to the scaffold generator created a model that is singular and a controller that is plural. It is customary for RESTful controllers to be plural, because when you send a GET request for /articles or /articles.xml you are expecting to get a collection of items in return. Issuing a /article or /article.xml to obtain a series of articles doesn't seem as logical.

All of the code for your controller exists within the following class definition:

class ArticlesController < ApplicationController
  # ...
end

As you can see, the class ArticlesController inherits from ApplicationController. The Application controller is an application-wide controller, and all the controllers that you define inherit from it. You can find its definition in appcontrollersapplication.rb. ApplicationController in turn inherits from ActionController::Base.

Let's tackle each action within ArticlesController, one at a time.

5.5.1. index

The index action is there to list all the existing articles. This is how it is defined:

# GET /articles
# GET /articles.xml
def index
  @articles = Article.find(:all)

  respond_to do |format|
    format.html # index.html.erb
    format.xml  { render :xml => @articles }
  end
end

The two comments are there to remind you that a GET /articles or GET /articles.xml request would lead to this method being invoked.

The first line of the method retrieves a list of all the records that are available in the articles table and stores them in the @articles instance variable:

@articles = Article.find(:all)

The find class method will return an array of Article objects because the :all argument was passed to it. Every instance variable that you define in an action becomes available in the corresponding view that gets rendered by Rails. By assigning an array of articles to @articles, you can use this instance variable to loop through the records in the view.

If you were to serve HTML only, the code of your action would be that single line and no further code would be required, because Rails knows, by convention, which files in the view layer need to be rendered. The controller definition that's generated through scaffolding also has Web Service support, as you saw when you rendered XML instead of HTML. For this reason, you need the following snippet as well:

respond_to do |format|
  format.html # index.html.erb
  format.xml  { render :xml => @articles }
end

Note that # index.html.erb is just a comment; it's not necessary. It's usually included for the sake of clarity.

Within the block passed to the respond_to method, you specify what to do depending on the format that's requested. Rails determines what format was requested by the client by analyzing the HTTP Accept header that it submitted.

If the format requested is HTML, Rails just renders the default view template associated with this action (which is index.html.erb in this case). If XML was requested by the client instead, Rails renders the list of articles retrieved, but in XML format. The block { render :xml => @articles } is actually smart enough to invoke the to_xml method on the @articles instance variable, and is therefore equivalent to { render :xml => @articles.to_xml }.

Supported Formats

So far you've seen format.html and format.xml. Other request formats are available out of the box in Rails: js, atom, rss, text, yaml, and ics (for iCalendar).

If you'd like to define your own format for a MIME type that is not supported by default, you can do so in configinitializersmime_types.rb. For example, if you add Mime::Type.register "text/richtext", :rtf to that file, you will then be able to use format.rtf within the block of the respond_to method.


5.5.2. show

This is the definition of the show action:

# GET /articles/1
# GET /articles/1.xml
def show
  @article = Article.find(params[:id])

  respond_to do |format|
    format.html # show.html.erb
    format.xml  { render :xml => @article }
  end
end

This snippet of code looks very similar to the one shown for the index action. However, this time you only need to hand back one record. You start by finding the record and assigning it to the @article instance variable:

@article = Article.find(params[:id])

params is a hash that stores the parameters passed to this action by the request. The path that triggers this action is either going to be /articles/:id or /articles/:id.:format, therefore params[:id] will store the value of the id contained in the URL. If the request is http://localhost:3000/articles/10, then params[:id] will be 10.

When you just pass a numeric value to the find method, this will return the record whose id is the same as that number. Hence, Article.find(10) will return the Article object whose id attribute is equal to 10. In short, Article.find(params[:id]) will retrieve the record associated with the id requested in the URL.

The respond_to method will do the same thing as the index method, with the sole exception being that only one record (that is @article) will be rendered as XML (not an entire list of them). Also, the name of the action is show this time, so the associated template that will be rendered if the requested format is HTML will be show.html.erb.

5.5.3. new

The definition of the new action is very similar to the show one:

# GET /articles/new
# GET /articles/new.xml
def new
  @article = Article.new

  respond_to do |format|
    format.html # new.html.erb
    format.xml  { render :xml => @article }
  end
end

The only obvious difference is that you assigned a new Article object to the @article instance variable, instead of finding a record, like you did for show.

As explained in a later section, whether the instance variable @article is a new object or an existing one will be an important distinction for Rails, because it uses it for deciding whether a "new" form or an "edit" one should be generated in the view.

Not surprisingly, new.html.erb gets rendered when the requested format is HTML.

5.5.4. edit

The edit action is the simplest of the lot, because by default it handles HTML requests only and, as such, doesn't need a respond_to method. The associated edit.html.erb template will be rendered automatically (which is Rails' default behavior). Here is the method definition:

# GET /articles/1/edit
def edit
  @article = Article.find(params[:id])
end

The requested record is retrieved and assigned to the @article instance variable. edit.html.erb provides the user with an input form to update the record. If the object referenced by @article contains any values in its attributes, these will already be pre-filled in the input form (this magic is possible thanks to helpers like form_for).

5.5.5. create

The create action gets a little trickier because it handles HTML and XML requests, plus it deals with two different cases, depending on whether or not the record was successfully saved. This is its definition:

# POST /articles
# POST /articles.xml
def create
  @article = Article.new(params[:article])

  respond_to do |format|
    if @article.save
      flash[:notice] = 'Article was successfully created.'
      format.html { redirect_to(@article) }
      format.xml  { render :xml => @article, :status => :created, :location => @
article }
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @article.errors, :status => :unprocessable_
entity }
    end
  end
end

In order to understand this action, let's try to see when this gets invoked for HTML requests. Before when you clicked the New Article link, you were redirected to /articles/new. That in turn invokes the new action and renders an empty form that allows you to input the details of an article you want to create. When you click the Create button, a POST /articles request is sent for you.

At this stage, the create action is invoked but you don't have an id in the params hash. What you have is an article parameter that contains all the attributes of the object that you'd like to create as inserted in the form. Hence, the first line of this action creates a new object with this data and assigns the object to @article:

@article = Article.new(params[:article])

Notice that at this point the record has not been saved yet in the database.

Then the method invokes the respond_to method in order to handle both HTML and XML requests as follows:

respond_to do |format|
     if @article.save
       flash[:notice] = 'Article was successfully created.'
       format.html { redirect_to(@article) }
       format.xml  { render :xml => @article, :status => :created, :location => @
article }
     else
       format.html { render :action => "new" }
       format.xml  { render :xml => @article.errors, :status => :unprocessable_
entity }
     end
   end

Here is what happens within the block. You first try to save in the database the object you created by calling the save instance method.

Behind the scenes this calls a create_or_update method, which will do exactly that, create the record if it's new or update it if this already exists.

If the record is successfully saved, the user is prompted with the message "Article was successfully created." as was shown in Figure 5-6. flash is a special type of hash (whose class is ActionController::Flash::FlashHash) that allows you to store data that will be exposed in the next action. In practice this means that you can store your message in flash[:notice] within the create action, and you'll be able to retrieve and display it when you're redirected to the show action (after the record is saved). As soon as you move onto yet another action or refresh the page, the flash content is discarded.

After storing your message in the flash, you can handle the HTML and XML cases separately. When the client wants HTML, you can redirect to the show action for the article you just created. Notice that the redirect_to method is smart enough to figure out that passing a single Article object implies that you want to show it. This would be equivalent to using redirect_to(article_url(@article)), but much more concisely. In the case of an XML request, you send the XML encoded object and pass a pair of headers back to the client.

If the record isn't able to be saved, as in the case of HTML requests, you can render the template for the action new. Notice that you don't have to perform a full redirect to the new action, because this would clear out all the data that the user just tried to save. If the requested format is XML, you can return the errors in XML format back to the client, and set the :status header to :unprocessable_entity.

This may appear complicated at first, but it's all very easy once you get the hang of it, and it's important to remember that you didn't actually write this code, but that it's there for you to build upon.

5.5.6. update

The update action is defined as follows:

# PUT /articles/1
  # PUT /articles/1.xml
  def update
    @article = Article.find(params[:id])

respond_to do |format|
      if @article.update_attributes(params[:article])
        flash[:notice] = 'Article was successfully updated.'
        format.html { redirect_to(@article) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @article.errors, :status => :unprocessable_
entity }
      end
    end
  end

This action is very similar to the create one except that it calls the method update_attributes instead of save, and passes it through the params[:article] argument. You'll also notice that upon successfully updating the record, in the XML response block there is simply a head :ok. This is a method that returns only headers and not content. The logic behind this is that if the update was successful, you'd inform the Web Service client of this with a 200 OK response. You can use symbols (for example, :ok) or status code (for example, 200).

A list of all the status codes is available (depending on your version and installation path) at C: ubylib ubygems1.8gemsactionpack-2.2.2libaction_controllerstatus_codes.rb.

If the update isn't successful, you can render the edit.html.erb template by issuing render :action => "edit" if the response requested is HTML, or you can provide the same error you saw for the create action if XML was requested.

This shouldn't be too confusing if you look at it from a user's standpoint. The user reaches the URL /articles/1/edit to edit a record, makes a few changes, and clicks the Update button. This sends a request that gets handled by the update action, which in turn tries to update the record. If it's able to do so, the user is redirected to /articles/1 and shown the updated record and a confirmation message (through flash). If it's not able to be saved for some reason, the user is sent back to the editing form (which hasn't been cleared of the existing data that he has already inserted).

5.5.7. destroy

Finally, the destroy action is defined as such:

# DELETE /articles/1
# DELETE /articles/1.xml
def destroy
  @article = Article.find(params[:id])
  @article.destroy

  respond_to do |format|
    format.html { redirect_to(articles_url) }
    format.xml  { head :ok }
  end
end

You first find the record that needs to be deleted and then invoke the destroy method. Then if the requested format is HTML, you then redirect the user to the list of all articles using the articles_url helper as an argument for the redirect_to method. This list will show that the deleted record is no longer there. If the request format was XML, you can simply confirm that the request was successful by calling head :ok.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.78.136