Railsでもう消したいassociationが使用されたら「それはDeprecatedである」と警告したい


#背景
ある日のぼく「ここのhas_manyとかもういらないよね〜！それに今は１：１なここの関係は、１：多にしないと今後ヤバそうだよな〜！よーし、変えるか〜！！！！」
（この後、数時間に及ぶ試行錯誤を行う）
ぼく「この関連、使用箇所多すぎ！！テスト通らねぇ！！寿命で死ぬ！！こんなもんに時間かけてられっか！！！！(ブチギレ)」
（自分ができるだけ手を動かさずに済む方法を考える）
ぼく「せや！もう使って欲しくない関連付けを使ってる場所で『この関連付けはそのうち消す(Deprecated)』ということを皆に知らせて実装を変えてもらおう！レッツ人海戦術！」

というわけで、タイトルのことをやってみました。

#やったこと
##時間がない人向け
こういうmoduleを作成しました。

```ruby:deprecatable.rb
# Deprecatedなassociationを設定したい場合ははこれを使用する

module Deprecatable
  extend ActiveSupport::Concern

  module ClassMethods
    def deprecated_associations(*names)
      names.each do |name|
        reflection = self.reflections[name]
        if reflection.collection?
          deprecated_collection_association(name)
        else
          deprecated_single_association(name)
        end
      end
    end

    private

    def deprecated_single_association(name)
      single_method_name_to_association_method_name_hash = {'association' => 'reader',
                                                            'association=' => 'writer'}.freeze

      deprecated_association(name, single_method_name_to_association_method_name_hash)
    end

    def deprecated_collection_association(name)
      collection_method_name_to_association_method_name_hash = {'collection' => 'reader',
                                                                'collection=' => 'writer',
                                                                'collection_singular_ids' => 'ids_reader',
                                                                'collection_singular_ids=' => 'ids_writer'}.freeze

      deprecated_association(name, collection_method_name_to_association_method_name_hash)
    end

    def deprecated_association(_name, _method_hash)
      name = _name.to_s

      _method_hash.keys.each do |_method_name|
        method_name = _method_name.gsub(/association|collection_singular/, name.singularize).gsub(/collection/, name.pluralize)
        association_method_name = _method_hash[_method_name]

        # 元のメソッドは利用不可にする
        alias_method "orig_#{method_name}".to_sym , method_name.to_sym
        private "orig_#{method_name}".to_sym

        # 元のメソッド名で新たなメソッドを定義
        define_method(method_name) do |*args|
          ActiveSupport::Deprecation.warn "#{name} association is being removed", caller(2,1) if Rails.env != 'test'
          association(_name).send(association_method_name.to_sym ,*args)
        end
      end
    end
  end
end
```

このモジュールをDeprecatedとしたい関連付けのあるクラスにincludeし、`deprecated_assosiations :hoges`のように記述すると、その関連付けが使用された時に警告がコンソールに表示されます。

```ruby:user.rb
class User < ActiveRecord::Base
  has_one :foo
  has_many :bars

  include Deprecatable
  deprecated_associations :foo, :bars
end
```


```bash:console
[1] pry(main)> User.first.foo
DEPRECATION WARNING: foo association is being removed. (called from eval at 使用しているパス )
# => foo
### 中略 ###
[2] pry(main)> User.first.bars
DEPRECATION WARNING: bars association is being removed. (called from eval at 使用しているパス )
# => bars
### 後略 ###
```

# まだ時間がある人向けの解説

## 重要キーワード

- ActiveRecord::Reflectionクラス
- ActiveRecord::Associationsクラス
- メタプログラミング（define_methodメソッド、privateメソッド）

ActiveRecord::ReflectionクラスとActiveRecord::Associationsについてはこちらの記事がよくまとまっていて、非常に参考になりました。ありがとうございます。
[Railsのコードを読む アソシエーションについて](http://qiita.com/kkyouhei/items/067d5bb8d79c71f1646b)

## 動作の流れ

1. deprecated_associationsメソッドの引数で与えられた名称の関連付けが`collection? == true`かを見てメソッドを振り分ける
2. メソッドの対応表を用意する
3. 既存のメソッドを置き換える

以下で各項の解説を行います。

# `collection? == true`かを見てメソッドを振り分ける

`self.reflections[:name]`で`:name`の関連付けの情報を取得できます。


#懸念点
- カバーしていないメソッドが大いにありそう

#あとがき

- 例えば `has_one :foo, deprecated: true` のように関連付けメソッドのオプションに`deprecated: true`みたいなオプションを付けられる様にしたかった
  - そうなると元のコードに手を加える必要がありそうな気しかしない
  - ~~Contributorになるしかねぇ！~~