Commit 2b2682d3690acb721a85c9cac50fe6ad9fcdc8b9

Authored by Andrew Kane
1 parent 41dd14bb

No more tire

lib/searchkick.rb
... ... @@ -5,65 +5,78 @@ require "searchkick/tasks"
5 5 require "tire"
6 6  
7 7 module Searchkick
8   - # TODO fix this monstrosity
9   - # TODO add custom synonyms
10   - def self.settings(options = {})
11   - synonyms = options[:synonyms] || []
12   - settings = {
13   - analysis: {
14   - analyzer: {
15   - searchkick_keyword: {
16   - type: "custom",
17   - tokenizer: "keyword",
18   - filter: ["lowercase", "snowball"]
  8 + module Model
  9 + def searchkick(field, options = {})
  10 + custom_settings = {
  11 + analysis: {
  12 + analyzer: {
  13 + searchkick_keyword: {
  14 + type: "custom",
  15 + tokenizer: "keyword",
  16 + filter: ["lowercase", "snowball"]
  17 + },
  18 + searchkick: {
  19 + type: "custom",
  20 + tokenizer: "standard",
  21 + # synonym should come last, after stemming and shingle
  22 + # shingle must come before snowball
  23 + filter: ["standard", "lowercase", "asciifolding", "stop", "snowball", "searchkick_index_shingle"]
  24 + },
  25 + searchkick_search: {
  26 + type: "custom",
  27 + tokenizer: "standard",
  28 + filter: ["standard", "lowercase", "asciifolding", "stop", "snowball", "searchkick_search_shingle"]
  29 + },
  30 + searchkick_search2: {
  31 + type: "custom",
  32 + tokenizer: "standard",
  33 + filter: ["standard", "lowercase", "asciifolding", "stop", "snowball"] #, "searchkick_search_shingle"]
  34 + }
19 35 },
20   - searchkick: {
21   - type: "custom",
22   - tokenizer: "standard",
23   - # synonym should come last, after stemming and shingle
24   - # shingle must come before snowball
25   - filter: ["standard", "lowercase", "asciifolding", "stop", "snowball", "searchkick_index_shingle"]
26   - },
27   - searchkick_search: {
28   - type: "custom",
29   - tokenizer: "standard",
30   - filter: ["standard", "lowercase", "asciifolding", "stop", "snowball", "searchkick_search_shingle"]
31   - },
32   - searchkick_search2: {
33   - type: "custom",
34   - tokenizer: "standard",
35   - filter: ["standard", "lowercase", "asciifolding", "stop", "snowball"] #, "searchkick_search_shingle"]
36   - }
37   - },
38   - filter: {
39   - searchkick_index_shingle: {
40   - type: "shingle",
41   - token_separator: ""
42   - },
43   - # lucky find http://web.archiveorange.com/archive/v/AAfXfQ17f57FcRINsof7
44   - searchkick_search_shingle: {
45   - type: "shingle",
46   - token_separator: "",
47   - output_unigrams: false,
48   - output_unigrams_if_no_shingles: true
  36 + filter: {
  37 + searchkick_index_shingle: {
  38 + type: "shingle",
  39 + token_separator: ""
  40 + },
  41 + # lucky find http://web.archiveorange.com/archive/v/AAfXfQ17f57FcRINsof7
  42 + searchkick_search_shingle: {
  43 + type: "shingle",
  44 + token_separator: "",
  45 + output_unigrams: false,
  46 + output_unigrams_if_no_shingles: true
  47 + }
49 48 }
50 49 }
51   - }
52   - }
53   - if synonyms.any?
54   - settings[:analysis][:filter][:searchkick_synonym] = {
55   - type: "synonym",
56   - ignore_case: true,
57   - synonyms: synonyms
58   - }
59   - settings[:analysis][:analyzer][:searchkick][:filter] << "searchkick_synonym"
60   - settings[:analysis][:analyzer][:searchkick_search][:filter].insert(-2, "searchkick_synonym")
61   - settings[:analysis][:analyzer][:searchkick_search][:filter] << "searchkick_synonym"
62   - settings[:analysis][:analyzer][:searchkick_search2][:filter] << "searchkick_synonym"
  50 + }.merge(options[:settings] || {})
  51 + synonyms = options[:synonyms] || []
  52 + if synonyms.any?
  53 + custom_settings[:analysis][:filter][:searchkick_synonym] = {
  54 + type: "synonym",
  55 + ignore_case: true,
  56 + synonyms: synonyms
  57 + }
  58 + custom_settings[:analysis][:analyzer][:searchkick][:filter] << "searchkick_synonym"
  59 + custom_settings[:analysis][:analyzer][:searchkick_search][:filter].insert(-2, "searchkick_synonym")
  60 + custom_settings[:analysis][:analyzer][:searchkick_search][:filter] << "searchkick_synonym"
  61 + custom_settings[:analysis][:analyzer][:searchkick_search2][:filter] << "searchkick_synonym"
  62 + end
  63 +
  64 + class_eval do
  65 + extend Searchkick::Search
  66 + extend Searchkick::Reindex
  67 + include Tire::Model::Search
  68 + include Tire::Model::Callbacks
  69 +
  70 + tire do
  71 + settings custom_settings
  72 + mapping do
  73 + indexes field, analyzer: "searchkick"
  74 + end
  75 + end
  76 + end
63 77 end
64   - settings
65 78 end
66 79 end
67 80  
68   -Tire::Model::Search::ClassMethodsProxy.send :include, Searchkick::Reindex
69   -Tire::Search::Search.send :include, Searchkick::Search
  81 +require "active_record"
  82 +ActiveRecord::Base.send(:extend, Searchkick::Model) if defined?(ActiveRecord)
... ...
lib/searchkick/reindex.rb
... ... @@ -3,13 +3,13 @@ module Searchkick
3 3  
4 4 # https://gist.github.com/jarosan/3124884
5 5 def reindex
6   - alias_name = klass.tire.index.name
  6 + alias_name = tire.index.name
7 7 new_index = alias_name + "_" + Time.now.strftime("%Y%m%d%H%M%S")
8 8  
9 9 # Rake::Task["tire:import"].invoke
10 10 index = Tire::Index.new(new_index)
11   - Tire::Tasks::Import.create_index(index, klass)
12   - scope = klass.respond_to?(:tire_import) ? klass.tire_import : klass
  11 + Tire::Tasks::Import.create_index(index, self)
  12 + scope = respond_to?(:searchkick_import) ? searchkick_import : self
13 13 scope.find_in_batches do |batch|
14 14 index.import batch
15 15 end
... ... @@ -37,6 +37,7 @@ module Searchkick
37 37 end
38 38  
39 39 puts "[IMPORT] Saved alias #{alias_name} pointing to #{new_index}"
  40 + true
40 41 end
41 42  
42 43 end
... ...
lib/searchkick/search.rb
1 1 module Searchkick
2 2 # can't check mapping for conversions since the new index may not be built
3 3 module Search
4   - def searchkick_query(fields, term, conversions = false)
5   - query do
6   - boolean do
7   - must do
8   - dis_max do
9   - query do
10   - match fields, term, boost: 10, operator: "and", analyzer: "searchkick_search"
11   - end
12   - query do
13   - match fields, term, boost: 10, operator: "and", analyzer: "searchkick_search2"
14   - end
15   - query do
16   - match fields, term, use_dis_max: false, fuzziness: 0.7, max_expansions: 1, prefix_length: 1, operator: "and", analyzer: "searchkick_search"
17   - end
18   - query do
19   - match fields, term, use_dis_max: false, fuzziness: 0.7, max_expansions: 1, prefix_length: 1, operator: "and", analyzer: "searchkick_search2"
20   - end
21   - end
22   - end
23   - if conversions
24   - should do
25   - nested path: "conversions", score_mode: "total" do
  4 + def search(term, conversions = false)
  5 + fields = ["name"]
  6 + tire.search do
  7 + query do
  8 + boolean do
  9 + must do
  10 + dis_max do
  11 + query do
  12 + match fields, term, boost: 10, operator: "and", analyzer: "searchkick_search"
  13 + end
  14 + query do
  15 + match fields, term, boost: 10, operator: "and", analyzer: "searchkick_search2"
  16 + end
  17 + query do
  18 + match fields, term, use_dis_max: false, fuzziness: 0.7, max_expansions: 1, prefix_length: 1, operator: "and", analyzer: "searchkick_search"
  19 + end
26 20 query do
27   - custom_score script: "log(doc['count'].value)" do
28   - match "query", term
29   - end
  21 + match fields, term, use_dis_max: false, fuzziness: 0.7, max_expansions: 1, prefix_length: 1, operator: "and", analyzer: "searchkick_search2"
30 22 end
31 23 end
32 24 end
  25 + # if conversions
  26 + # should do
  27 + # nested path: "conversions", score_mode: "total" do
  28 + # query do
  29 + # custom_score script: "log(doc['count'].value)" do
  30 + # match "query", term
  31 + # end
  32 + # end
  33 + # end
  34 + # end
  35 + # end
33 36 end
34 37 end
35 38 end
... ...
searchkick.gemspec
... ... @@ -23,4 +23,6 @@ Gem::Specification.new do |spec|
23 23 spec.add_development_dependency "bundler", "~> 1.3"
24 24 spec.add_development_dependency "rake"
25 25 spec.add_development_dependency "minitest"
  26 + spec.add_development_dependency "activerecord"
  27 + spec.add_development_dependency "pg"
26 28 end
... ...
test/searchkick_test.rb
1 1 require "test_helper"
2 2  
  3 +class Product < ActiveRecord::Base
  4 + searchkick :name, synonyms: [
  5 + "clorox => bleach",
  6 + "saranwrap => plastic wrap",
  7 + "scallion => green onion",
  8 + "qtip => cotton swab",
  9 + "burger => hamburger",
  10 + "bandaid => bandag"
  11 + ], settings: {number_of_shards: 1}
  12 +end
  13 +
3 14 class TestSearchkick < Minitest::Unit::TestCase
4 15  
5 16 def setup
6   - $index = Tire::Index.new("products")
7   - $index.delete
8   - synonyms = [
9   - "clorox => bleach",
10   - "saranwrap => plastic wrap",
11   - "scallion => green onion",
12   - "qtip => cotton swab",
13   - "burger => hamburger",
14   - "bandaid => bandag"
15   - ]
16   - index_options = {
17   - settings: Searchkick.settings(synonyms: synonyms).merge(number_of_shards: 1),
18   - mappings: {
19   - document: {
20   - properties: {
21   - name: {
22   - type: "string",
23   - analyzer: "searchkick"
24   - },
25   - conversions: {
26   - type: "nested",
27   - properties: {
28   - query: {
29   - type: "string",
30   - analyzer: "searchkick_keyword"
31   - },
32   - count: {
33   - type: "integer"
34   - }
35   - }
36   - }
37   - }
38   - }
39   - }
40   - }
41   - $index.create index_options
  17 + Product.index.delete
  18 + Product.create_elasticsearch_index
  19 + end
  20 +
  21 + def test_reindex
  22 + assert Product.reindex
42 23 end
43 24  
44 25 # exact
... ... @@ -206,9 +187,9 @@ class TestSearchkick &lt; Minitest::Unit::TestCase
206 187  
207 188 def store(documents)
208 189 documents.each do |document|
209   - $index.store document
  190 + Product.index.store document.merge(_type: "product")
210 191 end
211   - $index.refresh
  192 + Product.index.refresh
212 193 end
213 194  
214 195 def store_names(names)
... ... @@ -216,13 +197,7 @@ class TestSearchkick &lt; Minitest::Unit::TestCase
216 197 end
217 198  
218 199 def assert_search(term, expected)
219   - search =
220   - Tire.search "products", type: "document" do
221   - searchkick_query ["name"], term, true
222   - explain true
223   - end
224   -
225   - assert_equal expected, search.results.map(&:name)
  200 + assert_equal expected, Product.search(term).map(&:name)
226 201 end
227 202  
228 203 end
... ...
test/test_helper.rb
... ... @@ -2,6 +2,22 @@ require &quot;bundler/setup&quot;
2 2 Bundler.require(:default)
3 3 require "minitest/autorun"
4 4 require "minitest/pride"
  5 +require "active_record"
  6 +
  7 +# for debugging
  8 +# ActiveRecord::Base.logger = Logger.new(STDOUT)
  9 +
  10 +# rails does this in activerecord/lib/active_record/railtie.rb
  11 +ActiveRecord::Base.default_timezone = :utc
  12 +ActiveRecord::Base.time_zone_aware_attributes = true
  13 +
  14 +# migrations
  15 +ActiveRecord::Base.establish_connection :adapter => "postgresql", :database => "searchkick_test"
  16 +
  17 +ActiveRecord::Migration.create_table :products, :force => true do |t|
  18 + t.string :name
  19 + t.timestamps
  20 +end
5 21  
6 22 File.delete("elasticsearch.log") if File.exists?("elasticsearch.log")
7 23 Tire.configure { logger "elasticsearch.log", :level => "debug" }
... ...