Chapter 4. Controllers

Rails controllers connect your application’s models and views. Any web requests that your application receives are routed to the appropriate controller. The controller gets data from the model and then renders the appropriate view or redirects to a different location.

In this chapter, we continue working on our blog. Along the way, you’ll learn about controllers in detail. I’ll cover resource representation with REST, routing resources, and the types of actions a controller can take.

Representational State Transfer

Representational State Transfer, or REST, is a client-server software architecture introduced in 2000 by Dr. Roy Fielding, one of the authors of the HTTP specification. REST deals with the representation of resources, and in Rails, resources correspond to models. In RESTful architectures, clients initiate requests to servers. Servers process those requests and return responses to the clients. In a Rails application, the server that processes requests and returns responses is the controller. The controller interacts with the client through a collection of common URLs and HTTP verbs.

You’re probably already familiar with at least two of these HTTP verbs. A request for a web page is sometimes called a GET request. A GET request doesn’t change the state of the application; it simply returns data. When you submit form data on a web page, the result is usually a POST request. In an application using REST, a POST request is used to create a record on the server.

During our discussion of models in the last chapter, you learned about CRUD (create, read, update, and delete). REST uses the four HTTP verbs in Table 4-1, which correspond to each of those actions.

Table 4-1. Mapping Database Actions to HTTP Verbs

Database Action

HTTP Verb

Create

POST

Read

GET

Update

PATCH

Delete

DELETE

Your application determines how to handle a request based on the HTTP verb used. A GET request for a resource returns the data from the corresponding model; a PATCH request updates the model with new information; and a DELETE request destroys the model. All three of these actions use the same URL. Only the HTTP verb is different.

Rails applications add three more actions in addition to the four CRUD actions in Table 4-1. The index action displays a list of all resources; the new action displays a form for creating a new resource; and the edit action displays a form for editing an existing resource.

Each of these actions has a corresponding method in a Rails controller. These seven methods are summarized in Table 4-2.

Table 4-2. Default RESTful Actions

Action

Description

HTTP Verb

index

List all records

GET

show

Show one record

GET

new

Show form to create a record

GET

edit

Show form to edit a record

GET

create

Create a new record

POST

update

Update an existing record

PATCH

destroy

Delete a record

DELETE

We’ll cover each of these actions in this chapter, but first let’s see how URLs are generated.

Routing

Setting up all of these URLs and mapping actions to verbs might sound pretty complicated, but luckily, Rails routing handles all of this for you. Routes connect URLs to the code that comprises an application. First, let’s look at the most common type of route, the resource route.

Resources

Your application’s routes are stored in the file config/routes.rb. Open that file in your text editor.

Ignore all of the comments. Your file should only have three lines right now:

Rails.application.routes.draw do
  resources :posts
end

Rails applications use REST by default. The blog application currently has only one resource (blog posts), and the single line resources :posts builds a set of routes for your application. Use the rake command to display your application’s routes:

$ bin/rake routes
Prefix Verb   URI Pattern                Controller#Action
 posts GET    /posts(.:format)           posts#index
       POST   /posts(.:format)           posts#create
--snip--

This command outputs the route helper prefix, HTTP verb, URL pattern, and controller action for each of the seven default RESTful actions.

For example, a GET request to /posts calls the PostsController#index method. As you make changes to the routes file, run this command again to see how your application’s routes also change.

Nested Resources

When one resource belongs to another resource, you can add it as a nested resource. In the blog, comments belong to posts. Here’s how you represent that in config/routes.rb:

resources :posts do
  resources :comments
end

Add a block after resources :posts with a do, end pair. Then add resources :comments inside that block. This tells Rails that comments are only available inside of posts.

Restricted Resources

Adding resources :comments as you just saw creates routes for each of the seven default RESTful actions for comments. For now, let’s only worry about creating new comments. You can restrict the set of routes generated for a resource by adding an only clause to that resource in config/routes.rb:

resources :posts do
  resources :comments, only: :create
end

Now, only the comment create action is mapped to a URL. You should only provide routes to actions that you plan to implement.

Custom Routes

Some actions in your application may not correspond to any of the seven default actions. For example, your application may include a search action that returns a list of posts containing a specific term. In cases such as this, Rails lets you manually configure custom routes.

Custom routes are also useful for mapping old URLs to a new Rails application or simplifying URLs for complex actions. For example, imagine your application allows users to log in by creating a new session and log out by destroying their session. Adding resources :user_session creates paths like user_session/new. If you would rather use different paths, you can create custom routes for login and logout.

Rails.application.routes.draw do
  resources :posts do
    resources :comments, :only => :create
  end

  get 'login' => 'user_sessions#new'
  post 'login' => 'user_session#create'
  delete 'logout' => 'user_sessions#destroy'
