オブジェクト指向 Rails しばしば出てくる3つの悩みを解決する。

#はじめに
Railsで開発をしていると頭を悩ます３つの**しばしば**があります。

- １つのモデルで複数のモデルを操作してしまう

- 冗長なコードが複数のモデルまたはコントローラに出てくる

- 表示のためだけに、モデルにメソッドを追加する

これらを解決するためには、オブジェクト指向とデザインパターンによる考え方ができるとよいです。
この記事では、Railsでよりオブジェクト指向的に、より構造が単純で、より　DRYに書くためのパターンを学びます。

環境は　Rails 4 です。

# キーワードは３つ。

*  複雑なビジネスロジックを分離する **Non associated Model**
*  関心の分離を保ちつつ処理をまとめる **Model/Controller Concerns**
*  表示ロジックをモデルから分離する **Decorator**

# Non associated Model

## 解決する問題
- １つのモデルで複数のモデルを操作してしまう

##導入する理由
ActiveRecordは、１つのモデルが１つのテーブルにひもづくので、あるモデルに複数のモデルを操作するような処理は書くべきでない。

**さらに言うと、複数のモデルを操作するクラスは、テストしづらい。**

##回答
ActiveRecordに**ひもづかない**クラスを作り、複雑なロジックは分離する。

##例
車モデルに対して、複数の部品を組み立てる build_parts というアクションを考える。

