toshizou-Rails

複数モデルに同時にレコードを作成する(accepts_nested_attributes_forで1対1 ~has_one, belongs_to~ を扱う)

複数モデルに同時登録するときはaccepts_nested_attributes_forが便利。
ですが、ちょこちょこ実装するのにミスがあり、半日以上かかってしまった(m m) エラーが起きた原因と解決策をざっくりとまとめました。

TD:TR;

開発環境

ざっくり備忘録にする理由

  • いろんな記事を参考にしてもそれとは違うエラー原因だった
  • エラー解決まで非常に時間を要したため

ざっくりとした全体像

前準備

アプリ作成、DB作成

# gemのインストールする場所を指定
bundle config set path 'vendor/bundle';
# railsをインストール
gem install -v 5.1.7 rails
# railsアプリを作成
rails _5.1.7_ new tmp_app --database=mysql;
# DB作成
rails db:create
# userモデルの作成
rails g scaffold User name:string;
# birthモデルの作成
rails g scaffold Birth area:string;
# migrationの実行
rails db:migrate



先に完成後のコード一覧

  • ざっくりと大事そうなとこだけ載せてる
  • フォームはUserモデル側で作成してある
  • 以下は省略
    • viewファイル(index, create, show, update, destroy)
    • BirthモデルにUserの外部キーを追加すること

app/models/user.rb

class User < ApplicationRecord
  has_one: birth, inverse_of: user
  accepts_nested_attributes_for :birth, update_only: true
end

app/models/birth.rb

class Birth < ApplicationRecord
  beglongs_to: user, inverse_of: birth
end

app/views/users/new.html.erb

<%= form_with(model: @user, url: user_path, local: true) do |form| %>
  <%= render 'user_form', {form: form , user: @user} %>
  <div>
    <%= form.submit %>
  </div>
<% end %>

app/views/users/_user_form.html.erb

    <div class="form-group">
      <%= form.label :name %>
      <%= form.text_field :name, id: :user_name %>
    </div>
  
    <%= form.fields_for :birth do |d| %>
    <%# エリア %>
    <div class="form-group">
      <%= d.label :area %>
      <%= d.text_field :area %>
    </div>
  
    <% end %>

app/controllers/users_controllers.rb

class UsersController < ApplicationController
  before_action :set_user, only: [:edit, :update]
  def index
    @users = User.all
  end

  def new
    @user = User.new
    @user.build_birth
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to users_path, flash: { notice: "作成されました" }
    else
      render 'new'
    end
  end

  def edit
  end

  def update
    if @user.update(user_params)
      redirect_to users_path, flash: { notice: "変更されました" }
    else
      render 'edit'
    end
  end

  private
  def set_user
    @user = User.find(params[:id])
  end

  def user_params
    params.require(:user).permit(:name, birth_attributes: [:area])
  end
end

エラーとその解決法

問題点1(エラー ActiveModel::UnknownAttributeError - unknown attribute 'xxx_id' for yyy.)

# app/controllers/users_controllers.rb
:
def new
  @user = User.new
  # ↓↓↓ ここでエラーが発生
  @user.build_birth
end
:

問題点1の原因と解決法

  • DB設計が原因(Birthに外部キーをつけ忘れる ~user_id~)
  • Birthモデルに外部キー(user_id)を追加する(migrationファイルで編集・修正する)ことでエラーが解消される(newアクションが表示される)

作成時(bad)

user Birth
id id
name area

修正後(good)

user Birth
id id
name area
user_id



問題点2(エラー ActiveRecord::RecordNotSaved - Failed to remove the existing associated nutrient. The record failed to save after its foreign key was set to nil)

問題点2の原因と解決法

  • ただしエラーの原因はeditアクション
    • newアクションではbuild_yyyメソッドは問題ないが、editアクションでは必要ない(-> 削除する)
# app/controllers/users_controllers.rb
:
def edit
  # ↓↓↓ ここでエラーが発生(必要なかった)
  @user.build_birth
end
:
  • モデルでinverse_ofを上手いこと定義すると、userインスタンスに関連するbirthレコードを取得してきてくれる
  • たぶんこれで大丈夫
  • app/models/user.rb
class User < ApplicationRecord
  has_one: birth, inverse_of: user
  accepts_nested_attributes_for :birth, update_only: true
end
  • app/models/birth.rb
class Birth < ApplicationRecord
  beglongs_to: user, inverse_of: birth
end


<br>
<br>



 以上!!