Rails早くいってよなアレコレ

# はじめに
Railsで書いてると、便利なRailsAPIやGemを知らないがために、
#### ***早くいってよぉ〜***
と思うことが多々ありました。

後続の方の参考になればと、ここに残しておきます ;)

# 目次
1. Enum
    1. `ActiveRecord::Enum`
    1. Gem`enum_help`
1. Decoratorパターン(Gem`active_decorator`)
1. ValueObject
    1. `Struct.new`と` ActiveRecord::Aggregations#composed_of`
    1. `ActiveRecord::Type`

# Enum

## `ActiveRecord::Enum`
  - https://github.com/rails/rails/blob/master/activerecord/lib/active_record/enum.rb
  - Rails 4.1.0から導入

## Before
```ruby
#  status :integer not null, default 0
class Conversation < ActiveRecord::Base
  STATUS_ACTIVE = 1
  STATUS_ARCHIVED = 0
  def toggle_status
    if status == STATUS_ACTIVE
       status = STATUS_ARCHIVED
    else
       status = STATUS_ACTIVE
    end
    save!
  end    
end
```

## After
```ruby
#  status :integer not null, default 0
class Conversation < ActiveRecord::Base
  enum status: { active: 1, archived: 0 }
  def toggle_status
    if active?
       archived!
    else
       active!
    end
  end    
end
```

## Other Usages

```ruby
# conversation.update! status: 0
conversation.active!
conversation.active? # => true
conversation.status  # => "active"

# conversation.update! status: 1
conversation.archived!
conversation.archived? # => true
conversation.status    # => "archived"

# conversation.status = 1
conversation.status = "archived"

conversation.status = nil
conversation.status.nil? # => true
conversation.status      # => nil

Conversation.statuses[:active]    # => 0
Conversation.statuses["archived"] # => 1
```
- cf.) http://api.rubyonrails.org/v5.1/classes/ActiveRecord/Enum.html

## Gem`enum_help`
- `ActiveRecord::Enum`をi18nしてくれる
- https://github.com/zmbacker/enum_help

### setup
`Gemfile`

```rb
gem 'enum_help'
```
`config/locales/ja.yml`

```yml
ja:
  enums:
    conversation:
      status:
        active: アクティブ
        archived: アーカイブ済み
```

## usage
```rb
conversation.status    # => "active"
conversation.status_i18n # => "アクティブ"

conversation.archived!
conversation.status    # => "archived"
conversation.status_i18n # => "アーカイブ済み"

Conversation.statuses_i18n.invert
# => {"アクティブ"=>"active", "アーカイブ済み"=>"archived"}
```

### Drawing Checkboxes
`conversations/_form.slim`

```slim
= form_for @conversation do |f|
  .form-group
    = f.label :status, class: 'control-label required'
    .
      - Conversation.statuses_i18n.invert.each do |state|
        label.radio-inline
          = f.radio_button :status, state[1], class: 'optradio'
          = state[0]
```

## good/bad points
- マジックナンバーを意味あるメソッドでラップできる。
- 慣れないとFormでいじる時などハマるポイントはあるっちゃーある。
- デメリットは少ないので基本的に使うべきだと思う

