Skip to content

47colborne/cloud-sesame

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CloudSesame

Powerful and Flexible AWS CloudSearch Query DSL

Build Status Gem Version Code Climate Test Coverage

Installation

  • In terminal type:
gem install CloudSesame
  • Or add this line to the application in Gemfile:
gem 'CloudSesame'

AWS Credentials

  • Create a initializer file, example: config/initializers/cloud_sesame.rb
require 'cloud_sesame'

CloudSesame::Domain::Client.configure do |config|
  config.access_key = ENV['AWS_ACCESS_KEY_ID']
  config.secret_key = ENV['AWS_SECRET_ACCESS_KEY']
end

Model Setup

  • include CloudSesame in a model or class
  • define_cloudsearch with a block to setup class/modal specific setting

define_cloudsearch(&block)

Includes all Model/Class specific cloudsearch configurations

class Product
  include CloudSesame

  define_cloudsearch do 
    ...
  end
end

config.endpoint=(string)

Set AWS CloudSearch instance search endpoint

config.region=(string)

Set AWS CloudSearch isntance region

class Product
  include CloudSesame

  define_cloudsearch do 
    config.endpoint = ENV[...]
    config.region 	= ENV[...]
  end
end

default_size(integer = 10)

Set default search size

class Product
  include CloudSesame

  define_cloudsearch do 
    default_size 100
  end
end

define_sloppiness(integer)

Setup sloppy query, it is turned off by default

class Product
  include CloudSesame

  define_cloudsearch do 
    define_sloppiness 3
  end
end

define_fuzziness(&block)

Setup fuzziness, it is turned off by default. the block can set 3 values

  • max_fuzziness(integer = 3) maxinmum fuzziness per word
  • min_char_size(integer = 6) minimum word length to trigger fuzziness
  • fuzzy_percent(float = 0.17) percent used to calculate the fuzziness based on the word length, fuzziness whill choose between the calculated result and maximum fizziness, whichever is smaller. [(word.size * fuzzy_percent).round, max_fuzziness].min
class Product
  include CloudSesame

  define_cloudsearch do 
    define_fuzziness do
      max_fuzziness 3
      min_char_size 6
      fuzzy_percent 0.17
    end
  end
end

field(symbol, options = {})

calling field and pass in a field_name will create an field expression accessor

  • simple usage
field :name

and field expression accessor can be called to create a field expression

Product.cloudsearch.name("user")

{ filter_query: "name:'user'" }
  • aliased field name
field :text1, as: :name

and field expression accessor method name will be name

Product.cloudsearch.name("user")

{ filter_query: "text1:'user'" }
  • with query options is set to true
field :name, query: true			

{ query_options: { fields: ['name'] } }
  • with weight assigned to query options
field :tags, query: { weight: 2 }

{ query_options[:fields] = ['name', 'tags^2'] }
  • with facet options passed in
field :currency, facet: true

{ facets: { currency:{} } }
  • with facet buckets
field :discount, facet: { 				
  buckets: %w([10,100] [25,100] [50,100] [70,100]), 
  method: 'interval' 
}

{ facets: { discount: { buckets:["[10,100]","[25,100]","[50,100]","[70,100]"], method:"interval"} } }
  • with facet size set
field :manufacturer, facet: { size: 50 }
  • with facet sorting
field :category, facet: { sort: 'bucket', size: 10_000 }	

scope(symbol, proc, &block)

ActiveRecord styled scope method. Scope allows you to specify commonly-used queries which can be referenced as method calls on cloudsearch or inside of operator block set scope

  ...
  define_cloudsearch do 
    ....

    scope :popular, -> { or! { tags "popular"; popularity gt(70) } }
  end

call a scope

Product.cloudsearch.query("shoes").popular

Full Example

class Product < ActiveRecord::Base
  include CloudSesame

  define_cloudsearch do 
    config.endpoint = ENV['AWS_PRODUCT_SEARCH_ENDPOINT']
    config.region 	= ENV['AWS_PRODUCT_SEARCH_REGION']

    default_size <integer>

    define_sloppiness <integer>

    define_fuzziness {
      max_fuzziness <integer>
      min_char_size <integer>
      fuzzy_percent <float>
    }

    field :product_name,	query: { weight: <integer> }
    field :description,		query: true

    field :currency,		facet: true		
    field :discount,	 	facet: { 
      buckets: %w([10,100] [25,100] [50,100] [70,100]), 
      method: 'interval' 
    }
    field :manufacturer,	facet: { size: <integer> }
    field :category,		facet: { 
      sort: 'bucket', size: <integer> 
    }

    field :created_at

    scope :shoes_by_brand, ->(brand = nil) { query("shoes").and { manufacturer brand } if brand }

    scope :created_in, ->(days = nil) { and { created_at r.gt(Date.today - days) } if days }
  end
end

Inherit cloudsearch definition from another class/model

load_definition_from(Class/Model)

class ExclusiveProduct < Product
  load_definition_from Product
end

definition can be overrided by calling #define_cloudsearch again

define_cloudsearch {
  field :name, as: :product_name
}

Query DSL

  • .cloudsearch returns a CloudSesame::Domain::Base instance. It is the entry point to start building up the query
  • A CloudSesame::Query::Builder instance is expected to return from any query methods chained after .cloudsearch
Product.cloudsearch #=> <CloudSesame::Domain::Base/>
query = Product.cloudsearch.query("shoes") => <CloudSesame::Query::Builder query:'shoes'/>

Query methods can be chained and build up

query = Product.cloudsearch.query("shoes")
  .and { 
    manufacturer('puma')
    or!.not {
      manufacturer "nike", "adidas"
    }
  }
  .sort(price: :desc)
  .page(2)
  .size(100)
}

and query.compile will output the compiled hash

