概要
Engineerモデルと EngineerHiringモデルを has_one , belongs_toで関連づけて、accept_nested_attributes_forでまとめて更新しようとしている、
EngineerのsaveでEngineerHiringまで、保存することは出来たけど、そのupdateでタイトルのエラーが出た
原因
正確なエラーメッセージは
ActiveRecord::RecordNotSaved (Failed to remove the existing associated engineer_hiring. The record failed to save after its foreign key was set to nil.):
で、has_oneが指定されていた 更新の際に EngineerHiringのidが指定されていなかったため、関連づけをnullにしようとして上記エラーを出していた。模様です
解決方法
has_one :engineer_hirirengをする際に、dependent: :destroy、もしくは accept_nested_atributes_forでupdate_only: trueを宣言する
技術的な仕様を確認する
( https://api.rubyonrails.org/ より)
- has_one(name, scope=nil, **options)
- 他方のクラスが外部キーを持っているときに利用可能。こちらのクラスの方に外部キーがある場合は belongs_toを使う必要がある
- scopeを指定して関連づけることも出来る
- Optionsとして
- :dependent
- オーナーが破棄されたときに関連しているオブジェクトに何をするのか定義する
- :destroy 関連づけられたオブジェクトを破棄する
- その他 :delete, :nullifyとか
- オーナーが破棄されたときに関連しているオブジェクトに何をするのか定義する
- その他、:foreing_key、:class_nameとか
- :dependent
- accepts_nested_attributes_for(*attr_names)
- 関連づけの属性の書き込みについて定義する
- Optionとして
- update_only
- 1対1の関連について、関連づけデータが既に存在しているときにその属性をどの様に使うかを決める。デフォルトは false で、その場合 :id が指定されている場合は既存のデータが更新される。でなければ別のデータで置き換えられる。trueが設定されている場合は常にupdateされる
- update_only
状況の解説
(詳細は省略して紹介)
schema
schema.rb
create_table "engineers", comment: "技術者", force: :cascade do |t|
t.bigint "person_info_id", comment: "技術者個人情報"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["person_info_id"], name: "index_engineers_on_person_info_id"
end
create_table "engineer_hirings", comment: "技術者雇用所属(派遣元)", force: :cascade do |t|
t.bigint "engineer_id"
t.string "hiring_memo", comment: "技術者雇用メモ"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["engineer_id"], name: "index_engineer_hirings_on_engineer_id"
end
create_table "person_infos", force: :cascade do |t|
t.string "first_name"
t.string "middle_name"
t.string "last_name"
t.string "kana_first_name"
t.string "kana_middle_name"
t.string "kana_last_name"
t.integer "user_status", default: 1, comment: "{0:無効, 1:有効}"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
model
engineer.rb
class Engineer < ApplicationRecord
has_one :engineer_hiring, autosave: true
belongs_to :person_info, autosave: true
accepts_nested_attributes_for :engineer_hiring, update_only: true
accepts_nested_attributes_for :person_info
def self.parameters(param_hash,key)
param_hash.require(key).permit(
:person_info_attributes => [
:last_name,:first_name,:middle_name,
:kana_last_name,:kana_first_name, :kana_middle_name
],
:engineer_hiring_attributes => [
:hiring_memo
],
)
end
controller
engineer_controller.rb
def new
@engineer=Engineer.new
end
def create
@engineer=Engineer.new
save_engineer(params)
redirect_to action: "edit", id: @engineer.id
end
def edit
@engineer = Engineer.find(params[:id])
render action: "new"
end
def update
@engineer = Engineer.find(params[:id])
save_engineer(params)
redirect_to action: "edit", id: params[:id]
end
def save_engineer(params)
Engineer.transaction do
@engineer.attributes = Engineer.parameters(params, :engineer)
@engineer.save!
end
end