連載
» 2015年01月08日 18時00分 UPDATE

開発現場でちゃんと使えるRails 4入門(12):Railsアプリの設計をMVCごとに見直しリファクタリングして連載総まとめ (1/2)

エンタープライズ領域での採用も増えてきたRuby on Railsを使ってWebアプリケーションを作るための入門連載。最新版の4に対応しています。今回は、サンプルプロジェクトをMVCごとにRailsアプリの設計を見直してリファクタリングすることで、これまでの連載のおさらいをします。

[著:林慶、監修:山根剛司,株式会社アジャイルウェア]

※編集部注

本連載はRuby on Rails 4の入門連載です。Rubyについて学びたい方は連載「若手エンジニア/初心者のためのRuby 2.1入門」をご覧ください。


「開発現場でちゃんと使えるRails 4入門」のインデックス

連載目次

 前回の『「設定より規約」の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

スペック(spec)の追加

 ここまででも「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
       1|2 次のページへ

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。