motoki6318
@motoki6318 (元木 啓介)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

難問!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

1Answer

CampTagCamp が存在していて,
Camp のオブジェクトをid 指定して取り出したい.

今,@camp には,CampTag オブジェクトが入っているので,
@camp.save した後なら,CampTagsave した @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">>

#<CampTags:xxxx
  @camp=#<Camp id: 36,
  >
>

例えば,CampTag@camp への read_accessor を作れば一旦は目的を達成できますか?

0Like

Comments

  1. @motoki6318

    Questioner

    回答感謝します!
    この問題に関しては自己解決できています。
    解決方法としては```@camp.save(@tag_list)```の下に```camp = Camp.order(updated_at: :desc).limit(1)```と記述することで保存された最新のレコードを一つだけ取り出して```@camp_id = camp.ids```と変数に入れてあげて最後に```CampItemRelation.create(camp_id: @camp_id[0], item_id: item_id)```としてかんれんづけをおこないました。
    貴重な意見ありがとうございました!

Your answer might help someone💌