LoginSignup
0
3

More than 3 years have passed since last update.

【Rails】商品出品時にカテゴリテーブルのIDと紐づける方法

Posted at

はじめに

某プログラミングスクールで、フリマアプリのクローンサイトを作成中につまづきました。
内容としては、出品のタイミングでカテゴリを紐づけて投稿したいけど、そのidがうまく機能しないといったもの。困ったもんだ。

やりたいこと

商品を登録するitemテーブルに、categoryテーブルの紐付けを行いたい。
categoryテーブルはancestryというgemを使って作成しています。
ざっくり言うと、categoryテーブルの中で、親カテゴリ、子カテゴリ孫カテゴリが管理されてます。

テーブルの構成としてはこんな感じ。
ちょっとわかりにくいかも

nameカラム ancestryカラム
親カテゴリ メンズ、レディースなど null
子カテゴリ アウター、ボトムスなど 1、2など
孫カテゴリ Tシャツ、スラックスなど 1/y、2/yなど

ancestryカラムがnullのものが親カテゴリ
ancestryカラムが1だったり2だったりするものが子カテゴリ
ancestryカラム が1/1だったり、1/2だったり、2/1だったり、2/2だったりするのが孫カテゴリ
こんな風に管理しています。

現状

非同期通信でカテゴリーの選択肢は表示できている。
けど・・・送られてきたデータみたらなんかitemのハッシュからcategory_idがハブられてるじゃないのっ!!
おめぇの席ねぇから。状態です。確かに、これじゃ登録できない。
スクリーンショット 2020-04-14 19.08.22.png

きっとこの子がしっかり皆に受け入れられればうまくいくはず。

 解決前の実装

解決する前の状態です。
商品登録画面で、親カテゴリを選択すると、子カテゴリのプルダウンを表示。
さらに、子カテゴリを選択すると、孫カテゴリのプルダウンが表示されるように非同期通信で対応しています。
ここまで頑張ったんだし良い感じに登録してくれないものかね( ꇐ₃ꇐ )

解決前のソースコード

itemコントローラー

結論、ここに問題はなかったです。

item_controller.rb
#〜〜〜省略〜〜〜
  def create
    @item = Item.new(item_params)
    if @item.save!
      # 商品の投稿に成功したらindexに飛ばす処理
      redirect_to root_path
    else
      # 商品の投稿に失敗したらnewアクションを再度実行new.html.hamlを表示
      render :new
    end
  end
#〜〜〜中略〜〜〜
  # 親カテゴリーが選択された後に動くアクション
  def get_category_children
    #選択された親カテゴリーに紐付く子カテゴリーの配列を取得
    @category_children = Category.find_by(name: "#{params[:parent_name]}", ancestry: nil).children
  end

  # 子カテゴリーが選択された後に動くアクション。 ajaxからハッシュで子要素のIDを受け取る{child_id: childId}
  def get_category_grandchildren
    #選択された子カテゴリーに紐付く孫カテゴリーの配列を取得
    @category_grandchildren = Category.find("#{params[:child_id]}").children
    binding.pry
  end

  private

  #プライベートメソッドにしたいので、private配下に記述
  def item_params
    binding.pry
    params.require(:item).permit(:product_name, :price, :condition,:description, :delivery_fee, :shipping_origin, :days_to_ship,:buyer_id, images_attributes: [:image]).merge(user_id: current_user.id, seller_id: current_user.id)
  end
end

