Series
- Git Essentials (1)
- Ransack (2)
- 20in20 (5)
- Ruby On Rails In Windows 10 With WSL 2. (3)
Other posts in series:
Search And Sort Your Associations With The Ransack Gem
Last week I made a rather well received YouTube tutorial covering the Ransack gem. In the video I mentioned that we would probably see 2-3 parts covering the Ransack gem, and here is part 2! Today we’re going to learn how to make it so you can search and sort associations.
Effectively, what this means is that when you have a collection of articles broken into categories, you can filter by category. Say for example you had a list of publications. Each publication type has many journals under it. Maybe you’re only interested in the anime publications. Or maybe your thing is scientific journal articles.
Now this is when I should point out some of the limitations of the Ransack gem. While attempting to create this tutorial, I ran into repeated blockers with Ransack. The main blocker, however, is that multi-column searching isn’t really supported. This means that if you want to search both the category of an article and its title at the same time, we’re out of luck. Of course, rolling our own search solution would mean being able to handles this far more easily.
GitHub Repository For This Tutorial.
Video Version of Tutorial
Timestamps
0:00 Intro And Ransack Video Overview
0:52 Creating The Association For Ransack Searches
3:07 Using Faker To Seed The Database
5:10 The Ransack Association Models
5:35 The Ransack Association Controllers
6:22 The Ransack Search And Sort Views
12:50 Outro And Ransack Video Summary
Part 1 - Create The Categories
If we’re going to search an association, the first thing we’ll need is an association. In order to do this, we’re going to create a scaffold to give the articles a one to many relationship. Doing this means that each category will have many articles, and each article will belong to one category. You will also want to seed the database with some categories, which I’ll add below the shell commands. All of this code can be seen below.
rails g scaffold categories name
rails g migration add_categories_to_articles category:references
rails db:migrate
# migrate/seeds.rb
Category.create(name: "Manga")
Category.create(name: "Journal")
Category.create(name: "Tabloid")
Category.create(name: "Fanfic")
Category.create(name: "Movie Review")
50.times do |x|
Article.create(title: Faker::Lorem.sentences(number: 1),
body: Faker::Lorem.paragraph(sentence_count: 5),
category_id: Faker::Number.between(from: 1, to: Category.count - 1))
end
# article.rb
class Article < ApplicationRecord
belongs_to :category
end
# category.rb
class Category < ApplicationRecord
has_many :articles
end
Part 2 - The Controller For The Ransack Gem
Controllers are usually fairly easy to work with, and not much has changed since part 1. In fact, the only change is going to be at the very bottom. Inside of the params, you just need to add the category_id as an article param. To help you out, I’ve highlighted lines 67-69 where you’ll add this down below in a slightly different color. I’m so sorry about the lack of contrast, my editor is trying its best!
class ArticlesController < ApplicationController
before_action :set_article, only: %i[ show edit update destroy ]
# GET /articles or /articles.json
def index
@q = Article.ransack(params[:q])
@articles = @q.result(distinct: true)
end
# GET /articles/1 or /articles/1.json
def show
end
# GET /articles/new
def new
@article = Article.new
end
# GET /articles/1/edit
def edit
end
# POST /articles or /articles.json
def create
@article = Article.new(article_params)
respond_to do |format|
if @article.save
format.html { redirect_to @article, notice: "Article was successfully created." }
format.json { render :show, status: :created, location: @article }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @article.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /articles/1 or /articles/1.json
def update
respond_to do |format|
if @article.update(article_params)
format.html { redirect_to @article, notice: "Article was successfully updated." }
format.json { render :show, status: :ok, location: @article }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @article.errors, status: :unprocessable_entity }
end
end
end
# DELETE /articles/1 or /articles/1.json
def destroy
@article.destroy
respond_to do |format|
format.html { redirect_to articles_url, notice: "Article was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_article
@article = Article.find(params[:id])
end
# Only allow a list of trusted parameters through.
def article_params
params.require(:article).permit(:title, :body, :category_id)
end
end
Part 3 - The Views (Forms)
The next step will be to modify the forms. I have opted for one form that allows you to search any one column. If you wanted to, you could add a select dropdown to search specific categories that way instead. I just wanted to show how to do an all in one search bar.
We’ll modify the search form first, and then we’ll modify the new article form. Keep in mind that each of the symbols in the search field could be its own search field. You could have a :category_name_cont as well as a title_or_body_cont field.
# views/articles/_search_form.html.erb
<%= search_form_for @q do |f| %>
<%= f.search_field :title_or_body_or_category_name_cont, placeholder: "Search..." %>
<%= f.submit "Search!" %>
<% end %>
<%= form_with(model: article) do |form| %>
<% if article.errors.any? %>
<%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:
<% article.errors.each do |error| %>
- <%= error.full_message %>
<% end %>
<% end %>
<%= form.label :title %>
<%= form.text_field :title %>
<%= form.label :category %>
<%= form.select(:category_id, Category.all.collect {|category| [category.name, category.id]})%>
<%= form.label :body %>
<%= form.text_area :body %>
<%= form.submit %>
<% end %>
Part 4 - The Views (Index view)
Finally we are in the home stretch. For this section the only real addition is going to be the sort_link for the categories on line 9. You will also want to add the corresponding article.category.name to line 19.
The trick here is that although the article does not have a category name, you can still sort by it. Ransack is smart enough to know that :category_name is meant to call article.category.name!
<%= notice %>
Articles
<%= render 'articles/search_form.html.erb' %>
<%= sort_link(@q, :category_name, default_order: :asc) %>
<%= sort_link(@q, :title, "Title", default_order: :asc) %>
<%= sort_link(@q, :body, "Article Content", default_order: :desc) %>
<% @articles.each do |article| %>
<%= article.category.name %>
<%= article.title %>
<%= article.body %>
<%= link_to 'Show', article %>
<%= link_to 'Edit', edit_article_path(article) %>
<%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
<%= link_to 'New Article', new_article_path %>
<%= notice %>
Title:
<%= @article.title %>
Category:
<%= @article.category.name %>
Body:
<%= @article.body %>
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>
Part 5 - The Conclusion
And there you have it. The ability to search by an association inside of the same form that we used to search articles. This should hopefully get your creativity flowing and give you some ideas for how to expand the search. Maybe some checkboxes, a dropdown, or another search box.
Hopefully this tutorial was helpful. I’ve included a few images of the application as-taught below. If you come up with some improvements, feel free to tweet them at me.