Skip to content

Instantly share code, notes, and snippets.

@julianalucena
Last active August 29, 2015 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save julianalucena/34246b0c837fd163cc0f to your computer and use it in GitHub Desktop.
Save julianalucena/34246b0c837fd163cc0f to your computer and use it in GitHub Desktop.
module TextSearch
class BaseSearch
def initialize
@filters = {}
@page = 1
@per_page = Search::Application.config.results_count
@term = nil
end
class << self
def search(term = nil)
new.search(term)
end
end
def search(term = nil)
raise NotImplementedError, 'You must implement #search'
end
def page(number)
@page = number.to_i if number
self
end
def per_page(qty)
@per_page = qty.to_i if qty
self
end
def order(ordering)
return self if ordering.blank?
attribute, order = ordering.split(' ')
@search.sort { by attribute, order.downcase }
self
end
def results
@search.filter :and, @filters.values unless @filters.empty?
@search.size(@per_page)
@search.from(from_page)
@search.results
end
private
attr_reader :filters
def from_page
(@page - 1) * @per_page
end
end
end
module TextSearch
class BookSearch < BaseSearch
def search(term, country: 'BR', **options)
@search = Tire.search(search_indexes) do |s|
s.query do
boolean do
should { match :description, term, operator: "AND", boost: 5 }
should { match :'description.partial', term, operator: 'AND', boost: 4 }
should { match :'description.partial_middle', term, operator: 'AND', boost: 2 }
should { match :'description.partial_back', term, operator: 'AND', boost: 4 }
end
end
end
Filters::InactiveFilter.apply!(filters, false)
Filters::CountryFilter.apply!(filters, country)
self
end
def filter(conditions)
Filters::PriceFilter.apply!(filters, conditions)
Filters::CategoryFilter.apply!(filters, conditions)
Filters::PublisherFilter.apply!(filters, conditions)
self
end
def with_facets
Facets::PriceStatisticsFacet.apply!(@search, filters)
Facets::CategoryFacet.apply!(@search, filters)
Facets::PublisherFacet.apply!(@search, filters)
self
end
private
def search_indexes
[Book.index_name]
end
end
end
require 'spec_helper'
describe TextSearch::BookSearch do
describe "#search", elasticsearch: true do
it "return self" do
expect(subject.search('term')).to eq(subject)
end
context do
let!(:book) { FactoryGirl.create(:book) }
before { refresh_index_for Book }
after { reset_index_for Book}
it "matches with book's name" do
results = subject.search(book.name).results
expect(results).to have(1).item
expect(results.first.name).to eq(book.name)
end
context do
let(:book) { FactoryGirl.create(:book, name: 'lorem ipsumm dorem') }
it "matches with book's beggining partial name" do
results = subject.search('lorem').results
expect(results).to have(1).item
expect(results.first.name).to eq(book.name)
end
it "matches with book's ending partial name" do
results = subject.search('dorem').results
expect(results).to have(1).item
expect(results.first.name).to eq(book.name)
end
it "matches with book's middle partial name" do
results = subject.search('ipsumm').results
expect(results).to have(1).item
expect(results.first.name).to eq(book.name)
end
end
end
describe "default filters" do
describe "inactive filter" do
it "applies InactiveFilter with flag: true" do
expect(TextSearch::Filters::ActiveFilter).to receive(:apply!).
with(an_instance_of(Hash), true)
subject.search('term')
end
end
describe 'country filter' do
let(:country) { 'Country' }
it "applies CountryFilter with passed country" do
expect(TextSearch::Filters::CountryFilter).to receive(:apply!).
with(an_instance_of(Hash), country)
subject.search('term', country: country)
end
end
end
end
describe "#filter", elasticsearch: true do
let(:conditions) { double(Hash, :[] => nil) }
describe "price filter" do
it "applies PriceFilter with passed conditions" do
expect(TextSearch::Filters::PriceFilter).to receive(:apply!).
with(an_instance_of(Hash), conditions)
subject.search('term').filter(conditions)
end
end
describe "category filter" do
it "applies CategoryFilter with passed conditions" do
expect(TextSearch::Filters::CategoryFilter).to receive(:apply!).
with(an_instance_of(Hash), conditions)
subject.search('term').filter(conditions)
end
end
describe 'publisher filter' do
it "applies PublisherFilter with passed conditions" do
expect(TextSearch::Filters::PublisherFilter).to receive(:apply!).
with(an_instance_of(Hash), conditions)
subject.search('term').filter(conditions)
end
end
end
describe '#with_facets', elasticsearch: true do
describe "publisher facet" do
it "applies PublisherFacet" do
expect(TextSearch::Facets::PublisherFacet).to receive(:apply!)
subject.search('term').with_facets
end
end
describe "price_statistics facet" do
it "applies PriceStatisticsFacet" do
expect(TextSearch::Facets::PriceStatisticsFacet).to receive(:apply!)
subject.search('term').with_facets
end
end
describe 'category facet' do
it "applies CategoryFacet" do
expect(TextSearch::Facets::CategoryFacet).to receive(:apply!)
subject.search('term').with_facets
end
end
end
end
module TextSearch
module Facets
class CategoryFacet
class << self
def apply!(*args)
new(*args).apply!
end
end
def initialize(search, filters)
@search = search
@filters = filters
end
def apply!
facet_filters = filters.except(:categories_ids)
search.facet :category_id do
terms :categories_ids, size: 100, all_terms: false
unless facet_filters.empty?
facet_filter :and, facet_filters.values
end
end
end
private
attr_reader :search, :filters
end
end
end
require 'spec_helper'
describe TextSearch::Facets::CategoryFacet, elasticsearch: true do
include TextSearchHelpers
subject do
text_search_for(Book).add_facets do |search, filters|
described_class.apply!(search, filters)
end
end
after { reset_index_for Book }
let(:facets) { subject.with_facets.results.facets }
let(:facet) { facets['category_id'] }
let!(:book) { FactoryGirl.create(:book, category: category) }
let(:category) { FactoryGirl.create(:category) }
before { refresh_index_for Book }
it "has category_id facet" do
expect(facets).to have_key('category_id')
end
it "has qty of books per category" do
expect(facet['terms']).to have(2).items
categories_ids = facet['terms'].map { |f| f['term'] }
expect(categories_ids).to match_array([category.id])
quantities = facet['terms'].map { |f| f['count'] }
expect(quantities).to match_array([1])
end
context "when there are filters applied to the search" do
let(:facets) { subject.filter(id: book.id).with_facets.results.facets }
it "applies the filters to the facet" do
FactoryGirl.create(:book)
refresh_index_for Book
subject.add_filters do |filters, conditions|
filters[:id] = { term: conditions }
end
expect(facet['terms']).to have(2).items
end
it "does not apply category filter to the facet" do
subject.add_filters do |filters, _|
filters[:categories_ids] = { term: { categories_ids: category.id } }
end
expect(facet['terms']).to have(2).items
end
end
end
module TextSearch
module Filters
class CategoryFilter
class << self
def apply!(*args)
new(*args).apply!
end
end
def initialize(filters, conditions)
@filters = filters
@category_id = conditions[:category_id]
end
def apply!
return if category_id.blank?
filters[:categories_ids] = {
term: { categories_ids: category_id }
}
end
private
attr_reader :category_id
attr_accessor :filters
end
end
end
require 'spec_helper'
describe TextSearch::Filters::CategoryFilter, elasticsearch: true do
include TextSearchHelpers
subject do
text_search_for(Book).add_filters do |filters, conditions|
described_class.apply!(filters, conditions)
end
end
after { reset_index_for Book }
let(:category) { FactoryGirl.create(:category) }
let!(:book) { FactoryGirl.create(:book, category: category) }
before do
FactoryGirl.create(:book)
refresh_index_for Book
end
it "returns books that belongs to specified category" do
results = subject.filter(category_id: category.id).results
expect(results.count).to eq(1)
expect(results.first.id).to eq(book.id.to_s)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment