full_search 0.3.8 → 0.4.0
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 +4 -4
- data/README.md +209 -0
- data/lib/full_search/config.rb +3 -1
- data/lib/full_search/constants.rb +1 -1
- data/lib/full_search/index.rb +16 -12
- data/lib/full_search/schema_dumper_patch.rb +2 -0
- data/lib/full_search/search.rb +3 -1
- data/lib/full_search/test_helpers.rb +68 -2
- data/lib/full_search/version.rb +1 -1
- data/lib/generators/full_search/install/install_generator.rb +1 -1
- data/lib/generators/full_search/install/templates/full_search.rb +4 -0
- data/lib/tasks/full_search.rake +26 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8175568a013e8a29d107a5b3bceebc7e9be6b9c85f56d8ef178d3c2c72a73678
|
|
4
|
+
data.tar.gz: d77fe210358656fe1b471baeb374c27d2f493544739d4ec326c06aee3dddadf1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e786b098bc3cd046151835f11bbf98b4a5213feb46e70a0ae6872a4f24bba6eb22ec5e8a17ebe045328eaab1d2016682fda67e47c5582fb73ae72b262c7bf08
|
|
7
|
+
data.tar.gz: 4d5284fe9b059ee7010ff099cd88e494beffe1804084f29be86921bd895059cc98dd567fdf9c09ef20a2ec9af99badef3416f6085bb3f85e019f44657028244a
|
data/README.md
CHANGED
|
@@ -223,6 +223,18 @@ The rebuild task checks each model's stored config hash against the current DSL
|
|
|
223
223
|
bin/rails full_search:reset
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
+
### Schema dump
|
|
227
|
+
|
|
228
|
+
By default, `full_search` includes its FTS virtual tables in `db/schema.rb` so that `db:schema:load` produces a complete, loadable database. To exclude them:
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
FullSearch.configure do |config|
|
|
232
|
+
config.dump_schema_virtual_tables = false
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
When disabled, you must run `bin/rails full_search:prepare` after every `db:schema:load` to recreate the indexes.
|
|
237
|
+
|
|
226
238
|
### Rake tasks
|
|
227
239
|
|
|
228
240
|
| Task | Description |
|
|
@@ -233,6 +245,7 @@ bin/rails full_search:reset
|
|
|
233
245
|
| `full_search:optimize` | Run FTS5 [`optimize`](https://www.sqlite.org/fts5.html#the_optimize_command) to merge b-tree segments. Useful after bulk updates. |
|
|
234
246
|
| `full_search:backfill` | Force-rebuild FTS indexes for specified models (or all). Useful for recovery after bulk operations. |
|
|
235
247
|
| `full_search:status` | Show each model's index status (`ok` / `stale`) and count of empty sourced fields. |
|
|
248
|
+
| `full_search:health_check` | Verify all indexes are present and current; exits `1` if any table is missing or stale. Useful for deploy gates or Docker healthchecks. |
|
|
236
249
|
|
|
237
250
|
## Background jobs
|
|
238
251
|
|
|
@@ -375,6 +388,202 @@ FullSearch.multi_search(
|
|
|
375
388
|
)
|
|
376
389
|
```
|
|
377
390
|
|
|
391
|
+
## Testing
|
|
392
|
+
|
|
393
|
+
`full_search` ships with `FullSearch::TestHelpers`, a framework-agnostic module you can include in Minitest or RSpec. It provides helpers to rebuild, reindex, and reset FTS tables from your tests.
|
|
394
|
+
|
|
395
|
+
### Setup
|
|
396
|
+
|
|
397
|
+
Call `setup_for_tests!` once in your test boot file. It applies safe defaults:
|
|
398
|
+
|
|
399
|
+
- Disables rebuild locking (`lock_rebuilds = false`)
|
|
400
|
+
- Enables query-time auto-rebuild for stale indexes (`auto_rebuild_on_stale_query = true`)
|
|
401
|
+
- Keeps stale-query behaviour as `raise` so real config drift fails fast
|
|
402
|
+
- Forces Active Job to run inline so background reindex jobs execute synchronously
|
|
403
|
+
|
|
404
|
+
#### Minitest (`test/test_helper.rb`)
|
|
405
|
+
|
|
406
|
+
```ruby
|
|
407
|
+
require "minitest/autorun"
|
|
408
|
+
require "full_search"
|
|
409
|
+
require "full_search/test_helpers"
|
|
410
|
+
|
|
411
|
+
class ActiveSupport::TestCase
|
|
412
|
+
include FullSearch::TestHelpers
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
FullSearch::TestHelpers.setup_for_tests!
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
#### RSpec (`spec/rails_helper.rb`)
|
|
419
|
+
|
|
420
|
+
```ruby
|
|
421
|
+
require "full_search"
|
|
422
|
+
require "full_search/test_helpers"
|
|
423
|
+
|
|
424
|
+
RSpec.configure do |config|
|
|
425
|
+
config.include FullSearch::TestHelpers, type: :model
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
FullSearch::TestHelpers.setup_for_tests!
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Why you must rebuild after creating data
|
|
432
|
+
|
|
433
|
+
FTS tables are updated by database triggers on normal inserts/updates, but computed `source:` fields are evaluated by Ruby during a rebuild/reindex. The safest pattern is to create your records, then call `rebuild_full_search_index(model)` before searching.
|
|
434
|
+
|
|
435
|
+
### Available helpers
|
|
436
|
+
|
|
437
|
+
```ruby
|
|
438
|
+
# Rebuild a single model's FTS table from scratch (drops and recreates it).
|
|
439
|
+
# Accepts a model class, symbol, or string class name.
|
|
440
|
+
rebuild_full_search_index(Customer)
|
|
441
|
+
rebuild_full_search_index(:customer)
|
|
442
|
+
rebuild_full_search_index("Customer")
|
|
443
|
+
|
|
444
|
+
# Re-evaluate computed source: fields only. Leaves table structure untouched.
|
|
445
|
+
reindex_full_search(Customer)
|
|
446
|
+
|
|
447
|
+
# Rebuild every registered search model, or only the ones passed in.
|
|
448
|
+
reset_full_search!
|
|
449
|
+
reset_full_search!(Customer, Vehicle)
|
|
450
|
+
|
|
451
|
+
# Idempotently create any missing FTS tables/triggers for registered models.
|
|
452
|
+
ensure_full_search_tables
|
|
453
|
+
|
|
454
|
+
# Run a block with inline Active Job, restoring the adapter afterwards.
|
|
455
|
+
with_full_search_async_jobs_inline do
|
|
456
|
+
# ReindexJob / BackfillJob execute synchronously here
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# Run a block inside a rebuild, dropping the table at the end.
|
|
460
|
+
with_full_search_rebuild(Customer) do
|
|
461
|
+
# search and assert here
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Scope anonymous searchable models to a block so they don't leak into
|
|
465
|
+
# the global registry and affect later tests or Rake tasks.
|
|
466
|
+
with_full_search_models_registered do
|
|
467
|
+
model = Class.new(Customer) do
|
|
468
|
+
full_search { field :first_name, weight: 5 }
|
|
469
|
+
end
|
|
470
|
+
model.table_name = "customers"
|
|
471
|
+
rebuild_full_search_index(model)
|
|
472
|
+
# ... search and assert
|
|
473
|
+
end
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Minitest example
|
|
477
|
+
|
|
478
|
+
```ruby
|
|
479
|
+
class CustomerSearchTest < ActiveSupport::TestCase
|
|
480
|
+
def setup
|
|
481
|
+
@account = Account.create!(name: "Acme")
|
|
482
|
+
@customer = Customer.create!(account: @account, first_name: "Sam")
|
|
483
|
+
rebuild_full_search_index(Customer)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def test_finds_by_first_name
|
|
487
|
+
results = Customer.search("Sam", filters: {account_id: @account.id})
|
|
488
|
+
assert_includes results.to_a, @customer
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### RSpec example
|
|
494
|
+
|
|
495
|
+
```ruby
|
|
496
|
+
RSpec.describe Customer, type: :model do
|
|
497
|
+
let(:account) { create(:account) }
|
|
498
|
+
let(:customer) { create(:customer, account: account, first_name: "Sam") }
|
|
499
|
+
|
|
500
|
+
before { rebuild_full_search_index(Customer) }
|
|
501
|
+
|
|
502
|
+
it "finds by first name" do
|
|
503
|
+
results = Customer.search("Sam", filters: {account_id: account.id})
|
|
504
|
+
expect(results).to include(customer)
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Advanced configuration
|
|
510
|
+
|
|
511
|
+
If you need to override the defaults set by `setup_for_tests!`, use `FullSearch::TestHelpers.configure`:
|
|
512
|
+
|
|
513
|
+
```ruby
|
|
514
|
+
FullSearch::TestHelpers.configure do |config|
|
|
515
|
+
config.lock_rebuilds = true
|
|
516
|
+
config.auto_rebuild_on_stale_query = false
|
|
517
|
+
config.stale_query_behavior = :log_and_fallback
|
|
518
|
+
end
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## Production readiness
|
|
522
|
+
|
|
523
|
+
`full_search` is designed for small to medium SQLite-backed Rails apps. Before running in production, review this checklist.
|
|
524
|
+
|
|
525
|
+
### Initial deploy
|
|
526
|
+
|
|
527
|
+
1. Run your normal database setup so application tables exist:
|
|
528
|
+
```bash
|
|
529
|
+
bin/rails db:prepare:with_data
|
|
530
|
+
```
|
|
531
|
+
2. Create the FTS virtual tables and triggers:
|
|
532
|
+
```bash
|
|
533
|
+
bin/rails full_search:prepare
|
|
534
|
+
```
|
|
535
|
+
3. For containerized deploys, add step 2 to your entrypoint after `db:prepare:with_data`.
|
|
536
|
+
|
|
537
|
+
### Configuration
|
|
538
|
+
|
|
539
|
+
The generated initializer defaults to production-safe values. Do **not** change these in production:
|
|
540
|
+
|
|
541
|
+
- `auto_rebuild_schema` must be `false`. If enabled, every web/worker/console process tries to rebuild indexes on boot.
|
|
542
|
+
- `auto_rebuild_on_stale_query` must be `false`. A query-time rebuild under load can cause timeouts and race conditions.
|
|
543
|
+
|
|
544
|
+
If you need to change the search DSL, ship the change and then run:
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
bin/rails full_search:rebuild
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
from a single deployment step. The gem checks each model's stored config hash and only rebuilds indexes whose DSL has changed.
|
|
551
|
+
|
|
552
|
+
### Monitoring and maintenance
|
|
553
|
+
|
|
554
|
+
- **Health check** — use the built-in Rake task for deploy gates or container healthchecks:
|
|
555
|
+
```bash
|
|
556
|
+
bin/rails full_search:health_check
|
|
557
|
+
```
|
|
558
|
+
It exits `0` when every registered model has a current FTS table, or `1` if any table is missing or stale.
|
|
559
|
+
|
|
560
|
+
- **Status overview** — `bin/rails full_search:status` prints `ok` / `stale` and the count of empty sourced fields per model.
|
|
561
|
+
|
|
562
|
+
- **Scheduled optimize** — queue `FullSearch::OptimizeJob` once a day during a low-traffic window to merge FTS5 b-tree segments:
|
|
563
|
+
```yaml
|
|
564
|
+
# config/recurring.yml
|
|
565
|
+
full_search_optimize:
|
|
566
|
+
class: FullSearch::OptimizeJob
|
|
567
|
+
schedule: daily at 4am
|
|
568
|
+
description: "Merge FTS5 b-tree segments for full_search indexes"
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
- **Recovery after bulk operations** — if a bulk operation bypassed triggers, rebuild the affected index:
|
|
572
|
+
```bash
|
|
573
|
+
bin/rails 'full_search:backfill[customers]'
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Common errors
|
|
577
|
+
|
|
578
|
+
| Error | Meaning | Fix |
|
|
579
|
+
|-------|---------|-----|
|
|
580
|
+
| `FullSearch::MissingTableError` | The FTS table does not exist yet. | Run `bin/rails full_search:prepare`. |
|
|
581
|
+
| `FullSearch::ConfigChangedError` | The DSL has changed and the index is stale. | Run `bin/rails full_search:rebuild`. |
|
|
582
|
+
|
|
583
|
+
### Locking note
|
|
584
|
+
|
|
585
|
+
`lock_rebuilds` uses a Ruby `Mutex` and only prevents concurrent rebuilds within the same process/connection. It does not coordinate across processes or hosts. Always run `full_search:rebuild` from a single deployment step.
|
|
586
|
+
|
|
378
587
|
## Known limitations
|
|
379
588
|
|
|
380
589
|
- Queries run with `highlight: true` return an Array of records, not an `ActiveRecord::Relation`. No further chaining (`.where`, `.order`, `.limit`) is possible after highlighting is applied.
|
data/lib/full_search/config.rb
CHANGED
|
@@ -4,7 +4,8 @@ module FullSearch
|
|
|
4
4
|
class Config
|
|
5
5
|
attr_accessor :auto_rebuild_schema, :stale_query_behavior, :lock_rebuilds,
|
|
6
6
|
:default_async_reindex, :default_async_source_reindex,
|
|
7
|
-
:default_tokenizer, :auto_rebuild_on_stale_query, :min_like_prefix_length
|
|
7
|
+
:default_tokenizer, :auto_rebuild_on_stale_query, :min_like_prefix_length,
|
|
8
|
+
:dump_schema_virtual_tables
|
|
8
9
|
|
|
9
10
|
def initialize
|
|
10
11
|
@auto_rebuild_schema = false
|
|
@@ -15,6 +16,7 @@ module FullSearch
|
|
|
15
16
|
@default_tokenizer = FullSearch::Constants::DEFAULT_TOKENIZER
|
|
16
17
|
@auto_rebuild_on_stale_query = false
|
|
17
18
|
@min_like_prefix_length = FullSearch::Constants::DEFAULT_MIN_LIKE_PREFIX_LENGTH
|
|
19
|
+
@dump_schema_virtual_tables = true
|
|
18
20
|
end
|
|
19
21
|
end
|
|
20
22
|
|
data/lib/full_search/index.rb
CHANGED
|
@@ -36,7 +36,7 @@ module FullSearch
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
trigram_was_created = false
|
|
39
|
-
if
|
|
39
|
+
if trigram_table_needed?(model) && !trigram_table_exists?(model)
|
|
40
40
|
FullSearch::Typo.warn_unsupported!(model) unless FullSearch::Typo.supported?(model)
|
|
41
41
|
conn.execute(create_trigram_virtual_table_sql(model))
|
|
42
42
|
trigram_was_created = true
|
|
@@ -70,12 +70,12 @@ module FullSearch
|
|
|
70
70
|
conn.execute("DROP TABLE IF EXISTS #{qt(fts_table_name(model))};")
|
|
71
71
|
conn.execute("DROP TABLE IF EXISTS #{qt(trigram_table_name(model))};")
|
|
72
72
|
conn.execute(create_virtual_table_sql(model))
|
|
73
|
-
if
|
|
73
|
+
if trigram_table_needed?(model)
|
|
74
74
|
FullSearch::Typo.warn_unsupported!(model) unless FullSearch::Typo.supported?(model)
|
|
75
75
|
conn.execute(create_trigram_virtual_table_sql(model))
|
|
76
76
|
end
|
|
77
77
|
conn.execute(backfill_sql(model))
|
|
78
|
-
conn.execute(backfill_trigram_sql(model)) if
|
|
78
|
+
conn.execute(backfill_trigram_sql(model)) if trigram_table_needed?(model)
|
|
79
79
|
reindex_source_fields!(model) if dsl.fields.any?(&:source)
|
|
80
80
|
create_triggers!(model)
|
|
81
81
|
optimize!(model)
|
|
@@ -141,7 +141,7 @@ module FullSearch
|
|
|
141
141
|
if model.full_search_dsl.soft_delete_column
|
|
142
142
|
connection.execute(soft_delete_removal_trigger_sql(model))
|
|
143
143
|
end
|
|
144
|
-
if model
|
|
144
|
+
if trigram_table_needed?(model)
|
|
145
145
|
connection.execute(insert_trigram_trigger_sql(model))
|
|
146
146
|
connection.execute(delete_trigram_trigger_sql(model))
|
|
147
147
|
connection.execute(update_trigram_trigger_sql(model))
|
|
@@ -177,7 +177,7 @@ module FullSearch
|
|
|
177
177
|
else
|
|
178
178
|
connection.adapter_name.downcase.include?("sqlite")
|
|
179
179
|
end
|
|
180
|
-
rescue
|
|
180
|
+
rescue ActiveRecord::ConnectionNotEstablished, NoMethodError
|
|
181
181
|
connection.adapter_name.downcase.include?("sqlite")
|
|
182
182
|
end
|
|
183
183
|
|
|
@@ -199,6 +199,16 @@ module FullSearch
|
|
|
199
199
|
!table_exists?(model)
|
|
200
200
|
end
|
|
201
201
|
|
|
202
|
+
def trigram_table_needed?(model)
|
|
203
|
+
model.full_search_dsl&.typo_tolerance? && model.full_search_dsl.tokenize != "trigram"
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def trigram_table_exists?(model)
|
|
207
|
+
connection.execute(
|
|
208
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name=#{q(trigram_table_name(model))} LIMIT 1"
|
|
209
|
+
).any?
|
|
210
|
+
end
|
|
211
|
+
|
|
202
212
|
private
|
|
203
213
|
|
|
204
214
|
def connection
|
|
@@ -292,7 +302,7 @@ module FullSearch
|
|
|
292
302
|
).map { |r| r["name"] }
|
|
293
303
|
|
|
294
304
|
expected = trigger_names(model)
|
|
295
|
-
expected += trigram_trigger_names(model) if model
|
|
305
|
+
expected += trigram_trigger_names(model) if trigram_table_needed?(model)
|
|
296
306
|
return if (expected - existing).empty? && (existing - expected).empty?
|
|
297
307
|
|
|
298
308
|
rebuild!(model)
|
|
@@ -391,12 +401,6 @@ module FullSearch
|
|
|
391
401
|
SQL
|
|
392
402
|
end
|
|
393
403
|
|
|
394
|
-
def trigram_table_exists?(model)
|
|
395
|
-
connection.execute(
|
|
396
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name=#{q(trigram_table_name(model))} LIMIT 1"
|
|
397
|
-
).any?
|
|
398
|
-
end
|
|
399
|
-
|
|
400
404
|
def create_trigram_virtual_table_sql(model)
|
|
401
405
|
dsl = model.full_search_dsl
|
|
402
406
|
columns = (dsl.fields + dsl.filters.map { |f| FilterColumnPlaceholder.new(name: f.name) } + extra_columns(model))
|
data/lib/full_search/search.rb
CHANGED
|
@@ -89,7 +89,7 @@ module FullSearch
|
|
|
89
89
|
|
|
90
90
|
stored = begin
|
|
91
91
|
FullSearch::Index.stored_config_hash(model)
|
|
92
|
-
rescue
|
|
92
|
+
rescue ActiveRecord::StatementInvalid
|
|
93
93
|
nil
|
|
94
94
|
end
|
|
95
95
|
return unless stored
|
|
@@ -146,6 +146,8 @@ module FullSearch
|
|
|
146
146
|
return like_prefix_ids(term, candidate_limit: candidate_limit)
|
|
147
147
|
end
|
|
148
148
|
|
|
149
|
+
return [] unless FullSearch::Index.trigram_table_exists?(model)
|
|
150
|
+
|
|
149
151
|
trigram_table = qt(FullSearch::Index.trigram_table_name(model))
|
|
150
152
|
tbl = qt(model.table_name)
|
|
151
153
|
|
|
@@ -2,16 +2,82 @@
|
|
|
2
2
|
|
|
3
3
|
module FullSearch
|
|
4
4
|
module TestHelpers
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
class << self
|
|
6
|
+
def setup_for_tests!
|
|
7
|
+
configure do |config|
|
|
8
|
+
config.lock_rebuilds = false
|
|
9
|
+
config.auto_rebuild_on_stale_query = true
|
|
10
|
+
config.stale_query_behavior = :raise
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
inline_active_job_if_configured
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def configure
|
|
17
|
+
yield FullSearch.config
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def inline_active_job_if_configured
|
|
23
|
+
return unless defined?(ActiveJob::Base)
|
|
24
|
+
|
|
25
|
+
ActiveJob::Base.queue_adapter = :inline
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def rebuild_full_search_index(model)
|
|
30
|
+
model = resolve_full_search_model(model)
|
|
7
31
|
FullSearch::Index.drop!(model)
|
|
8
32
|
FullSearch::Index.rebuild!(model)
|
|
9
33
|
end
|
|
10
34
|
|
|
35
|
+
def reindex_full_search(model)
|
|
36
|
+
model = resolve_full_search_model(model)
|
|
37
|
+
FullSearch::Index.reindex_source_fields!(model)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def reset_full_search!(*models)
|
|
41
|
+
models = FullSearch.models.to_a if models.empty?
|
|
42
|
+
models.each { |model| rebuild_full_search_index(model) }
|
|
43
|
+
end
|
|
44
|
+
|
|
11
45
|
def ensure_full_search_tables
|
|
12
46
|
FullSearch.models.each do |model|
|
|
13
47
|
FullSearch::Index.ensure_table!(model)
|
|
14
48
|
end
|
|
15
49
|
end
|
|
50
|
+
|
|
51
|
+
def with_full_search_rebuild(model)
|
|
52
|
+
model = resolve_full_search_model(model)
|
|
53
|
+
rebuild_full_search_index(model)
|
|
54
|
+
yield
|
|
55
|
+
ensure
|
|
56
|
+
FullSearch::Index.drop!(model)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def with_full_search_async_jobs_inline
|
|
60
|
+
original_adapter = ActiveJob::Base.queue_adapter if defined?(ActiveJob::Base)
|
|
61
|
+
ActiveJob::Base.queue_adapter = :inline if defined?(ActiveJob::Base)
|
|
62
|
+
yield
|
|
63
|
+
ensure
|
|
64
|
+
ActiveJob::Base.queue_adapter = original_adapter if defined?(ActiveJob::Base)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def with_full_search_models_registered(*models)
|
|
68
|
+
previous_registry = FullSearch.models.dup
|
|
69
|
+
models.each { |model| FullSearch.register_model(model) }
|
|
70
|
+
yield
|
|
71
|
+
ensure
|
|
72
|
+
FullSearch.models.replace(previous_registry)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def resolve_full_search_model(model)
|
|
78
|
+
return model if model.is_a?(Class)
|
|
79
|
+
|
|
80
|
+
model.to_s.camelize.constantize
|
|
81
|
+
end
|
|
16
82
|
end
|
|
17
83
|
end
|
data/lib/full_search/version.rb
CHANGED
|
@@ -19,7 +19,7 @@ module FullSearch
|
|
|
19
19
|
|
|
20
20
|
say "Running full_search:prepare to create FTS tables..."
|
|
21
21
|
rake("full_search:prepare")
|
|
22
|
-
rescue => e
|
|
22
|
+
rescue RuntimeError => e
|
|
23
23
|
say "Skipping full_search:prepare — #{e.message}", :yellow
|
|
24
24
|
say "Run `bin/rails full_search:prepare` after your database is ready.", :yellow
|
|
25
25
|
end
|
|
@@ -12,4 +12,8 @@ FullSearch.configure do |config|
|
|
|
12
12
|
# Reindex computed source: fields synchronously (false) or via background job (true).
|
|
13
13
|
# Bulk imports should use FullSearch.bulk_import(Model) { ... } to defer reindexing.
|
|
14
14
|
config.default_async_source_reindex = true
|
|
15
|
+
|
|
16
|
+
# Uncomment to exclude full_search virtual tables from db/schema.rb.
|
|
17
|
+
# When false, you must run `bin/rails full_search:prepare` after db:schema:load.
|
|
18
|
+
# config.dump_schema_virtual_tables = false
|
|
15
19
|
end
|
data/lib/tasks/full_search.rake
CHANGED
|
@@ -108,4 +108,30 @@ namespace :full_search do
|
|
|
108
108
|
puts "#{model.table_name}: #{status}#{drift_info}"
|
|
109
109
|
end
|
|
110
110
|
end
|
|
111
|
+
|
|
112
|
+
desc "Verify full_search indexes are present and up to date (exit 1 if unhealthy)"
|
|
113
|
+
task health_check: :environment do
|
|
114
|
+
Rails.application.eager_load!
|
|
115
|
+
unhealthy = []
|
|
116
|
+
|
|
117
|
+
FullSearch.sorted_models.each do |model|
|
|
118
|
+
if FullSearch::Index.missing_table?(model)
|
|
119
|
+
unhealthy << "#{model.table_name}: missing FTS table"
|
|
120
|
+
next
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
stored = FullSearch::Index.stored_config_hash(model)
|
|
124
|
+
current = model.full_search_dsl.config_hash
|
|
125
|
+
if stored != current
|
|
126
|
+
unhealthy << "#{model.table_name}: stale config hash"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
if unhealthy.any?
|
|
131
|
+
unhealthy.each { |message| puts "[FAIL] #{message}" }
|
|
132
|
+
exit 1
|
|
133
|
+
else
|
|
134
|
+
puts "[OK] All full_search indexes are healthy"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
111
137
|
end
|