エンタープライズ領域での採用も増えてきたRuby on Railsを使ってWebアプリケーションを作るための入門連載。最新版の4に対応しています。今回は、サンプルプロジェクトをMVCごとにRailsアプリの設計を見直してリファクタリングすることで、これまでの連載のおさらいをします。
本連載はRuby on Rails 4の入門連載です。Rubyについて学びたい方は連載「若手エンジニア/初心者のためのRuby 2.1入門」をご覧ください。
前回の『「設定より規約」のRailsで必要なセッティングの基礎知識と国際化/多言語対応』まで、サンプルプロジェクトの「book_library」を題材にRailsのさまざまな機能を紹介してきましたが、今回はRailsアプリケーション開発を紹介してきた本連載のおさらいとして、サンプルプロジェクトをMVCごとにリファクタリングしたいと思います。
「book_library」は社内の書籍を管理するためのアプリケーションで、これまでRailsの各機能を紹介するため場当たり的にさまざまな機能を盛り込んできましたが、もっとシンプルに作ってみましょう。
まずは、連載第7回の「Rails開発を面白くするアクションコントローラーの5大機能とルーティングの基本」で解説したMVCの「C」、コントローラーです。コントローラーの見直しは名前空間やネストによる外部構造と、アクションの定義などの内部構造に分けて進めていきます。
コントローラーの外部構造は「ルーティング」により定義されますが、それに従って「app/controllers」以下のファイルの配置も決まります。現在の「config/routes.rb」は次のコードのようになっていますが、注意して変更していきます。
- BookLibrary::Application.routes.draw do
- root 'books#index'
- resources :books
- resources :users do
- get 'booking', on: :collection
- post 'message', on: :member
- resources :books, only: %i(index)
- end
- namespace :admin do
- resources :books, only: %i(index)
- end
- get 'admin' => 'admin/books#index'
- get 'admin/:id' => 'admin/books#show'
- post 'admin' => 'admin/books#create'
- end
まず2行目の「root」の定義ですが、これはこのままでもよいでしょう。
ですが、次の「resources :books」と「namespace :admin」のブロック内の「resources :books」はいずれも「books」のリソースを取り扱うルーティングです。しかし、本来であれば管理側でリソースの登録や更新、削除などをしたいところです。
一方、4行目の「resouces :users」はユーザー自身にユーザー登録や更新する操作をさせたいので、このままとしておきます。しかし、このブロック内部の「get」や「post」は必要な処理かもしれませんが、「resources :books」は「index」アクションだけでそれほど重要ではないかもしれません。
最後の「get」や「post」によるルーティングは連載中の説明のために追加しましたが、見るからに違和感がありますね。「namespace :admin」のルーティングで同じアクションを扱えるので削除してしまってもいいでしょう。
以上の内容を踏まえてルーティングを再定義すると、次のようになりました。ちなみに「namespace :admin」のブロック中で「root」を定義すると、「/admin」のルーティングを設定できます。
- BookLibrary::Application.routes.draw do
- # ユーザーサイド
- root 'books#index'
- resources :books, only: %w(index show)
- resources :users do
- get 'booking', on: :collection
- post 'message', on: :member
- end
- # 管理者サイド
- namespace :admin do
- root 'books#index'
- resources :books
- end
- end
前節のルーティングの再定義により、コントローラーごとにアクションを見直す必要があります。まず「BooksController(app/controllers/books_controller.rb)」から取り掛かりましょう。
このコントローラーは連載初期に「scaffold」により生成し、そのまま使ってきました。ですが、「new」「create」「edit」「update」「destroy」のアクションは「Admin::BooksController(app/controllers/admin/books_controller.rb)」に移動させたいと思います。対象の部分を切り取って、フィルターのオプションなどを整理すると「BooksController」は次のようになります。
- class BooksController < ApplicationController
- before_action :set_book, only: [:show]
- def index
- @query = Query.new(params[:query])
- if @query.valid?
- @books = Book.where("title like :keyword OR author like :keyword", keyword: @query.keyword)
- else
- @books = Book.all
- end
- end
- def show
- end
- private
- def set_book
- @book = Book.find(params[:id])
- end
- end
切り取った部分は「Admin::BooksController」で再利用しますが、リダイレクト先やビューのリンクなどが「admin」名前空間の中でないため、適宜修正する必要があります。修正した内容については「book_library」の「11」ディレクトリのコードを参照してください。
また、ビューに関しては削除したアクションへのリンクなど不必要なものを取り除きます。「index」アクションのビューを次に示します。
- h1 = t('.title')
- = form_for @query, url: books_path, method: :get do |f|
- = f.text_field :keyword
- = f.submit '検索'
- table
- thead
- tr
- th Title
- th Author
- th Outline
- tbody
- - @books.each do |book|
- tr
- td = link_to book.title, book
- td = book.author
- td = book.outline
ここまででも「books#index」のリファクタリングができましたが、次の変更に備えてスペック(spec)を追加しておくのも良い考えです。「spec/controllers/books_controller_spec.rb」に次のように「index」アクションなどのテストを追加します(テストについては連載代9回の「RailsテストフレームワークRSpecのインストールと基本的な使い方、基礎文法」を参照してください)。
- require 'rails_helper'
- RSpec.describe BooksController, :type => :controller do
- describe "index" do
- # @queryにQueryのインスタンスが代入される
- it "assigns a query as @query" do
- get :index, {}, {}
- expect(assigns(:query)).to an_instance_of Query
- end
- # クエリがないとき、@booksに全てのBookが代入される
- it "assigns all books as @books without query" do
- b1 = Book.create!(title: 'hoge')
- b2 = Book.create!(title: 'fuga')
- get :index, {}, {}
- expect(assigns(:books)).to eq [b1, b2]
- end
- # @booksにクエリで要求されたBookが代入される
- it "assigns books as @books required by query" do
- b1 = Book.create!(title: 'hoge')
- b2 = Book.create!(title: 'fuga')
- get :index, {query: {keyword: 'hoge'}}, {}
- expect(assigns(:books)).to eq [b1]
- end
- end
- ……
- end
Copyright © ITmedia, Inc. All Rights Reserved.
Coding Edge 鬮ォ�ェ陋滂ソス�ス�コ闕オ譁溷クキ�ケ譎「�ス�ウ驛「�ァ�ス�ュ驛「譎「�ス�ウ驛「�ァ�ス�ー