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
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- { ruby: '3.1', mongo: 'mongo:5.0', mongoid: '7' }
- { ruby: '3.2', mongo: 'mongo:6.0', mongoid: '8' }
- { ruby: '3.3', mongo: 'mongo:7.0', mongoid: '9' }
- { ruby: '3.4', mongo: 'mongo:8.0', mongoid: '9' }
- { ruby: '3.4', mongo: 'mongo:8.0', mongoid: '9', coverage: true }
- { ruby: '4.0', mongo: 'mongo:8.0', mongoid: '9' }
experimental: [false]

Expand All @@ -29,7 +29,7 @@ jobs:

env:
MONGOID_VERSION: ${{ matrix.entry.mongoid }}
COVERAGE: ${{ matrix.entry.ruby == '3.4' }}
COVERAGE: ${{ matrix.entry.coverage }}

steps:
- uses: actions/checkout@v6
Expand All @@ -44,7 +44,7 @@ jobs:
run: bundle exec rspec

- name: Coveralls
if: matrix.entry.ruby == '3.4'
if: matrix.entry.coverage
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## 0.6.0 (next)
* [#156](https://github.com/mongoid/mongoid_search/pull/156): Cover relevance search, configuration, output and Rails indexing tasks; fix scalar relevance scores and highest-relevance-first ordering (see [UPGRADING](UPGRADING.md)) - [@dblock](https://github.com/dblock).
* Your contribution here.
* [#155](https://github.com/mongoid/mongoid_search/pull/155): Replace Code Climate with SimpleCov and Coveralls reporting in CI and update status badges - [@dblock](https://github.com/dblock).
* [#154](https://github.com/mongoid/mongoid_search/pull/154): Test through Ruby 4.0, Mongoid 9, and MongoDB 8.0 with representative legacy coverage and explicitly bundle OpenStruct - [@dblock](https://github.com/dblock).
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ group :development do
gem 'database_cleaner-mongoid', '>= 2.0.0'
gem 'mongoid-compatibility'
gem 'ostruct'
gem 'railties', version =~ /^6/ ? '~> 5.2.0' : '~> 7.0.0'
gem 'rake', '>= 12.3.3'
gem 'rspec', '~> 3.1'
gem 'rubocop', '1.56.0'
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ Product.full_text_search('amazing apple', relevant_search: true)

Please note that relevant_search will return an Array and not a Criteria object. The search method should always be called in the end of the method chain.

Results are ordered by decreasing numeric relevance (the number of matching indexed keywords). This option uses MongoDB map-reduce, which is deprecated upstream and requires server-side JavaScript.

Upgrading from an earlier release? See [UPGRADING](UPGRADING.md) for the relevance ordering change in 0.6.0.

### index

Default is `_keywords`.
Expand Down
22 changes: 22 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Upgrading

## Upgrading to 0.6.0

### Relevance search returns highest-relevance results first

Searches with `relevant_search: true`, or with `Mongoid::Search.relevant_search = true`, now return results sorted by **decreasing numeric relevance**. Previously, results were sorted in ascending order, putting lower scores first; the map-reduce reducer could also return arrays instead of numeric scores. Relevance scores are now numeric counts of matching indexed keywords.

For example, a search matching documents with relevance scores of `1` and `2` now returns the document with score `2` first:

```ruby
Product.full_text_search('amazing apple', relevant_search: true).map(&:relevance)
# => [2.0, 1.0]
```

Remove any application-side `.reverse` or other sorting workaround used to put the most relevant results first. If your application intentionally needs the least relevant results first, sort the returned array explicitly:

```ruby
Product.full_text_search('amazing apple', relevant_search: true).sort_by(&:relevance)
```

Relevance search still returns an `Array`, not a Mongoid criteria. Searches with relevance disabled (the default) are unaffected. No keyword reindexing is needed.
4 changes: 2 additions & 2 deletions lib/mongoid_search/mongoid_search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def search_without_relevance(query, options)
end

def search_relevant(query, options)
results_with_relevance(query, options).sort_by { |o| o['value'] }.map do |r|
results_with_relevance(query, options).sort_by { |o| -o['value'] }.map do |r|
new(r['_id'].merge(relevance: r['value'])) do |o|
# Need to match the actual object
o.instance_variable_set('@new_record', false)
Expand Down Expand Up @@ -124,7 +124,7 @@ def results_with_relevance(query, options)

reduce = %{
function(key, values) {
return(values);
return Array.sum(values);
}
}

Expand Down
9 changes: 9 additions & 0 deletions spec/configuration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

require 'spec_helper'

describe Mongoid::Search do
it 'yields the public configuration module' do
expect { |block| described_class.setup(&block) }.to yield_with_args(described_class)
end
end
26 changes: 26 additions & 0 deletions spec/log_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

require 'spec_helper'

describe Mongoid::Search::Log do
around do |example|
original = described_class.silent
described_class.silent = false
example.run
ensure
described_class.silent = original
end

it 'prints failures in red' do
expect { described_class.red('F') }.to output("\033[31mF\033[0m").to_stdout
end

it 'prints successes in green' do
expect { described_class.green('.') }.to output("\033[32m.\033[0m").to_stdout
end

it 'does not print when silent' do
described_class.silent = true
expect { described_class.red('F') }.not_to output.to_stdout
end
end
7 changes: 5 additions & 2 deletions spec/mongoid_search_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,16 @@
end
end

# relevant search specs are currently broken due to calling sort on an array of arrays
xcontext 'relevant search' do
context 'relevant search' do
before do
Mongoid::Search.relevant_search = true
@imac = Product.create name: 'apple imac'
end

after do
Mongoid::Search.relevant_search = false
end

it 'should return results ordered by relevance and with correct ids' do
expect(Product.full_text_search('apple imac').map(&:_id)).to eq [@imac._id, @product._id]
end
Expand Down
57 changes: 57 additions & 0 deletions spec/railtie_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

require 'spec_helper'
require 'rake'
require 'rails/railtie'
require 'rails/application'
require 'mongoid_search/railtie'

describe Mongoid::Search::Railtie do
before(:all) do
@rake_application = Rake.application
Rake.application = Rake::Application.new
described_class.instance.send(:run_tasks_blocks, described_class.instance)
Rake::Task.define_task(:environment)
end

after(:all) do
Rake.application = @rake_application
end

around do |example|
silent = Mongoid::Search::Log.silent
example.run
ensure
Rake::Task['mongoid_search:index'].reenable
Mongoid::Search::Log.silent = silent
end

before do
application = instance_double(Rails::Application, eager_load!: nil)
allow(Rails).to receive(:application).and_return(application)
end

it 'registers the public indexing rake task with Rails' do
expect(Rake::Task.task_defined?('mongoid_search:index')).to be true
expect(Rake::Task['mongoid_search:index'].prerequisites).to include('environment')
end

it 'eager loads the application and reports when no models are registered' do
allow(Mongoid::Search).to receive(:classes).and_return([])
expect(Rails.application).to receive(:eager_load!)

expect { Rake::Task['mongoid_search:index'].invoke }
.to output("No model to index keywords.\n").to_stdout
end

it 'indexes the registered models' do
product = Product.create!(name: 'Coverage')
Product.collection.find(_id: product.id).update_one('$unset' => { _keywords: '' })
allow(Mongoid::Search).to receive(:classes).and_return([Product])
expect(Rails.application).to receive(:eager_load!)

expect { Rake::Task['mongoid_search:index'].invoke }
.to output(/Indexing documents for Product:.*Done/m).to_stdout
expect(product.reload._keywords).to eq(['coverage'])
end
end
Loading