Active Recordのアソシエーションにおける :inverse_of の役割
:inverse_of
オプションの役割がよくわからなかったので調べてみた。
Railsガイドを読むと、:inverse_of
オプションを指定することで、双方向関連付けを明示的に宣言できることがわかる。
通常の関連付けは、双方向で設定します。2つのモデルの両方に関連を定義する必要があります。
という前提の上、例にあるAuthor
モデルとBook
モデルを単純に一対多で関連付けているような場合は、2つのモデルが双方向の関連を共有していることをActive Recordが自動的に認識してくれるらしい。
ただし、:through
オプションで中間テーブルを利用する場合や、:foreign_key
オプションで外部キーの名前を指定する場合など、双方関連付けが自動認識されないケースがあるとのこと。
そこで、Active Recordが双方向の関連付けを認識できるようにするには、:inverse_of
オプションを使う。
:inverse_of
の指定によって挙動が変わる例
APIドキュメントの説明では、多対多の関係において、:inverse_of
オプションの設定によりsave
メソッドの挙動が異なる場合について書かれている。
ということで、実際に以下の関連付けを試してみた。
class Post < ApplicationRecord
has_many :taggings
has_many :tags, through: :taggings
end
class Tagging < ApplicationRecord
belongs_to :post
belongs_to :tag, inverse_of: :taggings
end
class Tag < ApplicationRecord
has_many :taggings
has_many :posts, through: :taggings
end
The last line ought to save the through record (a Tagging). This will only work if the :inverse_of is set
APIドキュメントに書かれている通り、belongs_to
の側で逆方向の関連付け(tag
から見たtaggings
への関連)を明示することで、tag
の新規登録時に、taggings
への登録も同時に行われるようになる。
Running via Spring preloader in process 443
Loading development environment (Rails 6.1.3.1)
irb(main):001:0> post = Post.first
(1.5ms) SELECT sqlite_version(*)
Post Load (0.3ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT ? [["LIMIT", 1]]
=>
#<Post:0x00007f97195ec998
...
irb(main):002:0> tag = post.tags.build name: 'ruby'
=> #<Tag:0x00007f971093a158 id: nil, name: "ruby", created_at: nil, updated_at: nil>
irb(main):003:0> tag.save
TRANSACTION (0.1ms) begin transaction
Tag Create (0.6ms) INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "ruby"], ["created_at", "2022-01-24 08:53:36.394773"], ["updated_at", "2022-01-24 08:53:36.394773"]]
TRANSACTION (1.8ms) commit transaction
=> true
一方、:inverse_of
オプションを指定しなければ、逆方向の関連は認識されない。
そのため、post
に対してtag
を新規で登録する際に、taggings
への登録は行われない。
class Tagging < ApplicationRecord
belongs_to :post
belongs_to :tag
end
Running via Spring preloader in process 957
Loading development environment (Rails 6.1.3.1)
irb(main):001:0> post = Post.first
(1.8ms) SELECT sqlite_version(*)
Post Load (0.2ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT ? [["LIMIT", 1]]
=>
#<Post:0x00007f97195455f8
...
irb(main):002:0> tag = post.tags.build name: 'ruby'
=> #<Tag:0x00007f97196b6518 id: nil, name: "ruby", created_at: nil, updated_at: nil>
irb(main):003:0> tag.save
TRANSACTION (0.1ms) begin transaction
Tag Create (0.5ms) INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "ruby"], ["created_at", "2022-01-24 09:00:56.115941"], ["updated_at", "2022-01-24 09:00:56.115941"]]
TRANSACTION (1.4ms) commit transaction
=> true
taggings
への登録は別で行いたい場合など、敢えて無効にすることもあるのかもしれない。
参考リンク
Discussion
@pofkuma さん、こんにちは。
とありますが、その下の実行結果には
tag.save
したあとにINSERT INTO "taggings"
が表示されていないので、説明と矛盾しているように見えます。もしかして貼り付けるログを間違えたりしていないでしょうか?ご確認よろしくお願いします🙏