diff --git a/lib/searchkick.rb b/lib/searchkick.rb index c7ef3ca..e919f58 100644 --- a/lib/searchkick.rb +++ b/lib/searchkick.rb @@ -5,65 +5,78 @@ require "searchkick/tasks" require "tire" module Searchkick - # TODO fix this monstrosity - # TODO add custom synonyms - def self.settings(options = {}) - synonyms = options[:synonyms] || [] - settings = { - analysis: { - analyzer: { - searchkick_keyword: { - type: "custom", - tokenizer: "keyword", - filter: ["lowercase", "snowball"] + module Model + def searchkick(field, options = {}) + custom_settings = { + analysis: { + analyzer: { + searchkick_keyword: { + type: "custom", + tokenizer: "keyword", + filter: ["lowercase", "snowball"] + }, + searchkick: { + type: "custom", + tokenizer: "standard", + # synonym should come last, after stemming and shingle + # shingle must come before snowball + filter: ["standard", "lowercase", "asciifolding", "stop", "snowball", "searchkick_index_shingle"] + }, + searchkick_search: { + type: "custom", + tokenizer: "standard", + filter: ["standard", "lowercase", "asciifolding", "stop", "snowball", "searchkick_search_shingle"] + }, + searchkick_search2: { + type: "custom", + tokenizer: "standard", + filter: ["standard", "lowercase", "asciifolding", "stop", "snowball"] #, "searchkick_search_shingle"] + } }, - searchkick: { - type: "custom", - tokenizer: "standard", - # synonym should come last, after stemming and shingle - # shingle must come before snowball - filter: ["standard", "lowercase", "asciifolding", "stop", "snowball", "searchkick_index_shingle"] - }, - searchkick_search: { - type: "custom", - tokenizer: "standard", - filter: ["standard", "lowercase", "asciifolding", "stop", "snowball", "searchkick_search_shingle"] - }, - searchkick_search2: { - type: "custom", - tokenizer: "standard", - filter: ["standard", "lowercase", "asciifolding", "stop", "snowball"] #, "searchkick_search_shingle"] - } - }, - filter: { - searchkick_index_shingle: { - type: "shingle", - token_separator: "" - }, - # lucky find http://web.archiveorange.com/archive/v/AAfXfQ17f57FcRINsof7 - searchkick_search_shingle: { - type: "shingle", - token_separator: "", - output_unigrams: false, - output_unigrams_if_no_shingles: true + filter: { + searchkick_index_shingle: { + type: "shingle", + token_separator: "" + }, + # lucky find http://web.archiveorange.com/archive/v/AAfXfQ17f57FcRINsof7 + searchkick_search_shingle: { + type: "shingle", + token_separator: "", + output_unigrams: false, + output_unigrams_if_no_shingles: true + } } } - } - } - if synonyms.any? - settings[:analysis][:filter][:searchkick_synonym] = { - type: "synonym", - ignore_case: true, - synonyms: synonyms - } - settings[:analysis][:analyzer][:searchkick][:filter] << "searchkick_synonym" - settings[:analysis][:analyzer][:searchkick_search][:filter].insert(-2, "searchkick_synonym") - settings[:analysis][:analyzer][:searchkick_search][:filter] << "searchkick_synonym" - settings[:analysis][:analyzer][:searchkick_search2][:filter] << "searchkick_synonym" + }.merge(options[:settings] || {}) + synonyms = options[:synonyms] || [] + if synonyms.any? + custom_settings[:analysis][:filter][:searchkick_synonym] = { + type: "synonym", + ignore_case: true, + synonyms: synonyms + } + custom_settings[:analysis][:analyzer][:searchkick][:filter] << "searchkick_synonym" + custom_settings[:analysis][:analyzer][:searchkick_search][:filter].insert(-2, "searchkick_synonym") + custom_settings[:analysis][:analyzer][:searchkick_search][:filter] << "searchkick_synonym" + custom_settings[:analysis][:analyzer][:searchkick_search2][:filter] << "searchkick_synonym" + end + + class_eval do + extend Searchkick::Search + extend Searchkick::Reindex + include Tire::Model::Search + include Tire::Model::Callbacks + + tire do + settings custom_settings + mapping do + indexes field, analyzer: "searchkick" + end + end + end end - settings end end -Tire::Model::Search::ClassMethodsProxy.send :include, Searchkick::Reindex -Tire::Search::Search.send :include, Searchkick::Search +require "active_record" +ActiveRecord::Base.send(:extend, Searchkick::Model) if defined?(ActiveRecord) diff --git a/lib/searchkick/reindex.rb b/lib/searchkick/reindex.rb index 7d9488e..72e5e95 100644 --- a/lib/searchkick/reindex.rb +++ b/lib/searchkick/reindex.rb @@ -3,13 +3,13 @@ module Searchkick # https://gist.github.com/jarosan/3124884 def reindex - alias_name = klass.tire.index.name + alias_name = tire.index.name new_index = alias_name + "_" + Time.now.strftime("%Y%m%d%H%M%S") # Rake::Task["tire:import"].invoke index = Tire::Index.new(new_index) - Tire::Tasks::Import.create_index(index, klass) - scope = klass.respond_to?(:tire_import) ? klass.tire_import : klass + Tire::Tasks::Import.create_index(index, self) + scope = respond_to?(:searchkick_import) ? searchkick_import : self scope.find_in_batches do |batch| index.import batch end @@ -37,6 +37,7 @@ module Searchkick end puts "[IMPORT] Saved alias #{alias_name} pointing to #{new_index}" + true end end diff --git a/lib/searchkick/search.rb b/lib/searchkick/search.rb index 909a2a5..3506c11 100644 --- a/lib/searchkick/search.rb +++ b/lib/searchkick/search.rb @@ -1,35 +1,38 @@ module Searchkick # can't check mapping for conversions since the new index may not be built module Search - def searchkick_query(fields, term, conversions = false) - query do - boolean do - must do - dis_max do - query do - match fields, term, boost: 10, operator: "and", analyzer: "searchkick_search" - end - query do - match fields, term, boost: 10, operator: "and", analyzer: "searchkick_search2" - end - query do - match fields, term, use_dis_max: false, fuzziness: 0.7, max_expansions: 1, prefix_length: 1, operator: "and", analyzer: "searchkick_search" - end - query do - match fields, term, use_dis_max: false, fuzziness: 0.7, max_expansions: 1, prefix_length: 1, operator: "and", analyzer: "searchkick_search2" - end - end - end - if conversions - should do - nested path: "conversions", score_mode: "total" do + def search(term, conversions = false) + fields = ["name"] + tire.search do + query do + boolean do + must do + dis_max do + query do + match fields, term, boost: 10, operator: "and", analyzer: "searchkick_search" + end + query do + match fields, term, boost: 10, operator: "and", analyzer: "searchkick_search2" + end + query do + match fields, term, use_dis_max: false, fuzziness: 0.7, max_expansions: 1, prefix_length: 1, operator: "and", analyzer: "searchkick_search" + end query do - custom_score script: "log(doc['count'].value)" do - match "query", term - end + match fields, term, use_dis_max: false, fuzziness: 0.7, max_expansions: 1, prefix_length: 1, operator: "and", analyzer: "searchkick_search2" end end end + # if conversions + # should do + # nested path: "conversions", score_mode: "total" do + # query do + # custom_score script: "log(doc['count'].value)" do + # match "query", term + # end + # end + # end + # end + # end end end end diff --git a/searchkick.gemspec b/searchkick.gemspec index 6050a0e..4d8b4e9 100644 --- a/searchkick.gemspec +++ b/searchkick.gemspec @@ -23,4 +23,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", "~> 1.3" spec.add_development_dependency "rake" spec.add_development_dependency "minitest" + spec.add_development_dependency "activerecord" + spec.add_development_dependency "pg" end diff --git a/test/searchkick_test.rb b/test/searchkick_test.rb index 676bb67..0729be5 100644 --- a/test/searchkick_test.rb +++ b/test/searchkick_test.rb @@ -1,44 +1,25 @@ require "test_helper" +class Product < ActiveRecord::Base + searchkick :name, synonyms: [ + "clorox => bleach", + "saranwrap => plastic wrap", + "scallion => green onion", + "qtip => cotton swab", + "burger => hamburger", + "bandaid => bandag" + ], settings: {number_of_shards: 1} +end + class TestSearchkick < Minitest::Unit::TestCase def setup - $index = Tire::Index.new("products") - $index.delete - synonyms = [ - "clorox => bleach", - "saranwrap => plastic wrap", - "scallion => green onion", - "qtip => cotton swab", - "burger => hamburger", - "bandaid => bandag" - ] - index_options = { - settings: Searchkick.settings(synonyms: synonyms).merge(number_of_shards: 1), - mappings: { - document: { - properties: { - name: { - type: "string", - analyzer: "searchkick" - }, - conversions: { - type: "nested", - properties: { - query: { - type: "string", - analyzer: "searchkick_keyword" - }, - count: { - type: "integer" - } - } - } - } - } - } - } - $index.create index_options + Product.index.delete + Product.create_elasticsearch_index + end + + def test_reindex + assert Product.reindex end # exact @@ -206,9 +187,9 @@ class TestSearchkick < Minitest::Unit::TestCase def store(documents) documents.each do |document| - $index.store document + Product.index.store document.merge(_type: "product") end - $index.refresh + Product.index.refresh end def store_names(names) @@ -216,13 +197,7 @@ class TestSearchkick < Minitest::Unit::TestCase end def assert_search(term, expected) - search = - Tire.search "products", type: "document" do - searchkick_query ["name"], term, true - explain true - end - - assert_equal expected, search.results.map(&:name) + assert_equal expected, Product.search(term).map(&:name) end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 0930e44..0d8dbab 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -2,6 +2,22 @@ require "bundler/setup" Bundler.require(:default) require "minitest/autorun" require "minitest/pride" +require "active_record" + +# for debugging +# ActiveRecord::Base.logger = Logger.new(STDOUT) + +# rails does this in activerecord/lib/active_record/railtie.rb +ActiveRecord::Base.default_timezone = :utc +ActiveRecord::Base.time_zone_aware_attributes = true + +# migrations +ActiveRecord::Base.establish_connection :adapter => "postgresql", :database => "searchkick_test" + +ActiveRecord::Migration.create_table :products, :force => true do |t| + t.string :name + t.timestamps +end File.delete("elasticsearch.log") if File.exists?("elasticsearch.log") Tire.configure { logger "elasticsearch.log", :level => "debug" } -- libgit2 0.21.0