【Rails】Action Controller(コントローラー)の書き方

はじめに

ActionController(または単にコントローラー)はMVCモデルのCに相当します。コントローラーはルーティングからリクエストを受け取ると、必要であればモデルと連携してデータベースからデータを取得し、それをビューに渡します。あるいは、ビューから受け取ったデータをモデルに渡し、データベースの更新を指示します。コントローラーはモデルとビューの橋渡し役を担っています。

本記事では、ActionController(コントローラー)の使い方についてまとめています。

コントローラーの基本

コントローラー名は複数形にすることが推奨されていますが、必ずしもそうしなければいけないというわけではありません。ただし、コントローラー名が複数形でない場合、ルーティングで作成されるヘルパーメソッド(_path_url)が一貫して使えなくなることにご注意ください。

コントローラーの作成

コントローラーを作成するには以下のコマンドを実行します。

$ rails generate controller Users

上記のコマンドを実行すると、以下のファイルとディレクトリが作成されます。

  • app/controllers/users_controller.rb
  • app/views/users/
  • test/controllers/users_controller_test.rb
  • app/helpers/users_helper.rb
  • app/assets/stylesheets/users.scss

アクションの追加

アクションを追加してコントローラーを作成したい場合は以下のコマンドを実行します。追加するアクションはスペースで区切って何個でも指定することができます。

$ rails generate controller Users index

上記のコマンドを実行すると、アクションを指定せずに実行したときと同じファイルとディレクトリに加え、users/index.html.erbというビューも作成されます。また、UsersController#indexアクションが追加され、UsersController#indexに対応するルーティングも追加されます。

Resourcefulアクションの追加

Resourcefulアクションを一度に追加してコントローラーを作成したい場合は以下のコマンドを実行します。

$ rails generate scaffold_controller Users

上記のコマンドを実行すると、UsersControllerにResourcefulアクションが完全な形で追加され、必要なビューもすべて作成されます。ただし、Resourcefulルーティングは追加されません

ヘルパーが作成できない場合

コントローラーを削除してすぐに再作成しようとすると、以下のエラーメッセージが出てヘルパーが作成できない場合があります。

The name 'UsersHelper' is either already used in your application or reserved by Ruby on Rails. Please choose an alternative or use --force to skip this check and run this generator again.

UsersHelperという名前が既に使われているというエラーメッセージです。ヘルパーは削除しても一定時間キャッシュに残るようです。すぐに再作成したい場合は、以下のコマンドを実行してキャッシュを削除します。

# Springプロセスの確認
$ ps -ef | grep spring
  501 21596     1   0 12:39PM ttys001    0:00.94 spring server | rails-contentful | started 43 mins ago   
  501 22251 21281   0  1:22PM ttys001    0:00.01 grep spring

# Springプロセスの停止
$ kill -9 21596

コントローラーの削除

コントローラーを削除するには以下のコマンドを実行します。scaffold_controllerで作成されたコントローラーも削除できます。

$ rails desctroy controller Users

コントローラー作成時に同時に作成されたすべてのファイルとディレクトリが削除されます。ただし、ルーティングは削除されません。

コントローラーの書き方

暗黙的なレンダリング

コントローラーはアクションにレンダリングを記述しなくても、コントローラーとアクションから暗黙的にレンダリングするビューを決定します。

class UsersController < ApplicationController
  def index
  end
end

このようなコントローラーがあり、/usersというリクエストを受け取るとindexアクションが実行され、暗黙的にusers/index.html.erbをレンダリングします。

変数の引き継ぎ

変数をビューに引き継ぎたい場合、変数名を@variableのように先頭に@をつけます。逆に言うと、ビューに引き継ぐ必要のない変数は@をつけないでください。

class UsersController < ApplicationController
  def index
    @message = "ようこそ、 #{params[:name]} さん"
  end
end

レンダリング

ビューのレンダリングを行うにはrenderメソッドを使います。暗黙的なレンダリングのセクションで説明したとおり、コントローラーとアクションの組み合わせと、ビューの名前が一致しているなら、明示的にrenderメソッドを記述する必要はありません。そうでない場合、レンダリングするビューを指定します。ビューの指定には様々な方法があります。

