Search And Sort Associations With The Rails Ransack Gem

Search And Sort Associations With The Rails Ransack Gem

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? %>
    <div id="error_explanation">
      <h2><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h2>

      <ul>
        <% article.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div class="field">
    <%= form.label :category %>
    <%= form.select(:category_id, Category.all.collect {|category| [category.name, category.id]})%>
  </div>

  <div class="field">
    <%= form.label :body %>
    <%= form.text_area :body %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% 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!

				
					<p id="notice"><%= notice %></p>

<h1>Articles</h1>

<%= render 'articles/search_form.html.erb' %>
<table>
  <thead>
    <tr>
      <th><%= sort_link(@q, :category_name, default_order: :asc) %>
      <th><%= sort_link(@q, :title, "Title", default_order: :asc) %></th>
      <th><%= sort_link(@q, :body, "Article Content", default_order: :desc) %></th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @articles.each do |article| %>
      <tr>
        <td><%= article.category.name %></td>
        <td><%= article.title %></td>
        <td><%= article.body %></td>
        <td><%= link_to 'Show', article %></td>
        <td><%= link_to 'Edit', edit_article_path(article) %></td>
        <td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Article', new_article_path %>

				
			
				
					<p id="notice"><%= notice %></p>

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Category:</strong>
  <%= @article.category.name %>
</p>

<p>
  <strong>Body:</strong>
  <%= @article.body %>
</p>

<%= 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.

Share this post

Share on twitter
Share on facebook
Share on linkedin
Share on reddit
Share on pinterest
Share on email

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.