ソースコード上に、binding.pryが書かれているあたり、すごい検証しまくった形跡が残ってますね。
恥ずかしい(笑

jsファイル

結論、この子がいじめっこの犯人でした。

category.js
$(document).ready(function() {

  // カテゴリーセレクトボックスのオプションを作成
  function appendOption(category){
    var html = `<option value="${category.name}" data-category="${category.id}">${category.name}</option>`;
    return html;
  }

  // 子カテゴリーの表示作成
  function appendChidrenBox(insertHTML){
    var childSelectHtml = '';
    childSelectHtml = `<div id="children_wrapper">
                        <select class= 'SellPage__Information__Box__Inner__Form', id= 'child_category' name="category_id">
                          <option value="---" data-category="---">---</option>
                          ${insertHTML}
                      </div>`;
    $('#PullDownCategory').append(childSelectHtml);
  }

    // 孫カテゴリーの表示作成
    function appendGrandchidrenBox(insertHTML){
      var grandchildSelectHtml = '';
      grandchildSelectHtml = `<div id="grandchildren_wrapper">
                          <select class= 'SellPage__Information__Box__Inner__Form', id= 'grandchild_category' name="category_id">
                            <option value="---" data-category="---">---</option>
                            ${insertHTML}
                        </div>`;
      $('#PullDownCategory').append(grandchildSelectHtml);
    }

    $('#parent_category').on('change', function(){
    // 変数"parentCategory"に、プルダウンで選択した値を代入
    var parentCategory = document.getElementById('parent_category').value;
    if(parentCategory != "---"){
      $.ajax({
        url: 'get_category_children',
        type: 'GET',
        data: { parent_name: parentCategory },
        dataType: 'json'
      })
      .done(function(children){
        $('#children_wrapper').remove(); //親が変更された時、子以下を削除するする
        $('#grandchildren_wrapper').remove();
        var insertHTML = '';
        children.forEach(function(child){
          insertHTML += appendOption(child);
        });
        appendChidrenBox(insertHTML);
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');
      })
    }else{
      $('#children_wrapper').remove(); //親カテゴリーが初期値になった時、子以下を削除するする
      $('#grandchildren_wrapper').remove();
    }
  });

  // 子カテゴリー選択後のイベント
  $('#PullDownCategory').on('change', '#child_category', function(){
    // カテゴリーの子要素に紐づくIDを取得して、そのIDに紐づく孫要素を取得する。
    // option:selected を指定する事で、プルダウンで選択したものの情報を取得できる事になる。
    var childId = $('#child_category option:selected').data('category');
    if (childId != "---"){
      // 自身で作成したget_category_grandchildrenのルーティングへ飛ばす。その際、プルダウンで選択されている子要素のIDも渡す。
      $.ajax({
        url: 'get_category_grandchildren',
        type: 'GET',
        data: { child_id: childId },
        dataType: 'json'
      })
      .done(function(grandchildren){
        if (grandchildren.length != 0) {
          $('#grandchildren_wrapper').remove(); //子が変更された時、孫以下を削除するする
          var insertHTML = '';
          grandchildren.forEach(function(grandchild){
            insertHTML += appendOption(grandchild);
          });
          appendGrandchidrenBox(insertHTML);
        }
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');
      })
    }else{
      $('#grandchildren_wrapper').remove();
    }
  });
});

Viewファイル

この子もなんも悪い子ではなかったです。
最初から疑ってなかったよ。うん。

new.html.haml
-# ~~~省略~~~
.SellPage__Information__Box
 .SellPage__Information__Box__About
  商品の詳細
 #PullDownCategory.SellPage__Information__Box__Inner
  .SellPage__Information__Box__Inner__SellText
   カテゴリー
  .SellPage__Information__Box__Inner__SellBox
   必須
  = f.select :category, @category_parent_array, {}, {class: 'SellPage__Information__Box__Inner__Form', id: 'parent_category'}

data-category="${category.id}を付与しているので、各選択肢のidは付与できている状態。
このdata-category="${category.id}の値をどうにかitem={}の仲間入りをさせたい。
そうすればcategory_id君はいじめから開放されるはず・・・!

解決&方法

やっぱり、jsが悪さをしていました
ソースコード書いたのお前じゃん。とか言われたら言い返せませんが、jsが悪さをしてたんです。

修正後.js
  // 子カテゴリーの表示作成
  function appendChidrenBox(insertHTML){
    var childSelectHtml = '';
    childSelectHtml = `<div id="children_wrapper">
                        <select class= 'SellPage__Information__Box__Inner__Form', id= 'child_category' name="item[category_id]">
                          <option value="---" data-category="---">---</option>
                          ${insertHTML}
                      </div>`;
    $('#PullDownCategory').append(childSelectHtml);
  }

  // 孫カテゴリーの表示作成
  function appendGrandchidrenBox(insertHTML){
    var grandchildSelectHtml = '';
    grandchildSelectHtml = `<div id="grandchildren_wrapper">
                        <select class= 'SellPage__Information__Box__Inner__Form', id= 'grandchild_category' name="item[category_id]">
                          <option value="---" data-category="---">---</option>
                          ${insertHTML}
                      </div>`;
    $('#PullDownCategory').append(grandchildSelectHtml);
  }

5行目、16行目の記述を変えています。
変更Before/Afterはこんな感じ↓

比較.js

// After
<select class= 'SellPage__Information__Box__Inner__Form', id= 'child_category' name="item[category_id]">
// Before
<select class= 'SellPage__Information__Box__Inner__Form', id= 'grandchild_category' name="category_id">

nameの記述を変更しています。
正直理解仕切れているか?というとそうではないです。
item[category_id]とすることでインスタンス変数@itemの要素の一つとして認識してくれるようです。
正直推測です。ですので、良い子の皆はこの記事を鵜呑みにしないようにしましょう(汗
もし、解説していただける方いたらコメント頂けますと幸いです・・・!

修正後のparamsの中身↓
スクリーンショット 2020-04-14 21.27.07.png

ちゃんとitem{ }の中に入れてもらえました。
やっぱり仲良くするのが一番です。

 さいごに

(しっかり理屈で理解しきれていないのに)記事にして大丈夫なのかな。という内容でした・・・。
有識者の方が解説してくださる事を願っています。

最後までお付き合いくださりありがとうございました!

0
3
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
0
3