アソシエーション先のテーブル保存(rails)

インスタンスを保存(.save)した時、いったいどこまでの情報が保存されると思いますか?
私はそのインスタンスの元となったモデルのテーブルだけが更新されると思ってました。

でも実際はアソシエーション先の情報まで更新されちゃうのです。
そう宣言した覚えもないのに情報が保存できてビックリしたので書き残しておきます。

中間テーブルのデータってどうやって保存するの?

groupモデルとuserモデルをhas_many throughでアソシエーションを組んでいます。
そして以下のようなアクションを定義しました。

def new
  @group = Group.new
  @group.users << current_user
end

def create
  @group = Group.new(create_params)
  @group.save
end

private
def create_params
  params.require(:group).permit(:name, user_ids: [])
end

checkbox等を使ってuser_idsを引っ張ってきて新規グループを保存する感じです。
わざわざ多対多のモデルにしたのは、中間テーブルってどうやって保存するんだろう?って疑問があったからです。

groupモデルなら@group.saveで保存したらいいし、userモデルなら@user.saveとか@group.user.saveとかで明示的に保存できます。
でも@group_user.saveって使わないじゃないですか?
これってどうやって保存しているんだろうってのがそもそもの疑問です。

save直前でpryで止めて確認すると、

@group
=> #<Group:0x00007ffb6f749268 id: nil, name: "sgggg", created_at: nil, updated_at: nil>

@group.users
=> [#<User id: 1, email: "aaa@aaa", name: "hogehoge">,
 #<User id: 2, email: "fdg@dfg", name: "fugafufa">]

ちゃんとgroupとuserが連携しています。
これを@group.saveしたらgroupのテーブルのみならず、group_userテーブルにも保存されます!

一体なぜ???保存するように指示した覚えはないけど。
@groupは保存しろと書いたけど、group_userについては何も書いてないよ?

意図しない挙動をしたので更に深掘ってみます。
@group.users[1][:name] = “hoge”のような処理を加えてアソシエーション先のuserテーブルの内容を更新してみました。
さて@group.saveを行うとどうなるでしょう?

なんとuserテーブルまでも更新されました!!!
@group.save でuserテーブルが更新されるんですよ!これはもはや事件です!
機械のくせに勝手なことすんじゃねーよ!!

中間テーブルの保存方法を探していたら、連携先の情報まで書き換えてしまうことが分かりました。
この仕様はどうなんだ?っと思ってrails guideを読み込むと、ありましたよ。

If you set the :autosave option to true, Rails will save any loaded association members and destroy members that are marked for destruction whenever you save the parent object. Setting :autosave to false is not the same as not setting the :autosave option. If the :autosave option is not present, then new associated objects will be saved, but updated associated objects will not be saved.

Active Record Associations — Ruby on Rails Guides
ActiveRecordAssociationsThisguidecoverstheassociationfeaturesofActiveRecord.Afterreadingthisguide,youwillknowhowto:Understandthevarioustypesofassociations.Decla...

オートセーブについて記載がない場合は、アソシエーション先はnewなら保存するが、updateはnot be savedと書かれてあるぞ。これなら納得の仕様だ。そうだろう、そうあるべきだろう。勝手に情報を書き換えられたら困るからな。

え?でもあれ?おかしいな。保存されたんだが?なんでだろう?
 → 検証中

話が脱線しちゃいましたが、newであればwill be savedと書かれてますので、アソシエーション先を新規作成した場合には基本的に親オブジェクトを保存した際に子オブジェクトも保存されるようです。なので@group.saveで明示しなくてもgroup_userテーブルにも保存されたんですね。

コメント