ComplexSearchRails?

How to Add Complex Search Term on Rails?

Today, we will learn how to implement Complex Search Term on Rails, also how to install Bootstrap with Bower and generate fake data with Faker gem.

Install new project with name “complex_search”-

$ rails new complex_search -d postgresql

Configure database.yml file inside config-

# config/database.yml

default: &default
  adapter: postgresql
  encoding: unicode
  username: postgres # Based on your username
  password: 123456 # Based on your password
  # For details on connection pooling, see rails configuration guide
  # http://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

Then open your command prompt & write below command for database migration-

$ cd complex_search
$ bundle exec rake db:create
$ bundle exec rake db:migrate
$ bundle exec rails server

Write localhost:3000 to your browser & will show below image-

We just completed the project setup, now needs to setup Bootstrap.

There are three ways to install Bootstrap into a Rails application: as a Ruby Gem, by referencing a publicly available version hosted on a CDN, or by downloading it and storing it within our application’s source.

We’re going to use that latter, assisted by a tool called Bower.
To install Bootstrap using Bower, we’ll first need to install Bower;  then we’ll instruct it to download and install Bootstrap; and, finally, we’ll configure the asset pipeline to make Bootstrap available.
Install Bower

Bower itself as a package manager for the web. It was created by Twitter and is analogous to RubyGems, but manages front-end assets (including CSS frameworks like Bootstrap and JavaScript libraries like Angular). Bower is written in JavaScript, and so it requires a JavaScript runtime in order to work. That means you’ll have to install Node JS.

Once you have Node JS installed, you’ll have access to the npm command-line application. This is a package manager for JavaScript, and we’ll use that to install Bower.
$ npm install -g bower

Then-

# complex_search/Gemfile
gem 'bower-rails'

We can install with bundler.

$ bundle install

We have to create Bowerfile file in the root directory then install Bootstrap.

# complex_search/Bowerfile
asset 'bootstrap-sass-official'

Then-

$ bundle exec rake bower:install

Add bootstrap to our asset pipeline

# complex_search/app/assets/stylsheet/application.css
/*
*= require_tree.
*= require_self
➤ *= require 'bootstrap-sass-official'
*/

We are just done with installing Bootstrap with Bower!

Now we will generate a model called Customer for implementing complex search term-

$ rails generate model Customer first_name:string last_name:string email:string username:string

The migration file created & look like below-

# complex_search/db/migrate/timestamps_create_customers.rb
class CreateCustomers < ActiveRecord::Migration[5.0]
  def change
    create_table :customers do |t|
      t.string :first_name, null: false
      t.string :last_name, null: false
      t.string :email, null: false
      t.string :username, null: false

      t.timestamps null: false
    end

    add_index :customers, :email, unique: true
    add_index :customers, :username, unique: true
  end
end

Then execute following command-

$ rake db:migrate
This will create a “customers” table in database, now generate fake data for implementing search query.
Adding faker gem-
# complex_search/Gemfile
gem 'faker'

Then, we’ll install it the gem-

$ bundle install

After that-

# complex_search/db/seeds.rb
500.times do |i|
  Customer.create!(
    first_name: Faker::Name.first_name,
    last_name: Faker::Name.last_name,
    username: "#{Faker::Internet.user_name}#{i}",
    email: Faker::Internet.user_name + i.to_s + "@#{Faker::Internet.domain_name}")
end

Then execute the above code-

$ bundle exec rake db:seed

We just created 500 fake data (rows) in database!

Building the search UI

$ rails generate controller customers index
# complex_search/config.routes.rb
resources :customers, only: [:index]
# complex_search/app/controller/customers_controller.rb

class CustomersController < ApplicationController
  	def index
  		@customers = Customer.all.limit(10)
  	end
end
# complex_search/app/views/customers/index.html.erb

<h1>Customer Search</h1>
<section class="search-form">
  <%= form_for :customers, method: :get do |f| %>
    <div class="input-group input-group lg">
      <%= label_tag :keywords, nil, class: "sr-only" %>
      <%= text_field_tag :keywords, nil, placeholder: "First Name, Last Name, or Email Address", class: "form-control input-lg" %>
      <span class="input-group-btn">
        <%= submit_tag "Find Customers", class: "btn btn-primary btn-lg" %>
      </span>
    </div>
  <% end %>
</section>

<section class="search-results">
       <h1 class="h3">Results</h1>

  <ol class="list-group">
    <% @customers.each do |customer| %>
      <li class="list-group-item clearfix">
        <h3 class="pull-right">
          <small class="text-uppercase">Joined</small>
          <%= l customer.created_at.to_date %>
        </h3>
        <h2 class="h3">
          <%= customer.first_name %> <%= customer.last_name %>
          <small><%= customer.username %></small>
        </h2>
        
        <h4><%= customer.email %></h4>
      </li>
    <% end %>
  </ol>
</section>

Now create a file inside models called “customer_search_term.rb” and then write following code-

# complex_search/app/models/customer_search_term.rb
class CustomerSearchTerm
  attr_reader :where_clause, :where_args, :order

  def initialize(search_term)
    search_term = search_term.downcase
    @where_clause = ""
    @where_args = {}
    if search_term =~ /@/
      build_for_email_search(search_term)
    else
      build_for_name_search(search_term)
    end
  end

  def build_for_name_search(search_term)
    @where_clause << case_insensitive_search(:first_name)
    @where_args[:first_name] = start_with(search_term)

    @where_clause << " OR #{case_insensitive_search(:last_name)}"
    @where_args[:last_name] = start_with(search_term)

    @order = "last_name asc"
  end

  def build_for_email_search(search_term)
    @where_clause << case_insensitive_search(:first_name)
    @where_args[:first_name] = start_with(extract_name(search_term))
    
    @where_clause << " OR #{case_insensitive_search(:last_name)}"
    @where_args[:last_name] = start_with(extract_name(search_term))

    @where_clause << " OR #{case_insensitive_search(:email)}"
    @where_args[:email] = search_term

    @order = "lower(email) = " + ActiveRecord::Base.connection.quote(search_term) + "desc, last_name asc"
  end

  def start_with(search_term)
    search_term + "%"
  end

  def case_insensitive_search(field_name)
    "lower(#{field_name}) like :#{field_name}"
  end

  def extract_name(email)
    email.gsub(/@.*$/,'').gsub(/[0-9]+/,'')
  end
end

Then modify code in controller file-

# complex_search/app/controllers/customers_controller.erb
class CustomersController < ApplicationController
  PAGE_SIZE = 10
  	def index
  		if params[:keywords].present?
  			@keywords = params[:keywords]
  			customer_search_term = CustomerSearchTerm.new(@keywords)
  			@customers = Customer.where(customer_search_term.where_clause, customer_search_term.where_args).order(customer_search_term.order)
  		else
  			@customers = []
  		end
  	end
end

And that’s all! We have implemented a full & complex search feature on the Rails project.

Happy coding 🙂


Posted

in

,

by

Comments

Leave a Reply

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