### 参考記事
[ActiveRecordのenumで気をつけたい３つのポイント](http://tech.misoca.jp/entry/2015/08/10/132419)

# Decorator
`active_decorator`
https://github.com/amatsuda/active_decorator

## Before
`index.slim`

```slim
- @remittances.each do |remittance|
  .row
    .col-md-3
      - if remittance.approved?
        smal.label.label-info
          = fa_icon('check-square-o', text: status_i18n)
      - else
        smal.label.label-warning
          = fa_icon('exclamation-triangle', text: status_i18n)
      h4 = remittance.mansion_name
```

## After
`remittance_decorator.rb`

```ruby
module RemittanceDecorator
  def status_label
    if approved?
      content_tag(:small, class: 'label label-info') do
        concat fa_icon('check-square-o', text: status_i18n)
      end
    elsif unapproved?
      content_tag(:small, class: 'label label-warning') do
        concat fa_icon('exclamation-triangle', text: status_i18n)
      end
    end
  end
end
```

`index.slim`

```slim
- @remittances.each do |remittance|
  .row
    .col-md-3
      = remittance.status_label
      h4 = remittance.mansion_name
```

## good/bad points

- view側では「ステータスのラベルを出したい」という要求のみを書くだけで良い。
    - slimテンプレートからロジックを排除できる
- ぶっちゃけReactなりVueなりを入れればもっと綺麗にかける
    - 止むを得ずrubyのテンプレートエンジンを使うときの逃げ道

### 参考記事
[http://tech.gmo-media.jp/post/91900069058/rails-presenter-decorator](http://tech.gmo-media.jp/post/91900069058/rails-presenter-decorator)

# ValueObject
## `Struct.new`と` ActiveRecord::Aggregations::ClassMethods#composed_of`
- n個のフィールドを１つのオブジェクトに格納するパターン
- [Usage](https://apidock.com/rails/ActiveRecord/Aggregations/ClassMethods/composed_of)
- [ソース(ActiveRecord::Aggregations::ClassMethods#composed_of)](https://github.com/rails/rails/blob/831be98f9a6685da3410b0c9bf4143bd8dd647af/activerecord/lib/active_record/aggregations.rb#L226-L242)

## Before
```ruby
# first_name:string, middle_name:string, last_name:string
class Person < ApplicationRecord
  def full_name
    if middle_name
      "#{first_name} #{middle_name} #{last_name}"
    else
      simple
    end
  end
  def simple_name
    "#{first_name} #{last_name}"
  end
end
```

## After
```ruby
class Name < Struct.new(:first, :middle, :last)
  def full
    to_s
  end
  def simple
    "#{first} #{last}"
  end
  def to_s
    if middle
      "#{first} #{middle} #{last}"
    else
      simple
    end
  end
end

# first_name:string, middle_name:string, last_name:string
class Person < ApplicationRecord
  composed_of :name, mapping: [%w[first_name first],
                               %w[middle_name middle],
                               %w[last_name last]]
end
```

## Usage
```ruby
p = Person.create(name: Name.new('Emma','Charlotte', 'Watson'))
p.attributes # => {"id"=>4, first_name: "Emma", middle_name: "Charlotte", last_name: "Watson"}
p.name.simple # => "Emma Watson"
p.name.full # => "Emma Charlotte Watson"
Person.find_by(name: Name.new('emma', 'charlotte', 'watson')) # => #<Person:0x007fd9c4204f90 id: 5, first_name: "Emma", middle_name: "Charlotte", last_name: "Watson"}
```

### good/bad points
- Good
    - 複数フィールドを跨いだ**値そのもの**に振る舞いを持たせる事ができる
        - 名前をどう出すか？はPersonが持つというより、**名前そのもの**が関心を持っている。
    - Fat Modelにならずに多様なふるまいを定義できる
- Bad
    - `Struct#new`を理解していないと、めちゃつまづく
    - `find_by`に渡すと全てのフィールドを条件にするので、使い勝手が悪い
    - `Struct`間を跨いだバリデーションとかで冗長的になる

## `ActiveRecord::Type`
- フィールドとオブジェクトが1:1になるパターン
- https://github.com/rails/rails/blob/master/activerecord/lib/active_record/type.rb

## Before
```ruby
# target_year_month:integer
class Salary < ApplicationRecord
  def target_time
    Time.mktime(target_year_month.to_s[0, 4], target_year_month.to_s[4, 2]) if value
  end
end
```
- DBからすると、年月までしか情報としてはいらないので、Date型、DateTime型でもつには冗長的な情報
- でもモデルではTime型として扱いたい情報

## After
```ruby
# config/initializers/active_record/year_month.rb
class YearMonth < ActiveRecord::Type::Integer
  def cast(value)
    if value.kind_of? Time
      str = format('%04d', value.year) + format('%02d', value.month)
      super str.to_i
    else
      super
    end
  end
  # DBから値を取り出す時のキャスト処理
  def deserialize(value)
    Time.mktime(value.to_s[0, 4], value.to_s[4, 2]) if value
  end
end

ActiveRecord::Type.register(:year_month, YearMonth)

# app/models/salary.rb
# target_year_month:integer
class Salary < ApplicationRecord
  attribute :target_year_month, :year_month
end
```

## Usage
```ruby
Salary.create(target_year_month: Time.current)
# => INSERT INTO `salaries` (`target_year_month`) VALUES (201709)
Salary.find_by(target_year_month: 201709)
# => SELECT  `salaries`.* FROM `salaries` WHERE `salaries`.`target_year_month` = 201709 LIMIT 1
s = Salary.find_by(target_year_month: Time.current)
# =>  SELECT  `salaries`.* FROM `salaries` WHERE `salaries`.`target_year_month` = 201709 LIMIT 1
s.target_year_month.class # => Time
```

## Good/Bad Points
- `YearMonth型`という型を自作することで、共通処理を１箇所にまとめることができる。
- 継承元には`String`、`Binary`、`Value `など様々なクラスがあるので、基本なんでもできる。
    - どんな構造のCSVがくるかわからない場合など、動的な構造を`Hash`で入れてる場合などにも有用だとおもいます。
- 正直デメリットがあまり見つからない。サービス開始時点で使いたかったw

### 参考記事
[Active Record::Type::Valueを継承した独自タイプの作成](http://morizyun.github.io/blog/activerecord-type-cast-serialize/index.html)
[Using the Rails 5 Attributes API Today, in Rails 4.2](https://nvisium.com/blog/2015/06/22/using-rails-5-attributes-api-today-in/)

# 以上！！！