end

Now your application’s login page should be at the path /login. When a user visits the login page, his or her browser sends a GET request for this path. The controller displays the login form in response to that GET request. When the user submits the form, the browser sends a POST request to the same path with the contents of the form. The controller then creates a new session for the user in response to the POST request. When the user clicks the log out button, a DELETE request to the path /logout destroys the user’s session.

We aren’t adding authentication to the blog application, but you can still add these routes to config/routes.rb if you want to see the routes created. Remove them before moving on because accessing a path that doesn’t correspond to a controller action results in an error.

The Root Route

Finally, let’s create a root route so we don’t have to add /posts to the browser’s address bar every time. The root route sets the home page for your application. Add root 'posts#index' near the end of config/routes.rb:

Rails.application.routes.draw do
  resources :posts do
    resources :comments, :only => :create
  end
  root 'posts#index'
end

Now, accessing your server without a path should display the posts index page. You should always include a root route for your application.

Paths and URLs

Adding a route also automatically creates helpers for your controllers and views. You can use these helpers, shown in Table 4-3, instead of manually typing URLs in your application. That way, if you decide to change your application’s URLs in the future, you won’t have to search for and update all of the old URLs in your code.

Table 4-3. Rails Path and URL Helpers

Path Helpers

URL Helpers

posts_path

posts_url

new_post_path

new_post_url

edit_post_path(id)

edit_post_url(id)

post_path(id)

post_url(id)

The path helpers include only the path, whereas the URL helpers also include the protocol, server, and port (if not standard). Rails applications generally use path helpers. The URL helpers are useful for situations in which the full URL is needed, such as to generate URLs for inclusion in emails.

The first part of each method name matches the prefix displayed by the bin/rake routes command.

You can test these helpers in the Rails console like this:

2.1.0 :001 > app.posts_path
 => "/posts"
2.1.0 :002 > app.post_path(1)
 => "/posts/1"
2.1.0 :003 > app.new_post_path
 => "/posts/new"
2.1.0 :004 > app.root_path
 => "/"

Testing these helpers is a useful sanity check when working with Rails routes. If you forget which helper to use to create a path, you can type it in the console to see the result.

Controller Actions

The convention in Rails is to have a controller corresponding to each resource. That controller includes methods for each action. (Remember the principle from Chapter 2: convention over configuration.) The Rails scaffold generator created a controller for posts. Open the file app/controllers/posts_controller.rb to see the Ruby code behind these methods. I recommend running the Rails server as you work your way through the rest of this chapter:

$ bin/rails server

Now, let’s look at each controller method in turn, starting with index and working our way down to destroy.

The index action retrieves all posts from the database:

def index
  @posts = Post.all
end

You see the familiar @post = Post.all in that method. You may be surprised that this is the only line of code in the index method. By default, Rails renders a view file matching the action name, in this case app/views/posts/index.html.erb. (We’ll discuss views in the next chapter.)

Go to http://localhost:3000/posts in your browser to see the results of the index action.

The show action retrieves a single post from the database, but the show method contains no code at all:

def show
end

This method relies on a Rails before_action, which you should see on line two of the controller:

before_action :set_post, only: [:show, :edit, :update, :destroy]

The before_action is a class method that automatically calls the set_post method, shown next, before the methods show, edit, update, and destroy. This eliminates duplicate code in these methods. (Remember DRY: Don’t repeat yourself.)

def set_post
  @post = Post.find(params[:id])
end

The set_post method is defined near the bottom of the controller under the keyword private. It calls the Post.find method to retrieve the post with an id corresponding to a parameter passed to the controller. Parameters are covered in more detail in the next section, so for now, let’s continue examining these controller methods.

The new action displays a form for adding a new post:

def new
  @post = Post.new
end

The form uses data from a newly created post. Click the New Post link at the bottom of the post index page to see this form.

The edit action displays a form for editing an existing post. Like the show method, this method contains no code:

def edit
end

This form uses data retrieved by the set_post method discussed previously.

A Brief Detour from Actions

Before discussing create, update, and destroy, let’s talk about a few key Rails topics that you need to know to understand those methods. In this section, we’ll explore parameters, render/redirect, response formats, and the flash.

Parameters

Parameters generally represent part of the URL used to request a page or values from a form, and they’re accessible in the controller as a hash named params. For example, the set_post method you saw earlier retrieved the id of the requested post from the params hash, like this:

@post = Post.find(params[:id])

You can see the parameters passed with each request in the output from the rails server command in your terminal. For example, go to http://localhost:3000/posts/1 and then look at the Rails server output in your terminal:

  Started GET "/posts/1" for 127.0.0.1 at 2014-03-31 20:30:03 -0500
  Processing by PostsController#show as HTML
