Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What is going on with this article?
@jkr_2255

複雑な動作をするRailsのコールバックを書きたい場合に

More than 3 years have passed since last update.

RailsのActiveRecordには、特定のタイミングでコールバックを実行する仕組みがあります。とはいえ、複雑な処理を行おうとすると少し悩むことになるかもしれません。

コールバックとは

詳細はRails Guideに譲りますが、before_save/after_saveなど、ActiveRecordが特定の動作をした段階で動くルーチンを仕掛けることができます。

コールバックとして設定できるもの

コールバックはbefore_save 動作というような形で定義しますが、この「動作」に指定できるものも何種類かあります。なお、Railsのコールバックは内部的にActiveSupport::Callbacks詳細)を使っているので、指定できるものはこれに準じます。

ブロック、proc、lambda

「ルーチン」でいちばんイメージしやすいものですが、これらはinstance_evalでそのインスタンスのコンテキストで実行されます1。比較的わかりやすいとは思いますが、複雑な処理を書くには向かない書式です。

  • メリット: ごくかんたんな処理を行う場合、シンプルに書ける
  • デメリット:複雑なものを書くには向かない
before_validation do
  # 中身は省略
end

シンボル

シンボルを渡した場合、インスタンスに対してシンボルの名前のメソッドが実行されます。

  • メリット:モデルのインスタンス変数も使える
  • デメリット:コールバック専用のコードだった場合、インスタンスにメソッドとして持たせるのはじゃまになる
before_validation :convert_name

その他のオブジェクト

その他のオブジェクトを渡すと、そのオブジェクトの、コールバック名と同じメソッドを、モデルインスタンスを引数にして呼び出します。コールバックを別なところへ分離できて便利です。

コールバックオブジェクトのインスタンスについて

この形で指定する場合、after_save SomeObject.newのようにも書けますが、クラス内に書くことからもわかるように、インスタンスはモデル全体で共通ですので、引数指定でインスタンスを作り分ける、というような状況でなければわざわざインスタンスを作る意味もそこまで大きくありません。そのメソッドをモジュールメソッドとして実装して、Moduleオブジェクトそのものを指定する、という手段もありです。

インスタンス変数を引き回したい!

ただ、モジュールメソッドで実装してしまうと、CallBackModule.after_xxx(model)のような形で呼ばれることとなります。下請けメソッドが必要な規模のコールバックを書こうとすれば、モデル内の値を参照するのに、いちいちmodelを引き回す必要が出てきてしまいます。

これでは不便ですし、モデル以外にインスタンス変数やメソッドを持たせても便利ですので、「外から見ればクラスメソッド、内部処理はインスタンスを作ってそこで処理」というような形で書けば、使う方も中身も比較的わかりやすくなります。

class SomeCallback
  # インスタンス変数の準備
  def initialize(model)
    @model = model
    @some_children = @model.some_children
  end

  # インスタンス変数を使って作業
  def all_children?
    @some_children.all? do
      # 略
    end
  end

  # メインルーチン
  def after_save
    some_value = all_children ? @model.some_value : 5
    # 後略
  end

  # フロントエンド用メソッド
  def self.after_save(model)
    new(model).after_save
  end

  # 外部から直接newはしない
  private_class_method :new
end


# モデル側
after_save SomeCallback

  1. 文字列もinstance_evalされますが、これは非推奨とのことです。 

3
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
jkr_2255
qiitadon
Qiitadon(β)から生まれた Qiita ユーザー・コミュニティです。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
3
Help us understand the problem. What is going on with this article?