full_search 0.3.7 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d25d24145bf1aef1508210eb9d6879b21218ed8760ef6a61812706720a12ac17
4
- data.tar.gz: 94b71b2b86db367ef8fde640550ab7b5945b50829d618c5e4eb41a4d9a6e0d65
3
+ metadata.gz: a86070d05b32fc24ce6ac007a33c673c3831760ce76554bbb9d636e6e0c6be0f
4
+ data.tar.gz: 293b0be9931c7ffa5de9971aa52b2bf9014c2b0816d1364f839ccba57af8f814
5
5
  SHA512:
6
- metadata.gz: 4074fd91514f5f209cbf6d5f4b2fdd36eed20ccfd894e0ccaac330e25bf7e0cda0c2c689833f2fcf947df768cf0282e88d717f1a001797e901ee0ebb18ab6e1a
7
- data.tar.gz: f16291785a67d4e9cec7e48fe407a3c4735e21711752feea5ff2ad88573ef8010732feb8eaa9a1f1c40bfb14c14f476dd5f242fb9e624ba68f1a4ec18ca077ba
6
+ metadata.gz: cd9854cc22d14bd739723e5011a831fd34d0e523ef6a6df09bf45fa1f3a7880c466f4ba121eb07d9d92daf65449edf74b5e0ddc42dc15b521ea34e78b496be3a
7
+ data.tar.gz: f704e822cee03ad98f2ed96dbc802e3e5b777df659ecdc3d8187ace6088494ab318b6f1598c74b840697909b465e539a76ee9f758d6a709041d9e0b198d45129
data/README.md CHANGED
@@ -193,6 +193,20 @@ end
193
193
 
194
194
  ### In production
195
195
 
196
+ **`auto_rebuild_schema`** — Must be `false` in production. The generated initializer defaults to `Rails.env.local?`, so it's already off. If enabled, every process (web, worker, console) attempts schema rebuilds on boot, which is unnecessary and can cause issues during zero-downtime deploys where old and new processes overlap.
197
+
198
+ **`auto_rebuild_on_stale_query`** — Must be `false` in production. A query-time rebuild is dangerous — it can cause timeouts or race conditions under load. The generated initializer defaults to `Rails.env.local?`, keeping it off in production.
199
+
200
+ **`lock_rebuilds`** — This option uses a Ruby `Mutex` and only prevents concurrent rebuilds within the same process. It does **not** coordinate across processes or hosts. Multi-process or multi-host deployments must run `full_search:rebuild` from a single deployment step.
201
+
202
+ **Docker entrypoint** — For containerized environments, add `full_search:prepare` after `db:prepare:with_data`:
203
+
204
+ ```bash
205
+ ./bin/rails full_search:prepare
206
+ ```
207
+
208
+ `full_search:prepare` is idempotent — it only creates missing FTS tables and installs triggers, making it safe to run on every deploy.
209
+
196
210
  Auto-rebuild runs on every Rails process boot (web, worker, console). For zero-downtime deploys where old processes still serve traffic, or if you prefer explicit control, set `auto_rebuild_schema` to `false` and run the rebuild task manually:
197
211
 
