読んで欲しい人
- acts_as_list gemの仕組みを知りたい人へ
-
acts_as_list
メソッドを追加するとなぜゆえ色々な処理をよしなにやってくれているのか?気になる人 - 過去というか、絶賛気になっている自分へ
環境
- Rails8
- Ruby.3.3.6
やること
acts_as_list gemで提供されているメソッドがどんな仕組みになっているかを探索しようと思っています
特にacts_as_list
が気になっているので、そのコードを読んでいこうかなと
使い方はまとめたけど、仕組みは分からずじまいだったので
ネタもないし
長いので、シリーズ的に出してきます
acts_as_list
メソッド
早速、acts_as_list
が定義されている場所へ
optionsについて確認
まぁ長いので最初のコメントアウトを確認します
module ClassMethods
# Configuration options are:
#
# * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
# * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
# (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
# to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
# Example: <tt>acts_as_list scope: 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
# * +top_of_list+ - defines the integer used for the top of the list. Defaults to 1. Use 0 to make the collection
# act more like an array in its indexing.
# * +add_new_at+ - specifies whether objects get added to the :top or :bottom of the list. (default: +bottom+)
# `nil` will result in new items not being added to the list on create.
# * +sequential_updates+ - specifies whether insert_at should update objects positions during shuffling
# one by one to respect position column unique not null constraint.
# Defaults to true if position column has unique index, otherwise false.
# If constraint is <tt>deferrable initially deferred<tt>, overriding it with false will speed up insert_at.
# * +touch_on_update+ - configuration to disable the update of the model timestamps when the positions are updated.
オプションについての説明すね
-
column
: 位置を保持するために使用するカラム名を指定(デフォルト:position
) -
scope
: リストとして考慮する範囲を制限。シンボルを指定すると、_id
が追加されていない場合は追加され、それを外部キーの制限として使用します。より厳密な範囲が必要な場合は、文字列全体を指定することも可能 例:acts_as_list scope: 'todo_list_id = #{todo_list_id} AND completed = 0'
-
top_of_list
: リストの先頭として使用される整数を定義。デフォルトは1。0を使用すると、コレクションが配列のインデックスのように動作 -
add_new_at
: オブジェクトがリストのtop
またはbottom
に追加されるかを指定(デフォルト:bottom
)nil
を指定すると、新しいアイテムは作成時にリストに追加されない -
sequential_updates
:insert_at
が位置カラムの一意の非NULL制約を尊重するために、オブジェクトの位置を一つずつ更新するかどうかを指定。位置カラムに一意のインデックスがある場合はデフォルトでtrue
、そうでない場合はfalse
。制約がdeferrable initially deferred
の場合、false
に設定するとinsert_at
の速度が早くなる -
touch_on_update
: 位置が更新されたときにモデルのタイムスタンプを更新しないようにする設定
なんでacts_as_list
メソッドを定義すると、新たらしいレコードを作成した時に最後に良い感じに追加してくれるのだろう?と思っていましたがここでオプションがbottom
で指定されたんだ〜
とか思いつつacts_as_list
を見に行きます
acts_as_list
メソッド
def acts_as_list(options = {})
configuration = { column: "position", scope: "1 = 1", top_of_list: 1, add_new_at: :bottom, touch_on_update: true }
configuration.update(options) if options.is_a?(Hash)
caller_class = self
ActiveRecord::Acts::List::PositionColumnMethodDefiner.call(caller_class, configuration[:column], configuration[:touch_on_update])
ActiveRecord::Acts::List::ScopeMethodDefiner.call(caller_class, configuration[:scope])
ActiveRecord::Acts::List::TopOfListMethodDefiner.call(caller_class, configuration[:top_of_list])
ActiveRecord::Acts::List::AddNewAtMethodDefiner.call(caller_class, configuration[:add_new_at])
ActiveRecord::Acts::List::AuxMethodDefiner.call(caller_class)
ActiveRecord::Acts::List::CallbackDefiner.call(caller_class, configuration[:add_new_at])
ActiveRecord::Acts::List::SequentialUpdatesMethodDefiner.call(caller_class, configuration[:column], configuration[:sequential_updates])
include ActiveRecord::Acts::List::InstanceMethods
include ActiveRecord::Acts::List::NoUpdate
end
configuration
にディフォルトの値を設定、その後、ユーザーから受け取ったoption
でディフォルトの値を更新します。
何も指定しなかったら、ディフォルト値のまま
次にcaller_class
にself
を代入しているんですが、ここでいう自分のオブジェクトはなんなのか?
まずここがよくわからないので調査します
selfのオブジェクトについて
RailsではClassMethods
モジュール(特定のクラスにクラスメソッドを追加するためのモジュール)なるものがよく使用されるみたいです
詳しくは触れませんが、acts_as_list
メソッドを使いたいクラス内でacts_as_list
を定義してあげると、そのクラスのクラスメソッドになるというスーパー便利なことをやっているらしいです
ということで、self
には、acts_as_list
を定義したオブジェクトが含まれています。
今後調査するもの
1つ1つ深掘りしていくとかなり時間がかかりそうなのでscope
だったり、position
はどんな仕組みで実装されているのかを見て行こうと思います