難問!undefined method `id'
アプリの仕様
- キャンプの記録をするアプリです
- ユーザーはキャンプのタイトルとキャンプスタイルという名のタグをつけて投稿します。
- キャンプタイトルとタグを同時に保存するためにフォームオブジェクトを使用しています。
- ユーザーは自分の持っているアイテムを登録できます。
- キャンプには複数のアイテムを持っていける。アイテムは何度でもキャンプに持っていけることからキャンプとアイテムは多対多の関係です。
- キャンプ記録を作成する際持って行ったアイテムの登録を実現させたい
エラー内容
undefined method `id' for #<CampTags:0x00007ff6085ac980>
Request
Parameters:
{"authenticity_token"=>"6h75fWC/+zhPx96ZoRyir+kyevpfqLqawP75tlOKrGHOXHlfKtMlG0qvpQMLgrjc5Ld3x1bvduvrtllnJPZagw==",
"camp_tags"=>{"title"=>"bboobb", "style"=>"無骨キャンプ", "item_ids"=>["1", "2"]},
"commit"=>"Send"}
- @camp.idを取り出したいがidがないと出る
状況
- @item_idsをeachにかけることでitemのidを一つずつ取り出すのは成功しています。
- campのデータについてはtagと同時に作成されるためFormオブジェクトで保存
- itemに関しては保存してあったものをselectによって選択する
- campとtagの情報については登録保存は成功している
- @camp_id = 3と直球にidを指定してやるとitemとcampの関連付けはできた
- @campを取り出せれば解決する
- @campを指定すると:camp_tagsからidを取り出そうとするため@camp.idが無いとエラーになる
@campを確認するとidは保存されているようです。
>> @camp
=> #<CampTags:0x00007ff6085ac980 @errors=#<ActiveModel::Errors:0x00007ff6085ac250 @base=#<CampTags:0x00007ff6085ac980 ...>, @messages={}, @details={}>, @title="bboobb", @style="無骨キャンプ", @item_ids=["1", "2"], @user_id=1, @validation_context=nil, @camp=#<Camp id: 36, user_id: 1, title: "bboobb", created_at: "2021-03-17 06:28:53", updated_at: "2021-03-17 06:28:53">, @tag_id=#<Tag id: 4, style: "無骨キャンプ", created_at: "2021-03-16 09:04:46", updated_at: "2021-03-16 09:04:46">>
>>
該当するコード
コントローラー
camps_controller.rb
class CampsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_item, only: [:index, :new, :show, :create]
def index
@tags = Tag.all
end
def new
@camp = CampTags.new
end
def create
@camp = CampTags.new(camp_params)
if @camp.valid?
@tag_list = camp_params[:style].split(/[[:blank:]]+/).select(&:present?)
@camp.save(@tag_list)
@camp_id = @camp.id
@item_ids = @camp.item_ids
@item_ids.each do |item_id|
CampItemRelation.create(camp_id: @camp_id, item_id: item_id)
end
return redirect_to root_path
else
render "new"
end
end
private
def camp_params
params.require(:camp_tags).permit(:title, :style, item_ids: []).merge(user_id: current_user.id)
end
def set_item
if user_signed_in?
user = User.find(current_user.id)
@items = user.items
end
end
end
formオブジェクト
camp_tags.rb
class CampTags
include ActiveModel::Model
attr_accessor :title, :style, :user_id, :item_ids
with_options presence: true do
validates :title
validates :style
end
def save(tag_list)
@camp = Camp.create(user_id: user_id, title: title)
tag_list.each do |tag|
unless Tag.find_by(style: tag)
@tag = Tag.create(style: tag)
CampTagRelation.create(camp_id: @camp.id, tag_id: @tag.id)
else
@tag_id = Tag.find_by(style: tag)
CampTagRelation.create(camp_id: @camp.id, tag_id: @tag_id.id)
end
end
end
end
入力フォーム
new.html.erb
<div class="wrapper">
<div class="side-ber">
<%= render "side_ber" %>
</div>
<%= form_with model: @camp, url: camps_path, class:'form-wrap', local: true do |f| %>
<div class='form'>
<div class="title-field">
<%= f.label :title, "キャンプタイトル" %>
<%= f.text_area :title, class:"input-title" %>
</div>
<div class="tag-field", id='tag-field'>
<%= f.label :style, "キャンプスタイル" %>
<%= f.text_field :style, class:"input-tag" %>
</div>
<p>使用アイテムを選択 </p>
<select name="camp_tags[item_ids][]" multiple>
<% @items.each do |item| %>
<option value=<%= item.id %>><%= item.name %></option>
<% end %>
</select>
</div>
<div class="submit-post">
<%= f.submit "Send", class: "submit-btn" %>
</div>
<% end %>
</div>
ルーティング
routes.rb
Rails.application.routes.draw do
root to: "camps#index"
resources :camps, only: [:new, :create, :show]
resources :items, only: [:new, :create, :show]
devise_for :users
end
テーブル同士のアソシエーション
item.rb
class Item < ApplicationRecord
extend ActiveHash::Associations::ActiveRecordExtensions
belongs_to :genre
belongs_to :user
has_many :camp_item_relations
has_many :camps, through: :camp_item_relations
with_options presence: true do
validates :genre_id, numericality: { other_than: 1, message: 'Select'}
validates :name
validates :feature
validates :price, numericality: { with: /\A[0-9]+\z/, message: 'Half-width number' }
end
end
camp.rb
class Camp < ApplicationRecord
belongs_to :user
has_many :camp_tag_relations
has_many :tags, through: :camp_tag_relations
has_many :camp_item_relations
has_many :items, through: :camp_item_relations
validates :title, presence: true
end
itemとcampの中間テーブルのモデル
camp_item_relations.rb
class CampItemRelation < ApplicationRecord
belongs_to :camp
belongs_to :item
end
試したこと
コントローラーではitem_idsを扱える
フォームオブジェクトではcamp.idを扱えることから
CampItemRelation.createでのcampとitemのidの保存を別々で行ってみた。
最初にフォームオブジェクトで
CampItemRelation.create(camp_id: @camp.id)
として
次にコントローラーで
CampItemRelation.create(item_id: item_id)
こうすることでデータを扱えるうちに保存できると考えましたが、データはどうやら同時に保存しないといけないらしくこれらの記述は読み飛ばされたようでした。
camps_controller.rb
class CampsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_item, only: [:index, :new, :show, :create]
def index
@tags = Tag.all
end
def new
@camp = CampTags.new
end
def create
@camp = CampTags.new(camp_params)
if @camp.valid?
@tag_list = camp_params[:style].split(/[[:blank:]]+/).select(&:present?)
@camp.save(@tag_list)
@item_ids = @camp.item_ids
@item_ids.each do |item_id|
CampItemRelation.create(item_id: item_id)
end
return redirect_to root_path
else
render "new"
end
end
private
def camp_params
params.require(:camp_tags).permit(:title, :style, item_ids: []).merge(user_id: current_user.id)
end
def set_item
if user_signed_in?
user = User.find(current_user.id)
@items = user.items
end
end
end
camp_tags.rb
class CampTags
include ActiveModel::Model
attr_accessor :title, :style, :user_id, :item_ids
with_options presence: true do
validates :title
validates :style
end
def save(tag_list)
@camp = Camp.create(user_id: user_id, title: title)
CampItemRelation.create(camp_id: @camp.id)
tag_list.each do |tag|
unless Tag.find_by(style: tag)
@tag = Tag.create(style: tag)
CampTagRelation.create(camp_id: @camp.id, tag_id: @tag.id)
else
@tag_id = Tag.find_by(style: tag)
CampTagRelation.create(camp_id: @camp.id, tag_id: @tag_id.id)
end
end
end
end
データはどうやったら取り出せるのでしょうか
どうかご教授ください!
0