はじめに
某プログラミングスクールで、フリマアプリのクローンサイトを作成中につまづきました。
内容としては、出品のタイミングでカテゴリを紐づけて投稿したいけど、その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がハブられてるじゃないのっ!!
おめぇの席ねぇから。状態です。確かに、これじゃ登録できない。
きっとこの子がしっかり皆に受け入れられればうまくいくはず。
# 解決前の実装
解決する前の状態です。
商品登録画面で、親カテゴリを選択すると、子カテゴリのプルダウンを表示。
さらに、子カテゴリを選択すると、孫カテゴリのプルダウンが表示されるように非同期通信で対応しています。
ここまで頑張ったんだし良い感じに登録してくれないものかね( ꇐ₃ꇐ )
##解決前のソースコード
itemコントローラー
結論、ここに問題はなかったです。
#〜〜〜省略〜〜〜
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ファイル
結論、この子がいじめっこの犯人でした。
$(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ファイル
この子もなんも悪い子ではなかったです。
最初から疑ってなかったよ。うん。
-# ~~~省略~~~
.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が悪さをしてたんです。
// 子カテゴリーの表示作成
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はこんな感じ↓
// 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の要素の一つとして認識してくれるようです。
正直推測です。ですので、良い子の皆はこの記事を鵜呑みにしないようにしましょう(汗
もし、解説していただける方いたらコメント頂けますと幸いです・・・!
ちゃんとitem{ }の中に入れてもらえました。
やっぱり仲良くするのが一番です。
# さいごに
(しっかり理屈で理解しきれていないのに)記事にして大丈夫なのかな。という内容でした・・・。
有識者の方が解説してくださる事を願っています。
最後までお付き合いくださりありがとうございました!