Railsでレコードの新規追加をする際に、自動的に独自IDの付与をしたいケースがあると思います。
例えば、my_idフィールドの最大値+1を付与するなど。
部屋(room)の名前を登録したときに、独自id = my_id を付与するサンプルを書いてみました。
成功例
def assign
my_id = -1
room = Room.find_by(room: params[:room])
if !room.nil?
my_id = room.my_id
else
Room.connection.execute(
"INSERT INTO rooms (my_id, room, created_at, updated_at) SELECT COALESCE(max(my_id), 0)+1, '#{params[:room]}', '#{Time.now}', '#{Time.now}' from rooms"
)
my_id = Room.find_by(roomr: params[:room]).my_id
end
redirect_to("/rooms/#{my_id}")
end
ポイントは
- my_idの最大値検索をINSERT文のサブクエリで行い、一回のクエリ発行になるようにすること (排他)
- created_at, update_atに現在時刻を設定すること
- COALESCEでnull対策をすること
失敗例
def assign
my_id = -1
room = Room.find_by(room: params[:room])
if !room.nil?
my_id = room.my_id
else
new_room = Room.create(
my_id: Room.max(my_id) + 1,
room: params[:room]
)
my_id = new_room.my_id
end
redirect_to("/rooms/#{my_id}")
end
失敗例のほうでは、begin transaction ~ commit transaction の間に
- SELECT文 --> Room.max(my_id)の検索のため
- INSERT文 --> Room.createのため
が発行されるのですが、
複数のクライアントから同時にassignを呼ばれた際に排他が十分でなく
my_idが重複してしまいます
本当は
本当は生SQLは発行せずにActiveRecordで記述したいのですが、
いい方法ないですかね?
参考URL
INSERT時にカラムの最大値+1を持ってくる
INSERT文と同時にMAX+1を行いたいです。
INSERT時にデータ登録とmaxの発番がしたい
ActiveRecord の find_or_create_by を確実に実行するには