ルビーオンレイルザーの皆さん、 Fat モデル対策やってますか。 Fat モデル対策と言えば Concern ですね。 app/models/concerns/ ディレクトリに module を置いてモデルに include させるというアレです。
しかしただ module を作って Fat モデルのコードを移動し、元のモデル側に include させるだけでは結局モデルのインスタンスに生えるメソッドの数に変わりはないので臭いものに蓋をしてるだけになります。 Rubocop の Metrics/LineLength 警告を逃れるためだけの module 乱立はあんまり意味がないでしょう。間違って別の module で同名のメソッドを定義してしまい意図しない挙動になってしまうことも考えられます。
最近自分がやってるのは、 include される module に定義するメソッドはせいぜい一つか二つにして、このメソッドから別クラス( Plain Old Ruby Object)に定義したメソッドを呼び出す(委譲する)というものです。モデルに得体の知れないメソッドが増えないので便利。
例えば以下のようなモデルがあるとします。
app/models/entry.rbapp/models/comment.rb
両方ともレコードの新規登録があったときに通知を行いたい。共通の処理なので Notifiable モジュールを作ってそれを Entry モデルと Comment モデルでそれぞれ include しましょう。ここまでは皆さんよくやると思います。
app/models/concerns/notifiable.rb
module Notifiable
private
def notify
# do something
end
endclass Entry < ApplicationRecord
include Notifiable
has_many :comments
after_commit :notify, on: :create
endclass Comment < ApplicationRecord
include Notifiable
belongs_to :entry
after_commit :notify, on: :create
endしかし Entry と Comment では通知内容が異なるので単純に #notify メソッドを callback で実行すればよいというわけではない。通知用パラメーターを生成する処理をモデルに書くとよいのですが、そういうのを繰り返した結果が Fat モデル地獄なので通知内容を生成するクラスを別に作ります。こんな感じ。
app/models/concerns/notifiable/entry_notification.rbapp/models/concerns/notifiable/comment_notification.rb
Base クラスを作って共通処理をまとめ、継承させると便利でしょう。
app/models/concerns/notifications/base_notification.rb
module Notifiable
class BaseNotification
def initialize(object)
@object = object
end
def perform
NotificationJob.perform(notification_params)
end
end
endmodule Notifiable
class EntryNotification < BaseNotification
def notification_params
{
recipient_ids: @object.subscriber_ids,
title: @object.title
}
end
end
endmodule Notifiable
class CommentNotification < BaseNotification
def notification_params
{
recipient_ids: @object.thread_joiner_ids,
title: @object.body.truncate(25)
}
end
end
endNotifiable モジュールはこんな感じになります。
module Notifiable
private
def notifiy
notification.perform
end
def notification
"#{self.class}Notification".constantize.new(self)
end
end図にするとこんな感じ。
この Notifiable モジュールを include しても Model には #notify メソッドと #notification メソッドしか追加されず、通知処理の実装をモデルから分離することができます。 Entry クラスも Comment クラスも #notify メソッドより先のことは何も気にしなくてよくなる。リソースが追加されたときに #notify メソッドを実行することだけに責任を持てばよいし、通知を飛ばすという処理自体は Notifiable::EntryNotification と Notifiable::CommentNotification クラスの責任になります。
私はこれで Fat モデルのコードを concerns ディレクトリにしまい込んで臭いものに蓋をするような対応におさらばしました。よろしければお試し下さい。またもし他に良い方法をご存じであれば教えて下さい。