3

I'm trying to implement a complex search over a model in a separate controller. I have a pupils model. The whole app has a front page handled by a separate main_controller which has no model. The main_controller and its related index view are supposed to provide the front page and display data from several models.

Now I want to search the model with several search criteria of different types. The search criteria are string comparisons, number comparisons and booleans (e.g. active, if true show only active pupils else show all pupils). Railscast #111 showed how to create such a search based on a model and separate search controller. I created such a controller and it works fine. I am stuck with showing the relevant parts in my main/index.

Here is the code:

main/index.html.haml

 - model_class = Adult
 - model_class = Pupil
 - model_class = MainSearch

.page-header
= render :partial => 'main_searches/form', :main_search => MainSearch.new

Just the call to the form here at the moment.

models/main_search.rb

class MainSearch < ActiveRecord::Base

  def pupils
    @pupils ||= find_pupils
  end

  private
    def find_pupils
      pupils = Pupil.order(:name_sur)
      pupils = pupils.where(id: id_search) if id_search.present?
      pupils = pupils.where("name_first like ?", "%#{name_first}%") if name_first.present?
      pupils = pupils.where("name_sur like ?", "%#{name_sur}%") if name_sur.present?
      pupils = pupils.where(active: "true") if active == true  #show only active or all
      pupils
    end
end

This defines the search.

controllers/main_searches_controller.rb

class MainSearchesController < ApplicationController
  before_action :set_main_search, only: [:show, :update, :destroy]

  def show
    @main_search = MainSearch.find(params[:id])
  end

  def new
    @main_search = MainSearch.new
  end

  def create
    @main_search = MainSearch.new(main_search_params)

    if @main_search.save
      redirect_to root_path, notice: 'Main search was successfully created.'
    else
      render action: 'new'
    end
  end
end

As shown in the railscast.

views/main_searches/_form.html.haml

%h1 Advanced Search

  = form_for :main_search, :url => main_searches_path do |f|
    .field
      = f.label :id_search
      %br/
      = f.text_field :id_search
    [... ommitted some fields here ...]
    .field
      = f.label :active
      %br/
      = f.check_box :active, {}, true, false


  .actions= f.submit "Search"

Rendered in the new view.

views/main_searches/_results.html.haml

%h1 Search Results

.container-fluid
  .row-fluid
    .span4

      %table.table{style: "table-layout:fixed"}
        %thead
          %tr
            %th= "id"
            %th= "name_sur"
            %th= "name first"
            %th= "a"
      %div{style: "overflow-y:scroll; height: 200px"}
        %table.table.table-striped{style: "table-layout:fixed"}
          %tbody
            - @main_search.pupils.each do |pupil|
              %tr
                %td= pupil.id
                %td= link_to pupil.name_sur, pupil_path(pupil)
                %td= pupil.name_first
                %td= pupil.active

Displays the results.

So basically everything works for one model as seen in the railscast. What I need now is to let the user handle everything in the main_controller somehow. At the moment I cant get the @main_search object passed to the _results.html.haml partial. What am I missing here? Or is this even the right way to do such a search?

Thanks for your help in advance.

engineersmnky
  • 25,495
  • 2
  • 36
  • 52
mandulis
  • 133
  • 1
  • 8
  • [Here is an approach](https://github.com/codeschool/FeatureFocus/blob/basecamp/app/models/search.rb) I watched in a [video](https://www.codeschool.com/screencasts/basecamp-search) recently. In your controller you would call it like so: `@main_search = Search.for(params[:id])`. – Brian Oct 06 '14 at 19:04
  • Is there a specific reason you want to store searches in the database? I understand the functionality but seems like especially for multi-model searches this could become unwieldy since each model will probably have it's own set of characteristics. Are these searches going to be associated with a User or some other Entity? If not seems like it might make more sense to create an abstract (for lack of a better word) model and run your searches through it instead. – engineersmnky Oct 06 '14 at 19:25
  • @Brian Thanks, I will check that first thing in the morning. --engineersmnky You are right, it could get messy. At first I wanted to store searches based on users, but after checking with the customer that is optional. What exactly do you mean with an abstract model? A temporal table? – mandulis Oct 06 '14 at 21:32

1 Answers1

2

Brians solution actually worked quite well. I created a separate model without a related database and put all the search logic into that file. Notice, it is only a class and not related to ActiveRecord.

class MainSearch

  def self.for(id_search, name_sur, name_first, active)

      #escape for security
      name_sur = "%#{name_sur}%"
      name_first = "%#{name_first}%"
      active = "%#{active}%"

      pupils = Pupil.order(:name_sur)
      pupils = pupils.where(id: id_search) if id_search.present?
      pupils = pupils.where("name_first like ?", "%#{name_first}%") if name_first.present?
      pupils = pupils.where("name_sur like ?", "%#{name_sur}%") if name_sur.present?
      pupils = pupils.where(active: "true") if active == true  #show only active or all
  end
end

My main_controller now can call this function and pass values from the params hash:

    @pupils = MainSearch.for(params[:id_search], params[:name_sur], params[:name_first], params[:active])

The parameters come from views/main/_form_search.html.haml :

.search-box
  = form_tag root_path, method: :get do

    = label_tag :id_search
    %br/
    = text_field_tag 'id_search', nil
    %br/
    = label_tag :name_sur
    %br/
    = text_field_tag 'name_sur', nil
    %br/
    = label_tag :name_sur
    [...]
    = submit_tag "Submit"

The final step is to show the results. As you can see in the main_controller, I just fill the @pupils object and thus can render it easily in any view. Here is the excerpt from the views/main/index.html.haml

.span8
  .row-fluid
    %table.table{style: "table-layout:fixed"}
      %thead
        %tr
          %th= "id"
          %th= sortable "name_sur"
          %th= "name first"
          %th= "a"
    %div{style: "overflow-y:scroll; height: 200px"}
      %table.table.table-striped{style: "table-layout:fixed"}
        %tbody
          - @pupils.each do |pupil|
            %tr
              %td= pupil.id
              %td= link_to pupil.name_sur, pupil_path(pupil)
              %td= pupil.name_first
              %td= pupil.active

    = render :partial => 'form_search'

This setup keeps all the control in the main_controller and only needs one external file (the search model). In this model I can search for data across several models (as seen in the video) and combine search criteria as I please and even do some performance shenanigans in there. I am quite happy with the result. SO thanks for all the input.

mandulis
  • 133
  • 1
  • 8