Skip to content
73 changes: 63 additions & 10 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2023-11-06 11:54:44 UTC using RuboCop version 1.27.0.
# on 2024-01-10 10:49:28 UTC using RuboCop version 1.27.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand Down Expand Up @@ -30,6 +30,15 @@ Layout/EmptyLinesAroundModuleBody:
Exclude:
- 'lib/meilisearch-rails.rb'

# Offense count: 3
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: symmetrical, new_line, same_line
Layout/MultilineMethodCallBraceLayout:
Exclude:
- 'spec/integration_spec.rb'
- 'spec/ms_clean_up_job_spec.rb'

# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyle, IndentationWidth.
Expand All @@ -44,6 +53,13 @@ Lint/SuppressedException:
Exclude:
- 'lib/meilisearch-rails.rb'

# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
Lint/UnusedBlockArgument:
Exclude:
- 'lib/meilisearch-rails.rb'

# Offense count: 2
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods.
Expand All @@ -54,7 +70,7 @@ Lint/UnusedMethodArgument:
# Offense count: 11
# Configuration parameters: IgnoredMethods, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 102
Max: 104

# Offense count: 1
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
Expand All @@ -75,17 +91,17 @@ Metrics/CyclomaticComplexity:
# Offense count: 16
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
Metrics/MethodLength:
Max: 99
Max: 103

# Offense count: 1
# Configuration parameters: CountComments, CountAsOne.
Metrics/ModuleLength:
Max: 435
Max: 449

# Offense count: 8
# Configuration parameters: IgnoredMethods.
Metrics/PerceivedComplexity:
Max: 33
Max: 34

# Offense count: 1
Naming/AccessorMethodName:
Expand All @@ -107,7 +123,7 @@ Naming/MethodParameterName:
Exclude:
- 'lib/meilisearch-rails.rb'

# Offense count: 15
# Offense count: 20
RSpec/BeforeAfterAll:
Exclude:
- 'spec/integration_spec.rb'
Expand All @@ -118,7 +134,7 @@ RSpec/DescribeClass:
Exclude:
- 'spec/integration_spec.rb'

# Offense count: 37
# Offense count: 46
# Configuration parameters: CountAsOne.
RSpec/ExampleLength:
Max: 19
Expand All @@ -132,7 +148,7 @@ RSpec/FilePath:
- 'spec/settings_spec.rb'
- 'spec/utilities_spec.rb'

# Offense count: 26
# Offense count: 25
# Configuration parameters: AssignmentOnly.
RSpec/InstanceVariable:
Exclude:
Expand All @@ -150,6 +166,12 @@ RSpec/MultipleDescribes:
Exclude:
- 'spec/integration_spec.rb'

# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
RSpec/MultipleSubjects:
Exclude:
- 'spec/ms_clean_up_job_spec.rb'

# Offense count: 1
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
RSpec/VerifiedDoubles:
Expand Down Expand Up @@ -188,14 +210,45 @@ Style/InverseMethods:
Exclude:
- 'lib/meilisearch-rails.rb'

# Offense count: 8
# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
Style/MultilineIfModifier:
Exclude:
- 'lib/meilisearch-rails.rb'

# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: AllowedMethods.
# AllowedMethods: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with
Style/NestedParenthesizedCalls:
Exclude:
- 'spec/ms_clean_up_job_spec.rb'

# Offense count: 9
# Configuration parameters: AllowedMethods.
# AllowedMethods: respond_to_missing?
Style/OptionalBooleanParameter:
Exclude:
- 'lib/meilisearch-rails.rb'

# Offense count: 20
# Offense count: 13
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiterals:
Exclude:
- 'spec/integration_spec.rb'
- 'spec/ms_clean_up_job_spec.rb'

# Offense count: 2
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyleForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInArguments:
Exclude:
- 'spec/integration_spec.rb'

# Offense count: 19
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Expand Down
26 changes: 24 additions & 2 deletions lib/meilisearch-rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ def additional_indexes
# lazy load the ActiveJob class to ensure the
# queue is initialized before using it
autoload :MSJob, 'meilisearch/rails/ms_job'
autoload :MSCleanUpJob, 'meilisearch/rails/ms_clean_up_job'
end

# this class wraps an MeiliSearch::Index document ensuring all raised exceptions
Expand Down Expand Up @@ -382,7 +383,11 @@ def meilisearch(options = {}, &block)

proc = if options[:enqueue] == true
proc do |record, remove|
MSJob.perform_later(record, remove ? 'ms_remove_from_index!' : 'ms_index!')
if remove
MSCleanUpJob.perform_later(record.ms_entries)
else
MSJob.perform_later(record, 'ms_index!')
end
end
elsif options[:enqueue].respond_to?(:call)
options[:enqueue]
Expand Down Expand Up @@ -454,7 +459,7 @@ def meilisearch(options = {}, &block)
end
end
elsif respond_to?(:after_destroy)
after_destroy { |searchable| searchable.ms_enqueue_remove_from_index!(ms_synchronous?) }
after_destroy_commit { |searchable| searchable.ms_enqueue_remove_from_index!(ms_synchronous?) }
end
end

Expand Down Expand Up @@ -563,6 +568,19 @@ def ms_index!(document, synchronous = false)
end.compact
end

def ms_entries_for(document:, synchronous:)
primary_key = ms_primary_key_of(document)
raise ArgumentError, 'Cannot index a record without a primary key' if primary_key.blank?

ms_configurations.filter_map do |options, settings|
{
synchronous: synchronous || options[:synchronous],
index_uid: options[:index_uid],
primary_key: primary_key
}.with_indifferent_access unless ms_indexing_disabled?(options)
end
end

