diff --git a/CHANGELOG.md b/CHANGELOG.md index 418d3bb..c2ae7fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * [#57](https://github.com/mongoid/mongoid_fulltext/pull/57): Respect criteria and single-collection inheritance, skip dangling references, and apply result limits after filtering - [@tomasc](https://github.com/tomasc), [@dblock](https://github.com/dblock). * [#56](https://github.com/mongoid/mongoid_fulltext/pull/56): Support Unicode-aware regular expressions for alphabets without enumerating characters - [@dblock](https://github.com/dblock). * [#54](https://github.com/mongoid/mongoid_fulltext/pull/54): Insert ngrams in a batch per document index and continue past empty indexes - [@dblock](https://github.com/dblock). +* [#53](https://github.com/mongoid/mongoid_fulltext/pull/53): Support indexing array fields containing strings - [@dblock](https://github.com/dblock). * [#51](https://github.com/mongoid/mongoid_fulltext/pull/51): Modernize CI through Ruby 4.0, Mongoid 9, and MongoDB 8 with real coverage, RuboCop 1.92, and fork-safe Danger workflows - [@dblock](https://github.com/dblock). * [#45](https://github.com/mongoid/mongoid_fulltext/pull/45): Allow for class level ngram updates to not timeout - [@roykolak](https://github.com/roykolak). * Your contribution here. diff --git a/README.md b/README.md index 808baee..34db79a 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,8 @@ class Artist end ``` +Array fields containing strings are supported alongside scalar fields, for example `fulltext_search_in :title, :tags` with `field :tags, type: Array`. Each array element is indexed independently, so ngrams do not span tags. Nil and empty elements are ignored; changing or clearing the array replaces its old index entries. + To restrict the number of results returned, pass the `:max_results` parameter to `fulltext_search`: ``` ruby diff --git a/lib/mongoid/full_text_search.rb b/lib/mongoid/full_text_search.rb index aa74628..1643cc2 100644 --- a/lib/mongoid/full_text_search.rb +++ b/lib/mongoid/full_text_search.rb @@ -386,7 +386,7 @@ def update_ngram_index coll.find(removal_query).remove_all end # extract ngrams from fields - field_values = fulltext_config[:ngram_fields].map { |field| send(field) } + field_values = fulltext_config[:ngram_fields].flat_map { |field| send(field) } ngrams = field_values.inject({}) { |accum, item| accum.update(self.class.all_ngrams(item, fulltext_config, false)) } next if ngrams.empty? diff --git a/spec/models/tagged_artwork.rb b/spec/models/tagged_artwork.rb new file mode 100644 index 0000000..d0de77b --- /dev/null +++ b/spec/models/tagged_artwork.rb @@ -0,0 +1,9 @@ +class TaggedArtwork + include Mongoid::Document + include Mongoid::FullTextSearch + + field :title, type: String + field :tags, type: Array + + fulltext_search_in :title, :tags +end diff --git a/spec/mongoid/array_fields_spec.rb b/spec/mongoid/array_fields_spec.rb new file mode 100644 index 0000000..4cddc1e --- /dev/null +++ b/spec/mongoid/array_fields_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe 'Array fields' do + it 'searches each tag as well as scalar fields' do + artwork = TaggedArtwork.create!(title: 'Painting', tags: %w[ghost accounting]) + %w[painting ghost accounting].each do |query| + expect(TaggedArtwork.fulltext_search(query)).to eq([artwork]) + end + end + + it 'does not create ngrams across array element boundaries' do + artwork = TaggedArtwork.create!(tags: %w[ab cd]) + config = TaggedArtwork.mongoid_fulltext_config.values.first + coll = artwork.collection.database[TaggedArtwork.mongoid_fulltext_config.keys.first] + expected = %w[ab cd].inject({}) do |ngrams, tag| + ngrams.update(TaggedArtwork.all_ngrams(tag, config, false)) + end + actual = Hash[coll.find.map { |document| [document['ngram'], document['score']] }] + expect(actual).to eq(expected) + end + + it 'handles nil, empty, and repeated elements without duplicate records' do + artwork = TaggedArtwork.create!(tags: [nil, '', 'ghost', 'ghost']) + expect(TaggedArtwork.fulltext_search('ghost')).to eq([artwork]) + coll = artwork.collection.database[TaggedArtwork.mongoid_fulltext_config.keys.first] + ngrams = coll.find.map { |document| document['ngram'] } + expect(ngrams).to eq(ngrams.uniq) + end + + it 'removes old tags when the array changes or becomes empty' do + artwork = TaggedArtwork.create!(tags: ['ghost']) + artwork.update_attributes!(tags: ['accounting']) + expect(TaggedArtwork.fulltext_search('ghost')).to eq([]) + expect(TaggedArtwork.fulltext_search('accounting')).to eq([artwork]) + artwork.update_attributes!(tags: []) + expect(TaggedArtwork.fulltext_search('accounting')).to eq([]) + end + + it 'still indexes scalar fields when the array is nil' do + artwork = TaggedArtwork.create!(title: 'Painting', tags: nil) + expect(TaggedArtwork.fulltext_search('painting')).to eq([artwork]) + end +end