➊   Parameters: {"id"=>"1"}
    Post Load (0.3ms) SELECT "posts".* FROM "posts"
      WHERE "posts"."id" = ? LIMIT 1 [["id", "1"]]
    Rendered posts/show.html.erb within layouts/application (233.9ms)
  Completed 200 OK in 274ms (Views: 245.5ms | ActiveRecord: 26.2ms)

In this case, the 1 in the URL represents the id of the requested post ➊. Because we requested a single post, the show method is called, and this id is used to find the post in set_post.

Form data is represented by a nested hash with values. For example, editing this post results in a params hash more like this:

  {
    "utf8"=>"✓",
    "authenticity_token"=>"...",
➊   "post"=>{"title"=>"First Post", "body"=>""},
    "commit"=>"Update Post",
➋   "id"=>"1"
  }

You still access params[:id] ➋ to find the correct post, and you can also access params[:post] ➊ to see the new values submitted by the user. Because these are user-submitted values, you should ensure that your application only accepts data for the appropriate attributes. Malicious users could send requests with invalid parameters in an attempt to attack your application.

For blog posts, you only want users to be able to edit the title and body attributes. Rails includes a feature called Strong Parameters, which makes specifying which attributes your application accepts easy. You can see the feature in action in the post_params method:

def post_params
  params.require(:post).permit(:title, :body)
end

This method first requires the params hash to contain a nested hash with the key :post. It then returns only the permitted values (:title and :body) from this nested hash. Using the earlier example params hash, post_params returns a hash like this:

{"title" => "First Post", "body" => ""}

Other values in the params[:post] hash are silently ignored. Remember, always use the post_params method when accessing the parameters for a newly created or updated post.

Render or Redirect

Every action must either render a view or redirect to another action. By default, an action renders a file matching the action name. For example, the show method in the posts controller looks for a file named app/views/posts/show.html.erb and uses that file to build the HTML response that is sent back to the user.

You can tell Rails to render the response for a different action with the render method like this:

render action: "edit"

The ability to specify actions is helpful if you need to render a different view based on user input. This example is from the update method. If the post could not be updated with the data provided by the user, this method renders the edit view again, giving the user a chance to correct the data.

Sometimes you need to send the user to a page other than the one he or she requested. Use the redirect_to method to take care of this. For example, if the user enters valid data while creating or updating a post, the controller action redirects the user to that post:

redirect_to @post

When you call redirect_to, the address in the user’s browser changes to reflect the new page, and another request is made. You can see this by watching the address bar as you submit form data and by looking at the output from rails server in your terminal.

To see this in action, first go to http://localhost:3000/posts/new in your browser. This is the new post form. Enter a title for the new post, and then click the Create Post button. Watch the address bar closely after clicking the button.

The form makes a POST request to http://localhost:3000/posts. This request is routed to the create method. After creating the post, you are redirected to http://localhost:3000/posts/3, assuming that your new post has an id of 3. The address is changed automatically by the redirect_to method.

Response Formats

Rails can generate responses in several formats, though all I’ve discussed so far is HTML. Scaffold-generated controllers can also include JavaScript Object Notation (JSON) responses, which are useful for creating application programming interfaces (APIs). Other formats include XML and even PDF.

You can try another response type in your web browser by visiting this URL: http://localhost:3000/posts.json. This URL is the same as the posts index URL used earlier, except it has .json added to the end. Rails recognizes this as a JSON request and renders the collection of posts as JSON, as in Figure 4-1.

Posts in JSON format

Figure 4-1. Posts in JSON format

You specify the formats an action accepts and the responses to each format with a call to the respond_to method. This method accepts a block with a single parameter representing the requested format. Here is an example from the destroy method:

respond_to do |format|
 format.html { redirect_to posts_url }
 format.json { head :no_content }
end

This method is called right after a post is destroyed. If the client requests HTML data, this block redirects to posts_url, the index page. If the client requests JSON data, by adding .json to the end of the URL, this block responds with an empty header to indicate the post no longer exists.

The Flash

Flash messages are alerts to the user that are only valid for a single request. Flash messages are stored in the user’s session, typically in a cookie. They are usually styled differently to stand out. For example, the stylesheet included with Rails scaffolding uses green text for flash messages.

Flash messages are helpful for sending error messages or other notifications to the user. They are generally set on a redirect. Here’s an example from the create method in the posts controller:

redirect_to @post, notice: 'Post was successfully created.'

When a post is successfully created, the user is redirected to the new post and a flash message like the one in Figure 4-2 is shown.

A flash message

Figure 4-2. A flash message

The create flash message is the green text, and it matches the message added earlier.

Back to Controller Actions

