Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Localized fields support #38

Open
wants to merge 78 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
aaf800f
Mongoid 7 compatibility
tomasc May 5, 2018
65c2202
readd danger
tomasc May 5, 2018
4364224
update changelog
tomasc May 5, 2018
6a6fd8c
SCI support
tomasc May 5, 2018
9f57c79
add support for criteria
tomasc May 5, 2018
d4f634c
update README
tomasc May 5, 2018
70c7ea6
typo
tomasc May 5, 2018
8d6afe2
further explain
tomasc May 5, 2018
e6a1a09
cleanup
tomasc May 5, 2018
94a2361
mongoid-compatibility added _or_newer? in 0.5.1
tomasc May 5, 2018
878b210
add database_cleaner
tomasc May 5, 2018
cc8b461
Merge branch 'master' into localized-fields
tomasc May 5, 2018
3bf0a2a
localized fields support
tomasc May 5, 2018
8e818bd
Use database_cleaner.
dblock May 5, 2018
a6eeef9
Mark failing specs pending.
dblock May 6, 2018
82eab98
Mongoid 7 compatibility
tomasc May 5, 2018
8b3ccb5
readd danger
tomasc May 5, 2018
57abbea
update changelog
tomasc May 5, 2018
b59427d
SCI support
tomasc May 5, 2018
df1ccff
add support for criteria
tomasc May 5, 2018
9832f53
update README
tomasc May 5, 2018
a9b7c36
typo
tomasc May 5, 2018
4b21baa
further explain
tomasc May 5, 2018
13bdee3
cleanup
tomasc May 5, 2018
3c4c469
Merge branch 'sci-support' of https://github.com/tomasc/mongoid_fullt…
tomasc May 7, 2018
5dcd0cd
Mongoid 7 compatibility
tomasc May 5, 2018
20fbcdf
readd danger
tomasc May 5, 2018
1ff06cc
update changelog
tomasc May 5, 2018
6c8ba8c
mongoid-compatibility added _or_newer? in 0.5.1
tomasc May 5, 2018
6ff4ce6
add database_cleaner
tomasc May 5, 2018
71d6396
Merge branch 'master' of https://github.com/tomasc/mongoid_fulltext
tomasc May 7, 2018
7dd9949
Mongoid 7 compatibility
tomasc May 5, 2018
240f547
readd danger
tomasc May 5, 2018
e2c5b7a
update changelog
tomasc May 5, 2018
287b8ad
SCI support
tomasc May 5, 2018
ce14915
add support for criteria
tomasc May 5, 2018
7be12fa
update README
tomasc May 5, 2018
cac1fa4
typo
tomasc May 5, 2018
85c9152
further explain
tomasc May 5, 2018
b77df68
cleanup
tomasc May 5, 2018
335fa6e
mongoid-compatibility added _or_newer? in 0.5.1
tomasc May 5, 2018
4d9b5c9
add database_cleaner
tomasc May 5, 2018
3bd912e
localized fields support
tomasc May 5, 2018
fec3921
Merge branch 'sci-support' of https://github.com/tomasc/mongoid_fullt…
tomasc May 7, 2018
7132041
make Rubocop happy
tomasc May 7, 2018
a38327e
Merge branch 'master' into localized-fields
tomasc May 7, 2018
48e017b
Merge branch 'sci-support' into localized-fields
tomasc May 7, 2018
b172e64
fix RuboCop too many lines
tomasc May 7, 2018
fafbf1c
Revert "fix RuboCop too many lines"
tomasc May 7, 2018
ff04f9c
bump module length
tomasc May 7, 2018
a27b81e
Update spec_helper.rb
tomasc May 7, 2018
6098c38
Merge branch 'master' of https://github.com/tomasc/mongoid_fulltext
tomasc May 8, 2018
5b87ef2
fix
tomasc May 8, 2018
10314d5
Merge branch 'master' into sci-support
tomasc May 8, 2018
fe9f7a0
rubocop
tomasc May 8, 2018
7078f9a
Mongoid 7 compatibility
tomasc May 5, 2018
dc21126
update changelog
tomasc May 5, 2018
9baa190
SCI support
tomasc May 5, 2018
0af33c2
add support for criteria
tomasc May 5, 2018
880ef89
update README
tomasc May 5, 2018
7ab7164
typo
tomasc May 5, 2018
d509b5e
further explain
tomasc May 5, 2018
d278868
cleanup
tomasc May 5, 2018
ebc16e2
make Rubocop happy
tomasc May 7, 2018
dcde6b8
fix RuboCop too many lines
tomasc May 7, 2018
af8958c
Revert "fix RuboCop too many lines"
tomasc May 7, 2018
41a2fb4
bump module length
tomasc May 7, 2018
5c0946d
Update spec_helper.rb
tomasc May 7, 2018
95df357
fix
tomasc May 8, 2018
22f5942
rubocop
tomasc May 8, 2018
c28a1b6
Merge branch 'sci-support' of https://github.com/tomasc/mongoid_fullt…
tomasc May 8, 2018
d0a26ad
update changelog
tomasc May 8, 2018
f48d76d
use class attr
tomasc May 8, 2018
9f7e02c
require
tomasc May 8, 2018
f34a171
Merge branch 'master' into localized-fields
tomasc May 8, 2018
c5efbbc
Merge branch 'master' into localized-fields
tomasc May 8, 2018
5e4bfa6
Merge branch 'sci-support' into localized-fields
tomasc May 8, 2018
aafd2ab
add available locales
tomasc May 8, 2018
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
12 changes: 6 additions & 6 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 2018-05-08 14:54:25 +0200 using RuboCop version 0.55.0.
# on 2018-05-08 16:11:28 +0200 using RuboCop version 0.55.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 All @@ -21,7 +21,7 @@ Layout/EndAlignment:
Exclude:
- 'lib/mongoid/full_text_search.rb'

