Commit 29ab35b496ce3ec09572a600397de945492cceb6
Exists in
master
and in
21 other branches
Merge remote-tracking branch 'upstream/master' into adv_facets
Showing
18 changed files
with
465 additions
and
194 deletions
Show diff stats
CHANGELOG.md
1 | +## 0.7.1 | ||
2 | + | ||
3 | +- Fixed huge issue w/ zero-downtime reindexing on 0.90 | ||
4 | + | ||
5 | +## 0.7.0 | ||
6 | + | ||
7 | +- Added support for Elasticsearch 1.1 | ||
8 | +- Dropped support for Elasticsearch below 0.90.4 (unfortunate side effect of above) | ||
9 | + | ||
10 | +## 0.6.3 | ||
11 | + | ||
12 | +- Removed patron since no support for Windows | ||
13 | +- Added error if `searchkick` is called multiple times | ||
14 | + | ||
15 | +## 0.6.2 | ||
16 | + | ||
17 | +- Added logging | ||
18 | +- Fixed index_name option | ||
19 | +- Added ability to use proc as the index name | ||
20 | + | ||
21 | +## 0.6.1 | ||
22 | + | ||
23 | +- Fixed huge issue w/ zero-downtime reindexing on 0.90 and elasticsearch-ruby 1.0 | ||
24 | +- Restore load: false behavior | ||
25 | +- Restore total_entries method | ||
26 | + | ||
27 | +## 0.6.0 | ||
28 | + | ||
29 | +- Moved to elasticsearch-ruby | ||
30 | +- Added support for modifying the query and viewing the response | ||
31 | +- Added support for page_entries_info method | ||
32 | + | ||
1 | ## 0.5.3 | 33 | ## 0.5.3 |
2 | 34 | ||
3 | - Fixed bug w/ word_* queries | 35 | - Fixed bug w/ word_* queries |
README.md
@@ -147,7 +147,7 @@ Product.search "fresh honey" # fresh AND honey | @@ -147,7 +147,7 @@ Product.search "fresh honey" # fresh AND honey | ||
147 | To change this, use: | 147 | To change this, use: |
148 | 148 | ||
149 | ```ruby | 149 | ```ruby |
150 | -Product.search "fresh honey", partial: true # fresh OR honey | 150 | +Product.search "fresh honey", operator: "or" # fresh OR honey |
151 | ``` | 151 | ``` |
152 | 152 | ||
153 | By default, results must match the entire word - `back` will not match `backpack`. You can change this behavior with: | 153 | By default, results must match the entire word - `back` will not match `backpack`. You can change this behavior with: |
@@ -504,7 +504,7 @@ And to search, use: | @@ -504,7 +504,7 @@ And to search, use: | ||
504 | ```ruby | 504 | ```ruby |
505 | Animal.search "*" # all animals | 505 | Animal.search "*" # all animals |
506 | Dog.search "*" # just dogs | 506 | Dog.search "*" # just dogs |
507 | -Animal.search "*", type: [Dog, Cat] # just cats and dogs [master] | 507 | +Animal.search "*", type: [Dog, Cat] # just cats and dogs |
508 | ``` | 508 | ``` |
509 | 509 | ||
510 | **Note:** The `suggest` option retrieves suggestions from the parent at the moment. | 510 | **Note:** The `suggest` option retrieves suggestions from the parent at the moment. |
@@ -576,7 +576,13 @@ end | @@ -576,7 +576,13 @@ end | ||
576 | And use the `query` option to search: | 576 | And use the `query` option to search: |
577 | 577 | ||
578 | ```ruby | 578 | ```ruby |
579 | -Product.search query: {match: {name: "milk"}} | 579 | +products = Product.search query: {match: {name: "milk"}} |
580 | +``` | ||
581 | + | ||
582 | +View the response with: | ||
583 | + | ||
584 | +```ruby | ||
585 | +products.response | ||
580 | ``` | 586 | ``` |
581 | 587 | ||
582 | To keep the mappings and settings generated by Searchkick, use: | 588 | To keep the mappings and settings generated by Searchkick, use: |
@@ -587,9 +593,7 @@ class Product < ActiveRecord::Base | @@ -587,9 +593,7 @@ class Product < ActiveRecord::Base | ||
587 | end | 593 | end |
588 | ``` | 594 | ``` |
589 | 595 | ||
590 | -## Experimental [master] | ||
591 | - | ||
592 | -Modify the query generated by Searchkick. | 596 | +To modify the query generated by Searchkick, use: |
593 | 597 | ||
594 | ```ruby | 598 | ```ruby |
595 | query = Product.search "2% Milk", execute: false | 599 | query = Product.search "2% Milk", execute: false |
@@ -597,15 +601,9 @@ query.body[:query] = {match_all: {}} | @@ -597,15 +601,9 @@ query.body[:query] = {match_all: {}} | ||
597 | products = query.execute | 601 | products = query.execute |
598 | ``` | 602 | ``` |
599 | 603 | ||
600 | -View the response with: | ||
601 | - | ||
602 | -```ruby | ||
603 | -products.response | ||
604 | -``` | ||
605 | - | ||
606 | ## Reference | 604 | ## Reference |
607 | 605 | ||
608 | -Searchkick requires Elasticsearch `0.90.0` or higher. | 606 | +Searchkick requires Elasticsearch `0.90.4` or higher. |
609 | 607 | ||
610 | Reindex one record | 608 | Reindex one record |
611 | 609 | ||
@@ -754,6 +752,10 @@ rake searchkick:reindex:all | @@ -754,6 +752,10 @@ rake searchkick:reindex:all | ||
754 | 752 | ||
755 | 4. Once it finishes, replace search calls w/ searchkick calls | 753 | 4. Once it finishes, replace search calls w/ searchkick calls |
756 | 754 | ||
755 | +## Note about 0.6.0 and 0.7.0 | ||
756 | + | ||
757 | +If running Searchkick `0.6.0` or `0.7.0` and Elasticsearch `0.90`, we recommend upgrading to Searchkick `0.6.1` or `0.7.1` to fix an issue that causes downtime when reindexing. | ||
758 | + | ||
757 | ## Elasticsearch Gotchas | 759 | ## Elasticsearch Gotchas |
758 | 760 | ||
759 | ### Inconsistent Scores | 761 | ### Inconsistent Scores |
@@ -770,7 +772,7 @@ For convenience, this is set by default in the test environment. | @@ -770,7 +772,7 @@ For convenience, this is set by default in the test environment. | ||
770 | 772 | ||
771 | ## Thanks | 773 | ## Thanks |
772 | 774 | ||
773 | -Thanks to Karel Minarik for [Tire](https://github.com/karmi/tire), Jaroslav Kalistsuk for [zero downtime reindexing](https://gist.github.com/jarosan/3124884), and Alex Leschenko for [Elasticsearch autocomplete](https://github.com/leschenko/elasticsearch_autocomplete). | 775 | +Thanks to Karel Minarik for [Elasticsearch Ruby](https://github.com/elasticsearch/elasticsearch-ruby) and [Tire](https://github.com/karmi/tire), Jaroslav Kalistsuk for [zero downtime reindexing](https://gist.github.com/jarosan/3124884), and Alex Leschenko for [Elasticsearch autocomplete](https://github.com/leschenko/elasticsearch_autocomplete). |
774 | 776 | ||
775 | ## Roadmap | 777 | ## Roadmap |
776 | 778 | ||
@@ -797,7 +799,5 @@ Everyone is encouraged to help improve this project. Here are a few ways you can | @@ -797,7 +799,5 @@ Everyone is encouraged to help improve this project. Here are a few ways you can | ||
797 | To get started with development and testing: | 799 | To get started with development and testing: |
798 | 800 | ||
799 | 1. Clone the repo | 801 | 1. Clone the repo |
800 | -2. Install PostgreSQL and create a database called `searchkick_test` (`psql -d postgres -c "create database searchkick_test"`) | ||
801 | -3. Install Elasticsearch | ||
802 | -4. `bundle` | ||
803 | -5. `rake test` | 802 | +2. `bundle` |
803 | +3. `rake test` |
gemfiles/mongoid4.gemfile
@@ -3,4 +3,4 @@ source 'https://rubygems.org' | @@ -3,4 +3,4 @@ source 'https://rubygems.org' | ||
3 | # Specify your gem's dependencies in searchkick.gemspec | 3 | # Specify your gem's dependencies in searchkick.gemspec |
4 | gemspec path: "../" | 4 | gemspec path: "../" |
5 | 5 | ||
6 | -gem "mongoid", github: "mongoid/mongoid" | 6 | +gem "mongoid", "4.0.0.beta1" |
lib/searchkick.rb
1 | -require "tire" | 1 | +require "active_model" |
2 | +require "elasticsearch" | ||
3 | +require "hashie" | ||
2 | require "searchkick/version" | 4 | require "searchkick/version" |
5 | +require "searchkick/index" | ||
3 | require "searchkick/reindex" | 6 | require "searchkick/reindex" |
4 | require "searchkick/results" | 7 | require "searchkick/results" |
5 | require "searchkick/query" | 8 | require "searchkick/query" |
@@ -7,9 +10,14 @@ require "searchkick/search" | @@ -7,9 +10,14 @@ require "searchkick/search" | ||
7 | require "searchkick/similar" | 10 | require "searchkick/similar" |
8 | require "searchkick/model" | 11 | require "searchkick/model" |
9 | require "searchkick/tasks" | 12 | require "searchkick/tasks" |
10 | -require "searchkick/logger" if defined?(Rails) | 13 | +require "searchkick/logging" if defined?(Rails) |
11 | 14 | ||
12 | module Searchkick | 15 | module Searchkick |
16 | + | ||
17 | + def self.client | ||
18 | + @client ||= Elasticsearch::Client.new(url: ENV["ELASTICSEARCH_URL"]) | ||
19 | + end | ||
20 | + | ||
13 | @callbacks = true | 21 | @callbacks = true |
14 | 22 | ||
15 | def self.enable_callbacks | 23 | def self.enable_callbacks |
@@ -0,0 +1,137 @@ | @@ -0,0 +1,137 @@ | ||
1 | +module Searchkick | ||
2 | + class Index | ||
3 | + attr_reader :name | ||
4 | + | ||
5 | + def initialize(name) | ||
6 | + @name = name | ||
7 | + end | ||
8 | + | ||
9 | + def create(options = {}) | ||
10 | + client.indices.create index: name, body: options | ||
11 | + end | ||
12 | + | ||
13 | + def delete | ||
14 | + client.indices.delete index: name | ||
15 | + end | ||
16 | + | ||
17 | + def exists? | ||
18 | + client.indices.exists index: name | ||
19 | + end | ||
20 | + | ||
21 | + def refresh | ||
22 | + client.indices.refresh index: name | ||
23 | + end | ||
24 | + | ||
25 | + def store(record) | ||
26 | + client.index( | ||
27 | + index: name, | ||
28 | + type: document_type(record), | ||
29 | + id: record.id, | ||
30 | + body: search_data(record) | ||
31 | + ) | ||
32 | + end | ||
33 | + | ||
34 | + def remove(record) | ||
35 | + client.delete( | ||
36 | + index: name, | ||
37 | + type: document_type(record), | ||
38 | + id: record.id | ||
39 | + ) | ||
40 | + end | ||
41 | + | ||
42 | + def import(records) | ||
43 | + if records.any? | ||
44 | + client.bulk( | ||
45 | + index: name, | ||
46 | + type: document_type(records.first), | ||
47 | + body: records.map{|r| data = search_data(r); {index: {_id: data["_id"] || data["id"] || r.id, data: data}} } | ||
48 | + ) | ||
49 | + end | ||
50 | + end | ||
51 | + | ||
52 | + def retrieve(record) | ||
53 | + client.get( | ||
54 | + index: name, | ||
55 | + type: document_type(record), | ||
56 | + id: record.id | ||
57 | + )["_source"] | ||
58 | + end | ||
59 | + | ||
60 | + def klass_document_type(klass) | ||
61 | + klass.model_name.to_s.underscore | ||
62 | + end | ||
63 | + | ||
64 | + protected | ||
65 | + | ||
66 | + def client | ||
67 | + Searchkick.client | ||
68 | + end | ||
69 | + | ||
70 | + def document_type(record) | ||
71 | + klass_document_type(record.class) | ||
72 | + end | ||
73 | + | ||
74 | + def search_data(record) | ||
75 | + source = record.search_data | ||
76 | + | ||
77 | + # stringify fields | ||
78 | + source = source.inject({}){|memo,(k,v)| memo[k.to_s] = v; memo} | ||
79 | + | ||
80 | + # Mongoid 4 hack | ||
81 | + if defined?(BSON::ObjectId) and source["_id"].is_a?(BSON::ObjectId) | ||
82 | + source["_id"] = source["_id"].to_s | ||
83 | + end | ||
84 | + | ||
85 | + options = record.class.searchkick_options | ||
86 | + | ||
87 | + # conversions | ||
88 | + conversions_field = options[:conversions] | ||
89 | + if conversions_field and source[conversions_field] | ||
90 | + source[conversions_field] = source[conversions_field].map{|k, v| {query: k, count: v} } | ||
91 | + end | ||
92 | + | ||
93 | + # hack to prevent generator field doesn't exist error | ||
94 | + (options[:suggest] || []).map(&:to_s).each do |field| | ||
95 | + source[field] = nil if !source[field] | ||
96 | + end | ||
97 | + | ||
98 | + # locations | ||
99 | + (options[:locations] || []).map(&:to_s).each do |field| | ||
100 | + if source[field] | ||
101 | + if source[field].first.is_a?(Array) # array of arrays | ||
102 | + source[field] = source[field].map{|a| a.map(&:to_f).reverse } | ||
103 | + else | ||
104 | + source[field] = source[field].map(&:to_f).reverse | ||
105 | + end | ||
106 | + end | ||
107 | + end | ||
108 | + | ||
109 | + cast_big_decimal(source) | ||
110 | + | ||
111 | + # p search_data | ||
112 | + | ||
113 | + source.as_json | ||
114 | + end | ||
115 | + | ||
116 | + # change all BigDecimal values to floats due to | ||
117 | + # https://github.com/rails/rails/issues/6033 | ||
118 | + # possible loss of precision :/ | ||
119 | + def cast_big_decimal(obj) | ||
120 | + case obj | ||
121 | + when BigDecimal | ||
122 | + obj.to_f | ||
123 | + when Hash | ||
124 | + obj.each do |k, v| | ||
125 | + obj[k] = cast_big_decimal(v) | ||
126 | + end | ||
127 | + when Enumerable | ||
128 | + obj.map do |v| | ||
129 | + cast_big_decimal(v) | ||
130 | + end | ||
131 | + else | ||
132 | + obj | ||
133 | + end | ||
134 | + end | ||
135 | + | ||
136 | + end | ||
137 | +end |
lib/searchkick/logger.rb
@@ -1,19 +0,0 @@ | @@ -1,19 +0,0 @@ | ||
1 | -require "tire/rails/logger" | ||
2 | -require "tire/rails/logger/log_subscriber" | ||
3 | - | ||
4 | -class Tire::Rails::LogSubscriber | ||
5 | - | ||
6 | - # better output format | ||
7 | - def search(event) | ||
8 | - self.class.runtime += event.duration | ||
9 | - return unless logger.debug? | ||
10 | - | ||
11 | - payload = event.payload | ||
12 | - | ||
13 | - name = "%s (%.1fms)" % [payload[:name], event.duration] | ||
14 | - query = payload[:search].to_s | ||
15 | - | ||
16 | - debug " #{color(name, YELLOW, true)} #{query}" | ||
17 | - end | ||
18 | - | ||
19 | -end |
@@ -0,0 +1,89 @@ | @@ -0,0 +1,89 @@ | ||
1 | +# based on https://gist.github.com/mnutt/566725 | ||
2 | + | ||
3 | +module Searchkick | ||
4 | + class Query | ||
5 | + def execute_with_instrumentation | ||
6 | + event = { | ||
7 | + name: "#{searchkick_klass.name} Search", | ||
8 | + query: params | ||
9 | + } | ||
10 | + ActiveSupport::Notifications.instrument("search.searchkick", event) do | ||
11 | + execute_without_instrumentation | ||
12 | + end | ||
13 | + end | ||
14 | + | ||
15 | + alias_method_chain :execute, :instrumentation | ||
16 | + end | ||
17 | + | ||
18 | + # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/log_subscriber.rb | ||
19 | + class LogSubscriber < ActiveSupport::LogSubscriber | ||
20 | + def self.runtime=(value) | ||
21 | + Thread.current[:searchkick_runtime] = value | ||
22 | + end | ||
23 | + | ||
24 | + def self.runtime | ||
25 | + Thread.current[:searchkick_runtime] ||= 0 | ||
26 | + end | ||
27 | + | ||
28 | + def self.reset_runtime | ||
29 | + rt, self.runtime = runtime, 0 | ||
30 | + rt | ||
31 | + end | ||
32 | + | ||
33 | + def search(event) | ||
34 | + self.class.runtime += event.duration | ||
35 | + return unless logger.debug? | ||
36 | + | ||
37 | + payload = event.payload | ||
38 | + name = "#{payload[:name]} (#{event.duration.round(1)}ms)" | ||
39 | + type = payload[:query][:type] | ||
40 | + | ||
41 | + # no easy way to tell which host the client will use | ||
42 | + host = Searchkick.client.transport.hosts.first | ||
43 | + debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/#{CGI.escape(payload[:query][:index])}#{type ? "/#{type.map{|t| CGI.escape(t) }.join(",")}" : ""}/_search?pretty -d '#{payload[:query][:body].to_json}'" | ||
44 | + end | ||
45 | + end | ||
46 | + | ||
47 | + # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/railties/controller_runtime.rb | ||
48 | + module ControllerRuntime | ||
49 | + extend ActiveSupport::Concern | ||
50 | + | ||
51 | + protected | ||
52 | + | ||
53 | + attr_internal :searchkick_runtime | ||
54 | + | ||
55 | + def process_action(action, *args) | ||
56 | + # We also need to reset the runtime before each action | ||
57 | + # because of queries in middleware or in cases we are streaming | ||
58 | + # and it won't be cleaned up by the method below. | ||
59 | + Searchkick::LogSubscriber.reset_runtime | ||
60 | + super | ||
61 | + end | ||
62 | + | ||
63 | + def cleanup_view_runtime | ||
64 | + searchkick_rt_before_render = Searchkick::LogSubscriber.reset_runtime | ||
65 | + runtime = super | ||
66 | + searchkick_rt_after_render = Searchkick::LogSubscriber.reset_runtime | ||
67 | + self.searchkick_runtime = searchkick_rt_before_render + searchkick_rt_after_render | ||
68 | + runtime - searchkick_rt_after_render | ||
69 | + end | ||
70 | + | ||
71 | + def append_info_to_payload(payload) | ||
72 | + super | ||
73 | + payload[:searchkick_runtime] = (searchkick_runtime || 0) + Searchkick::LogSubscriber.reset_runtime | ||
74 | + end | ||
75 | + | ||
76 | + module ClassMethods | ||
77 | + def log_process_action(payload) | ||
78 | + messages, runtime = super, payload[:searchkick_runtime] | ||
79 | + messages << ("Searchkick: %.1fms" % runtime.to_f) if runtime.to_f > 0 | ||
80 | + messages | ||
81 | + end | ||
82 | + end | ||
83 | + end | ||
84 | +end | ||
85 | + | ||
86 | +Searchkick::LogSubscriber.attach_to :searchkick | ||
87 | +ActiveSupport.on_load(:action_controller) do | ||
88 | + include Searchkick::ControllerRuntime | ||
89 | +end |
lib/searchkick/model.rb
@@ -2,18 +2,22 @@ module Searchkick | @@ -2,18 +2,22 @@ module Searchkick | ||
2 | module Model | 2 | module Model |
3 | 3 | ||
4 | def searchkick(options = {}) | 4 | def searchkick(options = {}) |
5 | + raise "Only call searchkick once per model" if respond_to?(:searchkick_index) | ||
6 | + | ||
5 | class_eval do | 7 | class_eval do |
6 | - cattr_reader :searchkick_options, :searchkick_env, :searchkick_klass, :searchkick_index | 8 | + cattr_reader :searchkick_options, :searchkick_env, :searchkick_klass |
7 | 9 | ||
8 | class_variable_set :@@searchkick_options, options.dup | 10 | class_variable_set :@@searchkick_options, options.dup |
9 | class_variable_set :@@searchkick_env, ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development" | 11 | class_variable_set :@@searchkick_env, ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development" |
10 | class_variable_set :@@searchkick_klass, self | 12 | class_variable_set :@@searchkick_klass, self |
11 | class_variable_set :@@searchkick_callbacks, options[:callbacks] != false | 13 | class_variable_set :@@searchkick_callbacks, options[:callbacks] != false |
14 | + class_variable_set :@@searchkick_index, options[:index_name] || [options[:index_prefix], model_name.plural, searchkick_env].compact.join("_") | ||
12 | 15 | ||
13 | - # set index name | ||
14 | - # TODO support proc | ||
15 | - index_name = options[:index_name] || [options[:index_prefix], model_name.plural, searchkick_env].compact.join("_") | ||
16 | - class_variable_set :@@searchkick_index, Tire::Index.new(index_name) | 16 | + def self.searchkick_index |
17 | + index = class_variable_get :@@searchkick_index | ||
18 | + index = index.call if index.respond_to? :call | ||
19 | + Searchkick::Index.new(index) | ||
20 | + end | ||
17 | 21 | ||
18 | extend Searchkick::Search | 22 | extend Searchkick::Search |
19 | extend Searchkick::Reindex | 23 | extend Searchkick::Reindex |
@@ -45,7 +49,11 @@ module Searchkick | @@ -45,7 +49,11 @@ module Searchkick | ||
45 | def reindex | 49 | def reindex |
46 | index = self.class.searchkick_index | 50 | index = self.class.searchkick_index |
47 | if destroyed? or !should_index? | 51 | if destroyed? or !should_index? |
48 | - index.remove self | 52 | + begin |
53 | + index.remove self | ||
54 | + rescue Elasticsearch::Transport::Transport::Errors::NotFound | ||
55 | + # do nothing | ||
56 | + end | ||
49 | else | 57 | else |
50 | index.store self | 58 | index.store self |
51 | end | 59 | end |
@@ -55,79 +63,6 @@ module Searchkick | @@ -55,79 +63,6 @@ module Searchkick | ||
55 | respond_to?(:to_hash) ? to_hash : serializable_hash | 63 | respond_to?(:to_hash) ? to_hash : serializable_hash |
56 | end | 64 | end |
57 | 65 | ||
58 | - def to_indexed_json | ||
59 | - source = search_data | ||
60 | - | ||
61 | - # stringify fields | ||
62 | - source = source.inject({}){|memo,(k,v)| memo[k.to_s] = v; memo} | ||
63 | - | ||
64 | - # Mongoid 4 hack | ||
65 | - if defined?(BSON::ObjectId) and source["_id"].is_a?(BSON::ObjectId) | ||
66 | - source["_id"] = source["_id"].to_s | ||
67 | - end | ||
68 | - | ||
69 | - options = self.class.searchkick_options | ||
70 | - | ||
71 | - # conversions | ||
72 | - conversions_field = options[:conversions] | ||
73 | - if conversions_field and source[conversions_field] | ||
74 | - source[conversions_field] = source[conversions_field].map{|k, v| {query: k, count: v} } | ||
75 | - end | ||
76 | - | ||
77 | - # hack to prevent generator field doesn't exist error | ||
78 | - (options[:suggest] || []).map(&:to_s).each do |field| | ||
79 | - source[field] = nil if !source[field] | ||
80 | - end | ||
81 | - | ||
82 | - # locations | ||
83 | - (options[:locations] || []).map(&:to_s).each do |field| | ||
84 | - if source[field] | ||
85 | - if source[field].first.is_a?(Array) # array of arrays | ||
86 | - source[field] = source[field].map{|a| a.map(&:to_f).reverse } | ||
87 | - else | ||
88 | - source[field] = source[field].map(&:to_f).reverse | ||
89 | - end | ||
90 | - end | ||
91 | - end | ||
92 | - | ||
93 | - # change all BigDecimal values to floats due to | ||
94 | - # https://github.com/rails/rails/issues/6033 | ||
95 | - # possible loss of precision :/ | ||
96 | - cast_big_decimal = | ||
97 | - proc do |obj| | ||
98 | - case obj | ||
99 | - when BigDecimal | ||
100 | - obj.to_f | ||
101 | - when Hash | ||
102 | - obj.each do |k, v| | ||
103 | - obj[k] = cast_big_decimal.call(v) | ||
104 | - end | ||
105 | - when Enumerable | ||
106 | - obj.map! do |v| | ||
107 | - cast_big_decimal.call(v) | ||
108 | - end | ||
109 | - else | ||
110 | - obj | ||
111 | - end | ||
112 | - end | ||
113 | - | ||
114 | - cast_big_decimal.call(source) | ||
115 | - | ||
116 | - # p search_data | ||
117 | - | ||
118 | - source.to_json | ||
119 | - end | ||
120 | - | ||
121 | - # TODO remove | ||
122 | - | ||
123 | - def self.document_type | ||
124 | - model_name.to_s.underscore | ||
125 | - end | ||
126 | - | ||
127 | - def document_type | ||
128 | - self.class.document_type | ||
129 | - end | ||
130 | - | ||
131 | end | 66 | end |
132 | end | 67 | end |
133 | 68 |
lib/searchkick/query.rb
@@ -35,15 +35,10 @@ module Searchkick | @@ -35,15 +35,10 @@ module Searchkick | ||
35 | 35 | ||
36 | operator = options[:operator] || (options[:partial] ? "or" : "and") | 36 | operator = options[:operator] || (options[:partial] ? "or" : "and") |
37 | 37 | ||
38 | - # model and eagar loading | ||
39 | - load = options[:load].nil? ? true : options[:load] | ||
40 | - load = (options[:include] ? {include: options[:include]} : true) if load | ||
41 | - | ||
42 | # pagination | 38 | # pagination |
43 | page = [options[:page].to_i, 1].max | 39 | page = [options[:page].to_i, 1].max |
44 | per_page = (options[:limit] || options[:per_page] || 100000).to_i | 40 | per_page = (options[:limit] || options[:per_page] || 100000).to_i |
45 | offset = options[:offset] || (page - 1) * per_page | 41 | offset = options[:offset] || (page - 1) * per_page |
46 | - index_name = options[:index_name] || searchkick_index.name | ||
47 | 42 | ||
48 | conversions_field = searchkick_options[:conversions] | 43 | conversions_field = searchkick_options[:conversions] |
49 | personalize_field = searchkick_options[:personalize] | 44 | personalize_field = searchkick_options[:personalize] |
@@ -83,9 +78,9 @@ module Searchkick | @@ -83,9 +78,9 @@ module Searchkick | ||
83 | fields: [field], | 78 | fields: [field], |
84 | query: term, | 79 | query: term, |
85 | use_dis_max: false, | 80 | use_dis_max: false, |
86 | - operator: operator, | ||
87 | - cutoff_frequency: 0.001 | 81 | + operator: operator |
88 | } | 82 | } |
83 | + shared_options[:cutoff_frequency] = 0.001 unless operator == "and" | ||
89 | queries.concat [ | 84 | queries.concat [ |
90 | {multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search")}, | 85 | {multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search")}, |
91 | {multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search2")} | 86 | {multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search2")} |
@@ -126,13 +121,16 @@ module Searchkick | @@ -126,13 +121,16 @@ module Searchkick | ||
126 | path: conversions_field, | 121 | path: conversions_field, |
127 | score_mode: "total", | 122 | score_mode: "total", |
128 | query: { | 123 | query: { |
129 | - custom_score: { | 124 | + function_score: { |
125 | + boost_mode: "replace", | ||
130 | query: { | 126 | query: { |
131 | match: { | 127 | match: { |
132 | query: term | 128 | query: term |
133 | } | 129 | } |
134 | }, | 130 | }, |
135 | - script: "doc['count'].value" | 131 | + script_score: { |
132 | + script: "doc['count'].value" | ||
133 | + } | ||
136 | } | 134 | } |
137 | } | 135 | } |
138 | } | 136 | } |
@@ -151,7 +149,9 @@ module Searchkick | @@ -151,7 +149,9 @@ module Searchkick | ||
151 | field: options[:boost] | 149 | field: options[:boost] |
152 | } | 150 | } |
153 | }, | 151 | }, |
154 | - script: "log(doc['#{options[:boost]}'].value + 2.718281828)" | 152 | + script_score: { |
153 | + script: "log(doc['#{options[:boost]}'].value + 2.718281828)" | ||
154 | + } | ||
155 | } | 155 | } |
156 | end | 156 | end |
157 | 157 | ||
@@ -162,7 +162,7 @@ module Searchkick | @@ -162,7 +162,7 @@ module Searchkick | ||
162 | personalize_field => options[:user_id] | 162 | personalize_field => options[:user_id] |
163 | } | 163 | } |
164 | }, | 164 | }, |
165 | - boost: 100 | 165 | + boost_factor: 100 |
166 | } | 166 | } |
167 | end | 167 | end |
168 | 168 | ||
@@ -171,16 +171,16 @@ module Searchkick | @@ -171,16 +171,16 @@ module Searchkick | ||
171 | filter: { | 171 | filter: { |
172 | term: options[:personalize] | 172 | term: options[:personalize] |
173 | }, | 173 | }, |
174 | - boost: 100 | 174 | + boost_factor: 100 |
175 | } | 175 | } |
176 | end | 176 | end |
177 | 177 | ||
178 | if custom_filters.any? | 178 | if custom_filters.any? |
179 | payload = { | 179 | payload = { |
180 | - custom_filters_score: { | 180 | + function_score: { |
181 | + functions: custom_filters, | ||
181 | query: payload, | 182 | query: payload, |
182 | - filters: custom_filters, | ||
183 | - score_mode: "total" | 183 | + score_mode: "sum" |
184 | } | 184 | } |
185 | } | 185 | } |
186 | end | 186 | end |
@@ -279,18 +279,22 @@ module Searchkick | @@ -279,18 +279,22 @@ module Searchkick | ||
279 | end | 279 | end |
280 | end | 280 | end |
281 | 281 | ||
282 | + # model and eagar loading | ||
283 | + load = options[:load].nil? ? true : options[:load] | ||
284 | + | ||
282 | # An empty array will cause only the _id and _type for each hit to be returned | 285 | # An empty array will cause only the _id and _type for each hit to be returned |
283 | # http://www.elasticsearch.org/guide/reference/api/search/fields/ | 286 | # http://www.elasticsearch.org/guide/reference/api/search/fields/ |
284 | payload[:fields] = [] if load | 287 | payload[:fields] = [] if load |
285 | 288 | ||
286 | - tire_options = {load: load, size: per_page, from: offset} | ||
287 | if options[:type] or klass != searchkick_klass | 289 | if options[:type] or klass != searchkick_klass |
288 | - tire_options[:type] = [options[:type] || klass].flatten.map(&:document_type) | 290 | + @type = [options[:type] || klass].flatten.map{|v| searchkick_index.klass_document_type(v) } |
289 | end | 291 | end |
290 | 292 | ||
291 | - @search = Tire::Search::Search.new(index_name, tire_options) | ||
292 | @body = payload | 293 | @body = payload |
293 | @facet_limits = facet_limits | 294 | @facet_limits = facet_limits |
295 | + @page = page | ||
296 | + @per_page = per_page | ||
297 | + @load = load | ||
294 | end | 298 | end |
295 | 299 | ||
296 | def searchkick_index | 300 | def searchkick_index |
@@ -305,20 +309,30 @@ module Searchkick | @@ -305,20 +309,30 @@ module Searchkick | ||
305 | klass.searchkick_klass | 309 | klass.searchkick_klass |
306 | end | 310 | end |
307 | 311 | ||
308 | - def document_type | ||
309 | - klass.document_type | 312 | + def params |
313 | + params = { | ||
314 | + index: options[:index_name] || searchkick_index.name, | ||
315 | + body: body | ||
316 | + } | ||
317 | + params.merge!(type: @type) if @type | ||
318 | + params | ||
310 | end | 319 | end |
311 | 320 | ||
312 | def execute | 321 | def execute |
313 | - @search.options[:payload] = body | ||
314 | begin | 322 | begin |
315 | - response = @search.json | ||
316 | - rescue Tire::Search::SearchRequestFailed => e | 323 | + response = Searchkick.client.search(params) |
324 | + rescue => e # TODO rescue type | ||
317 | status_code = e.message[1..3].to_i | 325 | status_code = e.message[1..3].to_i |
318 | if status_code == 404 | 326 | if status_code == 404 |
319 | raise "Index missing - run #{searchkick_klass.name}.reindex" | 327 | raise "Index missing - run #{searchkick_klass.name}.reindex" |
320 | - elsif status_code == 500 and (e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]") or e.message.include?("No query registered for [multi_match]")) | ||
321 | - raise "Upgrade Elasticsearch to 0.90.0 or greater" | 328 | + elsif status_code == 500 and ( |
329 | + e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]") or | ||
330 | + e.message.include?("No query registered for [multi_match]") or | ||
331 | + e.message.include?("[match] query does not support [cutoff_frequency]]") or | ||
332 | + e.message.include?("No query registered for [function_score]]") | ||
333 | + ) | ||
334 | + | ||
335 | + raise "This version of Searchkick requires Elasticsearch 0.90.4 or greater" | ||
322 | else | 336 | else |
323 | raise e | 337 | raise e |
324 | end | 338 | end |
@@ -333,7 +347,13 @@ module Searchkick | @@ -333,7 +347,13 @@ module Searchkick | ||
333 | response["facets"][field]["other"] = facet["total"] - facet["terms"].sum{|term| term["count"] } | 347 | response["facets"][field]["other"] = facet["total"] - facet["terms"].sum{|term| term["count"] } |
334 | end | 348 | end |
335 | 349 | ||
336 | - Searchkick::Results.new(response, @search.options.merge(term: term, model_name: searchkick_klass.model_name)) | 350 | + opts = { |
351 | + page: @page, | ||
352 | + per_page: @per_page, | ||
353 | + load: @load, | ||
354 | + includes: options[:include] || options[:includes] | ||
355 | + } | ||
356 | + Searchkick::Results.new(searchkick_klass, response, opts) | ||
337 | end | 357 | end |
338 | 358 | ||
339 | private | 359 | private |
lib/searchkick/reindex.rb
@@ -2,36 +2,28 @@ module Searchkick | @@ -2,36 +2,28 @@ module Searchkick | ||
2 | module Reindex | 2 | module Reindex |
3 | 3 | ||
4 | # https://gist.github.com/jarosan/3124884 | 4 | # https://gist.github.com/jarosan/3124884 |
5 | + # http://www.elasticsearch.org/blog/changing-mapping-with-zero-downtime/ | ||
5 | def reindex | 6 | def reindex |
6 | alias_name = searchkick_index.name | 7 | alias_name = searchkick_index.name |
7 | - new_index = alias_name + "_" + Time.now.strftime("%Y%m%d%H%M%S%L") | ||
8 | - index = Tire::Index.new(new_index) | 8 | + new_name = alias_name + "_" + Time.now.strftime("%Y%m%d%H%M%S%L") |
9 | + index = Searchkick::Index.new(new_name) | ||
9 | 10 | ||
10 | clean_indices | 11 | clean_indices |
11 | 12 | ||
12 | - success = index.create searchkick_index_options | ||
13 | - raise index.response.to_s if !success | 13 | + index.create searchkick_index_options |
14 | 14 | ||
15 | - if a = Tire::Alias.find(alias_name) | 15 | + # check if alias exists |
16 | + if Searchkick.client.indices.exists_alias(name: alias_name) | ||
16 | searchkick_import(index) # import before swap | 17 | searchkick_import(index) # import before swap |
17 | 18 | ||
18 | - a.indices.each do |i| | ||
19 | - a.indices.delete i | ||
20 | - end | ||
21 | - | ||
22 | - a.indices.add new_index | ||
23 | - response = a.save | ||
24 | - | ||
25 | - if response.success? | ||
26 | - clean_indices | ||
27 | - else | ||
28 | - raise response.to_s | ||
29 | - end | 19 | + # get existing indices to remove |
20 | + old_indices = Searchkick.client.indices.get_alias(name: alias_name).keys | ||
21 | + actions = old_indices.map{|name| {remove: {index: name, alias: alias_name}} } + [{add: {index: new_name, alias: alias_name}}] | ||
22 | + Searchkick.client.indices.update_aliases body: {actions: actions} | ||
23 | + clean_indices | ||
30 | else | 24 | else |
31 | searchkick_index.delete if searchkick_index.exists? | 25 | searchkick_index.delete if searchkick_index.exists? |
32 | - response = Tire::Alias.create(name: alias_name, indices: [new_index]) | ||
33 | - raise response.to_s if !response.success? | ||
34 | - | 26 | + Searchkick.client.indices.update_aliases body: {actions: [{add: {index: new_name, alias: alias_name}}]} |
35 | searchkick_import(index) # import after swap | 27 | searchkick_import(index) # import after swap |
36 | end | 28 | end |
37 | 29 | ||
@@ -42,10 +34,10 @@ module Searchkick | @@ -42,10 +34,10 @@ module Searchkick | ||
42 | 34 | ||
43 | # remove old indices that start w/ index_name | 35 | # remove old indices that start w/ index_name |
44 | def clean_indices | 36 | def clean_indices |
45 | - all_indices = JSON.parse(Tire::Configuration.client.get("#{Tire::Configuration.url}/_aliases").body) | 37 | + all_indices = Searchkick.client.indices.get_aliases |
46 | indices = all_indices.select{|k, v| v["aliases"].empty? && k =~ /\A#{Regexp.escape(searchkick_index.name)}_\d{14,17}\z/ }.keys | 38 | indices = all_indices.select{|k, v| v["aliases"].empty? && k =~ /\A#{Regexp.escape(searchkick_index.name)}_\d{14,17}\z/ }.keys |
47 | indices.each do |index| | 39 | indices.each do |index| |
48 | - Tire::Index.new(index).delete | 40 | + Searchkick::Index.new(index).delete |
49 | end | 41 | end |
50 | indices | 42 | indices |
51 | end | 43 | end |
@@ -73,7 +65,7 @@ module Searchkick | @@ -73,7 +65,7 @@ module Searchkick | ||
73 | items = [] | 65 | items = [] |
74 | scope.all.each do |item| | 66 | scope.all.each do |item| |
75 | items << item if item.should_index? | 67 | items << item if item.should_index? |
76 | - if items.length % batch_size == 0 | 68 | + if items.length == batch_size |
77 | index.import items | 69 | index.import items |
78 | items = [] | 70 | items = [] |
79 | end | 71 | end |
lib/searchkick/results.rb
1 | module Searchkick | 1 | module Searchkick |
2 | - class Results < Tire::Results::Collection | ||
3 | - attr_reader :response | 2 | + class Results |
3 | + include Enumerable | ||
4 | + extend Forwardable | ||
5 | + | ||
6 | + attr_reader :klass, :response, :options | ||
7 | + | ||
8 | + def_delegators :results, :each, :empty?, :size, :slice, :[], :to_ary | ||
9 | + | ||
10 | + def initialize(klass, response, options = {}) | ||
11 | + @klass = klass | ||
12 | + @response = response | ||
13 | + @options = options | ||
14 | + end | ||
15 | + | ||
16 | + def results | ||
17 | + @results ||= begin | ||
18 | + if options[:load] | ||
19 | + hit_ids = hits.map{|hit| hit["_id"] } | ||
20 | + records = klass | ||
21 | + if options[:includes] | ||
22 | + records = records.includes(options[:includes]) | ||
23 | + end | ||
24 | + records = records.find(hit_ids) | ||
25 | + hit_ids = hit_ids.map(&:to_s) | ||
26 | + records.sort_by{|r| hit_ids.index(r.id.to_s) } | ||
27 | + else | ||
28 | + hits.map{|hit| Hashie::Mash.new(hit["_source"]) } | ||
29 | + end | ||
30 | + end | ||
31 | + end | ||
4 | 32 | ||
5 | def suggestions | 33 | def suggestions |
6 | - if @response["suggest"] | ||
7 | - @response["suggest"].values.flat_map{|v| v.first["options"] }.sort_by{|o| -o["score"] }.map{|o| o["text"] }.uniq | 34 | + if response["suggest"] |
35 | + response["suggest"].values.flat_map{|v| v.first["options"] }.sort_by{|o| -o["score"] }.map{|o| o["text"] }.uniq | ||
8 | else | 36 | else |
9 | raise "Pass `suggest: true` to the search method for suggestions" | 37 | raise "Pass `suggest: true` to the search method for suggestions" |
10 | end | 38 | end |
11 | end | 39 | end |
12 | 40 | ||
41 | + def each_with_hit(&block) | ||
42 | + results.zip(hits).each(&block) | ||
43 | + end | ||
44 | + | ||
13 | def with_details | 45 | def with_details |
14 | each_with_hit.map do |model, hit| | 46 | each_with_hit.map do |model, hit| |
15 | details = {} | 47 | details = {} |
@@ -20,13 +52,48 @@ module Searchkick | @@ -20,13 +52,48 @@ module Searchkick | ||
20 | end | 52 | end |
21 | end | 53 | end |
22 | 54 | ||
23 | - # fixes deprecation warning | ||
24 | - def __find_records_by_ids(klass, ids) | ||
25 | - @options[:load] === true ? klass.find(ids) : klass.includes(@options[:load][:include]).find(ids) | 55 | + def facets |
56 | + response["facets"] | ||
26 | end | 57 | end |
27 | 58 | ||
28 | def model_name | 59 | def model_name |
29 | - @options[:model_name] | 60 | + klass.model_name |
61 | + end | ||
62 | + | ||
63 | + def total_count | ||
64 | + response["hits"]["total"] | ||
65 | + end | ||
66 | + alias_method :total_entries, :total_count | ||
67 | + | ||
68 | + def current_page | ||
69 | + options[:page] | ||
70 | + end | ||
71 | + | ||
72 | + def per_page | ||
73 | + options[:per_page] | ||
74 | + end | ||
75 | + alias_method :limit_value, :per_page | ||
76 | + | ||
77 | + def total_pages | ||
78 | + (total_count / per_page.to_f).ceil | ||
79 | + end | ||
80 | + | ||
81 | + def offset_value | ||
82 | + current_page * per_page | ||
83 | + end | ||
84 | + | ||
85 | + def previous_page | ||
86 | + current_page > 1 ? (current_page - 1) : nil | ||
87 | + end | ||
88 | + | ||
89 | + def next_page | ||
90 | + current_page < total_pages ? (current_page + 1) : nil | ||
91 | + end | ||
92 | + | ||
93 | + protected | ||
94 | + | ||
95 | + def hits | ||
96 | + @response["hits"]["hits"] | ||
30 | end | 97 | end |
31 | 98 | ||
32 | end | 99 | end |
lib/searchkick/similar.rb
@@ -2,8 +2,8 @@ module Searchkick | @@ -2,8 +2,8 @@ module Searchkick | ||
2 | module Similar | 2 | module Similar |
3 | 3 | ||
4 | def similar(options = {}) | 4 | def similar(options = {}) |
5 | - like_text = self.class.searchkick_index.retrieve(document_type, id).to_hash | ||
6 | - .keep_if{|k,v| k[0] != "_" and (!options[:fields] or options[:fields].map(&:to_sym).include?(k)) } | 5 | + like_text = self.class.searchkick_index.retrieve(self).to_hash |
6 | + .keep_if{|k,v| !options[:fields] || options[:fields].map(&:to_s).include?(k) } | ||
7 | .values.compact.join(" ") | 7 | .values.compact.join(" ") |
8 | 8 | ||
9 | # TODO deep merge method | 9 | # TODO deep merge method |
lib/searchkick/version.rb
searchkick.gemspec
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec| | @@ -8,8 +8,8 @@ Gem::Specification.new do |spec| | ||
8 | spec.version = Searchkick::VERSION | 8 | spec.version = Searchkick::VERSION |
9 | spec.authors = ["Andrew Kane"] | 9 | spec.authors = ["Andrew Kane"] |
10 | spec.email = ["andrew@chartkick.com"] | 10 | spec.email = ["andrew@chartkick.com"] |
11 | - spec.description = %q{Search made easy} | ||
12 | - spec.summary = %q{Search made easy} | 11 | + spec.description = %q{Intelligent search made easy} |
12 | + spec.summary = %q{Searchkick learns what your users are looking for. As more people search, it gets smarter and the results get better. Itโs friendly for developers - and magical for your users.} | ||
13 | spec.homepage = "https://github.com/ankane/searchkick" | 13 | spec.homepage = "https://github.com/ankane/searchkick" |
14 | spec.license = "MIT" | 14 | spec.license = "MIT" |
15 | 15 | ||
@@ -18,8 +18,9 @@ Gem::Specification.new do |spec| | @@ -18,8 +18,9 @@ Gem::Specification.new do |spec| | ||
18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) | 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) |
19 | spec.require_paths = ["lib"] | 19 | spec.require_paths = ["lib"] |
20 | 20 | ||
21 | - spec.add_dependency "tire" | ||
22 | - spec.add_dependency "tire-contrib" | 21 | + spec.add_dependency "activemodel" |
22 | + spec.add_dependency "elasticsearch", "~> 0.4.11" | ||
23 | + spec.add_dependency "hashie" | ||
23 | 24 | ||
24 | spec.add_development_dependency "bundler", "~> 1.3" | 25 | spec.add_development_dependency "bundler", "~> 1.3" |
25 | spec.add_development_dependency "rake" | 26 | spec.add_development_dependency "rake" |
test/index_test.rb
@@ -3,8 +3,11 @@ require_relative "test_helper" | @@ -3,8 +3,11 @@ require_relative "test_helper" | ||
3 | class TestIndex < Minitest::Unit::TestCase | 3 | class TestIndex < Minitest::Unit::TestCase |
4 | 4 | ||
5 | def test_clean_indices | 5 | def test_clean_indices |
6 | - old_index = Tire::Index.new("products_test_20130801000000000") | ||
7 | - different_index = Tire::Index.new("items_test_20130801000000000") | 6 | + old_index = Searchkick::Index.new("products_test_20130801000000000") |
7 | + different_index = Searchkick::Index.new("items_test_20130801000000000") | ||
8 | + | ||
9 | + old_index.delete if old_index.exists? | ||
10 | + different_index.delete if different_index.exists? | ||
8 | 11 | ||
9 | # create indexes | 12 | # create indexes |
10 | old_index.create | 13 | old_index.create |
@@ -18,7 +21,7 @@ class TestIndex < Minitest::Unit::TestCase | @@ -18,7 +21,7 @@ class TestIndex < Minitest::Unit::TestCase | ||
18 | end | 21 | end |
19 | 22 | ||
20 | def test_clean_indices_old_format | 23 | def test_clean_indices_old_format |
21 | - old_index = Tire::Index.new("products_test_20130801000000") | 24 | + old_index = Searchkick::Index.new("products_test_20130801000000") |
22 | old_index.create | 25 | old_index.create |
23 | 26 | ||
24 | Product.clean_indices | 27 | Product.clean_indices |
test/inheritance_test.rb
@@ -10,7 +10,7 @@ class TestInheritance < Minitest::Unit::TestCase | @@ -10,7 +10,7 @@ class TestInheritance < Minitest::Unit::TestCase | ||
10 | end | 10 | end |
11 | 11 | ||
12 | def test_child_index_name | 12 | def test_child_index_name |
13 | - assert_equal "animals_test", Dog.searchkick_index.name | 13 | + assert_equal "animals-#{Date.today.year}", Dog.searchkick_index.name |
14 | end | 14 | end |
15 | 15 | ||
16 | def test_child_search | 16 | def test_child_search |
test/sql_test.rb
@@ -26,6 +26,9 @@ class TestSql < Minitest::Unit::TestCase | @@ -26,6 +26,9 @@ class TestSql < Minitest::Unit::TestCase | ||
26 | assert_equal 2, products.per_page | 26 | assert_equal 2, products.per_page |
27 | assert_equal 3, products.total_pages | 27 | assert_equal 3, products.total_pages |
28 | assert_equal 5, products.total_count | 28 | assert_equal 5, products.total_count |
29 | + assert_equal 5, products.total_entries | ||
30 | + assert_equal 2, products.limit_value | ||
31 | + assert_equal 4, products.offset_value | ||
29 | end | 32 | end |
30 | 33 | ||
31 | def test_pagination_nil_page | 34 | def test_pagination_nil_page |
@@ -245,12 +248,17 @@ class TestSql < Minitest::Unit::TestCase | @@ -245,12 +248,17 @@ class TestSql < Minitest::Unit::TestCase | ||
245 | 248 | ||
246 | def test_load_false | 249 | def test_load_false |
247 | store_names ["Product A"] | 250 | store_names ["Product A"] |
248 | - assert_kind_of Tire::Results::Item, Product.search("product", load: false).first | 251 | + assert_kind_of Hash, Product.search("product", load: false).first |
252 | + end | ||
253 | + | ||
254 | + def test_load_false_methods | ||
255 | + store_names ["Product A"] | ||
256 | + assert_equal "Product A", Product.search("product", load: false).first.name | ||
249 | end | 257 | end |
250 | 258 | ||
251 | def test_load_false_with_include | 259 | def test_load_false_with_include |
252 | store_names ["Product A"] | 260 | store_names ["Product A"] |
253 | - assert_kind_of Tire::Results::Item, Product.search("product", load: false, include: [:store]).first | 261 | + assert_kind_of Hash, Product.search("product", load: false, include: [:store]).first |
254 | end | 262 | end |
255 | 263 | ||
256 | # TODO see if Mongoid is loaded | 264 | # TODO see if Mongoid is loaded |
test/test_helper.rb
@@ -2,14 +2,12 @@ require "bundler/setup" | @@ -2,14 +2,12 @@ require "bundler/setup" | ||
2 | Bundler.require(:default) | 2 | Bundler.require(:default) |
3 | require "minitest/autorun" | 3 | require "minitest/autorun" |
4 | require "minitest/pride" | 4 | require "minitest/pride" |
5 | +require "logger" | ||
5 | 6 | ||
6 | ENV["RACK_ENV"] = "test" | 7 | ENV["RACK_ENV"] = "test" |
7 | 8 | ||
8 | File.delete("elasticsearch.log") if File.exists?("elasticsearch.log") | 9 | File.delete("elasticsearch.log") if File.exists?("elasticsearch.log") |
9 | -Tire.configure do | ||
10 | - logger "elasticsearch.log", :level => "debug" | ||
11 | - pretty true | ||
12 | -end | 10 | +Searchkick.client.transport.logger = Logger.new("elasticsearch.log") |
13 | 11 | ||
14 | if defined?(Mongoid) | 12 | if defined?(Mongoid) |
15 | Mongoid.configure do |config| | 13 | Mongoid.configure do |config| |
@@ -145,7 +143,7 @@ class Store | @@ -145,7 +143,7 @@ class Store | ||
145 | end | 143 | end |
146 | 144 | ||
147 | class Animal | 145 | class Animal |
148 | - searchkick autocomplete: [:name], suggest: [:name] | 146 | + searchkick autocomplete: [:name], suggest: [:name], index_name: -> { "#{self.name.tableize}-#{Date.today.year}" } |
149 | end | 147 | end |
150 | 148 | ||
151 | Product.searchkick_index.delete if Product.searchkick_index.exists? | 149 | Product.searchkick_index.delete if Product.searchkick_index.exists? |