Filters and Indifferent Access

Filters

Our controllers are quite cleaned up now, but there's still a line that repeats in several actions:

@post = Post.find() ...

We can instead move this post lookup to filter method and call it before each action that needs it using before_action:

### app/controllers/posts_controller.rb ###

class PostsController < ApplicationController
  before_action :find_post, only: [:show, :edit, :update, :destroy]

  # ...

  def show
    @comment = Comment.new
  end

  def edit
  end

  def update
    if @post.set_attributes('title' => params['title'], 'author' => params['author'], 'body' => params['body'])
      redirect_to posts_path
    else
      render 'edit'
    end
  end

  def destroy
    @post.destroy
    redirect_to posts_path
  end

  private

  def find_post
    @post = Post.find(params['id'])
  end
end
### app/controllers/comments_controller.rb ###

class CommentsController < ApplicationController
  before_action :find_post, only: [:create, :destroy]

  # ...

  def create
    @comment = @post.build_comment(
      'body' => params['body'], 'author' => params['author']
    )

    if @comment.save
      redirect_to post_path(@post.id)
    else
      render 'posts/show'
    end
  end

  def destroy
    @post.delete_comment(params['id'])

    redirect_to post_path(@post.id)
  end

  private

  def find_post
    @post = Post.find(params['post_id'])
  end
end

If we don't tell it otherwise, before_action will call the specified method before every action. We don't want to do that, so we specify just the actions we want by passing in an extra argument which is a hash: {only: [:show, :edit, :update, :destroy]} so it'll be applied only to those actions. If this filter only applies to one action then we can exclude the array and only pass in a single symbol (e.g. {only: :show}). We can also pass in the hash with the except key with an array of actions to exclude, and apply to all other actions.

You'll also notice that find_post is private in both controllers. In Rails, a controller action that's a private method will never be routed to, which is exactly what we want.

Other than before_action, Rails also allows you to define after_action and around_action. They are similar to before_action filters. after_action will execute code after the specified actions and around_action will execute code before and after the specified actions -- a typical use case for it is logging, where you can write to the log when the code execution starts and when it finishes.

Indifferent Access

Notice, that as our app has progressed we have accessed values in our params hash in two different ways: with symbols, and strings. Let's take a closer look at this rails feature:

# First we'll place a debugger in our index action in the comments controller
class CommentsController < ApplicationController

  # ...

  def index
    binding.pry
    @comments = Comment.all
  end

  # ...
end

Next, let's navigate to the comments page. From there we can look around using pry in the terminal.

4: def index
=> 5:   binding.pry
6:   @comments = Comment.all
7: end

[1] pry(#<CommentsController>)> params
=> {"controller"=>"comments", "action"=>"index"}
# Since we are accessing a collection, the only parameters by default are the controller name and action name.
# Let's try accessing the action name using our params hash.
[2] pry(#<CommentsController>)> params['action']
=> "index"
[3] pry(#<CommentsController>)> params[:action]
=> "index"

Rails gives us the option to access params using either a Symbol or a String. If we check the class of our hash, we'll see what type of data structure gives us this flexibility.

[4] pry(#<CommentsController>)> params.class
=> ActionController::Parameters

If we go a bit further and check the documentation, we'll see that this class inherits from ActiveSupport::HashWithIndifferentAccess. It's that parent class that allows us to access the values in our params hash with either a String or a Symbol.
Now would be a good time to explain why we've been slowly changing out code to use symbols. Symbols are unique identifiers, they also take up less memory, and are a bit easier to type.
Overall, using symbols to access our params is the preferred method. So, from now on that is what we'll be using. One last thing to do then, would be to go through our comments and posts controllers, and change the params access from string to symbol.

### app/controllers/posts_controller.rb ###

# ...

private

def find_post
  @post = Post.find(params[:id]) #used to be params['id']
end
### app/controllers/comments_controller.rb ###

def destroy
  @post.delete_comment(params[:id])

  redirect_to post_path(@post.id)
end

With that we're done investigating controller filters and parameters. In the next chapter, we'll cover more useful conventions that Rails provides for us. We'll also give a short review of what we've gone over thus far.