Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 54 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,22 +241,7 @@ multi_search_results = MeiliSearch::Rails.multi_search(
)
```

You can iterate through the results with `.each` or `.each_result`:

```erb
<% multi_search_results.each do |record| %>
<p><%= record.title %></p>
<p><%= record.author %></p>
<% end %>

<p>Harry Potter and the Philosopher's Stone</p>
<p>J. K. Rowling</p>
<p>Harry Potter and the Chamber of Secrets</p>
<p>J. K. Rowling</p>
<p>Attack on Titan</p>
<p>Iseyama</p>
```

Use `#each_result` to loop through pairs of your provided keys and the results:
```erb
<% multi_search_results.each_result do |klass, results| %>
<p><%= klass.name.pluralize %></p>
Expand All @@ -280,6 +265,59 @@ You can iterate through the results with `.each` or `.each_result`:
</ul>
```

Records are loaded when the keys are models, or when `:class_name` option is passed:

```ruby
multi_search_results = MeiliSearch::Rails.multi_search(
'books' => { q: 'Harry', class_name: 'Book' },
'mangas' => { q: 'Attack', class_name: 'Manga' }
)
```

Otherwise, hashes are returned.

The index to search is inferred from the model if the key is a model, if the key is a string the key is assumed to be the index unless the `:index_name` option is passed:

```ruby
multi_search_results = MeiliSearch::Rails.multi_search(
'western' => { q: 'Harry', class_name: 'Book', index_name: 'books_production' },
'japanese' => { q: 'Attack', class_name: 'Manga', index_name: 'mangas_production' }
)
```

### Multi search the same index <!-- omit in toc -->

You can search the same index multiple times by specifying `:index_name`:

```ruby
query = 'hero'
multi_search_results = MeiliSearch::Rails.multi_search(
'Isekai Manga' => { q: query, class_name: 'Manga', filters: 'genre:isekai', index_name: 'mangas_production' }
'Shounen Manga' => { q: query, class_name: 'Manga', filters: 'genre:shounen', index_name: 'mangas_production' }
'Steampunk Manga' => { q: query, class_name: 'Manga', filters: 'genre:steampunk', index_name: 'mangas_production' }
)
```

### Deprecated #each <!-- omit in toc -->

**DEPRECATED:** You used to be able to iterate through a flattened collection with `.each`:

```erb
<% multi_search_results.each do |record| %>
<p><%= record.title %></p>
<p><%= record.author %></p>
<% end %>

<p>Harry Potter and the Philosopher's Stone</p>
<p>J. K. Rowling</p>
<p>Harry Potter and the Chamber of Secrets</p>
<p>J. K. Rowling</p>
<p>Attack on Titan</p>
<p>Iseyama</p>
```

But this has been deprecated in favor of **federated search**.

See the [official multi search documentation](https://www.meilisearch.com/docs/reference/api/multi_search).

## 🪛 Options
Expand Down
2 changes: 2 additions & 0 deletions lib/meilisearch/rails/multi_search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module Rails
class << self
def multi_search(searches)
search_parameters = searches.map do |(index_target, options)|
index_target = options.delete(:index_name) || index_target

paginate(options) if pagination_enabled?
normalize(options, index_target)
end
Expand Down
45 changes: 34 additions & 11 deletions lib/meilisearch/rails/multi_search/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,57 @@ def initialize(searches, raw_results)
@results = {}
@metadata = {}

searches.zip(raw_results['results']).each do |(index_target, search_options), result|
index_target = search_options[:class_name].constantize if search_options[:class_name]
searches.zip(raw_results['results']).each do |(target, search_options), result|
results_class = if search_options[:class_name]
search_options[:class_name].constantize
elsif target.instance_of?(Class)
target
end

@results[index_target] = case index_target
when String, Symbol
result['hits']
else
load_results(index_target, result)
end
@results[target] = results_class ? load_results(results_class, result) : result['hits']

@metadata[index_target] = result.except('hits')
@metadata[target] = result.except('hits')
end
end

include Enumerable

def each_hit(&block)
@results.each do |_index_target, results|
MeiliSearch::Rails.logger.warn(
<<~DEPRECATION
[meilisearch-rails] Flattening multi search results is deprecated.
If you do not want the results to be grouped, please use federated search instead.
DEPRECATION
)

@results.each_value do |results|
results.each(&block)
end
end
alias each each_hit

def each(&block)
MeiliSearch::Rails.logger.info(
<<~INFO
[meilisearch-rails] #each on a multi search now iterates through grouped results.
If you do not want the results to be grouped, please use federated search instead.
To quickly go back to the old deprecated behavior, use `#each_hit`.
INFO
)

@results.each(&block)
end

def each_result(&block)
@results.each(&block)
end

def to_a
MeiliSearch::Rails.logger.warn(
<<~DEPRECATION
[meilisearch-rails] Flattening multi search results is deprecated.
If you do not want the results to be grouped, please use federated search instead.
DEPRECATION
)
@results.values.flatten(1)
end
alias to_ary to_a
Expand Down
56 changes: 56 additions & 0 deletions spec/multi_search/result_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,57 @@
)
end

