Edited at

【Rails】検索機能、プルダウン選択によるソート機能


はじめに

 勉強の一環としてフリーマーケットサイトのコピーを作成しています。

 その際に導入した検索機能ソート機能について上手く実装できたので紹介します。

 コードが冗長となっている箇所や置き換えることのできるメソッドなど多くあるかと思いますので、その時はご指摘やご指導いただけると幸いです。

 ※Rails5.0を使用しているため、 form_tagを使用しています。


導入したい機能と挙動


  • 検索機能

  • 出品日時や価格によるソート機能

  • プルダウンを選択後にページ遷移する機能

  • ソート後のプルダウンの固定

( ※以下はダミーデータです。商品ではありません。)


使用ファイル


  • header.html.haml

  • search.html.haml

  • products_controller.rb

  • products_search.js


検索機能について


検索フォームビュー


_header.html.haml

= form_tag('/search', method: :get, class: 'search-form') do

%input.search-form__input{name: "keyword", placeholder: "何をお探しですか?", type: "text", value: ""}
%button.btn-search{title: "検索", type: "submit"}
= fa_icon "search",class: "search-form__icon"

 submitされた時に送られる値であるname属性をkeywordとすることで、コントローラで params[:keyword]により値を受け取れます。

(ここではproductsコントローラー)


検索結果とプルダウン選択ビュー


search.html.haml

.l-side

.search-sort
.select-wrap
%select{name: :sort_order, class: 'sort_order'}
%option{value: "location.pathname", name: "location.pathname"}
並び替え
%option{value: "price_asc"}
価格の安い順
%option{value: "price_desc"}
価格の高い順
%option{value: "created_at_asc"}
出品の古い順
%option{value: "created_at_desc"}
出品の新しい順

 プルダウン選択後の値をjQueryで取得できるように、optionタグのvalue属性にそれぞれ値を記述します。

 商品検索結果の記述は省略します。


出品日時や価格によるソート機能


productsコントローラー


products_controller.rb

def search

@keyword = params[:keyword]
# orderメソッドへ代入する値を条件分岐
# params[:sort].nil? ? sort = "created_at DESC" : sort = params[:sort]をリファクタリング
sort = params[:sort] || "created_at DESC"
# 入力された値をLIKE句により各カラムと一致したものを抽出する。
@products = Product.where('name LIKE(?) OR description LIKE(?)', "%#{@keyword}%", "%#{@keyword}%").order(sort)
@count = @products.count
# 検索結果が"0"だった場合、全ての商品を表示させる
if @count == 0
@products = Product.order(sort)
end
end

 products変数末尾のorderメソッドには、選択したオプションタグのvalue属性を参照して送られてきた値によってソートするように、if文で条件分岐します。params[:sort]の初期値はnilとなるため、nilには投稿新着順で表記するためにcreatecd_at DESCを代入します。


jQuery


products_search.js

$(function() {

// プルダウンメニューを選択することでイベントが発生
$('select[name=sort_order]').change(function() {

// 選択したoptionタグのvalue属性を取得する
var this_value = $(this).val();
// value属性の値により、ページ遷移先の分岐
if (this_value == "price_asc") {
html = "&sort=price+asc"
} else if (this_value == "price_desc") {
html = "&sort=price+desc"
} else if (this_value == "created_at_asc") {
html = "&sort=created_at+asc"
} else if (this_value == "created_at_desc") {
html = "&sort=created_at+desc"
} else {
html = ""
};
// 現在の表示ページ
var current_html = window.location.href;
// ソート機能の重複防止
if (location['href'].match(/&sort=*.+/) != null) {
var remove = location['href'].match(/&sort=*.+/)[0]
var current_html = current_html.replace(remove, '')
};
// ページ遷移
window.location.href = current_html + html
});
// ページ遷移後の挙動
$(function () {
if (location['href'].match(/&sort=*.+/) != null) {
// option[selected: 'selected']を削除
if ($('select option[selected=selected]')) {
$('select option:first').prop('selected', false);
}

var selected_option = location['href'].match(/&sort=*.+/)[0].replace('&sort=', '');

if(selected_option == "price+asc") {
var sort = 1
} else if (selected_option == "price+desc") {
var sort = 2
} else if (selected_option == "created_at+asc") {
var sort = 3
} else if (selected_option == "created_at+desc") {
var sort = 4
}

var add_selected = $('select[name=sort_order]').children()[sort]
$(add_selected).attr('selected', true)
}
});
});