![sample-model.png](https://qiita-image-store.s3.amazonaws.com/0/49986/153d8024-a939-b150-cec7-b2dc9c1ff8a9.png)


```rb:bad

#cars_controller.rb
class CarsController < ApplicationController
  before_action :set_car, only:[:show, :update, :edit, :delete]
  :
  def build_parts
    @car.build_parts
    respond_to do |format|
      :
    end
  end

  private
    def set_car
      @car = Car.find(params[:id])
    end
end

#car.rb
class Car < ActiveRecord::Base
  def build_parts
    self.class.transaction do
      self.suspension.create
      self.tire.create
      self.body.create
      :
      複雑なロジック
    end
  end
end
```
↓

```rb:good

#cars_controller.rb
class CarsController < ApplicationController
  :
  def build_parts
    car_builder = CarBuilder.new(@car)
    car_builder.build_parts
    respond_to do |format|
      :
    end
  end

  private
    def set_car
      @car = Car.find(params[:id])
    end
end

#car.rb
class Car < ActiveRecord::Base
end

#car_builder.rb
class CarBuilder
  attr_reader :car
  def initialize(car)
    @car = car
  end

  def build_parts
    car.class.transaction do
      car.suspension.create
      car.tire.create
      car.body.create
      :
      複雑なロジック
  end
end
```

テーブルに関連づいているCarモデルは、自分のことしか見ないクラスになりました。
>例における実装は、builderパターンが元になっています。
参考 [Builderパターン](http://www.techscore.com/tech/DesignPattern/Builder.html/)


# Model/Controller Concerns
## 解決する問題
- 冗長なコードが複数のモデルに出てくる

##導入する理由
ActiveRecordは、１つのモデル/コントローラが１つのテーブルにひもづくので、モデル単位でロジックを意識する。
これが行き過ぎると、モデル/コントローラごとに同じようなメソッドが出てきて、DRYではなくなってくる。

##回答
app/controllers/Concerns、または、 app/models/concernsに共通メソッドをまとめる。

##例1(models/concerns)
写真と動画にコメントしたいケースを考える。

![IMG_6120.JPG](https://qiita-image-store.s3.amazonaws.com/0/49986/77d814ce-0eb5-51c5-8ff5-70c0c8198671.jpeg)

```rb:not_dry

#comment.rb
class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
    :
end

#picture.rb
class Picture < ActiveRecord::Base
  has_many :comments, as: :commentable

  #duplicate code
  def comments_by_user(id)
    comments.where(user_id: id)
  end
end

#movie.rb
class Movie < ActiveRecord::Base
  has_many :comments, as: :commentable

  #duplicate code
  def comments_by_user(id)
    comments.where(user_id: id)
  end
end
```

↓

```rb:dry

#app/models/concerns/commentable.rb
module Commentable
  extend ActiveSupport::Concern

  included do
    has_many :comments, as: :commentable
  end

  def comments_by_user(id)
    comments.where(user_id: id)
  end
end

または

module Commentable
  def self.included(base)
    base.class_eval do
      has_many :comments, as: :commentable
    end
  end

  def comments_by_user(id)
    comments.where(user_id: id)
  end
end

----
#picture.rb
class Picture < ActiveRecord::Base
  include Commentable
    :
end

----
#movie.rb
class Movie < ActiveRecord::Base
  include Commentable
    :
end
```

> polymorphic 関連がわからない、という人は、以下の記事がわかりやすいです。
[Rails4でポリモフィックのリレーションを実装する](http://ruby-rails.hatenadiary.com/entry/20141207/1417926599)

ActiveSupport::Concernを使う例のほうが、すっきり見えるので、私は好んで使っています。

##例2(controllers/concerns)
先ほどの例で、写真と動画のサムネイルをそれようのパスに保存したいケースを考える。

```rb:not_dry

#pictures_controller.rb
class PicturesController < ApplicationController
  def create
      :
    #duplicate code
    thumb_path = "/thumb/#{@picture.file_name}"
    thumb = @picture.make_thumbnail thumb_path
    thumb.save
  end
end

#movies_controller.rb
class MoviesController < ApplicationController
  def create
      :
    #duplicate code
    thumb_path = "/thumb/#{@movie.file_name}"
    thumb = @movie.make_thumbnail thumb_path
    thumb.save
  end
end
```

↓

```rb:dry
#app/controllers/concerns/previewable.rb
module Previewable
  def thumbnail(content)
    thumb_path = "/thumb/#{content.file_name}"
    thumb = content.make_thumbnail thumb_path
    thumb.save    
  end
end

#pictures_controller.rb
class PicturesController < ApplicationController
  include Previewable

  def create
      :
    thumbnail(@picture)
  end
end

#movies_controller.rb
class MoviesController < ApplicationController
  include Previewable
  def create
      :
    thumbnail(@movie)  
  end
end

```

# Decorator
## 解決する問題
- 表示のためだけに、モデルにメソッドを追加する

##導入する理由
表示のためのロジックがモデルに増えてくると、肝心のビジネスロジックが見づらくなってしまう。

##回答
app/decorators/にモデル単位で表示するための固有のロジックをまとめる。

##例
日付をフォーマットして表示したいケースを考える。

```rb:bad
#sample.html.haml
 :
= "created_at #{@sample.formatted_date}"
 :

#samples_controller.rb
class SamplesController < ApplicationController
  before_action :set_sample, only: [:show, :update, :edit, :delete]
    :
  def show
  end

  private
  def set_sample
    @sample = Sample.find(params[:id])
  end 
end

#sample.rb
class Sample < ActiveRecord::Base
  def formatted_date
    self.created_at.strftime("%Y/%m/%d %H:%M:%S")
  end
end
```

↓

```rb:good
#sample.html.haml
 :
= "created_at #{@sample_decorator.formatted_date}"
 :

# app/decorators/sample_decorator.rb
class SampleDecorator
  attr_reader :sample
  def initialze(sample)
    @sample = sample
  end

  def method_missing(method_name, *args, &block)
    sample.send(method_name, *args, &block)
  end

  def respond_to_missing?(method_name, invalid_private = false)
    sample.respond_to(method_name, invalid_private)
  end

  def formatted_date
    sample.created_at.strftime("%Y/%m/%d %H:%M:%S")
  end

end

#samples_controller.rb
class SampleController < ApplicationController
  before_action :set_sample, only: [:show, :update, :edit, :delete]
    :
  def show
  end

  private
  def set_sample
    @sample_decolator = SampleDecorator.new(Sample.find(params[:id]))
  end
end

#sample.rb
class Sample < ActiveRecord::Base
end

```

ポイントは、**method_missingとrespond_to_missing**をDecoratorに実装すること。
これにより、sample_decoratorから、sampleのメソッドを呼び出すことができます。

```rb:call_native_model_method.rb
  :
 @sample_decorator.find(params[:id])
  :
```
>参考：デザインパターン
[Decorator](http://www.techscore.com/tech/DesignPattern/Decorator.html/)

>補足：ヘルパーについて
この議論をするとき、よくヘルパーに切り出す、という記事を見つけます。
この方法でも、モデルを汚さずに、かつ、手軽に表示ロジックを切り出せますが、ネームスペースを汚してしまう。というデメリットもあります。
Decoratorとして、明示的にclassに切り出せるほうが、より局所化できるので、(OOP的には）ベターな方法だと思います。

Decoratorをパッケージングしてくれている[Draper](https://github.com/drapergem/draper)というGemがあります。
scaffoldするたびに、Decoratorを自力定義する必要がないので、効率化できます。

#最後に
よりDRYに、またオブジェクト指向でいうところの[関心の分離](https://ja.wikipedia.org/wiki/%E9%96%A2%E5%BF%83%E3%81%AE%E5%88%86%E9%9B%A2)ができたのではないかと思います。
Railsは本当によくできたフレームワーク（お前が言うな！）なので、逆に[OOP](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0)を意識しなくてもある程度形になってくれるのですが、[OOP](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0)をある程度知ってなかったら、このような気づきはなかったと思います。

あ、例に出したコードや記事の内容について、もちろん異論はありです。
いろんな意見を聞かせていただければと。