Share
Embed
create_tableをしただけなのに〜囚われのuuid編〜
Roppongi.rb #25( https://roppongirb.connpass.com/event/338053/ )
Omotesando.rb #104( https://omotesandorb.connpass.com/event/338101/ )
の共同回をSmartHRで開催した際の会場提供企業LTです。
DaisukeShinoku
December 13, 2024
Transcript
30 slides
-
作業記録アプリケーション WorkType Work 1:N WorkLog 1 or 0 :1 or
0 class Work < ApplicationRecord belongs_to :work_type end class WorkType < ApplicationRecord has_many :works, dependent: :destroy end -
作業記録アプリケーション WorkType Work 1:N WorkLog 1 or 0 :1 or
0 def create @work = Work.new(work_params) @work.save! end def work_params params.require(:work).permit(:name, :work_type_id) end -
作業記録アプリケーション WorkType Work 1:N WorkLog 1 : 1 class WorkLog
< ApplicationRecord belongs_to :work end class Work < ApplicationRecord has_one :work_log end -
作業記録アプリケーション WorkType Work 1:N WorkLog 1 : 1 class Work
< ApplicationRecord def complete! ApplicationRecord.transaction do WorkLog.create!(completed_at: Time.zone.now, work: self) update!(completed: true) end end end -
開発する新機能 WorkType Work 1:N WorkLog 0..1 : 0..1 WorkLogType 0..1:
N 作業マスタの名称を「海岸 工事」→「港湾工事」に変 更したい! work_type.update(name: "港湾工事") 作業ログマスタ側は updateではなく name: “海岸工事”とname: “港湾工事” をそれぞれ別のレコードとして持ちたい -
マイグレーションファイル作成&実行 class CreateWorkLogTypes < ActiveRecord::Migration[7.1] def change create_table :work_log_types do
|t| t.string :name, null: false t.timestamps end add_reference :work_logs, :work_log_type, type: :uuid, null: true end end -
モデル作成 & リレーション class WorkLog < ApplicationRecord belongs_to :work_log_type, optional:
true end class WorkLogType < ApplicationRecord has_many :work_logs end -
Work#complete!(修正前) class Work < ApplicationRecord def complete! ApplicationRecord.transaction do WorkLog.create!(completed_at:
Time.zone.now, work: self) update!(completed: true) end end end -
Work#complete!(修正後) class Work < ApplicationRecord def complete!(work_type) ApplicationRecord.transaction do work_log_type
= WorkLogType.find_or_create_by(name: work_type.name) work_log = WorkLog.build(completed_at: Time.zone.now, work_log_type:, work: self) work_log.save! update!(completed: true) end end end -
データの確認 irb(main):001> WorkLogType.all WorkLogType Load (0.9ms) SELECT... => [#<WorkLogType:0x00000001230dabf0 id:
1, name: "港湾工事", created_at: ..., updated_at: ...> ] WorkLogTypeのレコードが作成されている!ヨシッ! -
データの確認 よくみたらWorkLogのwork_log_type_idがnilになっています。 これは何かがマズそうです・・・ irb(main):003> WorkLog.order(created_at: :desc).last => #<WorkLog:0x0000000124837320 id: "4f1cd8c7-3c73-4b7a-8c87-a4b3eec68e28",
work_id: "56781d31-442b-48fe-a9fe-a80e63e486d5", completed_at: ..., created_at: ..., updated_at: ..., work_log_type_id: nil> -
Work#complete!(修正後) class Work < ApplicationRecord def complete!(work_type) ApplicationRecord.transaction do work_log_type
= WorkLogType.find_or_create_by(name: work_type.name) work_log = WorkLog.build(completed_at: Time.zone.now, work_log_type:, work: self) work_log.save! update!(completed: true) end end end -
モデル作成 & リレーション class WorkLog < ApplicationRecord belongs_to :work_log_type, optional:
true end class WorkLogType < ApplicationRecord has_many :work_logs end -
マイグレーションファイル作成&実行 class CreateWorkLogTypes < ActiveRecord::Migration[7.1] def change create_table :work_log_types do
|t| t.string :name, null: false t.timestamps end add_reference :work_logs, :work_log_type, type: :uuid, null: true end end -
マイグレーションファイル作成&実行 class CreateWorkLogTypes < ActiveRecord::Migration[7.1] def change create_table :work_log_types do
|t| t.string :name, null: false t.timestamps end add_reference :work_logs, :work_log_type, type: :uuid, null: true end end -
Schemaを確認する # 主キーにuuidをできているテーブル create_table "work_types", id: :uuid, default: -> {
"gen_random_uuid()" }, force: :cascade do |t| t.string "name", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end # idがデフォルトのまま・・・ create_table "work_log_types", force: :cascade do |t| t.string "name", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end # にも関わらず関連先の外部キーはuuidにしてしまっている create_table "work_logs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.uuid "work_log_type_id" end -
外部キー制約忘れ PG::DatatypeMismatch: ERROR: foreign key constraint "fk_rails_d1282d5b36" cannot be implemented
DETAIL: Key columns "work_log_type_id" and "id" are of incompatible types: uuid and bigint. マイグレーション時に気付けそうなものだが・・・ 外部キー制約も付け忘れていて検知できなかった・・・ def change add_reference :work_logs, :work_log_type, type: :uuid, null: true, foreign_key: true end -
仕組みで解決 全テーブルの主キーがuuidであることを担保するspecを用意 →CI実行時に気付けるようになりました! describe "primary key" do it "すべてのtableのprimary keyがuuidになっており、defaultが設定されていること"
do aggregate_failures do all_tables.each do |tbl| p_key_name = AR::Base.connection.primary_key(tbl) p_key_column = AR::Base.connection.columns(tbl).find { |c| c.name == p_key_name } expect(p_key_column.sql_type).to eq("uuid"), "Table: #{tbl}" expect(p_key_column.default_function).to eq("gen_random_uuid()"), "Table: #{tbl}" end end end end -
まとめ(自戒) • 新機能作成時のcreate_tableのマイグレーションは「期待とワク ワク」を持ちながらも「慎重さ」を忘れない • 主キーbigint & 外部キーはuuid & 関係性がoptional:
true ◦ このパターンに嵌まって不具合に気付けなかった • 人間はミスする生き物なのでなるべく仕組みで解決する