#背景
ある日のぼく「ここのhas_manyとかもういらないよね〜!それに今は1:1なここの関係は、1:多にしないと今後ヤバそうだよな〜!よーし、変えるか〜!!!!」
(この後、数時間に及ぶ試行錯誤を行う)
ぼく「この関連、使用箇所多すぎ!!テスト通らねぇ!!寿命で死ぬ!!こんなもんに時間かけてられっか!!!!(ブチギレ)」
(自分ができるだけ手を動かさずに済む方法を考える)
ぼく「せや!もう使って欲しくない関連付けを使ってる場所で『この関連付けはそのうち消す(Deprecated)』ということを皆に知らせて実装を変えてもらおう!レッツ人海戦術!」
というわけで、タイトルのことをやってみました。
#やったこと
##時間がない人向け
こういうmoduleを作成しました。
# 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
のように記述すると、その関連付けが使用された時に警告がコンソールに表示されます。
class User < ActiveRecord::Base
has_one :foo
has_many :bars
include Deprecatable
deprecated_associations :foo, :bars
end
[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のコードを読む アソシエーションについて
動作の流れ
- deprecated_associationsメソッドの引数で与えられた名称の関連付けが
collection? == true
かを見てメソッドを振り分ける - メソッドの対応表を用意する
- 既存のメソッドを置き換える
以下で各項の解説を行います。
collection? == true
かを見てメソッドを振り分ける
self.reflections[:name]
で:name
の関連付けの情報を取得できます。
#懸念点
- カバーしていないメソッドが大いにありそう
#あとがき
- 例えば
has_one :foo, deprecated: true
のように関連付けメソッドのオプションにdeprecated: true
みたいなオプションを付けられる様にしたかった- そうなると元のコードに手を加える必要がありそうな気しかしない
Contributorになるしかねぇ!