URLについて

 URLに&sort=price+ascと記述することで、コントローラーのparams[:sort]price ascが送られ、該当テーブルのpriceカラムを昇順(ASC, ascending)に表示します。

http://localhost:3000/search?utf8=✔︎&keyword=ねこ&sort=price+asc

 したがって、optionタグのvalue属性の値により、該当する値を取得します。


products_search.js

if (this_value == "price_asc") {

html = "&sort=price+asc"
} else if (this_value == "price_desc") {
html = "&sort=price+desc"
} else if (this_value == "created_at_asc") {
html = "&sort=created_at+asc"
} else if (this_value == "created_at_desc") {
html = "&sort=created_at+desc"
} else {
html = ""
};

 window.location.hrefメソッドによりページを遷移することができるため、if文の戻り値を現在のURLへ加えることで表示している商品データを、希望の条件にソートすることができました。

window.location.href = window.location.href + html

 2度以上オプションタグを選択すると、以下のように重複して記述してしまいます。

URL省略)~keyword=ねこ&sort=price+asc&sort=price+desc&sort=price+asc

 location['href']により現在のURLを取得して、正規表現により最初に該当した&sort=とそれ以降の文字列を取得し、replaceメソッドにより無に返しました。

 また、最初期のURLには該当文字列がないため、エラー回避をif文で行います。


products_search.js

if (location['href'].match(/&sort=*.+/) != null) {

var remove = location['href'].match(/&sort=*.+/)[0]
var current_html = current_html.replace(remove, '')
};


ここまでの記述でプルダウンによるソートは完了しました。

しかし、ソートするたびにページを更新するため、optionタグが初期値である並び替えに戻ってしまいます。

そのため、ページ遷移後にoptionタグへ


selected属性を付与させることで選択肢をあらかじめ選択済みにさせます。


ソート後のプルダウンの固定

  $(function ()により、ページが読み込み完了してDOMの構築が完了した時点でfunction()内が実行されます。現在の表示ページURLに&sort=があった場合のみイベントを実行させます。


products_search.js

 // ページ遷移後の挙動

$(function () {
// 表示URLに"&sort="がある場合のみ実行
if (location['href'].match(/&sort=*.+/) != null) {
// 現在のoptionタグにselected属性が与えられている場合、削除する。
if ($('select option[selected=selected]')) {
$('select option:first').prop('selected', false);
}
// 表示ページURLの"/&sort="以下記述を取得して、replaceメソッドにより"/&sort="を無に返します。
var selected_option = location['href'].match(/&sort=*.+/)[0].replace('&sort=', '');
// 上記で取得した値により条件分岐する。
if(selected_option == "price+asc") {
var sort = 1
} else if (selected_option == "price+desc") {
var sort = 2
} else if (selected_option == "created_at+asc") {
var sort = 3
} else if (selected_option == "created_at+desc") {
var sort = 4
}
// selectタグ内のoptionタグをchildreメソッドで取得すると配列で返されるため、
// 上記でif文で取得した値を、代入すると、選択したオプションタグを取得することができる。
var add_selected = $('select[name=sort_order]').children()[sort]
// 選択したオプションタグへpropメソッドにより、seletcted属性を付与する。
$(add_selected).prop('selected', true)
}
});


上の記述により、選択したプルダウンを固定することができました!!


あとがき

 この機能の実装を考えている間はとても楽しく過ごすことができました。

 エンジニアの皆様からすると、記述が冗長な箇所やそもそも活用されていない記述が見受けられるかもしれません。その際は、ご指摘やご指導いただけますと幸いでございます。よろしくお願いします。


参考文献