render :edit
render action: :edit
render "edit"
render "edit.html.erb"
render action: "edit"
render action: "edit.html.erb"
render "books/edit"
render "books/edit.html.erb"
render template: "books/edit"
render template: "books/edit.html.erb"
render "/path/to/rails/app/views/books/edit"
render "/path/to/rails/app/views/books/edit.html.erb"
render file: "/path/to/rails/app/views/books/edit"
render file: "/path/to/rails/app/views/books/edit.html.erb"

ビューの指定方法に限りませんが、Railsはひとつの結果を得るのに多彩な書き方をサポートをしていることが多いです。そのほとんどは歴史的な経緯から後方互換性を維持するために残されているだけに過ぎないのでしょう。

省略できるところは省略し、できるだけシンプルな書き方を選ぶのがいいと思います。

レンダリングの重複エラー

renderメソッドのよくある認識違いとして、「レンダリングを行ったら処理を抜ける」というものがあります。以下の例では、ユーザーが管理者ならば管理者用のビューをレンダリングしていますが、処理はそこで終わらず、次のrenderメソッドが実行されることになります。これはレンダリングの重複としてエラーになります。

class UsersController < ApplicationController
  def show
    @user = Users.find(params[:id])

    if @user.admin?
      render "admin_show"
    end

    render "show"
  end
end

これを回避するには、以下のようにデフォルトのレンダリングを削除します。ユーザーが管理者ならば管理者用のビューがレンダリングされ、そうでなければ暗黙的なレンダリングによりusers/show.html.erbがレンダリングされます。

class UsersController < ApplicationController
  def show
    @user = Users.find(params[:id])

    if @user.admin?
      render "admin_show"
    end
  end
end

リダイレクト

新しいURLのリクエストにリダイレクトするにはredirect_toメソッドを使います。リダイレクト先の指定にはルーティングで作成されるヘルパーメソッド(_path_url)が使えます。

class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    redirect_to user_path(@user.id)
  end
end

レンダリングとリダイレクトの違い

レンダリングはクライアントにビューの内容を返します。一方、リダイレクトは新しくリクエストを発行します。Railsは新しいリクエストのルーティングからコントローラーとアクションを決定し、そのアクションが実行されます。わかりにくい場合は、ざっくりと以下の通り覚えておけばいいと思います。

メソッド メソッド実行後に動くもの
レンダリング 指定したビュー
リダイレクト 指定したコントローラーのアクション

ストロングパラメーター

コントローラーはルーティング情報やビューから渡されたパラメーター情報をparamsハッシュで受け取ります。ストロングパラメーターで許可するパラメーターを定義することができます。

class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
  end

  private
    def user_params
      params.require(:user).permit(:email, :password, :password_confirmation)
    end
end

user_paramsというストロングパラメーターで許可されたパラメーター以外を使用しようとするとActiveModel::ForbiddenAttributes例外が発生します。

データの保持

コントローラーでデータの保持を行う方法は3つあります。いずれも通常のハッシュのようにキーと値を組み合わせて使います。使い方はほとんど同じですが、それぞれで有効期限が異なります。なお、キーとなる文字列は何でも構いません(日本語でも可)。

セッションやクッキーは暗号化されて保存されます。復号化にはCredentialsのsecret_key_baseの値が使用されます。secret_key_baseの値を変更してしまうとセッションやクッキーの値が復元できなくなってしまうのでご注意ください。

Flash(フラッシュ)

フラッシュの有効期限は次のリクエストが終わるまでです。コントローラーで行った処理の結果を簡単にユーザーに伝えるためなどに使われます。

class UsersController < ApplicationController
  def create
    @user = User.new(user_params)

    if @user.save
      flash[:notice] = "新しいユーザーを作成しました。"
      redirect_to user_path(@user)
    else
      flash[:alert] = "新しいユーザーの作成に失敗しました。"
      redirect_to root_path
    end
  end
end

Session(セッション)

