コード日進月歩

しんくうの技術的な小話、メモ、つれづれ、など

RailsのActiveRecordのcreate_xxxは元からrelationがあるレコードがあっても新規で追加されるので気をつける

当たり前といえば当たり前なんだけど、冪等性保つ動きを期待しちゃう側面もあるのでメモ

環境

$ bin/rails -v
Rails 5.2.2

概要

relationを行っているモデルに対して create_{{モデル名}} というメソッドを実行すると連動したレコードが作れるが実行するたびに作られてしまうので注意

今回使うモデル

# == Schema Information
#
# Table name: users
#
#  id         :bigint(8)        not null, primary key
#  name       :string(255)
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class User < ApplicationRecord
  has_many :message
end
# == Schema Information
#
# Table name: messages
#
#  id         :bigint(8)        not null, primary key
#  name       :string(255)
#  user_id    :integer
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class Message < ApplicationRecord
  belongs_to :user
end

実際のコード

Message と一緒に User を作りたい場合にbuild_xxxを使うと以下のように作ることができる

message = Message.new
#=> #<Message id: nil, name: nil, user_id: nil, created_at: nil, updated_at: nil>
message.build_user
#=> #<User id: nil, name: nil, created_at: nil, updated_at: nil>
message.save!
# (0.2ms)  BEGIN
#            User Create (0.3ms)  INSERT INTO `users` (`created_at`, `updated_at`) VALUES ('2018-12-23 16:58:57', '2018-12-23 16:58:57')
#            Message Create (0.2ms)  INSERT INTO `messages` (`user_id`, `created_at`, `updated_at`) VALUES (3, '2018-12-23 16:58:57', '2018-12-23 16:58:57')
#            (1.9ms)  COMMIT
#            => true

create_xxxも使うと同様に作ることができるが、冪等性があるわけではないので呼ぶたびに新しくレコードがつくられてしまう。

message.create_user
#   (0.1ms)  BEGIN
#  User Create (0.9ms)  INSERT INTO `users` (`created_at`, `updated_at`) VALUES ('2018-12-23 17:02:05', '2018-12-23 17:02:05')
#   (0.5ms)  COMMIT
#=> #<User id: 4, name: nil, created_at: "2018-12-23 17:02:05", updated_at: "2018-12-23 17:02:05">
message.create_user
#   (0.1ms)  BEGIN
#  User Create (2.0ms)  INSERT INTO `users` (`created_at`, `updated_at`) VALUES ('2018-12-23 17:02:06', '2018-12-23 17:02:06')
#   (0.4ms)  COMMIT
#=> #<User id: 5, name: nil, created_at: "2018-12-23 17:02:06", updated_at: "2018-12-23 17:02:06">
message.create_user
#   (0.1ms)  BEGIN
#  User Create (0.2ms)  INSERT INTO `users` (`created_at`, `updated_at`) VALUES ('2018-12-23 17:02:11', '2018-12-23 17:02:11')
#   (1.5ms)  COMMIT
#=> #<User id: 6, name: nil, created_at: "2018-12-23 17:02:11", updated_at: "2018-12-23 17:02:11">

既にある場合には作られたくない、などをするときは

message.create_user if message.user.nil?

のようにしてあげないといけないので注意。