スライドショースライドショー

【Rails入門】ViewとModelの間にDecorator(Draper)を置く

今回は、Ruby on Rails(以降、Rails)で、誰もが頭を悩ませている、ある問題を1つ解決しましょう。

それは、

・ModelとViewのどっちに書くの問題

です。

非常に簡単な例で、Modelでは日時を”2018-07-12 18:12:34”で保持していますが、Viewでは”2018/7/12”と表示する場合を考えましょう。

この日時のフォーマットを変更するコードは、ModelViewのどっちに書くのでしょう。

私は、ModelとViewの間にDecoratorを1つ追加して、Decoratorにコードを書くことをおすすめします!

どういうこと?と思ったあなたは、この記事を読むべきです。

それでは、始めましょう。

Decoratorとは

ちょっと概念的な話から始めますので、まずは結論から。

Model→Viewの関係でコードを書いていたところにDecoratorを追加して、Model→Decorator→Viewの関係でコードを書くことをおすすめします。

ここでは、「Decoratorを追加する」と、どうして「ModelとViewのどっちに書くの問題」が解決できるのかを、説明します。

私が書く記事でたびたび登場している以下の表をご覧ください。

コンポーネント(構成要素)説明備考
Model(モデル)データベースを取り扱うデータベースへの格納方法は、Modelに隠ぺいする
View(ビュー)画面表示を取り扱う表示方法は、Viewに隠ぺいする
Controller(コントローラー)(ユーザーの入力を受けて)ModelとViewにアレコレ指示するデータベースへの格納方法や表示方法は知らない

これを見ると、日時のフォーマットを変更するコードは、ModelにもViewにも書ける気がします。

Modelを見ると「データベースへの格納方法は、Modelに隠ぺいする」と書かれています。

データベースに格納する”2018-07-12 18:12:34”という形式は、Viewには知られたくないということですから、Viewで日付が必要になったらModel側で適切なフォーマットにするべきという考え方ができます。

一方、Viewを見ると「表示方法は、Viewに隠ぺいする」と書かれています。

”2018/7/12”という表示方法はView側に書くべきとも考えられます。

どちらも正しい気もしますが、どちらも正しくない気がします。

まさに、頭を悩ませるModelとViewのどっちに書くの問題ですね。

私は、Decoratorをおすすめする立場ですので、次のように考えています。

Modelは「データ管理だけ」を担当し、Viewは「情報表示だけ」を担当する、という役割分担です。

そして新しく登場するDecoratorには、「(プログラムで管理しやすい)データ」を「(ユーザーが理解しやすい)情報」に変換する役割を持たせます。

そして、先ほどの日時のフォーマットを変更するコードはDecoratorに書き、ViewではDecoratorで用意したメソッドを利用します。

ModelとViewの間にDecoratorを追加して、データを情報に変換する役割を持たせれば、ModelとViewのどっちに書くの問題」が解決できそうですね!

Draperとは

ここからは、RailsでDecoratorを実現する方法を紹介していきましょう。

この記事では、Railsらしくgemをインストールして、Decoratorを実現します。

Decoratorを簡単に導入するためのgemはいくつかあります。

The Ruby Toolboxでは、「Rails Presenters」というカテゴリーで集約されています。

参考:https://www.ruby-toolbox.com/categories/rails_presenters

今回は、最もダウンロード数が多いDraperを使ってDecoratorを導入してみます。

Draper以外にも、CellsActiveDecoratorApotomodisplay_caseなどが登録されていますね。

実装方法や使いかたが異なりますので、最高のgemを探す!という方は試してみると良いでしょう。

動作を理解するためのWebアプリを作成する

DraperによるDecoratorを理解するために、RailsをインストールしてWebアプリを作りましょう。

(1)Railsをインストールします。

私は、以下の記事を参考に、VirtualBoxで作成した仮想パソコンにインストールしたLinux Mintに、Railsの開発環境を作成しました。

基本的には記事の手順に従って操作しますが、app/samurai/sample1ディレクトリを作成する代わりに、app/samurai/decorator-demoディレクトリを作成しました。

Railsを起動して、ブラウザで画面が表示されることを確認したら、いったんRailsを終了してから次に進みます。

初心者でもかんたん!Ruby on Rails の開発環境の構築手順(Mac/Windows 両対応)
更新日 : 2019年8月9日

Linux Mintのインストールについては、以下の記事で詳しく説明しています。