198
212
  ```bash
@@ -8,6 +8,7 @@ module FullSearch
8
8
 
9
9
  def perform(model_name)
10
10
  model = model_name.to_s.constantize
11
+ FullSearch::Index.sqlite!(model)
11
12
  FullSearch::Index.rebuild!(model)
12
13
  end
13
14
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "full_search/errors"
4
+
3
5
  module FullSearch
4
6
  module Callbacks
5
7
  def self.install!(model)
@@ -64,34 +66,35 @@ module FullSearch
64
66
 
65
67
  value = FullSearch::Model.evaluate_source(record, field)
66
68
  table = qt(FullSearch::Index.fts_table_name(record.class))
67
- conn = ActiveRecord::Base.connection
69
+ conn = connection_for(record.class)
68
70
  conn.execute(
69
71
  "UPDATE #{table} SET #{qc(field.as || field.name)} = #{q(value.to_s)} WHERE rowid = #{q(record.id)}"
70
72
  )
71
- rescue => e
72
- raise unless e.message.include?("no such table")
73
+ rescue ActiveRecord::StatementInvalid => e
74
+ raise_missing_table_or_original(e, record.class)
73
75
  end
74
76
 
75
77
  def self.remove_record!(record)
76
78
  table = qt(FullSearch::Index.fts_table_name(record.class))
77
- conn = ActiveRecord::Base.connection
79
+ conn = connection_for(record.class)
78
80
  conn.execute("DELETE FROM #{table} WHERE rowid = #{q(record.id)}")
79
- rescue => e
80
- raise unless e.message.include?("no such table")
81
+ rescue ActiveRecord::StatementInvalid => e
82
+ raise_missing_table_or_original(e, record.class)
81
83
  end
82
84
 
83
85
  def self.reindex_dependents!(parent_record, dependent_model, field)
84
86
  fk = association_key(dependent_model, field.reindex_on)
85
- conn = ActiveRecord::Base.connection
87
+ conn = connection_for(dependent_model)
86
88
  sql = "SELECT id FROM #{qt(dependent_model.table_name)} WHERE #{qc(fk)} = #{q(parent_record.id)}"
87
89
  dependent_ids = conn.execute(sql).map { |r| r["id"] }
88
90
 
89
- dependent_ids.each do |dep_id|
90
- if field.async
91
- FullSearch::ReindexJob.perform_later(dependent_model.name, dep_id, field.name)
92
- else
93
- dependent = dependent_model.find_by(id: dep_id)
94
- reindex_field!(dependent, field.name) if dependent
91
+ return if dependent_ids.empty?
92
+
93
+ if field.async
94
+ dependent_ids.each { |id| FullSearch::ReindexJob.perform_later(dependent_model.name, id, field.name) }
95
+ else
96
+ dependent_model.where(id: dependent_ids).find_each(batch_size: 500) do |record|
97
+ reindex_field!(record, field.name)
95
98
  end
96
99
  end
97
100
  end
@@ -113,5 +116,20 @@ module FullSearch
113
116
  reflection = model.reflect_on_association(association_name.to_sym)
114
117
  reflection&.foreign_key&.to_s || "#{association_name}_id"
115
118
  end
119
+
120
+ def self.connection_for(klass)
121
+ klass.connection
122
+ rescue NoMethodError
123
+ ActiveRecord::Base.connection
124
+ end
125
+
126
+ def self.raise_missing_table_or_original(error, model_class)
127
+ if error.message.include?("no such table")
128
+ FullSearch::Instrumentation.instrument("missing_table", model: model_class.name, error: error.message)
129
+ return
130
+ end
131
+ raise error
132
+ end
133
+ private_class_method :raise_missing_table_or_original
116
134
  end
117
135
  end
@@ -12,9 +12,9 @@ module FullSearch
12
12
  @lock_rebuilds = true
13
13
  @default_async_reindex = true
14
14
  @default_async_source_reindex = true
15
- @default_tokenizer = "unicode61"
15
+ @default_tokenizer = FullSearch::Constants::DEFAULT_TOKENIZER
16
16
  @auto_rebuild_on_stale_query = false
17
- @min_like_prefix_length = 3
17
+ @min_like_prefix_length = FullSearch::Constants::DEFAULT_MIN_LIKE_PREFIX_LENGTH
18
18
  end
19
19
  end
20
20
 
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FullSearch
4
+ module Constants
5
+ MIN_TERM_LENGTH = 3
6
+ TWO_TYPO_MIN_LENGTH = 9
7
+ DEFAULT_MIN_LIKE_PREFIX_LENGTH = 3
8
+ REBUILDING_HASH = "__rebuilding__"
9
+ DEFAULT_TOKENIZER = "unicode61"
10
+ MAX_EXACT_MATCH_BOOST_IDS = 100
11
+ end
12
+ end
@@ -45,7 +45,9 @@ module FullSearch
45
45
  end
46
46
  str = name.to_s
47
47
  raise InvalidFieldError, "#{model_class.name}: duplicate exact_match name #{name.inspect}" if exact_matches.any? { |e| e.name == str }
48
- @exact_matches << ExactMatch.new(name: str, source: source, sql: sql&.to_s, normalize: normalize, version: version)
48
+ sql_str = sql&.to_s
49
+ validate_sql_fragment!(sql_str, "exact_match sql:") if sql_str
50
+ @exact_matches << ExactMatch.new(name: str, source: source, sql: sql_str, normalize: normalize, version: version)
49
51
  end
50
52
 
51
53
  def rank_by(column, direction = :desc)
@@ -92,7 +94,9 @@ module FullSearch
92
94
  end
93
95
 
94
96
  def index_if(sql: nil)
95
- @index_if_sql = sql&.to_s
97
+ sql_str = sql&.to_s
98
+ validate_sql_fragment!(sql_str, "index_if sql:") if sql_str
99
+ @index_if_sql = sql_str
96
100
  end
97
101
 
98
102
  def conditional_index?
@@ -109,7 +113,7 @@ module FullSearch
109
113
 
110
114
  def typo_tolerance(enabled = true, min_term_length: nil)
111
115
  @typo_tolerance = enabled
112
- @typo_tolerance_min_term_length = min_term_length || 3
116
+ @typo_tolerance_min_term_length = min_term_length || FullSearch::Constants::MIN_TERM_LENGTH
113
117
  end
114
118
 
115
119
  def typo_tolerance?
@@ -117,7 +121,7 @@ module FullSearch
117
121
  end
118
122
 
119
123
  def typo_tolerance_min_term_length
120
- @typo_tolerance_min_term_length || 3
124
+ @typo_tolerance_min_term_length || FullSearch::Constants::MIN_TERM_LENGTH
121
125
  end
122
126
 
123
127
  def config_hash
@@ -139,6 +143,12 @@ module FullSearch
139
143
 
140
144
  private
141
145
 
146
+ def validate_sql_fragment!(sql, option_name)
147
+ if sql.match?(/;|--|\/\*|\*\//)
148
+ raise InvalidFieldError, "#{model_class.name}: #{option_name} contains unsafe characters"
149
+ end
150
+ end
151
+
142
152
  def valid_name?(name)
143
153
  name.to_s.match?(/\A[a-zA-Z_]\w*\z/)
144
154
  end
@@ -59,7 +59,7 @@ module FullSearch
59
59
  length = query.length
60
60
  min_length = dsl.typo_tolerance_min_term_length.to_i
61
61
  return nil if length < min_length
62
- return 2 if length >= 9
62
+ return 2 if length >= FullSearch::Constants::TWO_TYPO_MIN_LENGTH
63
63
  1
64
64
  end
65
65
 
@@ -170,8 +170,8 @@ module FullSearch
170
170
  end
171
171
 
172
172
  def max_allowed_typos(length)
173
- return -1 if length < 3
174
- return 2 if length >= 9
173
+ return -1 if length < FullSearch::Constants::MIN_TERM_LENGTH
174
+ return 2 if length >= FullSearch::Constants::TWO_TYPO_MIN_LENGTH
175
175
  1
176
176
  end
177
177
 
@@ -65,21 +65,23 @@ module FullSearch
65
65
  create_metadata_table!
66
66
 
67
67
  with_rebuild_lock(model) do
68
- drop_triggers!(model)
69
- conn.execute("DROP TABLE IF EXISTS #{qt(fts_table_name(model))};")
70
- conn.execute("DROP TABLE IF EXISTS #{qt(trigram_table_name(model))};")
71
- conn.execute(create_virtual_table_sql(model))
72
- if dsl.typo_tolerance?
73
- FullSearch::Typo.warn_unsupported!(model) unless FullSearch::Typo.supported?(model)
74
- conn.execute(create_trigram_virtual_table_sql(model))
68
+ FullSearch::Instrumentation.instrument("rebuild", model: model.name) do
69
+ drop_triggers!(model)
70
+ conn.execute("DROP TABLE IF EXISTS #{qt(fts_table_name(model))};")
71
+ conn.execute("DROP TABLE IF EXISTS #{qt(trigram_table_name(model))};")
72
+ conn.execute(create_virtual_table_sql(model))
73
+ if dsl.typo_tolerance?
74
+ FullSearch::Typo.warn_unsupported!(model) unless FullSearch::Typo.supported?(model)
75
+ conn.execute(create_trigram_virtual_table_sql(model))
76
+ end
77
+ conn.execute(backfill_sql(model))
78
+ conn.execute(backfill_trigram_sql(model)) if dsl.typo_tolerance?
79
+ reindex_source_fields!(model) if dsl.fields.any?(&:source)
80
+ create_triggers!(model)
81
+ optimize!(model)
82
+ store_config_hash!(model, rebuilt_at: Time.current)
83
+ verified_tables.add(model.table_name)
75
84
  end
76
- conn.execute(backfill_sql(model))
77
- conn.execute(backfill_trigram_sql(model)) if dsl.typo_tolerance?
78
- reindex_source_fields!(model) if dsl.fields.any?(&:source)
79
- create_triggers!(model)
80
- optimize!(model)
81
- store_config_hash!(model, rebuilt_at: Time.current)
82
- verified_tables.add(model.table_name)
83
85
  end
84
86
  end
85
87
 
@@ -103,21 +105,35 @@ module FullSearch
103
105
  end
104
106
 
105
107
  def optimize!(model)
106
- sqlite!(model)
107
- connection.execute("INSERT INTO #{qt(fts_table_name(model))}(#{qt(fts_table_name(model))}) VALUES('optimize');")
108
+ FullSearch::Instrumentation.instrument("optimize", model: model.name) do
109
+ sqlite!(model)
110
+ connection.execute("INSERT INTO #{qt(fts_table_name(model))}(#{qt(fts_table_name(model))}) VALUES('optimize');")
111
+ end
108
112
  end
109
113
 
110
114
  def reindex_source_fields!(model)
111
115
  dsl = model.full_search_dsl
112
116
  return unless dsl
113
117
 
114
- model.find_each do |record|
115
- dsl.fields.each do |field|
116
- FullSearch::Callbacks.reindex_field!(record, field.name) if field.source
117
- end
118
+ source_fields = dsl.fields.select(&:source)
119
+ return if source_fields.empty?
120
+
121
+ model.find_each(batch_size: 500) do |record|
122
+ pairs = source_fields.to_h { |f| [column_name(f), FullSearch::Model.evaluate_source(record, f).to_s] }
123
+ next if pairs.empty?
124
+ upsert_source_row!(model, record.id, pairs)
118
125
  end
119
126
  end
120
127
 
128
+ def upsert_source_row!(model, rowid, values)
129
+ table = qt(fts_table_name(model))
130
+ sets = values.map { |name, value| "#{qc(name)} = #{q(value)}" }.join(", ")
131
+
132
+ connection.execute(<<~SQL)
133
+ UPDATE #{table} SET #{sets} WHERE rowid = #{q(rowid)};
134
+ SQL
135
+ end
136
+
121
137
  def create_triggers!(model)
122
138
  connection.execute(insert_trigger_sql(model))
123
139
  connection.execute(delete_trigger_sql(model))
@@ -501,15 +517,13 @@ module FullSearch
501
517
  # For multi-process or multi-host deployments, run full_search:rebuild from a single
502
518
  # deployment step. See README.
503
519
  def with_rebuild_lock(model)
504
- if FullSearch.config.lock_rebuilds
505
- connection.transaction do
520
+ connection.transaction do
521
+ if FullSearch.config.lock_rebuilds
506
522
  connection.execute(
507
- "INSERT INTO full_search_index_versions (table_name, config_hash, rebuilt_at) VALUES (#{q(model.table_name)}, #{q("__rebuilding__")}, datetime('now'))
523
+ "INSERT INTO full_search_index_versions (table_name, config_hash, rebuilt_at) VALUES (#{q(model.table_name)}, #{q(FullSearch::Constants::REBUILDING_HASH)}, datetime('now'))
508
524
  ON CONFLICT(table_name) DO UPDATE SET config_hash=excluded.config_hash;"
509
525
  )
510
- yield
511
526
  end
512
- else
513
527
  yield
514
528
  end
515
529
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FullSearch
4
+ module Instrumentation
5
+ def self.instrument(name, payload = {}, &block)
6
+ ActiveSupport::Notifications.instrument("full_search.#{name}", payload, &block)
7
+ end
8
+ end
9
+ end
@@ -19,42 +19,44 @@ module FullSearch
19
19
  @includes = includes
20
20
  end
21
21
 
22
- MIN_TERM_LENGTH = 3
22
+ MIN_TERM_LENGTH = FullSearch::Constants::MIN_TERM_LENGTH
23
23
 
24
24
  def relation
25
- validate_filter_keys!
26
- validate_required_filters!
27
- check_stale_config!
28
-
29
- return model.none if dsl.tokenize == "trigram" && query.length < MIN_TERM_LENGTH && !dsl.typo_tolerance?
30
-
31
- parsed = QueryParser.parse(query)
32
- exact_ids = ExactMatch.ids_for(model, query, filters)
33
- primary_ids = fts_match_ids(parsed)
34
- fallback_ids = (dsl.typo_tolerance? && matching_strategy != "all") ? trigram_match_ids(parsed, primary_ids, candidate_limit: per_strategy_limit) : []
35
- fuzzy_ids = (dsl.typo_tolerance? && matching_strategy != "all" && primary_ids.empty? && fallback_ids.empty?) ? fuzzy_match_ids(parsed, candidate_limit: per_strategy_limit) : []
36
-
37
- all_ids = (exact_ids + primary_ids + fallback_ids + fuzzy_ids).uniq
38
- return model.none if all_ids.empty?
39
-
40
- rel = model.where(id: all_ids)
41
- rel = rel.where(model.arel_table[dsl.soft_delete_column].eq(nil)) if dsl.soft_delete_column && !include_soft_deleted
42
- rel = apply_ranking(rel, all_ids, exact_ids)
43
- rel = scope.call(rel) if scope
44
- rel = rel.limit(limit) if limit
45
- rel = rel.offset(offset) if offset
46
- rel = rel.includes(includes) if includes
47
-
48
- if highlight
49
- records = rel.to_a
50
- Highlighter.apply!(records, model, query, record_ids: records.map(&:id))
51
- records
52
- elsif highlight_fields
53
- records = rel.to_a
54
- Highlighter.apply_fields!(records, model, query, record_ids: records.map(&:id))
55
- records
56
- else
57
- rel
25
+ FullSearch::Instrumentation.instrument("query", model: model.name, query: query) do
26
+ validate_filter_keys!
27
+ validate_required_filters!
28
+ check_stale_config!
29
+
30
+ return model.none if dsl.tokenize == "trigram" && query.length < MIN_TERM_LENGTH && !dsl.typo_tolerance?
31
+
32
+ parsed = QueryParser.parse(query)
33
+ exact_ids = ExactMatch.ids_for(model, query, filters)
34
+ primary_ids = fts_match_ids(parsed)
35
+ fallback_ids = (dsl.typo_tolerance? && matching_strategy != "all") ? trigram_match_ids(parsed, primary_ids, candidate_limit: per_strategy_limit) : []
36
+ fuzzy_ids = (dsl.typo_tolerance? && matching_strategy != "all" && primary_ids.empty? && fallback_ids.empty?) ? fuzzy_match_ids(parsed, candidate_limit: per_strategy_limit) : []
37
+
38
+ all_ids = (exact_ids + primary_ids + fallback_ids + fuzzy_ids).uniq
39
+ return model.none if all_ids.empty?
40
+
41
+ rel = model.where(id: all_ids)
42
+ rel = rel.where(model.arel_table[dsl.soft_delete_column].eq(nil)) if dsl.soft_delete_column && !include_soft_deleted
43
+ rel = apply_ranking(rel, all_ids, exact_ids)
44
+ rel = scope.call(rel) if scope
45
+ rel = rel.limit(limit) if limit
46
+ rel = rel.offset(offset) if offset
47
+ rel = rel.includes(includes) if includes
48
+
49
+ if highlight
50
+ records = rel.to_a
51
+ Highlighter.apply!(records, model, query, record_ids: records.map(&:id))
52
+ records
53
+ elsif highlight_fields
54
+ records = rel.to_a
55
+ Highlighter.apply_fields!(records, model, query, record_ids: records.map(&:id))
56
+ records
57
+ else
58
+ rel
59
+ end
58
60
  end
59
61
  end
60
62
 
@@ -279,7 +281,7 @@ module FullSearch
279
281
  def max_allowed_typos(length)
280
282
  min_length = dsl.typo_tolerance_min_term_length.to_i
281
283
  return -1 if length < min_length
282
- return 2 if length >= 9
284
+ return 2 if length >= FullSearch::Constants::TWO_TYPO_MIN_LENGTH
283
285
  1
284
286
  end
285
287
 
@@ -305,7 +307,8 @@ module FullSearch
305
307
  tbl = qt(model.table_name)
306
308
 
307
309
  if exact_ids.any?
308
- order_parts << "CASE #{tbl}.id #{exact_ids.map { |id| "WHEN #{q(id)} THEN 0" }.join(" ")} ELSE 1 END"
310
+ boost_ids = exact_ids.first(FullSearch::Constants::MAX_EXACT_MATCH_BOOST_IDS)
311
+ order_parts << "CASE #{tbl}.id #{boost_ids.map { |id| "WHEN #{q(id)} THEN 0" }.join(" ")} ELSE 1 END"
309
312
  end
310
313
 
311
314
  fts_table = qt(FullSearch::Index.fts_table_name(model))
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FullSearch
4
- VERSION = "0.3.7"
4
+ VERSION = "0.3.8"
5
5
  end
data/lib/full_search.rb CHANGED
@@ -4,7 +4,9 @@ require "active_record"
4
4
  require "active_support"
5
5
  require "active_support/concern"
6
6
 
7
+ require "full_search/constants"
7
8
  require "full_search/index_cache"
9
+ require "full_search/instrumentation"
8
10
  require "full_search/version"
9
11
  require "full_search/config"
10
12
  require "full_search/distance"
@@ -39,6 +41,10 @@ module FullSearch
39
41
  @models ||= Set.new
40
42
  end
41
43
 
44
+ def sorted_models
45
+ models.sort_by { |m| m.table_name.to_s }
46
+ end
47
+
42
48
  def register_model(model)
43
49
  models << model
44
50
  end
@@ -48,11 +54,11 @@ module FullSearch
48
54
  end
49
55
 
50
56
  def optimize!
51
- models.each { |model| Index.optimize!(model) }
57
+ sorted_models.each { |model| Index.optimize!(model) }
52
58
  end
53
59
 
54
60
  def setup!
55
- models.each do |model|
61
+ sorted_models.each do |model|
56
62
  Index.ensure_table!(model)
57
63
  Callbacks.install!(model)
58
64
  end
@@ -12,7 +12,7 @@ def resolve_full_search_models(args)
12
12
  klass || FullSearch.models.find { |m| m.table_name == name }
13
13
  end.compact
14
14
  else
15
- FullSearch.models
15
+ FullSearch.sorted_models
16
16
  end
17
17
  end
18
18
 
@@ -31,7 +31,7 @@ namespace :full_search do
31
31
  klass
32
32
  end.compact
33
33
  else
34
- FullSearch.models
34
+ FullSearch.sorted_models
35
35
  end
36
36
 
37
37
  FullSearch.setup!
@@ -77,7 +77,7 @@ namespace :full_search do
77
77
  task optimize: :environment do
78
78
  Rails.application.eager_load!
79
79
  FullSearch.optimize!
80
- FullSearch.models.each do |model|
80
+ FullSearch.sorted_models.each do |model|
81
81
  puts "Optimized #{FullSearch::Index.fts_table_name(model)}"
82
82
  end
83
83
  end
@@ -85,7 +85,7 @@ namespace :full_search do
85
85
  desc "Show full_search index status"
86
86
  task status: :environment do
87
87
  Rails.application.eager_load!
88
- FullSearch.models.each do |model|
88
+ FullSearch.sorted_models.each do |model|
89
89
  stored = FullSearch::Index.stored_config_hash(model)
90
90
  current = model.full_search_dsl.config_hash
91
91
  status = (stored == current) ? "ok" : "stale"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: full_search
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.7
4
+ version: 0.3.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben D'Angelo
@@ -144,6 +144,7 @@ files:
144
144
  - lib/full_search/bulk_import.rb
145
145
  - lib/full_search/callbacks.rb
146
146
  - lib/full_search/config.rb
147
+ - lib/full_search/constants.rb
147
148
  - lib/full_search/distance.rb
148
149
  - lib/full_search/dsl.rb
149
150
  - lib/full_search/errors.rb
@@ -151,6 +152,7 @@ files:
151
152
  - lib/full_search/highlighter.rb
152
153
  - lib/full_search/index.rb
153
154
  - lib/full_search/index_cache.rb
155
+ - lib/full_search/instrumentation.rb
154
156
  - lib/full_search/model.rb
155
157
  - lib/full_search/multi_search.rb
156
158
  - lib/full_search/optimize_job.rb