def ms_remove_from_index!(document, synchronous = false)
return if ms_without_auto_index_scope

Expand Down Expand Up @@ -940,6 +958,10 @@ def ms_synchronous?
@ms_synchronous
end

def ms_entries(synchronous = false)
self.class.ms_entries_for(document: self, synchronous: synchronous || ms_synchronous?)
end

private

def ms_mark_synchronous
Expand Down
19 changes: 19 additions & 0 deletions lib/meilisearch/rails/ms_clean_up_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module MeiliSearch
module Rails
class MSCleanUpJob < ::ActiveJob::Base
queue_as :meilisearch

def perform(documents)
documents.each do |document|
index = MeiliSearch::Rails.client.index(document[:index_uid])

if document[:synchronous]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it helpful to do the check instead of just calling async or sync. But I changed my mind 😅. Let's let the user decide; we don't know the use cases. Maybe it is too much to wait for a call in certain apps, but indispensable for others...

index.delete_document!(document[:primary_key])
else
index.delete_document(document[:primary_key])
end
end
end
end
end
end
48 changes: 46 additions & 2 deletions spec/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,34 @@
expect(results.size).to eq(1)
end

describe '#ms_entries' do
it 'returns all 3 indexes for a public book' do
book = Book.create!(
name: 'Frankenstein', author: 'Mary Shelley',
premium: false, released: true
)

expect(book.ms_entries).to contain_exactly(
a_hash_including("index_uid" => safe_index_uid('SecuredBook')),
a_hash_including("index_uid" => safe_index_uid('BookAuthor')),
a_hash_including("index_uid" => safe_index_uid('Book')),
)
end

it 'returns all 3 indexes for a non-public book' do
book = Book.create!(
name: 'Frankenstein', author: 'Mary Shelley',
premium: false, released: false
)

expect(book.ms_entries).to contain_exactly(
a_hash_including("index_uid" => safe_index_uid('SecuredBook')),
a_hash_including("index_uid" => safe_index_uid('BookAuthor')),
a_hash_including("index_uid" => safe_index_uid('Book')),
)
end
end

it 'returns facets using max values per facet' do
10.times do
Book.create! name: Faker::Book.title, author: Faker::Book.author, genre: Faker::Book.genre
Expand Down Expand Up @@ -932,7 +960,10 @@
end

describe 'ConditionallyEnqueuedDocument' do
before { allow(MeiliSearch::Rails::MSJob).to receive(:perform_later).and_return(nil) }
before do
allow(MeiliSearch::Rails::MSJob).to receive(:perform_later).and_return(nil)
allow(MeiliSearch::Rails::MSCleanUpJob).to receive(:perform_later).and_return(nil)
end

it 'does not try to enqueue an index job when :if option resolves to false' do
doc = ConditionallyEnqueuedDocument.create! name: 'test', is_public: false
Expand All @@ -952,7 +983,7 @@

doc.destroy!

expect(MeiliSearch::Rails::MSJob).to have_received(:perform_later).with(doc, 'ms_remove_from_index!')
expect(MeiliSearch::Rails::MSCleanUpJob).to have_received(:perform_later).with(doc.ms_entries)
end
end
end
Expand Down Expand Up @@ -1049,6 +1080,19 @@

expect(cat_index).to eq(dog_index)
end

describe '#ms_entries' do
it 'returns the correct entry for each animal' do
toby_dog = Dog.create!(name: 'Toby the Dog')
taby_cat = Cat.create!(name: 'Taby the Cat')

expect(toby_dog.ms_entries).to contain_exactly(
a_hash_including('primary_key' => /dog_\d+/))

expect(taby_cat.ms_entries).to contain_exactly(
a_hash_including('primary_key' => /cat_\d+/))
end
end
end

describe 'Songs' do
Expand Down
72 changes: 72 additions & 0 deletions spec/ms_clean_up_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require 'spec_helper'

RSpec.describe 'MeiliSearch::Rails::MSCleanUpJob' do
include ActiveJob::TestHelper

def clean_up_indexes
indexes.each(&:delete_all_documents)
end

def create_indexed_record
record

indexes.each do |index|
index.wait_for_task(index.tasks['results'].last['uid'])
end
end

subject(:clean_up) { MeiliSearch::Rails::MSCleanUpJob }

let(:record) do
Book.create name: "Moby Dick", author: "Herman Mellville",
premium: false, released: true
end

let(:record_entries) do
record.ms_entries(true).each { |h| h[:index_uid] += '_test' }
end

let(:indexes) do
%w[SecuredBook BookAuthor Book].map do |uid|
Book.index(safe_index_uid uid)
end
end

it 'removes record from all indexes' do
clean_up_indexes

create_indexed_record

clean_up.perform_now(record_entries)

indexes.each do |index|
expect(index.search('*')['hits']).to be_empty
end
end

context 'when record is already destroyed' do
subject(:record) do
Restaurant.create(
name: "Los Pollos Hermanos",
kind: "Mexican",
description: "Mexican chicken restaurant in Albuquerque, New Mexico.")
end

let(:indexes) { [Restaurant.index] }

it 'successfully deletes its document in the index' do
clean_up_indexes

create_indexed_record

record.delete # does not run callbacks, unlike #destroy

clean_up.perform_later(record_entries)
expect { perform_enqueued_jobs }.not_to raise_error

indexes.each do |index|
expect(index.search('*')['hits']).to be_empty
end
end
end
end
2 changes: 2 additions & 0 deletions spec/ms_job_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require 'spec_helper'

RSpec.describe 'MeiliSearch::Rails::MSJob' do
include ActiveJob::TestHelper

subject(:job) { MeiliSearch::Rails::MSJob }

let(:record) { double }
Expand Down
Loading