アプリの仕様
- キャンプの記録をするアプリです
- ユーザーはキャンプのタイトルとキャンプスタイルという名のタグをつけて投稿します。
- キャンプタイトルとタグを同時に保存するためにフォームオブジェクトを使用しています。
- ユーザーは自分の持っているアイテムを登録できます。
- キャンプには複数のアイテムを持っていける。アイテムは何度でもキャンプに持っていけることからキャンプとアイテムは多対多の関係です。
- キャンプ記録を作成する際持って行ったアイテムの登録を実現させたい
発生したエラー
undefined method `id' for #<CampTags:0x00007ff6085ac980>
Request
Parameters:
{"authenticity_token"=>"6h75fWC/+zhPx96ZoRyir+kyevpfqLqawP75tlOKrGHOXHlfKtMlG0qvpQMLgrjc5Ld3x1bvduvrtllnJPZagw==",
"camp_tags"=>{"title"=>"bboobb", "style"=>"無骨キャンプ", "item_ids"=>["1", "2"]},
"commit"=>"Send"}
状況
- @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">>
>>
該当するコード
コントローラー
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オブジェクト
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
入力フォーム
<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>
ルーティング
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
テーブル同士のアソシエーション
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
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の中間テーブル
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)
こうすることでデータを扱えるうちに保存できると考えましたが、データはどうやら同時に保存しないといけないらしくこれらの記述は読み飛ばされたようでした。
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
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
試したこと2
save処理後すぐに保存された最新のレコードを取り出す
def create
@camp = CampTags.new(camp_params)
if @camp.valid?
@tag_list = camp_params[:style].split(/[[:blank:]]+/).select(&:present?)
@camp.save(@tag_list)
camp = Camp.order(updated_at: :desc).limit(1)
@camp_id = camp.ids
@item_ids = @camp.item_ids
binding.pry
@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
camp = Camp.order(updated_at: :desc).limit(1)
とすることで最新のレコードを一個取り出しています。
答えまで後少し!!!
たどり着いた回答
def create
@camp = CampTags.new(camp_params)
if @camp.valid?
@tag_list = camp_params[:style].split(/[[:blank:]]+/).select(&:present?)
@camp.save(@tag_list)
camp = Camp.order(updated_at: :desc).limit(1)
@camp_id = camp.ids
@item_ids = @camp.item_ids
@item_ids.each do |item_id|
CampItemRelation.create(camp_id: @camp_id[0], item_id: item_id)
end
return redirect_to root_path
else
render "new"
end
end
saveメソッド後すぐに最新のレコードを取り出してそれを変数に入れる
order
で最新のレコードを一つしか取り出していないがidの取り出しにはids
としなければならないようです。
保存の際に@camp_id[0]
とすることで配列の一つ目のデータを指定することが叶っています。
以上