Askar
Askar

Reputation: 5854

search with Mongoid on Rails 4 not only on string fields but also on date ranges

parent.rb

class Parent
  include Mongoid::Document
  field :name, type: String
  field :hobby, type: String
  field :born, type: Date

  embeds_many :children
  accepts_nested_attributes_for :children
end

children.rb

class Child
  include Mongoid::Document
  field :hobby, type: String
  field :name, type: String
  field :born, type: Date

  embedded_in :parent
end

parents_controller.rb

class ParentsController < ApplicationController
  before_action :set_parent, only: [:show, :edit, :update, :destroy]

  # GET /parents
  # GET /parents.json
  def index
    @parents = Parent.all
  end

  # GET /parents/1
  # GET /parents/1.json
  def show
  end

  # GET /parents/new
  def new
    @parent = Parent.new
  end

  # GET /parents/1/edit
  def edit
  end

  # POST /parents
  # POST /parents.json
  def create
    @parent = Parent.new(parent_params)

    respond_to do |format|
      if @parent.save
        format.html { redirect_to @parent, notice: 'Parent was successfully created.' }
        format.json { render action: 'show', status: :created, location: @parent }
      else
        format.html { render action: 'new' }
        format.json { render json: @parent.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /parents/1
  # PATCH/PUT /parents/1.json
  def update
    respond_to do |format|
      if @parent.update_attributes(parent_params)
        format.html { redirect_to @parent, notice: 'Parent was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @parent.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /parents/1
  # DELETE /parents/1.json
  def destroy
    @parent.destroy
    respond_to do |format|
      format.html { redirect_to parents_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_parent
      @parent = Parent.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def parent_params
      params.require(:parent).permit(:name, :hobby, :born)
    end
end

Parents' index.html.erb:

<h1>Listing parents</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Born</th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>

  <tbody>
    <% @parents.each do |parent| %>
      <tr>
        <td><%= parent.name %></td>
        <td><%= parent.born %></td>
        <td><%= link_to 'Show', parent %></td>
        <td><%= link_to 'Edit', edit_parent_path(parent) %></td>
        <td><%= link_to 'Destroy', parent, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Parent', new_parent_path %>

I want to implement a search feature to search not only in the main document but in the nested as well. Search not only in text fields, but also with date ranges. Can anyone point me any resource (tutorial) regarding this? I couldn't find anything like Ryan's Simple Search railscast. I'd very obliged if someone could show me how I will have to modify my controllers and index.html.erb files.

There's also a Durran's page related to search in the github https://github.com/mongoid/mongoid/blob/master/lib/mongoid/contextual/text_search.rb. But frankly speaking it didn't give any clue to solve my issue.

Upvotes: 0

Views: 832

Answers (2)

Askar
Askar

Reputation: 5854

Thanks to @Pierre-Louis Gottfrois for advice that helped me do some further research.

I became able to search on two fields (name and hobby) as follows.

I've added onto the product's index.html.erb:

<%= form_tag parents_path, :method => 'get' do %>
<p>
    <%= text_field_tag :search, params[:search] %>
    <%= submit_tag "Search", :name => nil %>
</p>
<% end %>

Then in the parent.rb model I changed taking into account @Pierre-Louis Gottfrois's advice :

def self.search(search)
    if search
        any_of({name: search}, {hobby: search})
    end
  end

products_controller.rb changed:

def index
    if params[:search].empty?
      @parents = Parent.all
    else
      @parents = Parent.search(params[:search])
    end
end

There're 3 problems still exist and I thought it would be better split them in different posts:

  1. How to ignore case-sensitiveness while searching
  2. How to search including nested documents
  3. Ability to search by specifying date ranges
  4. Consider any word as a result if it contains a search-keyword

Upvotes: 1

Pierre-Louis Gottfrois
Pierre-Louis Gottfrois

Reputation: 17631

You'll want for example to have your index method taking a search param

def index
  @parents = if params[:search]
               Parent.where(name: params[:search])
             else
               Parent.all
             end
end

This is the basic idea. You may consider having a class method for doing more complex search (using born attributes for example).

def index
  if params[:search]
    @parents = Parent.search_for(params[:search])
  else
    @parents = Parent.all
  end
end

In your model:

class Parent
  def search_for(criterias)
    # Logic regarding criterias selection should go here.
    # Very basic idea bellow
    Parent.where(name: criterias[:name], born: criterias[:born])
  end
end

In your view:

// First case
<%= text_field_tag :search, '' %>

// Second case
<%= text_field_tag 'search[name]', '' %>
<%= text_field_tag 'search[born]', '' %>

Upvotes: 2

Related Questions