Now you should know everything you need to understand the create, update, and destroy actions. The methods written by the scaffold generator respond to requests for both HTML and JSON data with messages indicating success or errors, but let’s focus on the HTML responses for now. I’ll cover JSON responses in depth when I talk about building your own APIs.

Note

The formatting in each method has been adjusted slightly to better fit this page.

The create method is responsible for creating a post using the params from the new post form:

def create
  @post = Post.new(post_params)

  respond_to do |format|
    if @post.save
      format.html { redirect_to @post,
                    notice: 'Post was successfully created.' }
      format.json { render action: 'show',
                    status: :created, location: @post }
    else
      format.html { render action: 'new' }
      format.json { render json: @post.errors,
                    status: :unprocessable_entity }

    end
  end
end

The first line of the method @post = Post.new(post_params) uses Strong Parameters to ensure only the accepted parameters are allowed into the call to new. Inside the respond_to block, the return value of @post.save is checked. If it’s true, then the user is redirected to the newly created post. If it is false, then the new action is rendered again so the user can correct any errors.

The update method is similar to the create method. The main difference is that the code checks the return value of @post.update instead of @post.save.

def update
  respond_to do |format|
    if @post.update(post_params)
      format.html { redirect_to @post,
                    notice: 'Post was successfully updated.' }
      format.json { render action: 'show',
                    status: :ok, location: @post }

    else
      format.html { render action: 'edit' }
      format.json { render json: @post.errors,
                    status: :unprocessable_entity }
    end
  end
end

If @post.update returns true, the code redirects the user to the updated post; otherwise, it renders the edit form so the user can correct the errors.

The destroy method is simpler than the create and update methods because it doesn’t check the return value of @post.destroy.

def destroy
  @post.destroy
  respond_to do |format|
    format.html { redirect_to posts_url }
    format.json { head :no_content }
  end
end

After the post is destroyed, the code redirects the user back to the index page, posts_url.

Adding Comments

You added a route to the create comment action earlier, so now let’s add a simple controller for that action. You’ll add the form for entering new comments in the next chapter.

Generate a new controller for comments using the Rails generator:

  $ bin/rails generate controller comments
➊    create  app/controllers/comments_controller.rb
     invoke  erb
➋    create    app/views/comments
     invoke  test_unit
     create    test/controllers/comments_controller_test.rb
     invoke  helper
     create    app/helpers/comments_helper.rb
     invoke    test_unit
     create      test/helpers/comments_helper_test.rb
     invoke  assets
     invoke    coffee
     create      app/assets/javascripts/comments.js.coffee
     invoke    scss
     create      app/assets/stylesheets/comments.css.scss

Note that I specified only a controller, not scaffolding. This code generates an empty controller ➊ and an empty views directory ➋, as well as files for helpers, tests, and assets. We’ll have to fill in the details ourselves. Start by opening the file app/controllers/comments_controller.rb in your editor:

class CommentsController < ApplicationController
end

Because you’re implementing the create action, the first thing you need is a create method. You can model it after the create method in the posts controller. Assume that users won’t be adding comments via an API, so it isn’t necessary to generate JSON responses.

  class CommentsController < ApplicationController
    def create
1     @post = Post.find(params[:post_id])

2     if @post.comments.create(comment_params)
3       redirect_to @post,
                    notice: 'Comment was successfully created.'
      else
        redirect_to @post,
                    alert: 'Error creating comment.'
      end
    end
  end

This code first finds the correct post ➊ using the post_id in the params hash. It then uses the comments association to create a new comment ➋ and redirects back to the post ➌. Each call to redirect_to sets a flash message to indicate success or failure.

Because you’re using Strong Parameters in your application, you also need to add the comment_params method to specify the parameters you want to accept.

class CommentsController < ApplicationController
  --snip--

  private

  def comment_params
    params.require(:comment).permit(:author, :body)
  end
end

In the case of comments, you only accept an author and a body. Any other parameters are ignored. In the next chapter, you’ll update the post show view to display existing comments and include a form for creating new comments.

Summary

This chapter introduced many important Rails concepts—REST, routing, and controllers. I also discussed parameters, render versus redirect, response formats, and the flash.

We started at the database in the last chapter and worked our way forward in this chapter. In the next chapter, we’ll get all the way to the user and cover the last piece of the MVC puzzle: views.

Exercises

Q:

1. Good error messages are important for any application. If something goes wrong, your users need to know what the problem is and how to correct it. Currently, if a comment can’t be created, users see the message “Error creating comment.” Update the CommentsController create method to also show a list of error messages in the alert.

Q:

2. In Exercise 1 at the end of Chapter 3, you added an email field to the Comment model. Update the comment_params method in CommentsController to also accept this field.

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

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