【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】ダウンロードしたフリーフォントをWebpackerを使って導入する方法
# はじめに Webサイトのデザインを彩るひとつの要素にフォントの種類があります。Google Fontsの登場により、様々なフォントが手軽に導入できるようになりました。しかし、世の中にはGoogle Fontsが提供するフォント以外にもたくさん [...]
2021年10月16日 13:26
【Rails】mimemagicに依存しているRailsアプリでbundle installコマンドが失敗する事象の対処法
# はじめに Railsアプリで`bundle install`コマンドを実行しようとしたところ、以下のエラーメッセージが出力されコマンドに失敗しました。 ``` Your bundle is locked to mimemagic (0 [...]
2021年10月14日 14:44
【Rails】macOSのアップデート後にrailsコマンドが失敗する事象の対処法
# はじめに 久しぶりにRailsアプリに手を入れようと思い、`rails server`コマンドを実行してRailsサーバーを立ち上げようとしたところ、エラーが出て起動に失敗してしまいました。 前回までの間にしたことの中で思い当たることと [...]
2021年10月14日 12:11
【Rails】Railsアプリのデバッグ《マルチデバイス篇》
# はじめに 近年のWebアプリはレスポンシブ対応が当たり前になっています。最低でもPCとスマートフォンに対応したデザイン、ときにはその中間のタブレットに対応したデザインなんかも作成する必要があります。 Webアプリの開発はPCを使って行う [...]
2021年5月23日 13:02
【Rails】Railsアプリのデバッグ《Better Errors篇》
# はじめに Railsアプリの開発中になんらかのエラーが発生すると、デフォルトでは以下のような画面が表示されます(画像をクリックすると拡大します)。 <a class="gallery" data-group="gallery" href [...]
2021年5月22日 19:42