【Rails】Railsテクニック - delegateでカラム削除後のエラーを回避する - 

## はじめに

前の記事から空いてしまいましたが、今回は趣向を変えて、
仕事で行なっているRails開発から学んだテクニックを執筆していきます。

## あるテーブルから不要なカラムを削除することとなった

仕事では主に、Railsアプリのバックエンド側のシステム改修を行なっています。
規模が大きいRailsアプリでもあり、機能追加だけでなくもう使われなくなった機能の整理やリファクタリングが作業を占めています。
今回あるテーブルから、不要となったカラムを削除し他のテーブルのカラムを参照するという改修を行いました。

### 不要なカラムの削除

```bash
bundle exec rails g migration RemoveColumnFromExample1 column
```

```ruby:db/migrate/20180912084318_remove_corporation_from_example1.rb
class  RemoveColumnFromExample1 < ActiveRecord::Migration
  def change
    remove_column :example1, :corporation, :boolean
  end
end
```

今後は、以下のテーブルに追加したカラムを参照するようにしています。

```ruby:db/migrate/20180831001536_add_customer_type_to_example2.rb
class AddCoulumnToExample2 < ActiveRecord::Migration
  def up
    add_column :example2, :customer_type, :integer, limit: 1, default: 1, null: false
  end

  def down
    remove_column :example2, :customer_type
  end
end
```

モデルの関連付けは以下のようになっています。

```ruby:app/model/example1.rb
  has_one :example2_category_relationship, as: :creditable, dependent: :destroy
  delegate :example2, to: :example2_category_relationship, prefix: false, allow_nil: true
```

```ruby:app/model/example2.rb
  has_many :example2_category_relationships, dependent: :destroy
```

example1とexample2はポリモーフィックで関連付けています(ポリモーフィックに関してはこちら→[Railsのポリモーフィック関連とはなんなのか](https://qiita.com/itkrt2y/items/32ad1512fce1bf90c20b))。
example2では、example1で削除した`corporation` カラムと同じ用途で利用する `customer_type` カラムを追加しています。

```ruby:db/migrate/20180831001536_add_coulumn_to_example2.rb
class AddCoulumnToCredit < ActiveRecord::Migration
  def up
    add_column :example2, :customer_type, :integer, limit: 1, default: 1, null: false
  end

  def down
    remove_column :example2, :customer_type
  end
end
```

今後は、example2に追加されているcustomer_typeのカラムをcorporationが存在していた以前の用途として利用します。corporationカラムはboolean型でcorporation型に直接アクセス、またcorporation?やcorporation_changed?などのメソッドを利用していました。そのため、上二つのメソッドの代替として使えれば良い訳です。

しかし、単にカラムを削除しただけでは当然corporationを利用していた部分でundefined methodエラーを発生させるため、このエラーを解決しなければなりません。そこで、今回は `delegate` モジュールを利用して、example1から直接corporationメソッドを叩けるようにします。

## delegateを利用してcorporation?, corporation_changed?の代替メソッドを作る

example1のcorporationカラムを削除し、example2のcustomer_typeを利用するので`delegate`モジュールでexample1経由でexample2のメソッドにパブリックアクセスできるように、他のオブジェクトのメソッドを提供する`delegate`を利用します。

```ruby:app/models/example1.rb
  delegate :corporation?, to: :example2, prefix: false, allow_nil: true
  delegate :corporation_changed?, to: :example2, prefix: false, allow_nil: true
```

boolean型を指定するとActiveModelのサポートで末尾にクエスチョン付きのメソッドが使えるようになります。今回はcustomer_typeをinteger型にしたため、このメソットが使えません(?付きのメソッドについてはこちら→[Active Model Attribute Methods](https://api.rubyonrails.org/classes/ActiveModel/AttributeMethods.html#method-i-respond_to-3F))。同様にcorporationを削除したため、カラムの変更を確認するメソッドcorporation_changed?が使えません。
そこで、delegateでアクセスできるようになったexample2オブジェクトに`corporation?とcorporation_changed?`のメソッドを作っておきます。

```ruby:app/models/example2.rb
  def corporation?
    customer_type == "corporation"
  end

  def corporation_changed?
    customer_type == "corpotation" && customer_type_changed?
  end
```

カラムの存在チェックと変更チェックと比べれば、流石に冗長ですがこれでexample1からはcorporation?とcorporation_changed?が同じように使えるようになる訳です。また、 `allow_nil: true`の部分で、例えexample1にexample2が紐づいていない状態のデータがあったとしても、その場合はnilを返してくれるようになるので、`undefined method`のエラーを起こすことはありません。

## カラムを直接アクセスしている場合は...?

```ruby:app/models/example1.rb
  delegate :customer_type, to: :example2, prefix: false, allow_nil: true
```

ただし、`example1.corporation`のように直接カラムを参照しているような部分は流石に`undefined method`になるため、こちらはcustomer_typeに直接アクセスできるようにexample1でdelegateを直接アクセスできるようにします。
以上でカラム削除後のエラーを回避することができました。
説明できていなかった`delegate`のオプションは以下の通りです。

```ruby

:to - ターゲットとするオブジェクトを指定

:prefix - カスタムした名前でメソッドをdelegateのメソッドを叩きたいのであれば何か指定する

:allow_nil - falseにするとnilのraiseエラーを返してくれるが、今回はまだexample1とexample2のポリモーフィックの関連付けが済んでいないデータがたくさんあるのでtrueにしておく
```

## まとめ

delegateの紹介でした。パブリックに他のメソッドにアクセスできるのは良いですが、あまり多用しすぎるのは禁物かもしれません。特にallow_nil: trueにしておくと本来、nilが返ってはいけない部分もnilを許可してしまうのでエラーに気づきにくい恐れがあります。delegateだとリレーションのアクセス順序を一歩省略できてしまうため(と言っても、allow_nil: trueを指定している時点でnilが返っても良いと言っているようなものだから良いのか?)。とは言え、コードが冗長にならないように記述できるのはとても良いですね。
それでは。

## 参考文献


* delegate(Module)

[https://apidock.com/rails/Module/delegate](https://apidock.com/rails/Module/delegate)