Linux Mint Cinnamonエディションを使ってみよう
更新日 : 2019年5月22日

Draperをインストールする

Railsのインストールが済んでいれば、Draperのインストールは簡単です。

(1)Gemfileの最終行に以下の内容を追加します。

gem 'draper'

(2)以下のコマンドを1行ずつ順番に入力します。

bundle install

Draperがインストールされました。

簡単なDecoratorを使ってみる

簡単な例ですが、Modelで保持している”2018-07-12 18:12:34”という日時を、Viewでは”2018/7/12”と表示するDecoratorを作ってみましょう。

まずは、bin/rails generate scaffoldコマンドです。

(1)「端末」で以下のコマンドを1行ずつ順番に入力します。

bin/rails generate scaffold User name:string

実行結果:

Running via Spring preloader in process 20314
      invoke  active_record
      create    db/migrate/20180713061008_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      invoke  resource_route
       route    resources :users
      invoke  scaffold_controller
      create    app/controllers/users_controller.rb
      invoke    erb
      create      app/views/users
      create      app/views/users/index.html.erb
      create      app/views/users/edit.html.erb
      create      app/views/users/show.html.erb
      create      app/views/users/new.html.erb
      create      app/views/users/_form.html.erb
      invoke    test_unit
      create      test/controllers/users_controller_test.rb
      invoke    helper
      create      app/helpers/users_helper.rb
      invoke      test_unit
      invoke    decorator
      create      app/decorators/user_decorator.rb
      invoke      test_unit
      create        test/decorators/user_decorator_test.rb
      invoke    jbuilder
      create      app/views/users/index.json.jbuilder
      create      app/views/users/show.json.jbuilder
      create      app/views/users/_user.json.jbuilder
      invoke  test_unit
      create    test/system/users_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users.coffee
      invoke    scss
      create      app/assets/stylesheets/users.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.scss

通常のscaffoldで作成されるディレクトリ/ファイルに加えて、以下の4つが作成されています。

      invoke    decorator
      create      app/decorators/user_decorator.rb
      invoke      test_unit
      create        test/decorators/user_decorator_test.rb

Decoratorらしいディレクトリとファイルですね。

(2)以下のコマンドを1行ずつ順番に入力します。

bin/rails db:migrate
bin/rails console

(3)以下のコードを入力します。

User.create(name:"山田太郎")
User.create(name:"長瀬来")
User.create(name:"立川裕美")
User.create(name:"前田達郎")
User.create(name:"細川修二")
User.create(name:"木村拓磨")
exit

(4)app/views/users/index.html.erbを変更します。

変更前:

<%= notice %>

Users

<% @users.each do |user| %> <% end %>
Name
<%= user.name %> <%= link_to 'Show', user %> <%= link_to 'Edit', edit_user_path(user) %> <%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %>

<%= link_to 'New User', new_user_path %>

変更後:

<%= notice %>

Users

<% @users.each do |user| %> <% end %>
Name Created at
<%= user.name %> <%= user.created_at %> <%= link_to 'Show', user %> <%= link_to 'Edit', edit_user_path(user) %> <%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %>

<%= link_to 'New User', new_user_path %>

(5)app/views/users/show.html.erbを変更します。

「Created at」を表示するようにしました。

変更前:

<%= notice %>

Name: <%= @user.name %>

<%= link_to 'Edit', edit_user_path(@user) %> | <%= link_to 'Back', users_path %>

変更後:

<%= notice %>

Name: <%= @user.name %>

Created at: <%= @user.created_at %>

<%= link_to 'Edit', edit_user_path(@user) %> | <%= link_to 'Back', users_path %>

(6)以下のコマンドを入力します。

bin/rails server

Userの一覧をブラウザで確認してみましょう。

(5)ブラウザで「http://localhost:3000/users」にアクセスします。

「Created at」が表示されていますね。

ここでいずれかの「show」をクリックすると、やはり「Created at」が表示されていることが確認できます。

ここからは「Created at」の表示を「2018/7/13」に変更するために、Decoratorを作ります。

Controllerを変更する

まずは、ControllerからViewに対して、通常のオブジェクトを渡す代わりに、Decorator付きのオブジェクトを渡すように変更します。

(1)app/controllers/users_controller.rbを変更します。

変更前:

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    @users = User.all
  end

  # GET /users/1
  # GET /users/1.json
  def show
  end
(省略)