セッションの有効期限は1ヶ月です。ユーザーのログイン状態を一時的に保持しておくためなどに使われます。以下の例では、ヘルパー内でユーザー情報の登録/破棄を行っています。

module SessionsHelper
  def log_in(user)
    session[:user_id] = user.id
  end

  def log_out
    session.delete(:user_id)
  end

クッキーの有効期限はクライアントが明示的に削除するまでです。ユーザーのログイン状態を永続的に保持しておくためなどに使われます。以下の例では、ヘルパー内でユーザー情報の登録/破棄を行っています。

module SessionsHelper
  def remember(user)
    user.remember
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

  def forget(user)
    user.forget
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
  end
end

アクションの前後に行う処理の定義

アクションを実行する前、後、その両方に行う処理を定義することができます。ここでは、よく使うであろうbefore_actionの使い方のみ記述します(after_actionarround_actionの使い方もほとんど同じです)。

class DashboardsController < ApplicationController
  before_action :require_login

  def index
    @user = current_user
  end

  private
    def require_login
      redirect_to login_path if !logged_in?
    end
end

上記の例では、管理ページをレンダリングするアクションの前に、require_loginメソッドで管理者としてログインしているかのチェックを行い、ログインしていなければログインページにリダイレクトしています。

アクションの前後に行う処理の制限

アクションの前に行う処理を制限することができます。アクションを限定するにはonlyオプションをつけます。また、アクションを除外するにはexceptオプションをつけます(after_actionarround_actionも同様)。

class ArticlesController < ApplicationController
  before_action :require_login, except: [:index, :show]

  # 各アクションは省略

  private
    def require_login
      redirect_to login_path if !logged_in?
    end
end

上記の例では、indexshow以外のアクションでログインチェックを行っています。

ヘルパーメソッドの使用

コントローラーでヘルパーメソッドを使う場合、先頭にview_contextをつけます。

class ArticlesController < ApplicationController
  def index
    @articles = view_context.contentful.entries(content_type: 'article')
  end

  def show
    @article = view_context.contentful.entry(params[:id])
  end
end

まとめ

コントローラーはモデルとビューの橋渡し役に徹し、あまり凝った処理は行わないことが推奨されています。例えば、データベースから取得したデータの加工処理はコントローラーではなくモデルで行ったり、ビューで表示するメッセージをコントローラーでは作成しないようにします。

本記事を参考にして、ActionController(コントローラー)の使い方を覚えていただければと思います。

関連記事

【Rails】Webサーバー「Unicorn」の基本情報と実装方法
# はじめに Railsアプリを本番環境で稼働させるには、クライアントからのリクエストを捌くWebサーバーを導入する必要があります。WebサーバーはクライアントからのリクエストをRailsアプリに伝達し、Railsアプリで処理されたレスポンスをク [...]
2021年4月15日 12:17
【Rails】デプロイツール「Capistrano」の基本情報と実装方法
# はじめに アプリを本番環境にアップロードして誰でもアクセスできる状態にすることをデプロイと言います。デプロイで行うべきことは多岐にわたります。Railsアプリの場合で言えば、本番環境にアップロードすることはもちろんですが、Gemのインストール [...]
2021年4月14日 9:56
【Rails】Webpackerの基本情報と実装方法
# はじめに Rails 6からWebpackerが正式採用されました。Rails 5ではオプションで追加することができたWebpackerですが、Rails 6からは普通にアプリを作成するだけでWebpackerがインストールされ、必要な設定も [...]
2021年4月12日 14:36
【Rails】アセットパイプライン(Sprockets)の基本情報と実装方法
# はじめに Ruby on Railsにはアセットパイプラインという機能があります。アセットパイプラインは画像、CSS、JavaScriptといったアセットファイルを連結/圧縮することでRailsアプリを高速化します。また、より高級な言語で書か [...]
2021年4月11日 14:08
【Rails】Turbolinksの基本情報と実装方法
# はじめに Ruby on Railsにはページの遷移を高速化するTurbolinksという機能があります。Turbolinksは優れたJavaScriptライブラリですが、Rails 5からは標準で有効化されているため、Turbolinksを [...]
2021年4月10日 17:59