LoginSignup
54
64

More than 3 years have passed since last update.

【Rails】accepts_nested_attributes_forを使って子モデルも一緒に保存する

Last updated at Posted at 2019-11-17

こんばんは!スージーです。
個人アプリの中で勉強になった部分があったので備忘録として。

accepts_nested_attributes_for

参考:Rails API
https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

userが投稿する時に関連付けられたモデルも一括保存できます。

DB設計

Usersモデル

Column Type Options
email varchar null: false
nickname text null: false
profile text null: false
image text

アソシエーション

  • has_many :posts, dependent: :destroy
  • has_many :meals, dependent: :destroy

Postsモデル

Column Type Options
name varchar null: false
text text null: false
image text
elevation integer
walking_distance integer
difficulty integer
user_id integer null: false

アソシエーション

  • belongs_to :user
  • has_one :meal, dependent: :destroy

Mealsモデル

Column Type Options
name varchar null: false
image text
food_stuff text
cooking_time integer null: false
cooking_method integer
post_id integer null: false
user_id integer null: false

アソシエーション

belongs_to :post

モデルの説明をすると、ユーザーは投稿(post)で登った山の情報を登録します。
name=山の名前
text=説明
image=画像
elevation=山頂標高
walking_distance=歩行距離
difficulty=難易度

同時にnested modelの山メシ(meal)も登録します。
name=メシの名前
image=画像
cooking_time=調理時間
food_stuff=使う食材
cooking_method=調理方法

以上を入力してsubmitすればPostモデルとMealモデルが同時に保存されるのがaccepts_nested_attributes_forです。

実装

models/post.rb
class Post < ApplicationRecord
 belongs_to :user
 has_one    :meal,  dependent: :destroy
 accepts_nested_attributes_for :meal
 #他省略
end
posts_controller.rb
class PostsController < ApplicationController
#必要な部分のみ抜粋
 def new
  @post = Post.new
  @post.build_meal
 end

 def create
  @post = Post.create(post_params)
  if @post.save
    flash[:success] = "投稿が完了しました!"
    redirect_to :root
  else
    flash.now[:alert] = "投稿が失敗しました。"
    render :new:
  end
 end

 private

 def post_params
  params.require(:post).permit(
      :name,
      :text,
      :image,
      :elevation,
      :walking_distance,
      :difficulty,
      :prefecture_id,
      :remove_image,
      meal_attributes: [:id,
                        :name,
                        :image,
                        :cooking_time,
                        :food_stuff,
                        :cooking_method,
                        :user_id]
    ).merge(user_id: current_user.id)
 end
end

いつもと違うのは、
@post.build_meal
とparams内の
meal_attributes: [:id~以下省略]
です。
nested modelの許可するパラメーターの値はparams内でmeal_attributes:を使って記述します

new.html.erb
<%= form_for @post do |f| %>
 <%= f.label :山の名前 %>
 <%= f.text_field :name %>
 ・
 ・
 <%= f.label :難易度 %>
 <%= f.select :difficulty, Post.difficulties_i18n.invert %>
 <%#以下省略%>

 <%= f.fields_for :meal do |s| %>
  <%= s.label :山メシの名前 %>
  <%= s.text_field :name %>
   ・
   ・
   <%#以下省略%>
 <% end %>
 <%= f.submit %>
<% end %>

form_for @post do |f|内にf.field_for :meal do |s|をネストさせてあげます。

参照
Action View フォームヘルパー
https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

これで無事postモデルとmealモデルは一緒に保存されるようになりました

@user.mealのアソシエーションを組みたい

無事にDBに保存されるようになりましたが、今のままではpostモデルmealモデルはアソシエーションを組めていますが、userモデルmealモデルのアソシエーションが組めていません。

現状はuser_idは親モデルのpostモデルに外部キーとして保存される。しかし子モデルのmealモデルに保存されるのは親モデルpostのpost_idとなります。(has_one :mealbelongs_to :postの主従関係の為)

