当たり前かもしれないけど、これでハマったので自分への教訓として
この記事の内容
-
has_many
関連で、1側
のモデルに紐ついた多側
をnew
した後に、1側
をsave!
した時の挙動
結論
model.assertions.new
した後に model
をsave
するときはassertions
もまとめて保存されるから気をつけよう。(便利だけど)
環境
適当な関連
モデルのコード達
app/models/*.rb
class Post < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
end
やっていくぞい
has_many
関連で、 1側
のモデルに紐ついた 多側
を new
した後に、1側
をsave!
した時の挙動
まずは準備.rb
User.create(name: 'haito')
(0.1ms) begin transaction
SQL (0.8ms) INSERT INTO "users" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", "2015-10-06 07:03:05.166165"], ["name", "haito"], ["updated_at", "2015-10-06 07:03:05.166165"]]
(0.8ms) commit transaction
=> #<User id: 1, name: "haito", created_at: "2015-10-06 07:03:05", updated_at: "2015-10-06 07:03:05">
user = User.first
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> #<User id: 1, name: "haito", created_at: "2015-10-06 07:03:05", updated_at: "2015-10-06 07:03:05">
関連を確認.rb
user.posts
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ? [["user_id", 1]]
=> []
関連に対して(new|build)を実行.rb
user.posts.new(text: '万策尽きた〜')
=> #<Post id: nil, text: "万策尽きた〜", user_id: 1, created_at: nil, updated_at: nil>
user.posts
=> [#<Post id: nil, text: "万策尽きた〜", user_id: 1, created_at: nil, updated_at: nil>]
user.posts.new(text: 'どんどんどーなっつどーんといこー!')
=> #<Post id: nil, text: "どんどんどーなっつどーんといこー!", user_id: 1, created_at: nil, updated_at: nil>
user.posts
=> [#<Post id: nil, text: "万策尽きた〜", user_id: 1, created_at: nil, updated_at: nil>, #<Post id: nil, text: "どんどんどーなっつどーんといこー!", user_id: 1, created_at: nil, updated_at: nil>]
この時点で user
インスタンスに対して Post
モデルのインスタンスが紐付いているのが分かる。
ちょっと確認.rb
Post.all
Post Load (0.1ms) SELECT "posts".* FROM "posts"
=> []
しかし、まだ Post
モデルはまだ一つもDBに保存されていない
では保存.rb
user.save!
(0.1ms) begin transaction
SQL (0.3ms) INSERT INTO "posts" ("created_at", "text", "updated_at", "user_id") VALUES (?, ?, ?, ?) [["created_at", "2015-10-06 07:08:44.255076"], ["text", "万策尽きた〜"], ["updated_at", "2015-10-06 07:08:44.255076"], ["user_id", 1]]
SQL (0.1ms) INSERT INTO "posts" ("created_at", "text", "updated_at", "user_id") VALUES (?, ?, ?, ?) [["created_at", "2015-10-06 07:08:44.257063"], ["text", "どんどんどーなっつどーんといこー!"], ["updated_at", "2015-10-06 07:08:44.257063"], ["user_id", 1]]
(1.5ms) commit transaction
=> true
Post.all
Post Load (0.2ms) SELECT "posts".* FROM "posts"
=> [#<Post id: 1, text: "万策尽きた〜", user_id: 1, created_at: "2015-10-06 07:08:44", updated_at: "2015-10-06 07:08:44">,
#<Post id: 2, text: "どんどんどーなっつどーんといこー!", user_id: 1, created_at: "2015-10-06 07:08:44", updated_at: "2015-10-06 07:08:44">]
お〜 保存されましたね。1側
の User
モデルへのクエリは発行されていませんが、Postへのクエリはガンガン発行されるようです
ではちょっと別の視点から作ってみます。
Postモデルをnewしてから作成.rb
user = User.first
=> "省略"
post = Post.new(text: '上手くいかないことを人のせいにしているようなヤツは辞めちまえよ!')
=> #<Post id: nil, text: "上手くいかないことを人のせいにしているようなヤツは辞めちまえよ!", user_id: nil, created_at: nil, updated_at: nil>
post.save!
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "posts" ("created_at", "text", "updated_at") VALUES (?, ?, ?) [["created_at", "2015-10-06 07:14:06.612594"], ["text", "上手くいかないことを人のせいにしているようなヤツは辞めちまえよ!"], ["updated_at", "2015-10-06 07:14:06.612594"]]
(1.4ms) commit transaction
=> true
もちろん、ユーザーに紐ついていない状態なので保存されない。こいつを工夫してみる
Postインスタンスをごにょごにょ.rb
user.posts.push(post)
(0.1ms) begin transaction
SQL (0.3ms) UPDATE "posts" SET "updated_at" = ?, "user_id" = ? WHERE "posts"."id" = 3 [["updated_at", "2015-10-06 07:15:16.168755"], ["user_id", 1]]
(1.4ms) commit transaction
=> [#<Post id: 1, text: "万策尽きた〜", user_id: 1, created_at: "2015-10-06 07:08:44", updated_at: "2015-10-06 07:08:44">,
#<Post id: 2, text: "どんどんどーなっつどーんといこー!", user_id: 1, created_at: "2015-10-06 07:08:44", updated_at: "2015-10-06 07:08:44">,
#<Post id: 3, text: "上手くいかないことを人のせいにしているようなヤツは辞めちまえよ!", user_id: 1, created_at: "2015-10-06 07:14:06", updated_at: "2015-10-06 07:15:16">]
ary#push
は破壊的変更が発生するので、その時点でpost
インスタンスがuser
インスタンスに紐ついたので、post.user_id
が更新され、DBに保存されます。
fmfm
なるほどなぁ。
まとめ
関連モデルはもちろん便利だけれど、user.posts.new
とかそういうのは気をつけて使ったほうが良さそう。特に user
に紐ついたインスタンスまでまとめて保存されるのは、忘れないようにするために工夫は必要ですね。