今現在Railsの勉強をしてまして、Railsチュートリアルを一通り終えて作ったものをベースにちょっと改良を加えています。
今やろうとしているのは、投稿した内容にタグ付けし、タグで投稿を検索できるようにするというもの。
投稿のフォームはこんな感じ。
タグ機能はacts-as-taggable-onというgem、タグ表示のUIはBootstrap Tags InputというjQueryプラグインを使用しています。
目次
動作環境
- Ruby 2.3.0p0
- Rails 5.0.0.1
- acts-as-taggable-on 4.0.0
使用方法
こちらのブログ記事を参考にしました。
ruby-rails.hatenadiary.com
動作環境のバージョンは異なりますが問題なく使えました。
実装
こんな感じの検索フォームを用意しました。
見た目はいまいちですが。。
検索条件でタグを指定するとそのタグを使用している投稿が表示されます。
参考記事にも書かれていますが、タグでのレコード抽出方法にはいくつかあります。
検索条件に指定したタグのいずれかを含むレコードを検索
class Post < ApplicationRecord
acts_as_taggable_on :labels
acts_as_taggable
scope :find_by_tag, -> tags {
if tags.present?
tagged_with(tags, any: true)
end
}
end
class PostsController < ApplicationController
def search
@tags = params[:tags]
@posts = current_user.posts.find_by_tag(@tags)
respond_to do |format|
format.js
end
end
end
var searchResultHtml = "";
var count = 0;
<%if @posts.exists? %>
searchResultHtml = '<%=j render @posts%>';
count = <%=@posts.count%>
<%end%>
$('#post-search-result-table').html(searchResultHtml);
$('span#search-result-count').html(count.toString());
タグと関連付けたModelに対しtagged_with(tags, any: true)
というメソッドを使うことで、検索条件で指定したタグのいずれかを含むレコードが検索できます。
今回の実装ではajaxで検索結果を表示するようviewというかformで指定しているため、controllerでインスタンス変数にセットした検索結果をjs.erbで画面に反映しています。
検索条件に指定したタグすべてを含むレコードを検索
上記の実装でtagged_with(tags, any: true)
としていた個所をtagged_with(tags, match_all: true)
とします(“any"を"match_all"に変えている)。
詰まったところ
どちらの取得方法でも件数を取得するとこで詰まりました。。
検索条件に指定したタグのいずれかを含むレコードを検索した場合の件数取得
上記の実装でタグを条件に含む検索を実行すると、見事にエラー。こんなログ出ました(見やすいように改行を入れています)。
ActionView::Template::Error (SQLite3::SQLException: near "*": syntax error:
SELECT COUNT( posts.*) FROM "posts" WHERE "posts"."user_id" = ?
AND (EXISTS (SELECT 1 FROM taggings posts_taggings_7e240de
WHERE posts_taggings_7e240de.taggable_id = posts.id
AND posts_taggings_7e240de.taggable_type = 'Post'
AND posts_taggings_7e240de.tag_id in (2)
))):
おいおいまさかのSyntax error…
ぱっと見何がダメなのか分からなかったのですが、よくよく見てみると。。。最後のカッコが1つ多いような。。。
GitHubのリポジトリ見てみたら、issueに挙がってました。
.tagged_with([], any: true).count throws MySQL error · Issue #649 · mbleigh/acts-as-taggable-on · GitHub
issueはopenのままなんですが、解決方法を書いてくれてる人が。
どうやら.count
⇒.count(:all)
とすれば解消するようです。。
var searchResultHtml = "";
var count = 0;
<%if @posts.exists? %>
searchResultHtml = '<%=j render @posts%>';
count = <%=@posts.count(:all)%>
<%end%>
$('#post-search-result-table').html(searchResultHtml);
$('span#search-result-count').html(count.toString());
修正後の内容で発行されるクエリはこんな感じになりました。カッコの数が正しくなってます。あとcountの取り方が変わってますね(ここだけ見ても結果は同じですが)。
SELECT COUNT(*) FROM "posts" WHERE "posts"."user_id" = ?
AND (EXISTS (SELECT 1 FROM taggings posts_taggings_a94a8fe
WHERE posts_taggings_a94a8fe.taggable_id = posts.id
AND posts_taggings_a94a8fe.taggable_type = 'Post'
AND posts_taggings_a94a8fe.tag_id in (1)
))
検索条件に指定したタグすべてを含むレコードを検索した場合の件数取得
こちらも件数がちゃんと取得できておらず。
こちらはログに出力されたクエリがこんな感じになってました。
SELECT COUNT(*) AS count_all,
posts.id AS posts_id
FROM "posts"
JOIN taggings posts_taggings_a94a8fe
ON posts_taggings_a94a8fe.taggable_id = posts.id
AND posts_taggings_a94a8fe.taggable_type = 'Post'
AND posts_taggings_a94a8fe.tag_id = 1
LEFT OUTER JOIN taggings posts_taggings_group
ON posts_taggings_group.taggable_id = posts.id
AND posts_taggings_group.taggable_type = 'Post'
WHERE "posts"."user_id" = ?
GROUP BY posts.id
HAVING (COUNT(posts_taggings_group.taggable_id) = 1)
ORDER BY "posts"."created_at" DESC
どうもcountだけじゃなくpostのIDも取得されてしまってるようです。
結果はHashとして、{“1” => “1”,“1” => “2”}(件数 => ID)のような形で返却されます。
postのIDごとにgroup byしてるけど、件数はすべて1件ずつになる気がするので.count.length
でHashの要素数取れば合計の件数が取れるかなぁ。
しかしタグを指定していないと、こんな感じでcountだけを取得するクエリになっています。
SELECT COUNT(*)
FROM "posts"
WHERE "posts"."user_id" = ?
タグが指定されてるかどうかで件数の取り方を変えなあかん、てことでこんな実装になりました。汚い。
var searchResultHtml = "";
var count = "0";
<%if @posts.exists? %>
searchResultHtml = '<%=j render @posts%>';
<%if @tags.present?%>
count = <%=@posts.count.length%>;
<%elsif%>
count = <%=@posts.count%>
<%end%>
<%end%>
$('#post-search-result-table').html(searchResultHtml);
$('span#search-result-count').html(count.toString());
うーん詰まりまくったけどひとまずこんな感じでOKかなぁ。
railsというかrubyというか、まだまだ色々分かってないので詰まるとなかなか大変です。
長くなってしまいましたが今回はこの辺で。