コンソールで確認

$ rails console
[1] pry(main)> user = User.find(1)
  User Load (0.5ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User id: 1, email: "user1@example.com", created_at: "2019-11-17 07:52:29", updated_at: "2019-11-17 07:52:29", nickname: "ニックネーム1", profile: "宜しくお願いします!", image: "ユーザー.jpeg">
[2] pry(main)> user.posts
=> [#<Post:0x00007fefdaab89d0
  id: 1,
  name: "テスト山1",
  text: "楽しい山登り!",
  image: "利尻山.jpeg",
  elevation: 871,
  walking_distance: 11,
  difficulty: "normal",
  created_at: Sun, 17 Nov 2019 07:52:30 UTC +00:00,
  updated_at: Sun, 17 Nov 2019 07:52:30 UTC +00:00,
  user_id: 1,
  likes_count: 3,
  prefecture_id: 11>,
 ・
 ・
~以下省略~
[3] pry(main)> user.meals
NoMethodError: undefined method `meals' for #<User:0x00007fefd6cc5ba0>
from /Users/username/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activemodel-5.2.3/lib/active_model/attribute_methods.rb:430:in `method_missing'

mealモデルにuser_idが保存されないとuserとアソシエーションが組めずusers#showページ等で描写する事ができません。

解決策 hidden_fieldを使う

良い方法か分からないがhidden_fieldを使ってuser_idcurrent_user.idにすればいけるんじゃないかと思い付きました。

new.html.erb
<%= form_for @post do |f| %>
 <%= f.label :山の名前 %>
 <%= f.text_field :name %>
 ・
 ・
 <%= f.label :難易度 %>
 <%= f.select :difficulty, Post.difficulties_i18n.invert %>
 <%#以下省略%>

 <%= f.fields_for :meal do |s| %>
  <%#s.hidden_fieldを追加%>
  <%= s.hidden_field :user_id, value: current_user.id %>
  <%= s.label :山メシの名前 %>
  <%= s.text_field :name %>
   ・
   ・
   <%#以下省略%>
 <% end %>
 <%= f.submit %>
<% end %>

もう一度コンソールで確認してみる

$ rails console
[1] pry(main)> user = User.find(1)
  User Load (0.5ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User id: 1, email: "user1@example.com", created_at: "2019-11-17 07:52:29", updated_at: "2019-11-17 07:52:29", nickname: "ニックネーム1", profile: "宜しくお願いします!", image: "ユーザー.jpeg">
[2]pry(main)> user.meals
=> [#<Meal:0x00007fefdb2bb4c0
  id: 15,
  name: "カレー",
  image: nil,
  cooking_time: 11,
  food_stuff: "にんじん。じゃがいも",
  cooking_method: "煮込む",
  created_at: Sun, 17 Nov 2019 08:23:08 UTC +00:00,
  updated_at: Sun, 17 Nov 2019 08:23:08 UTC +00:00,
  post_id: 170,
  user_id: 1>,
~以下省略~

値が取れてる

ビューに表示する

users_controller.rb
class UsersController < ApplicationController
 def show
  @user = User.find(params[:id])
  @meals = @user.meals
  @posts = @user.posts
 end
 #他省略
end
users/show.html.erb
<% if @user.posts.any? || @user.meals.any? %>
 <% @posts.each do |post| %>
  <%= post.name %>
  <%= post.walking_distance %>
  <%#他省略%>
 <% end %>
 <% @meals.each do |meal| %>
  <%= meal.name %>
  調理時間:<%= meal.cooking_time %><%#他省略%>
 <% end %>
<% else %>
 まだ投稿はありません。
<% end %>

いけた

スクリーンショット 2019-11-17 20.08.32.png

まとめ

正しいやり方なのかは不明ですが、nested modelの使い方が理解できたのでまとめてみました。

54
64
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
64