Commit da2b7e143ad480faf2d2cbd5b1efb9bf661bf734

Authored by Rustam Sharshenov
2 parents ddc3a544 9f418251

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

  1 +## 0.8.4
  2 +
  3 +- Added `boost_by_distance`
  4 +- More flexible highlight options
  5 +- Better `env` logic
  6 +
1 ## 0.8.3 7 ## 0.8.3
2 8
3 - Added support for ActiveJob 9 - Added support for ActiveJob
@@ -124,7 +124,7 @@ Boost important fields @@ -124,7 +124,7 @@ Boost important fields
124 fields: ["title^10", "description"] 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 ```ruby 129 ```ruby
130 boost_by: [:orders_count] # give popular documents a little boost 130 boost_by: [:orders_count] # give popular documents a little boost
@@ -553,7 +553,7 @@ Highlight the search query in the results. @@ -553,7 +553,7 @@ Highlight the search query in the results.
553 bands = Band.search "cinema", fields: [:name], highlight: true 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 View the highlighted fields with: 558 View the highlighted fields with:
559 559
@@ -569,6 +569,20 @@ To change the tag, use: @@ -569,6 +569,20 @@ To change the tag, use:
569 Band.search "cinema", fields: [:name], highlight: {tag: "<strong>"} 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 ### Similar Items 586 ### Similar Items
573 587
574 Find similar items. 588 Find similar items.
@@ -602,6 +616,20 @@ Bounded by a box @@ -602,6 +616,20 @@ Bounded by a box
602 City.search "san", where: {location: {top_left: [38, -123], bottom_right: [37, -122]}} 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 ## Inheritance 633 ## Inheritance
606 634
607 Searchkick supports single table inheritance. 635 Searchkick supports single table inheritance.
@@ -703,15 +731,20 @@ rake searchkick:reindex CLASS=Product @@ -703,15 +731,20 @@ rake searchkick:reindex CLASS=Product
703 731
704 ### Performance 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 ```ruby 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 ### Automatic Failover 749 ### Automatic Failover
717 750
lib/searchkick.rb
@@ -63,6 +63,10 @@ module Searchkick @@ -63,6 +63,10 @@ module Searchkick
63 def self.callbacks? 63 def self.callbacks?
64 callbacks 64 callbacks
65 end 65 end
  66 +
  67 + def self.env
  68 + @env ||= ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
  69 + end
66 end 70 end
67 71
68 # TODO find better ActiveModel hook 72 # TODO find better ActiveModel hook
lib/searchkick/model.rb
@@ -5,15 +5,14 @@ module Searchkick @@ -5,15 +5,14 @@ module Searchkick
5 raise "Only call searchkick once per model" if respond_to?(:searchkick_index) 5 raise "Only call searchkick once per model" if respond_to?(:searchkick_index)
6 6
7 class_eval do 7 class_eval do
8 - cattr_reader :searchkick_options, :searchkick_env, :searchkick_klass 8 + cattr_reader :searchkick_options, :searchkick_klass
9 9
10 callbacks = options.has_key?(:callbacks) ? options[:callbacks] : true 10 callbacks = options.has_key?(:callbacks) ? options[:callbacks] : true
11 11
12 class_variable_set :@@searchkick_options, options.dup 12 class_variable_set :@@searchkick_options, options.dup
13 - class_variable_set :@@searchkick_env, ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"  
14 class_variable_set :@@searchkick_klass, self 13 class_variable_set :@@searchkick_klass, self
15 class_variable_set :@@searchkick_callbacks, callbacks 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 def self.searchkick_index 17 def self.searchkick_index
19 index = class_variable_get :@@searchkick_index 18 index = class_variable_get :@@searchkick_index
lib/searchkick/query.rb
@@ -209,6 +209,21 @@ module Searchkick @@ -209,6 +209,21 @@ module Searchkick
209 } 209 }
210 end 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 if custom_filters.any? 227 if custom_filters.any?
213 payload = { 228 payload = {
214 function_score: { 229 function_score: {
@@ -320,9 +335,21 @@ module Searchkick @@ -320,9 +335,21 @@ module Searchkick
320 payload[:highlight] = { 335 payload[:highlight] = {
321 fields: Hash[ fields.map{|f| [f, {}] } ] 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 end 353 end
327 end 354 end
328 355
lib/searchkick/reindex.rb
@@ -203,7 +203,7 @@ module Searchkick @@ -203,7 +203,7 @@ module Searchkick
203 } 203 }
204 } 204 }
205 205
206 - if searchkick_env == "test" 206 + if Searchkick.env == "test"
207 settings.merge!(number_of_shards: 1, number_of_replicas: 0) 207 settings.merge!(number_of_shards: 1, number_of_replicas: 0)
208 end 208 end
209 209
lib/searchkick/results.rb
@@ -26,17 +26,7 @@ module Searchkick @@ -26,17 +26,7 @@ module Searchkick
26 if options[:includes] 26 if options[:includes]
27 records = records.includes(options[:includes]) 27 records = records.includes(options[:includes])
28 end 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 end 30 end
41 31
42 # sort 32 # sort
@@ -143,5 +133,21 @@ module Searchkick @@ -143,5 +133,21 @@ module Searchkick
143 @response["hits"]["hits"] 133 @response["hits"]["hits"]
144 end 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 end 152 end
147 end 153 end
lib/searchkick/version.rb
1 module Searchkick 1 module Searchkick
2 - VERSION = "0.8.3" 2 + VERSION = "0.8.4"
3 end 3 end
test/boost_test.rb
@@ -104,4 +104,13 @@ class TestBoost &lt; Minitest::Test @@ -104,4 +104,13 @@ class TestBoost &lt; Minitest::Test
104 assert_first "tomato", "Tomato B", boost_where: {user_ids: {value: 2, factor: 10}} 104 assert_first "tomato", "Tomato B", boost_where: {user_ids: {value: 2, factor: 10}}
105 end 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 end 116 end
test/highlight_test.rb
@@ -19,6 +19,18 @@ class TestHighlight &lt; Minitest::Test @@ -19,6 +19,18 @@ class TestHighlight &lt; Minitest::Test
19 assert_equal "<em>Cinema</em> Orange", highlight[:color] 19 assert_equal "<em>Cinema</em> Orange", highlight[:color]
20 end 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 def test_multiple_words 34 def test_multiple_words
23 store_names ["Hello World Hello"] 35 store_names ["Hello World Hello"]
24 assert_equal "<em>Hello</em> World <em>Hello</em>", Product.search("hello", fields: [:name], highlight: true).with_details.first[1][:highlight][:name] 36 assert_equal "<em>Hello</em> World <em>Hello</em>", Product.search("hello", fields: [:name], highlight: true).with_details.first[1][:highlight][:name]