変更後:

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    @users = UserDecorator.decorate_collection(User.all)
  end

  # GET /users/1
  # GET /users/1.json
  def show
    @user = User.find(params[:id]).decorate
  end
(省略)

「http://localhost:3000/users」や「http://localhost:3000/users/1」にアクセスしたときに、Decoratorで機能を追加した(追加する予定の)@usersまたは@userを取得するようにしています。

先ほどと同じURLにアクセスしてみましょう。

(2)ブラウザで「http://localhost:3000/users」にアクセスします。

何も変わっていないことを確認してください。

無駄なことをしたのか!?と思うかもしれませんが、そうではありません。

ここでは、@usersをDecoratorで機能を追加した(追加する予定の)データに差し替えたにもかかわらず、何も変わらずにWebアプリが動作していることが重要です。

Decoratorを変更する

次に、Decorator(app/decorators/user_decorator.rb)を変更して、def created_atを定義し、機能を変更しましょう。

このように、インスタンス変数と同じ名前のメソッドを定義すると、ViewやModelを変更することなく、Decoratorを利用できます。

(1)app/decorators/user_decorator.rbを変更します。

変更前:

class UserDecorator < Draper::Decorator
  delegate_all

  # Define presentation-specific methods here. Helpers are accessed through
  # `helpers` (aka `h`). You can override attributes, for example:
  #
  #   def created_at
  #     helpers.content_tag :span, class: 'time' do
  #       object.created_at.strftime("%a %m/%d/%y")
  #     end
  #   end

end

変更後:

class UserDecorator < Draper::Decorator
  delegate_all

  # Define presentation-specific methods here. Helpers are accessed through
  # `helpers` (aka `h`). You can override attributes, for example:
  #
  #   def created_at
  #     helpers.content_tag :span, class: 'time' do
  #       object.created_at.strftime("%a %m/%d/%y")
  #     end
  #   end

  def created_at
    object.created_at.strftime("%Y/%-m/%-d")
  end
end

(2)ブラウザで「http://localhost:3000/users」にアクセスします。

Decoratorのdef created_atが呼び出され、日付のフォーマットが変わっていますね。

(3)「山田太郎」の「show」をクリックして、http://localhost:3000/users/1にアクセスします。

こちらでもDecoratorのdef created_atが呼び出され、日付のフォーマットが変わっています。

ModelとViewを変更しない

ここまで、ControllerDecoratorを変更してきました。

一方、ModelとViewは変更していません。

それでも、表示を変更できたということは、初めに話題にしたModelとViewのどっちに書くの問題は、どっちにも書かない(Decoratorに書く)という結論に至ったことになりますね。

まとめ

今回は、DraperによるDecoratorの実装方法を簡単に紹介しました。

Decoratorを使うもっとも大きなメリットは、Modelを変更しなくても、Viewを変更しなくても、表示を変更できるという点です。

今回のようにcreated_atという、インスタンス変数と同じ名前のメソッドを定義するのもポイントでした。

このように、Decoraterで機能を追加したオブジェクトと、機能を追加する前のオブジェクトを、Viewで区別する必要がないように、Decoratorをうまく定義する、というのも、Decoratorパターンの大事な考えかたですので、覚えておきましょう。

それでは、また。

LINEで送る
Pocket

無料でSEからWebエンジニアへ転職しませんか?



侍エンジニア塾では、完全未経験の方から現在SEだけどプログラミングはやっていないという経験者まで、幅広い方々の人生を好転させるプログラミング指導を行ってきました。SEの方とお話していくなかで、

  • システムエンジニアという職業だけどコードが書けない
  • 事務作業が多くスキルがないため将来が不安
  • スクールに通うと完全未経験者と同じスタートになるからレベルが合わない
という、すでに知識があるSEならではのお悩みがあることに気づきました。そんな方におすすめなのが、弊社の「転職コース 」です。

弊社では、マンツーマンでレッスンを行いますので、現在お持ちの知識レベルからカリキュラムを作成いたします。さらにこちらの転職コースは無料で受講を始められて転職成功でそのまま卒業できるというとてもお得なコースとなっています。

既に知識のあるSEといっても転職は年齢が若いほど受かりやすいため、まずは無料体験レッスンで今の現状や理想の働き方について一緒に考えていきましょう。

まずは無料体験レッスンを予約する

書いた人

侍テック編集部

侍テック編集部

おすすめコンテンツ

あなたにぴったりなプログラミング学習プランを無料で診断!

プログラミング学習の効率を劇的に上げる学習メソッドを解説