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 | 669 | class Product < ApplicationRecord |
670 | 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 | 674 | def search_data |
675 | 675 | { |
... | ... | @@ -2101,6 +2101,38 @@ And there’s a [new option](#default-scopes) for models with default scopes. |
2101 | 2101 | |
2102 | 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 | 2136 | ## History |
2105 | 2137 | |
2106 | 2138 | View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md). | ... | ... |
lib/searchkick/index.rb
... | ... | @@ -291,6 +291,11 @@ module Searchkick |
291 | 291 | end |
292 | 292 | |
293 | 293 | # private |
294 | + def conversions_v2_fields | |
295 | + @conversions_v2_fields ||= Array(options[:conversions_v2]).map(&:to_s) | |
296 | + end | |
297 | + | |
298 | + # private | |
294 | 299 | def suggest_fields |
295 | 300 | @suggest_fields ||= Array(options[:suggest]).map(&:to_s) |
296 | 301 | end | ... | ... |
lib/searchkick/index_options.rb
... | ... | @@ -357,6 +357,16 @@ module Searchkick |
357 | 357 | } |
358 | 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 | 370 | mapping_options = |
361 | 371 | [:suggest, :word, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight, :searchable, :filterable] |
362 | 372 | .to_h { |type| [type, (options[type] || []).map(&:to_s)] } | ... | ... |
lib/searchkick/model.rb
... | ... | @@ -3,7 +3,7 @@ module Searchkick |
3 | 3 | def searchkick(**options) |
4 | 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 | 7 | :filterable, :geo_shape, :highlight, :ignore_above, :index_name, :index_prefix, :inheritance, :language, |
8 | 8 | :locations, :mappings, :match, :merge_mappings, :routing, :searchable, :search_synonyms, :settings, :similarity, |
9 | 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 | 18 | |
19 | 19 | def initialize(klass, term = "*", **options) |
20 | 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 | 22 | :fields, :highlight, :includes, :index_name, :indices_boost, :limit, :load, |
23 | 23 | :match, :misspellings, :models, :model_includes, :offset, :operator, :order, :padding, :page, :per_page, :profile, |
24 | 24 | :request_params, :routing, :scope_results, :scroll, :select, :similar, :smart_aggs, :suggest, :total_entries, :track, :type, :where] |
... | ... | @@ -431,6 +431,7 @@ module Searchkick |
431 | 431 | } |
432 | 432 | |
433 | 433 | should.concat(set_conversions) |
434 | + should.concat(set_conversions_v2) | |
434 | 435 | end |
435 | 436 | |
436 | 437 | query = payload |
... | ... | @@ -642,6 +643,25 @@ module Searchkick |
642 | 643 | end |
643 | 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 | 665 | def set_exclude(field, analyzer) |
646 | 666 | Array(options[:exclude]).map do |phrase| |
647 | 667 | { | ... | ... |
lib/searchkick/record_data.rb
... | ... | @@ -58,6 +58,12 @@ module Searchkick |
58 | 58 | end |
59 | 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 | 67 | # hack to prevent generator field doesn't exist error |
62 | 68 | if !partial_reindex |
63 | 69 | index.suggest_fields.each do |field| | ... | ... |
test/conversions_test.rb
... | ... | @@ -16,6 +16,15 @@ class ConversionsTest < Minitest::Test |
16 | 16 | assert_equal_scores "tomato", conversions: false |
17 | 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 | 28 | def test_multiple_conversions |
20 | 29 | store [ |
21 | 30 | {name: "Speaker A", conversions_a: {"speaker" => 1}, conversions_b: {"speaker" => 6}}, | ... | ... |
test/models/product.rb
... | ... | @@ -10,6 +10,7 @@ class Product |
10 | 10 | ], |
11 | 11 | suggest: [:name, :color], |
12 | 12 | conversions: [:conversions], |
13 | + conversions_v2: [:conversions_v2], | |
13 | 14 | locations: [:location, :multiple_locations], |
14 | 15 | text_start: [:name], |
15 | 16 | text_middle: [:name], |
... | ... | @@ -22,7 +23,7 @@ class Product |
22 | 23 | similarity: "BM25", |
23 | 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 | 28 | class << self |
28 | 29 | attr_accessor :dynamic_data |
... | ... | @@ -33,6 +34,7 @@ class Product |
33 | 34 | |
34 | 35 | serializable_hash.except("id", "_id").merge( |
35 | 36 | conversions: conversions, |
37 | + conversions_v2: conversions_v2, | |
36 | 38 | user_ids: user_ids, |
37 | 39 | location: {lat: latitude, lon: longitude}, |
38 | 40 | multiple_locations: [{lat: latitude, lon: longitude}, {lat: 0, lon: 0}], | ... | ... |