みなさんはControllerを使いこなしていますか?
この記事では、Controllerの基本的な使いかたとして、以下のような内容を説明します。
・Controllerの作成方法は?
・Controllerでよく使われる機能は?
・Controllerはモジュール化できませんか?
Controllerには、ViewやModelを統括するというもっとも重要な役割があります。
もっとも重要な役割を粛々と行うためにも、Controllerの基礎をしっかりと学び、ViewやModelとの連携をスムーズにできるようにしておきましょう。
Controllerとは
Controllerは、MVCを構成するコンポーネントの1つです。
MVCは、以下の3つのコンポーネント(構成要素)から構成されるデザインパターンです。
コンポーネント(構成要素) | 説明 | 備考 |
---|---|---|
Model(モデル) | データベースを取り扱う | データベースへの格納方法は、Modelに隠ぺいする |
View(ビュー) | 画面表示を取り扱う | 表示方法は、Viewに隠ぺいする |
Controller(コントローラー) | (ユーザーの入力を受けて)ModelとViewにアレコレ指示する | データベースへの格納方法や表示方法は知らない |
Controllerはユーザーの意思を反映したHTTPリクエストを受け取り、ModelやViewと連携しながら、ユーザーに返すデータを決定する処理を担当します。
上の表のとおり、データの追加、削除、更新は、Modelを利用します。
Modelについては、以下の記事をご覧ください。
Controllerの使いかたを理解するためにRuby on Railsをインストールする
Controllerの使いかたを理解するために、Ruby on Railsをインストールしておきましょう。
(1)Ruby on Railsをインストールします。
私は、以下の記事を参考に、VirtualBoxで作成した仮想パソコンにインストールしたLinux Mintに、Ruby on Railsの開発環境を作成しました。
基本的には記事の手順に従って操作しますが、app/samurai/sample1ディレクトリを作成する代わりに、app/samurai/controller-demoディレクトリを作成しました。
また、Ruby on Railsを起動して、ブラウザで画面が表示されることを確認したら、いったんRuby on Railsを終了してから次に進みます。
Linux Mintのインストールについては、以下の記事で詳しく説明しています。
Controllerを作成する
では、Controllerを作成してみましょう。
Controller(+基本的なViewやルーティング)を作成するには、以下のコマンドを使用します。
bin/rails generate controller Controller名 アクション1 アクション2...
Controller名のあとに入力するアクションは複数指定できます。
ルーティングについては、以下の記事を参考にしてください。
では、実際にControllerを作成してみましょう。
(1)「端末」で以下のコマンドを入力します。
bin/rails generate controller Grapes action1 action2
実行結果:
Running via Spring preloader in process 18774 create app/controllers/grapes_controller.rb route get 'grapes/action2' route get 'grapes/action1' invoke erb create app/views/grapes create app/views/grapes/action1.html.erb create app/views/grapes/action2.html.erb invoke test_unit create test/controllers/grapes_controller_test.rb invoke helper create app/helpers/grapes_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/grapes.coffee invoke scss create app/assets/stylesheets/grapes.scss
作成されたファイルの用途は以下のとおりです。
ファイル名 | 説明 |
---|---|
app/controllers/grapes_controller.rb | Controllerファイル |
config/routes.rb※ | ルーティングファイル |
app/views/grapes/action1.html.erb app/views/grapes/action2.html.erb | Viewファイル |
test/controllers/grapes_controller_test.rb | test用ファイル |
app/helpers/grapes_helper.rb | 独自helper設定用のファイル |
app/assets/javascripts/grapes.coffee | coffeescript |
app/assets/stylesheets/grapes.scss | scss |
※config/routes.rbは変更されます。
Controllerの命名規則について
Modelと結びつきがある場合は、Controller名をGrapesのように複数形にします。
Railsでは、使用するファイルなどを名前によって自動的に推測するため、命名規則に従っていないと予期しないエラーが発生してしまいます。
名前を正しく決める必要があり、それだけなら欠点のように思えますが、その代わりに冗長なコードが減るという利点があります。
Controllerを削除する
Controller(+基本的なViewやルーティング)を削除するには、以下のコマンドを使用します。
bin/rails destroy controller Controller名
では、実際にGrapesを削除してみましょう。
(1)「端末」で以下のコマンドを入力します。
bin/rails destroy controller Grapes
実行結果:
Running via Spring preloader in process 19022 remove app/controllers/grapes_controller.rb invoke erb remove app/views/grapes invoke test_unit remove test/controllers/grapes_controller_test.rb invoke helper remove app/helpers/grapes_helper.rb invoke test_unit invoke assets invoke coffee remove app/assets/javascripts/grapes.coffee invoke scss remove app/assets/stylesheets/grapes.scss
rails generate controllerコマンドで作成されたファイルが削除されます。
ここで、config/routes.rbに設定されたルーティングの設定は残っていることに注意してください。
config/routes.rbには、他のControllerのアクションに関するルーティングも記述されているため、ファイルを削除すると問題になるためです。
そこで、config/routes.rbを編集して、Grapesに関するルーティングを削除しましょう
(2)config/routes.rbを編集します。
編集前:
Rails.application.routes.draw do get 'grapes/action1' get 'grapes/action2' # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html end
編集後:
Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html end
Controllerでよく使われる機能
次に、Controllerでよく使われる機能を紹介しましょう。
params
params[:id]のように書くと、Railsで送られてきた値(パラメータ)を受け取れます。
パラメータの受け渡しは、以下の記事を参考にしてください。
redirect_to
redirect_to action: :example1のように書くと、自動的なページの切り替え(次のアクションの呼び出し)を簡単に実装できます。
redirect_toの使いかたは、以下の記事を参考にしてください。
flash
flash[:notice] = “任意のメッセージ”のように書くことで、任意のメッセージをViewに表示できます。
flashの使いかたは、以下の記事を参考にしてください。
render
render “help”と書くことで、help(app/views/pages/help.html.erb)テンプレートを表示できます。
renderの使いかたは、以下の記事を参考にしてください。
ActiveSupport::Concernを使ってアクションをモジュール化する
一つのControllerにたくさんのアクションを記述しすぎると、コードが管理しにくくなったり、テストに時間がかかりすぎたりする問題が起きます。
そんなときは、ActiveSupport::Concernを使うことで、アクションをモジュール化できます。
Viewの場合は部分テンプレート(partial)を使って小分けにしますが、Controllerではモジュール化して小分けにします。
ここからは、実際にActiveSupport::Concernを使ってみましょう。
動作確認用のWebアプリは、scaffoldで作成します。
scaffoldについてわからない方は、以下の記事を参考にしてください。
(1)「端末」で以下のコマンドを1行ずつ順番に入力します。
bin/rails generate scaffold Banana name:string bin/rails db:migrate bin/rails console
(2)以下のコードを入力します。
Banana.create(name:"キャベンディッシュ") Banana.create(name:"ラカタン") Banana.create(name:"レディ・フィンガー") Banana.create(name:"シマバナナ") Banana.create(name:"プランテン") Banana.create(name:"グロスミッチェル") Banana.create(name:"ハイランド") exit
これで必要なファイルとデータが作成されました。
(3)以下のコマンドを入力します。
bin/rails server
scaffoldを使ってWebアプリを作成すると、ルート構成がRESTfulの考え方に従って作成されます。
app/controllers/bananas_controller.rb:
class BananasController < ApplicationController before_action :set_banana, only: [:show, :edit, :update, :destroy] # GET /bananas # GET /bananas.json def index @bananas = Banana.all end # GET /bananas/1 # GET /bananas/1.json def show end # GET /bananas/new def new @banana = Banana.new end # GET /bananas/1/edit def edit end # POST /bananas # POST /bananas.json def create @banana = Banana.new(banana_params) respond_to do |format| if @banana.save format.html { redirect_to @banana, notice: 'Banana was successfully created.' } format.json { render :show, status: :created, location: @banana } else format.html { render :new } format.json { render json: @banana.errors, status: :unprocessable_entity } end end end # PATCH/PUT /bananas/1 # PATCH/PUT /bananas/1.json def update respond_to do |format| if @banana.update(banana_params) format.html { redirect_to @banana, notice: 'Banana was successfully updated.' } format.json { render :show, status: :ok, location: @banana } else format.html { render :edit } format.json { render json: @banana.errors, status: :unprocessable_entity } end end end # DELETE /bananas/1 # DELETE /bananas/1.json def destroy @banana.destroy respond_to do |format| format.html { redirect_to bananas_url, notice: 'Banana was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_banana @banana = Banana.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def banana_params params.require(:banana).permit(:name) end end
今回はActiveSupport::Concernを使って、これらのアクションの一部を別のファイルに切り出してみましょう。
具体的には、app/controllers/concernsディレクトリにファイルを作成します。
(1)app/controllers/concerns/banana_one.rbを以下の内容で作成します。
module BananaOne extend ActiveSupport::Concern # GET /bananas # GET /bananas.json def index @bananas = Banana.all end # GET /bananas/1 # GET /bananas/1.json def show end # GET /bananas/new def new @banana = Banana.new end # GET /bananas/1/edit def edit end end
Railsらしく、module名は、頭文字大文字、単語の境界に「_」無しで、ファイル名は、頭文字小文字、単語の境界に「_」有りという命名規則があります。
上の例のように、module名をBananaOneにした場合は、ファイル名はbanana_one.rbにします。
この命名規則に従っていないと、ActiveSupport::Concernが適切に読み込めません。
(2)app/controllers/bananas_controller.rbを以下のように修正します。
変更後:
class BananasController < ApplicationController before_action :set_banana, only: [:show, :edit, :update, :destroy] include BananaOne # POST /bananas # POST /bananas.json def create @banana = Banana.new(banana_params) respond_to do |format| if @banana.save format.html { redirect_to @banana, notice: 'Banana was successfully created.' } format.json { render :show, status: :created, location: @banana } else format.html { render :new } format.json { render json: @banana.errors, status: :unprocessable_entity } end end end # PATCH/PUT /bananas/1 # PATCH/PUT /bananas/1.json def update respond_to do |format| if @banana.update(banana_params) format.html { redirect_to @banana, notice: 'Banana was successfully updated.' } format.json { render :show, status: :ok, location: @banana } else format.html { render :edit } format.json { render json: @banana.errors, status: :unprocessable_entity } end end end # DELETE /bananas/1 # DELETE /bananas/1.json def destroy @banana.destroy respond_to do |format| format.html { redirect_to bananas_url, notice: 'Banana was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_banana @banana = Banana.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def banana_params params.require(:banana).permit(:name) end end
(3)ブラウザで「http://localhost:3000/bananas」にアクセスします。
ActiveSupport::Concernでモジュール化したindexアクションもしっかり呼び出されていますね。
同様に、以下のURLにアクセスして、各アクションが呼び出せることを確認してみてください。
- http://localhost:3000/bananas/1(showアクション)
- http://localhost:3000/bananas/new(newアクション)
- http://localhost:3000/bananas/1/edit(editアクション)
このように、include BananaOneで、app/controllers/concerns/banana_one.rbの内容を読み込み、モジュール化する前と同様に呼び出せるのです。
なお、モジュール化したBananaOneは、別のControllerからも呼び出せるため、共通のアクションが複数のControllerに存在する場合にも活用できます。
respond_toを使ってJSONやXMLを取得する
respond_toを使うと、URLにあわせて表示するフォーマットを切り替えられます。
具体的には、以下のようなコードで、htmlとJSONとXMLを切り替えられるようになります。
class PostsController < ApplicationController before_action :set_post, only: [:show, :edit, :update, :destroy] # GET /posts # GET /posts.json def index @posts = Post.all respond_to do |format| format.html format.json {render :json => @posts} format.xml {render :xml => @posts} end end (省略)
respond_toの使いかたは、以下の記事で詳しく説明していますので、ぜひご覧ください。
まとめ
この記事では、Controllerの使いかたを説明しました。
作成方法と命名規則さえわかっていればControllerを作成できますが、このページで紹介したような機能をしっかりマスターしておけば、Webアプリを開発するときに必要な基本的な機能はカバーできるでしょう。
また、実装していく段階で、ファイルが大きくなりすぎたと思ったらActiveSupport::Concernを活用したモジュール化を検討してはいかがでしょうか。