Viết 1 bài ngắn ngắn thế này kể ra cũng không bõ cho lắm, nhưng 2 điều chỉnh khi search với Ransack ở đây thì toàn được hướng dẫn rời rạc trên Stackoverflow nên mình viết bài này ra. Rất mong các bạn chia sẻ thêm các case của các bạn phải chỉnh sửa khi dùng gem Ransack để mình có thể bổ sung thêm ở bài viết.
Lập 1 project Rails và chuẩn bị các bước
Ở bước này chạy lệnh rails new
, chuẩn bị các model và thêm gem Ransack. Do cũng lười biếng nên mình xin được phép skip bước này. Tuy nhiên thì mình sẽ up lên ảnh các bảng trong csdl mà mình sẽ dùng trong bài này.
Sau đó, bạn làm 1 form search có các filter: từ khoá tìm kiếm, tag, số comment. Và chúng ta đi vào vấn đề 1.
Vấn đề 1: Tìm kiếm các tag cách nhau bằng dấu phẩy
Yêu cầu bạn nhận được là ở form tìm kiếm tag, bạn có thể gõ nhiều tag, mỗi tag cách nhau bằng 1 dấu phẩy. Kết quả trả ra phải chứa 1 trong các tag ấy.
Phân tích ở đây là chúng ta dùng mối quan hệ là OR. Và chúng ta sẽ phải có cách tác động tới input với split()
của Ruby.
Vậy code sẽ được viết như sau
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <span class="token keyword">class</span> <span class="token class-name">PostsController</span> <span class="token operator"><</span> <span class="token constant">ApplicationController</span> <span class="token keyword">def</span> index params<span class="token punctuation">[</span><span class="token symbol">:q</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token symbol">:combinator</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"or"</span> params<span class="token punctuation">[</span><span class="token symbol">:q</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token symbol">:groupings</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> custom_words <span class="token operator">=</span> params<span class="token punctuation">[</span><span class="token symbol">:q</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token symbol">:tags_name_cont_any</span><span class="token punctuation">]</span> custom_words<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">","</span><span class="token punctuation">)</span><span class="token punctuation">.</span>each_with_index <span class="token keyword">do</span> <span class="token operator">|</span>word<span class="token punctuation">,</span> index<span class="token operator">|</span> params<span class="token punctuation">[</span><span class="token symbol">:q</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token symbol">:groupings</span><span class="token punctuation">]</span><span class="token punctuation">[</span>index<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span>tags_name_cont_any<span class="token punctuation">:</span> word<span class="token punctuation">}</span> <span class="token keyword">end</span> <span class="token variable">@q</span> <span class="token operator">=</span> <span class="token constant">Post</span><span class="token punctuation">.</span><span class="token function">ransack</span><span class="token punctuation">(</span>params<span class="token punctuation">[</span><span class="token symbol">:q</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token variable">@pre_posts</span> <span class="token operator">=</span> <span class="token variable">@q</span><span class="token punctuation">.</span>result <span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token symbol">:tags</span><span class="token punctuation">)</span> <span class="token variable">@posts</span> <span class="token operator">=</span> <span class="token keyword">if</span> params<span class="token punctuation">[</span><span class="token symbol">:tag</span><span class="token punctuation">]</span> <span class="token variable">@posts</span><span class="token punctuation">.</span><span class="token function">tagged_with</span><span class="token punctuation">(</span>params<span class="token punctuation">[</span><span class="token symbol">:tag</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">page</span><span class="token punctuation">(</span>params<span class="token punctuation">[</span><span class="token symbol">:page</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">per</span><span class="token punctuation">(</span><span class="token constant">Settings</span><span class="token punctuation">.</span>max_post_number_per_page<span class="token punctuation">)</span> <span class="token keyword">else</span> <span class="token variable">@posts</span><span class="token punctuation">.</span><span class="token function">page</span><span class="token punctuation">(</span>params<span class="token punctuation">[</span><span class="token symbol">:page</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">per</span><span class="token punctuation">(</span><span class="token constant">Settings</span><span class="token punctuation">.</span>max_post_number_per_page<span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Và ở view bạn đặt
1 2 3 4 5 | <span class="token erb language-erb"><span class="token delimiter punctuation"><%=</span> search_form_for <span class="token variable">@q</span> <span class="token keyword">do</span> <span class="token operator">|</span>f<span class="token operator">|</span> <span class="token delimiter punctuation">%></span></span> ... <span class="token erb language-erb"><span class="token delimiter punctuation"><%=</span> f<span class="token punctuation">.</span>search_field <span class="token symbol">:tags_name_cont_any</span> <span class="token delimiter punctuation">%></span></span> <span class="token erb language-erb"><span class="token delimiter punctuation"><%</span> <span class="token keyword">end</span> <span class="token delimiter punctuation">%></span></span> |
Tìm kiếm thoả mãn rồi, nhưng nhìn độ to của index
trông kinh dị quá. Thế nên mình viết lại code như sau
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <span class="token keyword">class</span> <span class="token class-name">PostsController</span> <span class="token operator"><</span> <span class="token constant">ApplicationController</span> before_action <span class="token symbol">:after_enter_tags_field</span><span class="token punctuation">,</span> only<span class="token punctuation">:</span> <span class="token symbol">:index</span> <span class="token keyword">def</span> index <span class="token variable">@q</span> <span class="token operator">=</span> <span class="token constant">Post</span><span class="token punctuation">.</span><span class="token function">ransack</span><span class="token punctuation">(</span>params<span class="token punctuation">[</span><span class="token symbol">:q</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token variable">@pre_posts</span> <span class="token operator">=</span> <span class="token variable">@q</span><span class="token punctuation">.</span>result <span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token symbol">:tags</span><span class="token punctuation">)</span> <span class="token variable">@posts</span> <span class="token operator">=</span> <span class="token keyword">if</span> params<span class="token punctuation">[</span><span class="token symbol">:tag</span><span class="token punctuation">]</span> <span class="token variable">@pre_posts</span><span class="token punctuation">.</span><span class="token function">tagged_with</span><span class="token punctuation">(</span>params<span class="token punctuation">[</span><span class="token symbol">:tag</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">page</span><span class="token punctuation">(</span>params<span class="token punctuation">[</span><span class="token symbol">:page</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">per</span><span class="token punctuation">(</span><span class="token constant">Settings</span><span class="token punctuation">.</span>max_post_number_per_page<span class="token punctuation">)</span> <span class="token keyword">else</span> <span class="token variable">@pre_posts</span><span class="token punctuation">.</span><span class="token function">page</span><span class="token punctuation">(</span>params<span class="token punctuation">[</span><span class="token symbol">:page</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">per</span><span class="token punctuation">(</span><span class="token constant">Settings</span><span class="token punctuation">.</span>max_post_number_per_page<span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">private</span> <span class="token keyword">def</span> after_enter_tags_field <span class="token keyword">return</span> <span class="token keyword">unless</span> params<span class="token punctuation">[</span><span class="token symbol">:q</span><span class="token punctuation">]</span> params<span class="token punctuation">[</span><span class="token symbol">:q</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token symbol">:combinator</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"or"</span> params<span class="token punctuation">[</span><span class="token symbol">:q</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token symbol">:groupings</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> custom_words <span class="token operator">=</span> params<span class="token punctuation">[</span><span class="token symbol">:q</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token symbol">:tags_name_cont_any</span><span class="token punctuation">]</span> custom_words<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">","</span><span class="token punctuation">)</span><span class="token punctuation">.</span>each_with_index <span class="token keyword">do</span> <span class="token operator">|</span>word<span class="token punctuation">,</span> index<span class="token operator">|</span> params<span class="token punctuation">[</span><span class="token symbol">:q</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token symbol">:groupings</span><span class="token punctuation">]</span><span class="token punctuation">[</span>index<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span>tags_name_cont_any<span class="token punctuation">:</span> word<span class="token punctuation">}</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Như vậy trông đỡ kinh dị hơn, dù có vẻ vẫn chưa gọn lắm
Vấn đề 2: Tìm kiếm dựa theo số lượng comment
Vấn đề tiếp theo đặt ra là tìm kiếm và trả ra kết quả của các post có số comment lớn hơn 1 con số nhất định. Lúc này chúng ta sẽ dùng query phụ và gem Arel
Với gem Arel, ta phải thêm như sau:
1 2 | gem <span class="token string">"arel"</span><span class="token punctuation">,</span> git<span class="token punctuation">:</span> <span class="token string">"https://github.com/rails/arel"</span> |
Sau đó, tại model, ta thêm như sau
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="token keyword">class</span> <span class="token class-name">Post</span> <span class="token operator"><</span> <span class="token constant">ApplicationRecord</span> has_many <span class="token symbol">:comments</span><span class="token punctuation">,</span> dependent<span class="token punctuation">:</span> <span class="token symbol">:destroy</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> ransacker <span class="token symbol">:comments_count</span> <span class="token keyword">do</span> query <span class="token operator">=</span> "<span class="token punctuation">(</span> <span class="token constant">SELECT</span> <span class="token function">COUNT</span><span class="token punctuation">(</span>comments<span class="token punctuation">.</span>post_id<span class="token punctuation">)</span> <span class="token constant">FROM</span> comments <span class="token constant">WHERE</span> comments<span class="token punctuation">.</span>post_id <span class="token operator">=</span> posts<span class="token punctuation">.</span>id <span class="token constant">GROUP</span> <span class="token constant">BY</span> comments<span class="token punctuation">.</span>post_id <span class="token punctuation">)</span>" <span class="token constant">Arel</span><span class="token punctuation">.</span><span class="token function">sql</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Và chúng ta đã có 1 thuộc tính tên comments_count
. Trong ransack cũng có _gteq
để so sánh lớn hơn hoặc bằng(Greater or Equal). Vậy chúng ta chỉ cần kết hợp lại là xong
1 2 3 4 5 6 | <%= search_form_for @q do |f| %> ... <%= f.search_field :tags_name_cont_any %> <%= f.number_field :spot_reviews_count_gteq %> <% end %> |
Tham khảo
https://stackoverflow.com/questions/53652254/multi-term-ransack-search-in-same-field-not-working