{
  query:'shoes',
  filter_query: "(and manufacturer:'puma' (not (or manufacturer:'nike' manufacturer:'adidas')))",
  sort: "price desc"
  start: 100
  size: 100
}

Simple Query

# Simple Query String
Product.cloudsearch.query("shoes")				
# OUTPUT: "shoes"

Product.cloudsearch.query("shoes -puma")			
# OUTPUT: "shoes -puma"

# With Sloppy Query
Product.cloudsearch.query("shoes")					
# OUTPUT: "(shoes|\"shoes\"~3)"

Product.cloudsearch.query("shoes -puma")			
# OUTPUT: "(shoes -puma|\"shoes -puma\"~3)"

# With Fuzzy Search
Product.cloudsearch.query("white shoes")			
# OUTPUT: "(shoes|(white~3+shoes~3))"

Product.cloudsearch.query("white shoes -puma")		
# OUTPUT: "(shoes -puma|(white~3+shoes~3+-puma))"

Pagination

Product.cloudsearch.page(3).size(100)				
# OUTPUT: { start: 200, size: 100 }

Product.cloudsearch.start(99).size(100)				
# OUTPUT: { start: 99, size: 100 }

Sorting

Product.cloudsearch.sort(name: :desc)				
# OUTPUT: { sort: "name desc" }

Product.cloudsearch.sort(name: :desc, price: :asc)	
# OUTPUT: { sort: "name desc,price asc" }

Return

Product.cloudsearch.all_fields						
# OUTPUT: { return: "_all_fields" }

Product.cloudsearch.no_fields						
# OUTPUT: { return: "_no_fields" }

Product.cloudsearch.score							
# OUTPUT: { return: "_score" }

Scope

Product.coudsearch.shoes_by_brand("puma")			
# OUTPUT: { query:"shoes" filter_query:"(and manufacturer:'puma')" }

Product.cloudsearch.created_in(7)					
# OUTPUT: { filter_query:"(and created_at:{Date.today - 7,}) }

Product.cloudsearch.or { 							
  created_in(7)									
  discount 25..100							
}	
# OUTPUT: { filter_query:"(or (and created_at:{Date.today - 7,}) discount:{25,100] }

AND & OR Block

Product.cloudsearch.and { ... }					
# OUTPUT: "(and ...)"

Product.cloudsearch.and {						
  or! { ... }									
  or! { ... }									 
}
# OUTPUT: "(and (or ...) (or ...))"
# NOTE: AND & OR are a ruby syntax, can not be used directly,
# so aliased them to (and!, all) and (or!, any)

Product.cloudsearch.and.not { ... }				
# OUTPUT: "(not (and ...))"

Product.cloudsearch.or { and!.not { ...} }		
# OUTPUT: "(or (not (and ...)))"

Field Methods

field_name(*values)
  • calling the field_name with multiple values will generate multiple field expression
  • fields can be chained together
Product.cloudsearch.name("shoes")				
# OUTPUT: "name:'shoes'"

Product.cloudsearch.name "shoes", "sneaker"
# OUTPUT: name:'shoes' name:'sneaker'

Product.cloudsearch.name("shoes").price(100)	
# OUTPUT: "(and name:'shoes' price:100)"

Product.cloudsearch.and { name "shoes"; price 25..100 }
# OUTPUT: "(and name:'shoes' price:[25,100])"
field_name.field_array_method(*values)
  • #not, #prefix (#start_with, #begin_with), #near can be called after the field_name
  • #not can be chained with another operator after, example: name.not.prefix
Product.cloudsearch.and { name.not "shoes"; ... }
# OUTPUT: "(and (not name:'shoes') ...)"

Product.cloudsearch.and { name.start_with "shoes" }
# OUTPUT: "(and (prefix field='name' 'shoes'))"

Product.cloudsearch.and { name.near "shoes" }
# OUTPUT: "(and (near field='name' 'shoes'))"

Product.cloudsearch.and { name.not.start_with "shoes" }
# OUTPUT: "(and (not (near field='name' 'shoes')))"

Product.cloudsearch.and { name(start_with("shoes"), near("puma")).not("nike") }
# OUTPUT: "(and (prefix field='name' 'shoes') (near field='name' 'puma') (not name:'nike'))"
  • #prefix (#start_with, #begin_with), #near can be called directly to generate a single field value
Product.cloudsearch.and { name.not start_with("shoes"), near("something") } 
# OUTPUT: "(not (prefix field='name' 'shoes') (not (near field='name' 'something')))"

Date, Time and Rage

  • field method accepts Ruby Date and Range Object and will automatically parse them into the CloudSearch format
Date.today	=> "'2016-01-18T00:00:00Z'"
Time.now	=> "'2016-01-18T14:36:57Z'"
25..100 	=> "[25,100]"
25...100	=> "[25,100}"
  • use #range or #r for more complicated range, range object accepts Date/Time object as well
r.gt(100)		=> "{100,}"
r.gte(100)		=> "[100,}"
r.lt(100)		=> "{,100}"
r.lte(100)		=> "{,100]"
r.gte(100).lt(200)	=> "[100,200}"
r.gt(Date.today)	=> "{'2016-01-18T00:00:00Z',}"

Search Related Methods

  • #search => send off the request, save and returns the response and clear the request
  • #found => returns the hits found from the response
  • #results => returns the hits.hit from the response
  • #each => Calls the given block once for each result, passing that result as a parameter. Returns the results itself.
  • #map => Creates a new array containing the results returned by the block.
  • #request => returns the cloudsearch ast tree
  • #response => returns the last response
  • #clear_request => resets the request
  • #clear_reponse => resets the response

Other Methods

  • #compile => compiles the current query and return the compiled hash

Releases

No releases published

Packages

No packages published

Languages