describe '#each' do
let(:logger) { instance_double('Logger', info: nil) }

before do
allow(MeiliSearch::Rails).to receive(:logger).and_return(logger)
end

it 'has the same behavior as #each_result' do
expect(result.each).to be_an(Enumerator)
expect(result.each).to contain_exactly(
['books_index', contain_exactly(
a_hash_including('author' => 'Walter Isaacson', 'name' => 'Steve Jobs')
)],
['products_index', contain_exactly(
a_hash_including('name' => 'palm pixi plus')
)],
['color_index', contain_exactly(
a_hash_including('name' => 'blue', 'short_name' => 'blu'),
a_hash_including('name' => 'black', 'short_name' => 'bla')
)]
)
end

it 'warns about changed behavior' do
result.each(&:to_s)

expect(logger).to have_received(:info).with(a_string_including('#each on a multi search now iterates through grouped results.'))
end
end

describe '#each_hit' do
let(:logger) { instance_double('Logger', warn: nil) }

before do
allow(MeiliSearch::Rails).to receive(:logger).and_return(logger)
end

it 'warns about deprecation' do
result.each_hit(&:to_s)

expect(logger).to have_received(:warn).with(a_string_including('Flattening multi search'))
end
end

describe '#to_a' do
let(:logger) { instance_double('Logger', warn: nil) }

before do
allow(MeiliSearch::Rails).to receive(:logger).and_return(logger)
end

it 'returns the hits' do
expect(result.to_a).to contain_exactly(
a_hash_including('author' => 'Walter Isaacson', 'name' => 'Steve Jobs'),
Expand All @@ -73,6 +123,12 @@
it 'aliases as #to_ary' do
expect(result.method(:to_ary).original_name).to eq :to_a
end

it 'warns about deprecation' do
result.to_a

expect(logger).to have_received(:warn).with(a_string_including('Flattening multi search'))
end
end

describe '#to_h' do
Expand Down
62 changes: 62 additions & 0 deletions spec/multi_search_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
require 'spec_helper'
require 'support/models/book'
require 'support/models/product'
require 'support/models/color'

describe 'multi-search' do
def reset_indexes
Expand Down Expand Up @@ -43,6 +46,65 @@ def reset_indexes
end
end

context 'with arbitrary keys' do
context 'when index_name is not present' do
it 'assumes key is index and errors' do
expect do
MeiliSearch::Rails.multi_search(
'test_group' => { q: 'Steve' }
)
end.to raise_error(MeiliSearch::ApiError)
end
end

context 'when :index_name is present' do
it 'searches the correct index' do
results = MeiliSearch::Rails.multi_search(
'books' => { q: 'Steve', index_name: Book.index.uid },
'products' => { q: 'palm', index_name: Product.index.uid, limit: 1 },
'colors' => { q: 'bl', index_name: Color.index.uid }
)

expect(results).to contain_exactly(
a_hash_including('author' => 'Walter Isaacson', 'name' => 'Steve Jobs'),
a_hash_including('name' => 'palm pixi plus'),
a_hash_including('name' => 'blue', 'short_name' => 'blu'),
a_hash_including('name' => 'black', 'short_name' => 'bla')
)
end

it 'allows searching the same index n times' do
index_name = Color.index.uid

results = MeiliSearch::Rails.multi_search(
'dark_colors' => { q: 'black', index_name: index_name },
'bright_colors' => { q: 'blue', index_name: index_name },
'nature_colors' => { q: 'green', index_name: index_name }
)

expect(results).to contain_exactly(
a_hash_including('name' => 'blue', 'short_name' => 'blu'),
a_hash_including('name' => 'black', 'short_name' => 'bla'),
a_hash_including('name' => 'green', 'short_name' => 'gre')
)
end

context 'when :class_name is also present' do
it 'loads results from the correct models' do
results = MeiliSearch::Rails.multi_search(
'books' => { q: 'Steve', index_name: Book.index.uid, class_name: 'Book' },
'products' => { q: 'palm', limit: 1, index_name: Product.index.uid, class_name: 'Product' },
'colors' => { q: 'bl', index_name: Color.index.uid, class_name: 'Color' }
)

expect(results).to contain_exactly(
steve_jobs, palm_pixi_plus, blue, black
)
end
end
end
end

context 'with index name keys' do
it 'returns hashes' do
results = MeiliSearch::Rails.multi_search(
Expand Down