# Offense count: 1
# Offense count: 2
# Configuration parameters: AllowSafeAssignment.
Lint/AssignmentInCondition:
Exclude:
Expand All @@ -43,11 +43,11 @@ Lint/UselessAssignment:
Exclude:
- 'spec/mongoid/full_text_search_spec.rb'

# Offense count: 5
# Offense count: 7
Metrics/AbcSize:
Max: 104

# Offense count: 7
# Offense count: 8
# Configuration parameters: CountComments, ExcludedMethods.
Metrics/BlockLength:
Max: 622
Expand All @@ -64,7 +64,7 @@ Metrics/MethodLength:
# Offense count: 1
# Configuration parameters: CountComments.
Metrics/ModuleLength:
Max: 223
Max: 243

# Offense count: 4
Metrics/PerceivedComplexity:
Expand Down Expand Up @@ -129,7 +129,7 @@ Style/NumericPredicate:
- 'spec/**/*'
- 'lib/mongoid/full_text_search.rb'

# Offense count: 262
# Offense count: 269
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### 0.8.3 (Next)

* [#37](https://github.com/mongoid/mongoid_fulltext/pull/37): Sci & criteria support - [@tomasc](https://github.com/tomasc).
* Your contribution here.

### 0.8.2 (8/5/2018)
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,40 @@ the AND of all of the individual results for each of the fields. Finally, if a f
but criteria for that filter aren't passed to `fulltext_search`, the result is as if the filter
had never been defined - you see both models that both pass and fail the filter in the results.

SCI Support
-----------

The search respects SCI. From the spec:

```ruby
class MyDoc
include Mongoid::Document
include Mongoid::FullTextSearch

field :title
fulltext_search_in :title
end

class MyInheritedDoc < MyDoc
end
```

```ruby
MyDoc.fulltext_search(…) # => will return both MyDoc as well as MyInheritedDoc documents
MyInheritedDoc.fulltext_search(…) # => will return only MyInheritedDoc documents
```

Criteria Support
----------------

It is also possible to pre-empt the search with Monogid criteria:

```ruby
MyDoc.where(value: 10).fulltext_search(…)
```

Please note that this will not work in case an index is shared by multiple classes (that are not connected through inheritance), since a criteria applies only to one class.

Indexing Options
----------------

Expand Down
134 changes: 86 additions & 48 deletions lib/mongoid/full_text_search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,18 @@ def fulltext_search_in(*args)
def create_fulltext_indexes
return unless mongoid_fulltext_config
mongoid_fulltext_config.each_pair do |index_name, fulltext_config|
fulltext_search_ensure_indexes(index_name, fulltext_config)
::I18n.available_locales.each do |locale|
fulltext_search_ensure_indexes(localized_index_name(index_name, locale), fulltext_config)
end
end
end

def localized_index_name(index_name, locale)
return index_name unless fields.values.any?(&:localized?)
return index_name unless ::I18n.available_locales.count > 1
"#{index_name}_#{locale}"
end

def fulltext_search_ensure_indexes(index_name, config)
db = collection.database
coll = db[index_name]
Expand Down Expand Up @@ -131,6 +139,7 @@ def fulltext_search(query_string, options = {})
end
index_name = options.key?(:index) ? options.delete(:index) : mongoid_fulltext_config.keys.first

loc_index_name = localized_index_name(index_name, ::I18n.locale)
# Options hash should only contain filters after this point

ngrams = all_ngrams(query_string, mongoid_fulltext_config[index_name])
Expand All @@ -140,9 +149,10 @@ def fulltext_search(query_string, options = {})
# get a count of the number of index documents containing that n-gram
ordering = { 'score' => -1 }
limit = mongoid_fulltext_config[index_name][:max_candidate_set_size]
coll = collection.database[index_name]
coll = collection.database[loc_index_name]
cursors = ngrams.map do |ngram|
query = { 'ngram' => ngram[0] }
query.update(document_type_filters)
query.update(map_query_filters(options))
count = coll.find(query).count
{ ngram: ngram, count: count, query: query }
Expand Down Expand Up @@ -191,7 +201,11 @@ def fulltext_search(query_string, options = {})
end

def instantiate_mapreduce_result(result)
result[:clazz].constantize.find(result[:id])
if criteria.selector.empty?
result[:clazz].constantize.find(result[:id])
else
criteria.where(_id: result[:id]).first
end
end

def instantiate_mapreduce_results(results, options)
Expand Down Expand Up @@ -280,11 +294,13 @@ def all_ngrams(str, config, bound_number_returned = true)

def remove_from_ngram_index
mongoid_fulltext_config.each_pair do |index_name, _fulltext_config|
coll = collection.database[index_name]
if Mongoid::Compatibility::Version.mongoid5_or_newer?
coll.find('class' => name).delete_many
else
coll.find('class' => name).remove_all
::I18n.available_locales.each do |locale|
coll = collection.database[localized_index_name(index_name, locale)]
if Mongoid::Compatibility::Version.mongoid5_or_newer?
coll.find('class' => name).delete_many
else
coll.find('class' => name).remove_all
end
end
end
end
Expand All @@ -295,6 +311,13 @@ def update_ngram_index

private

# add filter by type according to SCI classes
def document_type_filters
return {} unless fields['_type'].present?
kls = ([self] + descendants).map(&:to_s)
{ 'class' => { '$in' => kls } }
end

# Take a list of filters to be mapped so they can update the query
# used upon the fulltext search of the ngrams
def map_query_filters(filters)
Expand All @@ -316,57 +339,72 @@ def format_query_filter(operator, key, value)

def update_ngram_index
mongoid_fulltext_config.each_pair do |index_name, fulltext_config|
if condition = fulltext_config[:update_if]
case condition
when Symbol then next unless send condition
when String then next unless instance_eval condition
when Proc then next unless condition.call self
else; next
::I18n.available_locales.each do |locale|
loc_index_name = self.class.localized_index_name(index_name, locale)

if condition = fulltext_config[:update_if]
case condition
when Symbol then next unless send condition
when String then next unless instance_eval condition
when Proc then next unless condition.call self
else; next
end
end
end

# remove existing ngrams from external index
coll = collection.database[index_name.to_sym]
if Mongoid::Compatibility::Version.mongoid5_or_newer?
coll.find('document_id' => _id).delete_many
else
coll.find('document_id' => _id).remove_all
end
# extract ngrams from fields
field_values = fulltext_config[:ngram_fields].map { |field| send(field) }
ngrams = field_values.inject({}) { |accum, item| accum.update(self.class.all_ngrams(item, fulltext_config, false)) }
return if ngrams.empty?
# apply filters, if necessary
filter_values = nil
if fulltext_config.key?(:filters)
filter_values = Hash[fulltext_config[:filters].map do |key, value|
begin
[key, value.call(self)]
rescue StandardError
# Suppress any exceptions caused by filters
end
end.compact]
end
# insert new ngrams in external index
ngrams.each_pair do |ngram, score|
index_document = { 'ngram' => ngram, 'document_id' => _id, 'score' => score, 'class' => self.class.name }
index_document['filter_values'] = filter_values if fulltext_config.key?(:filters)
# remove existing ngrams from external index
coll = collection.database[loc_index_name.to_sym]
if Mongoid::Compatibility::Version.mongoid5_or_newer?
coll.insert_one(index_document)
coll.find('document_id' => _id).delete_many
else
coll.insert(index_document)
coll.find('document_id' => _id).remove_all
end
# extract ngrams from fields
field_values = fulltext_config[:ngram_fields].map do |field_name|
next send(field_name) if field_name == :to_s
next unless field = self.class.fields[field_name.to_s]
if field.localized?
send("#{field_name}_translations")[locale]
else
send(field_name)
end
end

ngrams = field_values.inject({}) { |accum, item| accum.update(self.class.all_ngrams(item, fulltext_config, false)) }
return if ngrams.empty?
# apply filters, if necessary
filter_values = nil
if fulltext_config.key?(:filters)
filter_values = Hash[fulltext_config[:filters].map do |key, value|
begin
[key, value.call(self)]
rescue StandardError
# Suppress any exceptions caused by filters
end
end.compact]
end
# insert new ngrams in external index
ngrams.each_pair do |ngram, score|
index_document = { 'ngram' => ngram, 'document_id' => _id, 'document_type' => model_name.to_s, 'score' => score, 'class' => self.class.name }
index_document['filter_values'] = filter_values if fulltext_config.key?(:filters)
if Mongoid::Compatibility::Version.mongoid5_or_newer?
coll.insert_one(index_document)
else
coll.insert(index_document)
end
end
end
end
end

def remove_from_ngram_index
mongoid_fulltext_config.each_pair do |index_name, _fulltext_config|
coll = collection.database[index_name]
if Mongoid::Compatibility::Version.mongoid5_or_newer?
coll.find('document_id' => _id).delete_many
else
coll.find('document_id' => _id).remove_all
::I18n.available_locales.each do |locale|
coll = collection.database[self.class.localized_index_name(index_name, locale)]
if Mongoid::Compatibility::Version.mongoid5_or_newer?
coll.find('document_id' => _id).delete_many
else
coll.find('document_id' => _id).remove_all
end
end
end
end
Expand Down
9 changes: 9 additions & 0 deletions spec/models/my_doc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class MyDoc
include Mongoid::Document
include Mongoid::FullTextSearch

field :title
field :value, type: Integer

fulltext_search_in :title
end
4 changes: 4 additions & 0 deletions spec/models/my_further_inherited_doc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
require 'models/my_inherited_doc.rb'

class MyFurtherInheritedDoc < MyInheritedDoc
end
2 changes: 2 additions & 0 deletions spec/models/my_inherited_doc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class MyInheritedDoc < MyDoc
end
8 changes: 8 additions & 0 deletions spec/models/my_localized_doc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class MyLocalizedDoc
include Mongoid::Document
include Mongoid::FullTextSearch

field :title, localize: true

fulltext_search_in :title
end
14 changes: 14 additions & 0 deletions spec/mongoid/criteria_search_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

require 'spec_helper'

describe Mongoid::FullTextSearch do
context 'Criteria' do
let!(:my_doc_1) { MyDoc.create!(title: 'My Doc 1') }
let!(:my_doc_2) { MyDoc.create!(title: 'My Doc 2', value: 10) }

let(:result) { MyDoc.where(value: 10).fulltext_search('doc') }

it { expect(result).not_to include my_doc_1 }
it { expect(result).to include my_doc_2 }
end
end
29 changes: 29 additions & 0 deletions spec/mongoid/localized_fields_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

require 'spec_helper'

describe Mongoid::FullTextSearch do
context 'Localized fields' do
let!(:my_doc) { MyLocalizedDoc.create!(title_translations: { en: 'Title', cs: 'Nazev' }) }

before(:each) do
@default_locale = ::I18n.locale
::I18n.locale = locale
end

after(:each) do
::I18n.locale = @default_locale
end

context 'en' do
let(:locale) { :en }
it { expect(MyLocalizedDoc.fulltext_search('title')).to include my_doc }
it { expect(MyLocalizedDoc.fulltext_search('nazev')).not_to include my_doc }
end

context 'cs' do
let(:locale) { :cs }
it { expect(MyLocalizedDoc.fulltext_search('title')).not_to include my_doc }
it { expect(MyLocalizedDoc.fulltext_search('nazev')).to include my_doc }
end
end
end
31 changes: 31 additions & 0 deletions spec/mongoid/sci_search_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

require 'spec_helper'

describe Mongoid::FullTextSearch do
context 'SCI' do
let!(:my_doc) { MyDoc.create!(title: 'My Doc') }
let!(:my_inherited_doc) { MyInheritedDoc.create!(title: 'My Inherited Doc') }
let!(:my_further_inherited_doc) { MyFurtherInheritedDoc.create!(title: 'My Inherited Doc') }

context 'root class returns results for subclasses' do
let(:result) { MyDoc.fulltext_search('doc') }
it { expect(result).to include my_doc }
it { expect(result).to include my_inherited_doc }
it { expect(result).to include my_further_inherited_doc }
end

context 'child class does not return superclass' do
let(:result) { MyInheritedDoc.fulltext_search('doc') }
it { expect(result).not_to include my_doc }
it { expect(result).to include my_inherited_doc }
it { expect(result).to include my_further_inherited_doc }
end

context 'child class does not return superclass' do
let(:result) { MyFurtherInheritedDoc.fulltext_search('doc') }
it { expect(result).not_to include my_doc }
it { expect(result).not_to include my_inherited_doc }
it { expect(result).to include my_further_inherited_doc }
end
end
end
Loading