Commit 01de136583eda91799d59da59ea14bdcdea14f33
1 parent
d5312b89
Exists in
conversions_v2
Added conversions_v2 option
Showing
8 changed files
with
88 additions
and
4 deletions
Show diff stats
README.md
@@ -669,7 +669,7 @@ Add conversion data with: | @@ -669,7 +669,7 @@ Add conversion data with: | ||
669 | class Product < ApplicationRecord | 669 | class Product < ApplicationRecord |
670 | has_many :searches, class_name: "Searchjoy::Search", as: :convertable | 670 | has_many :searches, class_name: "Searchjoy::Search", as: :convertable |
671 | 671 | ||
672 | - searchkick conversions: [:conversions] # name of field | 672 | + searchkick conversions_v2: [:conversions] # name of field |
673 | 673 | ||
674 | def search_data | 674 | def search_data |
675 | { | 675 | { |
@@ -2101,6 +2101,38 @@ And there’s a [new option](#default-scopes) for models with default scopes. | @@ -2101,6 +2101,38 @@ And there’s a [new option](#default-scopes) for models with default scopes. | ||
2101 | 2101 | ||
2102 | Check out the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md#500-2022-02-21) for the full list of changes. | 2102 | Check out the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md#500-2022-02-21) for the full list of changes. |
2103 | 2103 | ||
2104 | +## Upgrading Conversions | ||
2105 | + | ||
2106 | +Upgrade conversions without downtime. Add `conversions_v2` to your model and an additional field to `search_data`: | ||
2107 | + | ||
2108 | +```ruby | ||
2109 | +class Product < ApplicationRecord | ||
2110 | + searchkick conversions: [:conversions], conversions_v2: [:conversions_v2] | ||
2111 | + | ||
2112 | + def search_data | ||
2113 | + conversions = searches.group(:query).distinct.count(:user_id) | ||
2114 | + { | ||
2115 | + conversions: conversions, | ||
2116 | + conversions_v2: conversions | ||
2117 | + } | ||
2118 | + end | ||
2119 | +end | ||
2120 | +``` | ||
2121 | + | ||
2122 | +Reindex, then remove `conversions`: | ||
2123 | + | ||
2124 | +```ruby | ||
2125 | +class Product < ApplicationRecord | ||
2126 | + searchkick conversions_v2: [:conversions_v2] | ||
2127 | + | ||
2128 | + def search_data | ||
2129 | + { | ||
2130 | + conversions_v2: searches.group(:query).distinct.count(:user_id) | ||
2131 | + } | ||
2132 | + end | ||
2133 | +end | ||
2134 | +``` | ||
2135 | + | ||
2104 | ## History | 2136 | ## History |
2105 | 2137 | ||
2106 | View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md). | 2138 | View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md). |
lib/searchkick/index.rb
@@ -291,6 +291,11 @@ module Searchkick | @@ -291,6 +291,11 @@ module Searchkick | ||
291 | end | 291 | end |
292 | 292 | ||
293 | # private | 293 | # private |
294 | + def conversions_v2_fields | ||
295 | + @conversions_v2_fields ||= Array(options[:conversions_v2]).map(&:to_s) | ||
296 | + end | ||
297 | + | ||
298 | + # private | ||
294 | def suggest_fields | 299 | def suggest_fields |
295 | @suggest_fields ||= Array(options[:suggest]).map(&:to_s) | 300 | @suggest_fields ||= Array(options[:suggest]).map(&:to_s) |
296 | end | 301 | end |
lib/searchkick/index_options.rb
@@ -357,6 +357,16 @@ module Searchkick | @@ -357,6 +357,16 @@ module Searchkick | ||
357 | } | 357 | } |
358 | end | 358 | end |
359 | 359 | ||
360 | + Array(options[:conversions_v2]).each do |conversions_field| | ||
361 | + mapping[conversions_field] = { | ||
362 | + type: "rank_features" | ||
363 | + } | ||
364 | + end | ||
365 | + | ||
366 | + if (Array(options[:conversions_v2]).map(&:to_s) & Array(options[:conversions]).map(&:to_s)).any? | ||
367 | + raise ArgumentError, "Must have separate conversions fields" | ||
368 | + end | ||
369 | + | ||
360 | mapping_options = | 370 | mapping_options = |
361 | [:suggest, :word, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight, :searchable, :filterable] | 371 | [:suggest, :word, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight, :searchable, :filterable] |
362 | .to_h { |type| [type, (options[type] || []).map(&:to_s)] } | 372 | .to_h { |type| [type, (options[type] || []).map(&:to_s)] } |
lib/searchkick/model.rb
@@ -3,7 +3,7 @@ module Searchkick | @@ -3,7 +3,7 @@ module Searchkick | ||
3 | def searchkick(**options) | 3 | def searchkick(**options) |
4 | options = Searchkick.model_options.merge(options) | 4 | options = Searchkick.model_options.merge(options) |
5 | 5 | ||
6 | - unknown_keywords = options.keys - [:_all, :_type, :batch_size, :callbacks, :case_sensitive, :conversions, :deep_paging, :default_fields, | 6 | + unknown_keywords = options.keys - [:_all, :_type, :batch_size, :callbacks, :case_sensitive, :conversions, :conversions_v2, :deep_paging, :default_fields, |
7 | :filterable, :geo_shape, :highlight, :ignore_above, :index_name, :index_prefix, :inheritance, :language, | 7 | :filterable, :geo_shape, :highlight, :ignore_above, :index_name, :index_prefix, :inheritance, :language, |
8 | :locations, :mappings, :match, :merge_mappings, :routing, :searchable, :search_synonyms, :settings, :similarity, | 8 | :locations, :mappings, :match, :merge_mappings, :routing, :searchable, :search_synonyms, :settings, :similarity, |
9 | :special_characters, :stem, :stemmer, :stem_conversions, :stem_exclusion, :stemmer_override, :suggest, :synonyms, :text_end, | 9 | :special_characters, :stem, :stemmer, :stem_conversions, :stem_exclusion, :stemmer_override, :suggest, :synonyms, :text_end, |
lib/searchkick/query.rb
@@ -18,7 +18,7 @@ module Searchkick | @@ -18,7 +18,7 @@ module Searchkick | ||
18 | 18 | ||
19 | def initialize(klass, term = "*", **options) | 19 | def initialize(klass, term = "*", **options) |
20 | unknown_keywords = options.keys - [:aggs, :block, :body, :body_options, :boost, | 20 | unknown_keywords = options.keys - [:aggs, :block, :body, :body_options, :boost, |
21 | - :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_term, :debug, :emoji, :exclude, :explain, | 21 | + :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_v2, :conversions_term, :debug, :emoji, :exclude, :explain, |
22 | :fields, :highlight, :includes, :index_name, :indices_boost, :limit, :load, | 22 | :fields, :highlight, :includes, :index_name, :indices_boost, :limit, :load, |
23 | :match, :misspellings, :models, :model_includes, :offset, :operator, :order, :padding, :page, :per_page, :profile, | 23 | :match, :misspellings, :models, :model_includes, :offset, :operator, :order, :padding, :page, :per_page, :profile, |
24 | :request_params, :routing, :scope_results, :scroll, :select, :similar, :smart_aggs, :suggest, :total_entries, :track, :type, :where] | 24 | :request_params, :routing, :scope_results, :scroll, :select, :similar, :smart_aggs, :suggest, :total_entries, :track, :type, :where] |
@@ -431,6 +431,7 @@ module Searchkick | @@ -431,6 +431,7 @@ module Searchkick | ||
431 | } | 431 | } |
432 | 432 | ||
433 | should.concat(set_conversions) | 433 | should.concat(set_conversions) |
434 | + should.concat(set_conversions_v2) | ||
434 | end | 435 | end |
435 | 436 | ||
436 | query = payload | 437 | query = payload |
@@ -642,6 +643,25 @@ module Searchkick | @@ -642,6 +643,25 @@ module Searchkick | ||
642 | end | 643 | end |
643 | end | 644 | end |
644 | 645 | ||
646 | + def set_conversions_v2 | ||
647 | + conversions_fields = Array(options[:conversions_v2] || searchkick_options[:conversions_v2]).map(&:to_s) | ||
648 | + # disable if searchkick_options[:conversions] to make it easy to upgrade without downtime | ||
649 | + if conversions_fields.present? && options[:conversions_v2] != false && !(options[:conversions_v2].nil? && searchkick_options[:conversions]) | ||
650 | + conversions_fields.map do |conversions_field| | ||
651 | + { | ||
652 | + rank_feature: { | ||
653 | + field: "#{conversions_field}.#{options[:conversions_term] || term}", | ||
654 | + log: { | ||
655 | + scaling_factor: 1 | ||
656 | + } | ||
657 | + } | ||
658 | + } | ||
659 | + end | ||
660 | + else | ||
661 | + [] | ||
662 | + end | ||
663 | + end | ||
664 | + | ||
645 | def set_exclude(field, analyzer) | 665 | def set_exclude(field, analyzer) |
646 | Array(options[:exclude]).map do |phrase| | 666 | Array(options[:exclude]).map do |phrase| |
647 | { | 667 | { |
lib/searchkick/record_data.rb
@@ -58,6 +58,12 @@ module Searchkick | @@ -58,6 +58,12 @@ module Searchkick | ||
58 | end | 58 | end |
59 | end | 59 | end |
60 | 60 | ||
61 | + index.conversions_v2_fields.each do |conversions_field| | ||
62 | + unless source[conversions_field] || source[conversions_field.to_sym] | ||
63 | + source[conversions_field] ||= {} | ||
64 | + end | ||
65 | + end | ||
66 | + | ||
61 | # hack to prevent generator field doesn't exist error | 67 | # hack to prevent generator field doesn't exist error |
62 | if !partial_reindex | 68 | if !partial_reindex |
63 | index.suggest_fields.each do |field| | 69 | index.suggest_fields.each do |field| |
test/conversions_test.rb
@@ -16,6 +16,15 @@ class ConversionsTest < Minitest::Test | @@ -16,6 +16,15 @@ class ConversionsTest < Minitest::Test | ||
16 | assert_equal_scores "tomato", conversions: false | 16 | assert_equal_scores "tomato", conversions: false |
17 | end | 17 | end |
18 | 18 | ||
19 | + def test_conversions_v2 | ||
20 | + store [ | ||
21 | + {name: "Tomato A", conversions_v2: {"tomato" => 1}}, | ||
22 | + {name: "Tomato B", conversions_v2: {"tomato" => 2}}, | ||
23 | + {name: "Tomato C", conversions_v2: {"tomato" => 3}} | ||
24 | + ] | ||
25 | + assert_order "tomato", ["Tomato C", "Tomato B", "Tomato A"], conversions_v2: [:conversions_v2] | ||
26 | + end | ||
27 | + | ||
19 | def test_multiple_conversions | 28 | def test_multiple_conversions |
20 | store [ | 29 | store [ |
21 | {name: "Speaker A", conversions_a: {"speaker" => 1}, conversions_b: {"speaker" => 6}}, | 30 | {name: "Speaker A", conversions_a: {"speaker" => 1}, conversions_b: {"speaker" => 6}}, |
test/models/product.rb
@@ -10,6 +10,7 @@ class Product | @@ -10,6 +10,7 @@ class Product | ||
10 | ], | 10 | ], |
11 | suggest: [:name, :color], | 11 | suggest: [:name, :color], |
12 | conversions: [:conversions], | 12 | conversions: [:conversions], |
13 | + conversions_v2: [:conversions_v2], | ||
13 | locations: [:location, :multiple_locations], | 14 | locations: [:location, :multiple_locations], |
14 | text_start: [:name], | 15 | text_start: [:name], |
15 | text_middle: [:name], | 16 | text_middle: [:name], |
@@ -22,7 +23,7 @@ class Product | @@ -22,7 +23,7 @@ class Product | ||
22 | similarity: "BM25", | 23 | similarity: "BM25", |
23 | match: ENV["MATCH"] ? ENV["MATCH"].to_sym : nil | 24 | match: ENV["MATCH"] ? ENV["MATCH"].to_sym : nil |
24 | 25 | ||
25 | - attr_accessor :conversions, :user_ids, :aisle, :details | 26 | + attr_accessor :conversions, :conversions_v2, :user_ids, :aisle, :details |
26 | 27 | ||
27 | class << self | 28 | class << self |
28 | attr_accessor :dynamic_data | 29 | attr_accessor :dynamic_data |
@@ -33,6 +34,7 @@ class Product | @@ -33,6 +34,7 @@ class Product | ||
33 | 34 | ||
34 | serializable_hash.except("id", "_id").merge( | 35 | serializable_hash.except("id", "_id").merge( |
35 | conversions: conversions, | 36 | conversions: conversions, |
37 | + conversions_v2: conversions_v2, | ||
36 | user_ids: user_ids, | 38 | user_ids: user_ids, |
37 | location: {lat: latitude, lon: longitude}, | 39 | location: {lat: latitude, lon: longitude}, |
38 | multiple_locations: [{lat: latitude, lon: longitude}, {lat: 0, lon: 0}], | 40 | multiple_locations: [{lat: latitude, lon: longitude}, {lat: 0, lon: 0}], |