Commit da2b7e143ad480faf2d2cbd5b1efb9bf661bf734

Authored by Rustam Sharshenov
2 parents ddc3a544 9f418251

Merge branch 'master' of github.com:ankane/searchkick into include-search-data

CHANGELOG.md
  1 +## 0.8.4
  2 +
  3 +- Added `boost_by_distance`
  4 +- More flexible highlight options
  5 +- Better `env` logic
  6 +
1 7 ## 0.8.3
2 8  
3 9 - Added support for ActiveJob
... ...
README.md
... ... @@ -124,7 +124,7 @@ Boost important fields
124 124 fields: ["title^10", "description"]
125 125 ```
126 126  
127   -Boost by the value of a field
  127 +Boost by the value of a field (field must be numeric)
128 128  
129 129 ```ruby
130 130 boost_by: [:orders_count] # give popular documents a little boost
... ... @@ -553,7 +553,7 @@ Highlight the search query in the results.
553 553 bands = Band.search "cinema", fields: [:name], highlight: true
554 554 ```
555 555  
556   -**Note:** The `fields` option is required.
  556 +**Note:** The `fields` option is required, unless highlight options are given - see below.
557 557  
558 558 View the highlighted fields with:
559 559  
... ... @@ -569,6 +569,20 @@ To change the tag, use:
569 569 Band.search "cinema", fields: [:name], highlight: {tag: "<strong>"}
570 570 ```
571 571  
  572 +To highlight and search different fields, use:
  573 +
  574 +```ruby
  575 +Band.search "cinema", fields: [:name], highlight: {fields: [:description]}
  576 +```
  577 +
  578 +Additional options, including fragment size, can be specified for each field:
  579 +
  580 +```ruby
  581 +Band.search "cinema", fields: [:name], highlight: {fields: {name: {fragment_size: 200}}}
  582 +```
  583 +
  584 +You can find available highlight options in the [Elasticsearch reference](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-highlighting.html#_highlighted_fragments).
  585 +
572 586 ### Similar Items
573 587  
574 588 Find similar items.
... ... @@ -602,6 +616,20 @@ Bounded by a box
602 616 City.search "san", where: {location: {top_left: [38, -123], bottom_right: [37, -122]}}
603 617 ```
604 618  
  619 +### Boost By Distance
  620 +
  621 +Boost results by distance - closer results are boosted more
  622 +
  623 +```ruby
  624 +City.search "san", boost_by_distance: {field: :location, origin: [37, -122]}
  625 +```
  626 +
  627 +Also supports [additional options](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_decay_functions)
  628 +
  629 +```ruby
  630 +City.search "san", boost_by_distance: {field: :location, origin: [37, -122], function: :linear, scale: "30mi", decay: 0.5}
  631 +```
  632 +
605 633 ## Inheritance
606 634  
607 635 Searchkick supports single table inheritance.
... ... @@ -703,15 +731,20 @@ rake searchkick:reindex CLASS=Product
703 731  
704 732 ### Performance
705 733  
706   -For the best performance, add [Patron](https://github.com/toland/patron) to your Gemfile.
  734 +For the best performance, add [Typhoeus](https://github.com/typhoeus/typhoeus) to your Gemfile.
707 735  
708 736 ```ruby
709   -gem 'patron'
  737 +gem 'typhoeus'
710 738 ```
711 739  
712   -Searchkick will automatically use it.
  740 +And create an initializer with:
  741 +
  742 +```ruby
  743 +require "typhoeus/adapters/faraday"
  744 +Ethon.logger = Logger.new("/dev/null")
  745 +```
713 746  
714   -**Note:** Patron is not available for Windows.
  747 +**Note:** Typhoeus is not available for Windows.
715 748  
716 749 ### Automatic Failover
717 750  
... ...
lib/searchkick.rb
... ... @@ -63,6 +63,10 @@ module Searchkick
63 63 def self.callbacks?
64 64 callbacks
65 65 end
  66 +
  67 + def self.env
  68 + @env ||= ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
  69 + end
66 70 end
67 71  
68 72 # TODO find better ActiveModel hook
... ...
lib/searchkick/model.rb
... ... @@ -5,15 +5,14 @@ module Searchkick
5 5 raise "Only call searchkick once per model" if respond_to?(:searchkick_index)
6 6  
7 7 class_eval do
8   - cattr_reader :searchkick_options, :searchkick_env, :searchkick_klass
  8 + cattr_reader :searchkick_options, :searchkick_klass
9 9  
10 10 callbacks = options.has_key?(:callbacks) ? options[:callbacks] : true
11 11  
12 12 class_variable_set :@@searchkick_options, options.dup
13   - class_variable_set :@@searchkick_env, ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
14 13 class_variable_set :@@searchkick_klass, self
15 14 class_variable_set :@@searchkick_callbacks, callbacks
16   - class_variable_set :@@searchkick_index, options[:index_name] || [options[:index_prefix], model_name.plural, searchkick_env].compact.join("_")
  15 + class_variable_set :@@searchkick_index, options[:index_name] || [options[:index_prefix], model_name.plural, Searchkick.env].compact.join("_")
17 16  
18 17 def self.searchkick_index
19 18 index = class_variable_get :@@searchkick_index
... ...
lib/searchkick/query.rb
... ... @@ -209,6 +209,21 @@ module Searchkick
209 209 }
210 210 end
211 211  
  212 + boost_by_distance = options[:boost_by_distance]
  213 + if boost_by_distance
  214 + boost_by_distance = {function: :gauss, scale: "5mi"}.merge(boost_by_distance)
  215 + if !boost_by_distance[:field] or !boost_by_distance[:origin]
  216 + raise ArgumentError, "boost_by_distance requires :field and :origin"
  217 + end
  218 + function_params = boost_by_distance.select{|k,v| [:origin, :scale, :offset, :decay].include?(k) }
  219 + function_params[:origin] = function_params[:origin].reverse
  220 + custom_filters << {
  221 + boost_by_distance[:function] => {
  222 + boost_by_distance[:field] => function_params
  223 + }
  224 + }
  225 + end
  226 +
212 227 if custom_filters.any?
213 228 payload = {
214 229 function_score: {
... ... @@ -320,9 +335,21 @@ module Searchkick
320 335 payload[:highlight] = {
321 336 fields: Hash[ fields.map{|f| [f, {}] } ]
322 337 }
323   - if options[:highlight].is_a?(Hash) and tag = options[:highlight][:tag]
324   - payload[:highlight][:pre_tags] = [tag]
325   - payload[:highlight][:post_tags] = [tag.to_s.gsub(/\A</, "</")]
  338 +
  339 + if options[:highlight].is_a?(Hash)
  340 + if tag = options[:highlight][:tag]
  341 + payload[:highlight][:pre_tags] = [tag]
  342 + payload[:highlight][:post_tags] = [tag.to_s.gsub(/\A</, "</")]
  343 + end
  344 +
  345 + highlight_fields = options[:highlight][:fields]
  346 + if highlight_fields
  347 + payload[:highlight][:fields] = {}
  348 +
  349 + highlight_fields.each do |name, opts|
  350 + payload[:highlight][:fields]["#{name}.analyzed"] = opts || {}
  351 + end
  352 + end
326 353 end
327 354 end
328 355  
... ...
lib/searchkick/reindex.rb
... ... @@ -203,7 +203,7 @@ module Searchkick
203 203 }
204 204 }
205 205  
206   - if searchkick_env == "test"
  206 + if Searchkick.env == "test"
207 207 settings.merge!(number_of_shards: 1, number_of_replicas: 0)
208 208 end
209 209  
... ...
lib/searchkick/results.rb
... ... @@ -26,17 +26,7 @@ module Searchkick
26 26 if options[:includes]
27 27 records = records.includes(options[:includes])
28 28 end
29   - results[type] =
30   - if records.respond_to?(:primary_key) and records.primary_key
31   - # ActiveRecord
32   - records.where(records.primary_key => grouped_hits.map{|hit| hit["_id"] }).to_a
33   - elsif records.respond_to?(:all) and records.all.respond_to?(:for_ids)
34   - # Mongoid 2
35   - records.all.for_ids(grouped_hits.map{|hit| hit["_id"] }).to_a
36   - else
37   - # Mongoid 3+
38   - records.queryable.for_ids(grouped_hits.map{|hit| hit["_id"] }).to_a
39   - end
  29 + results[type] = results_query(records, grouped_hits)
40 30 end
41 31  
42 32 # sort
... ... @@ -143,5 +133,21 @@ module Searchkick
143 133 @response["hits"]["hits"]
144 134 end
145 135  
  136 + private
  137 +
  138 + def results_query(records, grouped_hits)
  139 + if records.respond_to?(:primary_key) and records.primary_key
  140 + # ActiveRecord
  141 + records.where(records.primary_key => grouped_hits.map{|hit| hit["_id"] }).to_a
  142 + elsif records.respond_to?(:all) and records.all.respond_to?(:for_ids)
  143 + # Mongoid 2
  144 + records.all.for_ids(grouped_hits.map{|hit| hit["_id"] }).to_a
  145 + elsif records.respond_to?(:queryable)
  146 + # Mongoid 3+
  147 + records.queryable.for_ids(grouped_hits.map{|hit| hit["_id"] }).to_a
  148 + else
  149 + raise "Not sure how to load records"
  150 + end
  151 + end
146 152 end
147 153 end
... ...
lib/searchkick/version.rb
1 1 module Searchkick
2   - VERSION = "0.8.3"
  2 + VERSION = "0.8.4"
3 3 end
... ...
test/boost_test.rb
... ... @@ -104,4 +104,13 @@ class TestBoost &lt; Minitest::Test
104 104 assert_first "tomato", "Tomato B", boost_where: {user_ids: {value: 2, factor: 10}}
105 105 end
106 106  
  107 + def test_boost_by_distance
  108 + store [
  109 + {name: "San Francisco", latitude: 37.7833, longitude: -122.4167},
  110 + {name: "San Antonio", latitude: 29.4167, longitude: -98.5000},
  111 + {name: "San Marino", latitude: 43.9333, longitude: 12.4667}
  112 + ]
  113 + assert_order "san", ["San Francisco", "San Antonio", "San Marino"], boost_by_distance: {field: :location, origin: [37, -122], scale: "1000mi"}
  114 + end
  115 +
107 116 end
... ...
test/highlight_test.rb
... ... @@ -19,6 +19,18 @@ class TestHighlight &lt; Minitest::Test
19 19 assert_equal "<em>Cinema</em> Orange", highlight[:color]
20 20 end
21 21  
  22 + def test_fields
  23 + store [{name: "Two Door Cinema Club", color: "Cinema Orange"}]
  24 + highlight = Product.search("cinema", fields: [:name, :color], highlight: {fields: [:name]}).with_details.first[1][:highlight]
  25 + assert_equal "Two Door <em>Cinema</em> Club", highlight[:name]
  26 + assert_equal nil, highlight[:color]
  27 + end
  28 +
  29 + def test_field_options
  30 + store_names ["Two Door Cinema Club are a Northern Irish indie rock band"]
  31 + assert_equal "Two Door <em>Cinema</em> Club are", Product.search("cinema", fields: [:name], highlight: {fields: {name: {fragment_size: 20}}}).with_details.first[1][:highlight][:name]
  32 + end
  33 +
22 34 def test_multiple_words
23 35 store_names ["Hello World Hello"]
24 36 assert_equal "<em>Hello</em> World <em>Hello</em>", Product.search("hello", fields: [:name], highlight: true).with_details.first[1][:highlight][:name]
... ...