3
\$\begingroup\$

I'm trying to implement search over two models. In this example Book and Article. Here are the attributes from both of these :

Article(id: integer, magazine: string, title: string, body: text, created_at: datetime, updated_at: datetime)
Book(id: integer, publisher: string, author: string, title: string, foreword: text, body: text, created_at: datetime, updated_at: datetime)

I'm trying to search over multiple attributes on both of these Models using the same search string and combining results. Because my next goal is to have a permlink to results.

Here is how I implemented search on these Models:

def self.search(q)
  Article.where('magazine like :value OR title like :value OR body like :value', value: "%#{q}%")
 end

def self.search(q)
  Book.where('publisher like :value OR author like :value OR title like :value OR foreword like :value OR body like :value', value: "%#{q}%")
end 

Here is the controller :

class HomeController < ApplicationController 
    def search
      articles = Article.search(params[:q])
      books = Book.search(params[:q])

      @results = articles + books       
    end
end

I use haml so here is my searchresult page :

%div.main
  %div.pull.left
    =link_to 'Back to search', '/',:class => 'btn btn-primary btn-lg'
  %div.clearfix
  %table.table.table-condensed
    %thead
      %tr
        %th Id
        %th Title
        %th Type
    %tbody
      - @results.each do |item|
        %tr
          %td #{item.id}
          %td #{item.title}
          %td #{item.class.name}

Is there a more elegant way to actually do the search? I don't have that many experience with rails but I was reading trough this article :

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

1. And I was thinking I could do this as 2. Extract Service Objects, but as two search methods search over different attributes I wasn't able to do this? I'd be glad to hear your take on this.

2. Regarding the permlink to my search. I'm wondering should I create another model named i.e SearchResult and then link the single search result to many Books and Articles?

Update

I'm not looking for solutions like Sunspot or Sphinx, as this is a small app in dev mode just for practising but still I want it to be as better as possible.

\$\endgroup\$
  • 1
    \$\begingroup\$ The most elegant and scalable way is to use a search engine like Sunspot or Sphinx or even Blacklight. \$\endgroup\$ – Mark Thomas Nov 22 '13 at 1:34
  • \$\begingroup\$ Hi Mark, I'm looking something for development as this is not a real app I'm just practising and getting started. \$\endgroup\$ – Gandalf StormCrow Nov 22 '13 at 9:17
  • 1
    \$\begingroup\$ @GandalfStormCrow - You can implement full text search yourself. That said, since it's a small app in dev mode, it's even more imperative that you try out different full text search engines. Once your app has become large and you have a lot of traction and your searching requirements have specialized so much that full-text search doesn't meet your purpose (I doubt that happening any time soon), then you can think about rolling out your own. \$\endgroup\$ – Chandranshu Nov 22 '13 at 9:38
  • \$\begingroup\$ use a full-text search engine is definitely the way to go. However, there's an alternative solution, that requires to change your schema : you have to implement multiple table inheritance with a common table (such as "contents", or any other relevant name) holding common fields between your two models (title, author,etc.). Your searches and permalinks will simply operate on this intermediate resource. \$\endgroup\$ – m_x Nov 22 '13 at 10:48
  • \$\begingroup\$ @m_x hi Can you show this trough example please? \$\endgroup\$ – Gandalf StormCrow Nov 22 '13 at 11:53
2
\$\begingroup\$

you want a separate model for search, i.e.

class SearchResult < AR::Base
  #columns: content, title, user
  belongs_to :searchable, polymorphic: true
end

class Book < AR::Base
  has_one :searchable, polymorphic: true, class_name: 'SearchResult'
  belongs_to :user

  after_create :autobuild_searchable
  after_commit :update_search


  private

  def autocreate_searchable
    self.create_searchable
  end

  def update_search_content
    searchable.update_attributes(content: "#{title} #{author}", title: title, user: user.username)
  end
end

class SearchController
  def search
    @result = SearchResult.where("content like ?", "%#{params[:q]}%")
    if params[:user]
      @result = @result.where(user: params[:user])
    end
  end
end

Finally in view you can render the actual results, i.e.

= render @result.map(&:searchable)

(rails will render correct partial for each element, whatever it's book, magazine or anything, it will look for views/magazines/_magazine, views/books/_book)

You can extend the search with extra columns to use them as additional filters (here i've added user) and you don't pollute your models with extra search logic. How will you handle the searchable content is up to you (here is just a primitive example). You can split it into separate columns (ie. search_field1, search_field2, search_field3), you can tokenize the input or build a fulltext index.

you can store a permalink in SearchResult or you can generate in on the fly. You may have some issues with rails path helpers on polymorphic associations, but those are easy to overcome.

If you have postgres as database i advise you to take a look on pg_search, it's simple and brilliant solution: https://github.com/Casecommons/pg_search and should give you better understanding on this topic.

The above code may not work, I didn't check it against Ruby/Rails, just trying to show an idea.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.