diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b11ec8b09..a5d70ee55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,7 @@ jobs: - '3.1' - '3.2' - '3.3' + - '3.4' - 'head' gem: ['sunspot', 'sunspot_rails', 'sunspot_solr'] update-format: ['xml', 'json'] diff --git a/README.md b/README.md index 4d0bedd9f..f7ea3e2c8 100644 --- a/README.md +++ b/README.md @@ -498,14 +498,21 @@ search.facet(:author_id).rows.each do |facet| end ``` -You can also filter facets using a regular expression. Requires Solr 7.7 or higher for `facet.matches` faceting support. +Facet values can be restricted to those matching a regular expression. Requires Solr 7.7 or +higher. + ```ruby -# Posts that insensitive case match 'Lord' followed by 'Rings' anywhere in the post title +# Counts for post titles mentioning 'Lord' before 'Rings', in any case search = Post.search do - facet :title, matches: '(?i)Lord.*Rings' + facet :title, matches: '(?i).*Lord.*Rings.*' end ``` +The pattern is a Java regular expression matched against the whole indexed value, so `Rings` +matches the value `Rings` and not the value `Lord of the Rings`. Wrap the pattern in `.*` to +match anywhere within a value. Only the facet the option is given to is restricted; other +facets in the same search return all of their values. + #### Query Facets ```ruby @@ -1129,7 +1136,7 @@ node contains replicas of all shards in the cluster. If you have 4 shards on sep nodes each of these nodes should have 4 replicas (one replica of each shard). More information and usage examples could be found here: -https://lucene.apache.org/solr/guide/6_6/shards-and-indexing-data-in-solrcloud.html +https://lucene.apache.org/solr/guide/6_6/shards-and-indexing-data-in-solrcloud.html ### Highlighting @@ -1513,16 +1520,20 @@ part of the `save` callbacks. There are a number of ways to index manually within Ruby: ```ruby # On a class itself -Person.reindex +Person.reindex # Reindexes every record in the scope Sunspot.commit # or commit(true) for a soft commit (Solr4) +Person.solr_remove_all_from_index # Eliminates Person record from the index, including records that no longer exist # On mixed objects Sunspot.index [post1, item2] Sunspot.index person3 Sunspot.commit # or commit(true) for a soft commit (Solr4) +Sunspot.remove_all!(User, Person) # With autocommit Sunspot.index! [post1, item2, person3] +Person.solr_remove_all_from_index! +Sunspot.remove_all!(User) ``` If you make a change to the object's "schema" (code in the `searchable` block), diff --git a/sunspot/lib/sunspot/query/abstract_field_facet.rb b/sunspot/lib/sunspot/query/abstract_field_facet.rb index 046964ffb..03c32991e 100644 --- a/sunspot/lib/sunspot/query/abstract_field_facet.rb +++ b/sunspot/lib/sunspot/query/abstract_field_facet.rb @@ -30,6 +30,9 @@ def to_params if @options[:prefix] params[qualified_param('prefix')] = @options[:prefix].to_s end + if @options[:matches] + params[qualified_param('matches')] = @options[:matches].to_s + end params[qualified_param('mincount')] = case when @options[:minimum_count] then @options[:minimum_count].to_i diff --git a/sunspot/lib/sunspot/query/field_facet.rb b/sunspot/lib/sunspot/query/field_facet.rb index 4e77d0cc7..4236d2a82 100644 --- a/sunspot/lib/sunspot/query/field_facet.rb +++ b/sunspot/lib/sunspot/query/field_facet.rb @@ -11,10 +11,7 @@ def initialize(field, options) end def to_params - super.tap do |params| - params[:"facet.field"] = [field_name_with_local_params] - params[:"facet.matches"] = @options[:matches] if @options[:matches] - end + super.merge(:"facet.field" => [field_name_with_local_params]) end private @@ -38,7 +35,7 @@ def field_name_with_local_params end "{!#{pairs.join(' ')}}#{@field.indexed_name}" end - end + end end end end diff --git a/sunspot/lib/sunspot/query/restriction.rb b/sunspot/lib/sunspot/query/restriction.rb index 0288da6a1..e9e0b7cc5 100644 --- a/sunspot/lib/sunspot/query/restriction.rb +++ b/sunspot/lib/sunspot/query/restriction.rb @@ -7,14 +7,14 @@ class < [to_filter_query] } end - # + # # Return the boolean phrase associated with this restriction object. # Differentiates between positive and negated boolean phrases depending # on whether this restriction is negated. @@ -88,7 +88,7 @@ def to_boolean_phrase @field.local_params(value) : value end - # + # # Boolean phrase representing this restriction in the positive. Subclasses # may choose to implement this method rather than #to_params; however, # this method delegates to the abstract #to_solr_conditional method, which @@ -105,7 +105,7 @@ def to_positive_boolean_phrase "#{Util.escape(@field.indexed_name)}:#{to_solr_conditional}" end - # + # # Boolean phrase representing this restriction in the negated. Subclasses # may choose to implement this method, but it is not necessary, as the # base implementation delegates to #to_positive_boolean_phrase. @@ -118,14 +118,14 @@ def to_negated_boolean_phrase "-#{to_positive_boolean_phrase}" end - # + # # Whether this restriction should be negated from its original meaning # def negated? #:nodoc: !!@negated end - # + # # Return a new restriction that is the negated version of this one. It # is used by disjunction denormalization. # @@ -184,7 +184,7 @@ def to_solr_conditional end end - # + # # Results must have field with value equal to given value. If the value # is nil, results must have no value for the given field. # @@ -213,7 +213,7 @@ def to_solr_conditional end end - # + # # Results must have field with value less than given value # class LessThan < Base @@ -230,7 +230,7 @@ def to_solr_conditional end end - # + # # Results must have field with value less or equal to than given value # class LessThanOrEqualTo < Base @@ -247,7 +247,7 @@ def to_solr_conditional end end - # + # # Results must have field with value greater than given value # class GreaterThan < Base @@ -264,7 +264,7 @@ def to_solr_conditional end end - # + # # Results must have field with value greater than or equal to given value # class GreaterThanOrEqualTo < Base @@ -281,7 +281,7 @@ def to_solr_conditional end end - # + # # Results must have field with value in given range # class Between < Base @@ -302,7 +302,7 @@ def to_solr_conditional end end - # + # # Results must have field with value included in given collection # class AnyOf < Base @@ -343,7 +343,7 @@ def negated? super end end - + private def to_solr_conditional @@ -355,7 +355,7 @@ def to_solr_conditional end end - # + # # Results must have a field with a value that begins with the argument. # Most useful for strings, but in theory will work with anything. # diff --git a/sunspot/spec/api/query/faceting_examples.rb b/sunspot/spec/api/query/faceting_examples.rb index 6773401e4..adc6ed5dd 100644 --- a/sunspot/spec/api/query/faceting_examples.rb +++ b/sunspot/spec/api/query/faceting_examples.rb @@ -89,6 +89,23 @@ end expect(connection).to have_last_search_with(:"f.title_ss.facet.prefix" => 'Test') end + + it 'sets the facet matches pattern' do + search do + facet :title, :matches => 'Test.*' + end + expect(connection).to have_last_search_with(:"f.title_ss.facet.matches" => 'Test.*') + end + + it 'does not restrict other facets in the same search' do + search do + facet :title, :matches => 'Test.*' + facet :category_ids + end + expect(connection).to have_last_search_with(:"f.title_ss.facet.matches" => 'Test.*') + expect(connection).not_to have_last_search_with(:"f.category_ids_im.facet.matches") + expect(connection).not_to have_last_search_with(:"facet.matches") + end it 'sends a query facet for :any extra' do search do diff --git a/sunspot/spec/api/query/scope_examples.rb b/sunspot/spec/api/query/scope_examples.rb index c2e4b3e1c..e2dcf49f1 100644 --- a/sunspot/spec/api/query/scope_examples.rb +++ b/sunspot/spec/api/query/scope_examples.rb @@ -34,7 +34,7 @@ 'expire_date_d:1983\-07\-08T00\:00\:00Z' ) end - + it 'scopes by exact match with boolean' do search do with :featured, false @@ -63,13 +63,6 @@ expect(connection).to have_last_search_including(:fq, 'average_rating_ft:{3\.0 TO *}') end - it 'scopes by between match with excluded end float range' do - search do - with(:average_rating).between 2.0...3.0 - end - expect(connection).to have_last_search_including(:fq, 'average_rating_ft:[2\.0 TO 3\.0}') - end - it 'scopes by short-form between match with integers' do search do with :blog_id, 2..4 @@ -84,6 +77,13 @@ expect(connection).to have_last_search_including(:fq, 'average_rating_ft:[2\.0 TO 4\.0]') end + it 'scopes by between match with excluded end float range' do + search do + with(:average_rating).between 2.0...3.0 + end + expect(connection).to have_last_search_including(:fq, 'average_rating_ft:[2\.0 TO 3\.0}') + end + it 'scopes by any match with integer' do search do with(:category_ids).any_of [2, 7, 12] @@ -132,7 +132,7 @@ end expect(connection).to have_last_search_including(:fq, '-average_rating_ft:{3\.0 TO *}') end - + it 'scopes by not between match with shorthand' do search do without(:blog_id, 2..4) diff --git a/sunspot/spec/integration/faceting_spec.rb b/sunspot/spec/integration/faceting_spec.rb index 8075e8c59..299a9a5e2 100644 --- a/sunspot/spec/integration/faceting_spec.rb +++ b/sunspot/spec/integration/faceting_spec.rb @@ -124,14 +124,6 @@ def self.test_field_type(name, attribute, field, *args) expect(search.facet(:title).rows.map { |row| row.value }.sort).to eq(%w(three two)) end - it 'should limit facet values by regex match' do - search = Sunspot.search(Post) do - with :blog_id, 1 - facet :title, :matches => 'o.+[aeiou]' - end - expect(search.facet(:title).rows.map { |row| row.value }.sort).to eq(%w(one four)) - end - it 'should return :all facet' do search = Sunspot.search(Post) do with :blog_id, 1 diff --git a/sunspot/spec/integration/field_grouping_spec.rb b/sunspot/spec/integration/field_grouping_spec.rb index 0179d0ed5..b2a7bbef7 100644 --- a/sunspot/spec/integration/field_grouping_spec.rb +++ b/sunspot/spec/integration/field_grouping_spec.rb @@ -19,8 +19,7 @@ group :title end - expect(search.group(:title).groups).to include { |g| g.value == "Title1" } - expect(search.group(:title).groups).to include { |g| g.value == "Title2" } + expect(search.group(:title).groups.map(&:value)).to include("Title1", "Title2") end it "returns the number of matches unique groups" do diff --git a/sunspot/spec/integration/indexing_spec.rb b/sunspot/spec/integration/indexing_spec.rb index 09dc84977..72f837f75 100644 --- a/sunspot/spec/integration/indexing_spec.rb +++ b/sunspot/spec/integration/indexing_spec.rb @@ -8,6 +8,7 @@ end it 'should correctly remove by model instance' do + Sunspot.remove_all! post = Post.new(:title => 'test post') Sunspot.index!(post) Sunspot.remove!(post) @@ -15,6 +16,7 @@ end it 'should correctly delete by ID' do + Sunspot.remove_all! post = Post.new(:title => 'test post') Sunspot.index!(post) Sunspot.remove_by_id!(Post, post.id) diff --git a/sunspot/sunspot.gemspec b/sunspot/sunspot.gemspec index a54e3d9bb..e0362be9e 100644 --- a/sunspot/sunspot.gemspec +++ b/sunspot/sunspot.gemspec @@ -28,9 +28,17 @@ Gem::Specification.new do |s| s.add_dependency 'rsolr', '>= 1.1.1', '< 3' s.add_dependency 'pr_geohash', '~>1.0' s.add_dependency 'bigdecimal' + s.add_dependency 'ostruct' + s.add_dependency 'logger' s.add_development_dependency 'rake', '~> 13.2' s.add_development_dependency 'rspec', '~> 3.7' s.add_development_dependency 'appraisal', '~> 2.5' + s.add_development_dependency 'rdoc' + + # Ruby 2.5 compatibility + if RUBY_VERSION < '2.6' + s.add_development_dependency 'psych', '< 4.0' + end s.rdoc_options << '--webcvs=http://github.com/outoftime/sunspot/tree/master/%s' << '--title' << 'Sunspot - Solr-powered search for Ruby objects - API Documentation' << diff --git a/sunspot/tasks/rdoc.rake b/sunspot/tasks/rdoc.rake index ac2814bf1..1c97e95a7 100644 --- a/sunspot/tasks/rdoc.rake +++ b/sunspot/tasks/rdoc.rake @@ -1,17 +1,20 @@ require 'sunspot/version' -rdoc_task = +# Try to load RDoc task - newer versions use 'rdoc/task', older versions use 'rake/rdoctask' +rdoc_task = nil +begin + require 'rdoc/task' + rdoc_task = RDoc::Task +rescue LoadError + # Fallback to older API for compatibility begin - require 'rdoc/task' - RDoc::Task + require 'rake/rdoctask' + rdoc_task = Rake::RDocTask rescue LoadError - begin - require 'rake/rdoctask' - Rake::RDocTask - rescue - nil - end + # RDoc is not available - skip rdoc tasks + rdoc_task = nil end +end if rdoc_task rdoc_task.new(:doc) do |rdoc| diff --git a/sunspot_rails/Appraisals b/sunspot_rails/Appraisals index ca3d08520..d24067e7a 100644 --- a/sunspot_rails/Appraisals +++ b/sunspot_rails/Appraisals @@ -21,6 +21,7 @@ if Gem::Version.new('3.0.0') <= ruby_version && ruby_version < Gem::Version.new( gem 'sunspot', path: File.expand_path('sunspot', ENV['SUNSPOT_LIB_HOME']) gem 'sunspot_solr', path: File.expand_path('sunspot_solr', ENV['SUNSPOT_LIB_HOME']) gem 'rails', "~> #{rails_version}" + gem 'json', '< 3' # Under json 3, these Rails versions' to_json raises "unknown keyword: quirks_mode" gem 'sprockets', '~> 3.0' gem 'progress_bar', '~> 1.0.5', require: false gem 'sqlite3', '~> 1.4.0' diff --git a/sunspot_rails/spec/spec_helper.rb b/sunspot_rails/spec/spec_helper.rb index 6ee6f4440..be3f77f6a 100644 --- a/sunspot_rails/spec/spec_helper.rb +++ b/sunspot_rails/spec/spec_helper.rb @@ -1,5 +1,9 @@ ENV["RAILS_ENV"] ||= 'test' +# Load these gems before Rails to avoid Ruby 3.0+ compatibility issues +require 'ostruct' +require 'logger' + require File.expand_path('config/environment', File.expand_path('../rails_app', __FILE__)) require File.expand_path('../../lib/sunspot_rails', __FILE__) require 'rspec/rails' diff --git a/sunspot_rails/sunspot_rails.gemspec b/sunspot_rails/sunspot_rails.gemspec index 4d4def536..42ecec647 100644 --- a/sunspot_rails/sunspot_rails.gemspec +++ b/sunspot_rails/sunspot_rails.gemspec @@ -32,11 +32,18 @@ Gem::Specification.new do |s| s.add_dependency 'rails', '>= 5' s.add_dependency 'sunspot', Sunspot::VERSION + s.add_dependency 'ostruct' + s.add_dependency 'logger' s.add_development_dependency 'appraisal', '~> 2.5' s.add_development_dependency 'bundler', '>= 1.3.0', '< 2.0' if RUBY_VERSION <= '2.0.0' s.add_development_dependency 'nokogiri', '< 1.7' if RUBY_VERSION <= '2.0.0' s.add_development_dependency 'rake', '~> 13.2' + + # Ruby 2.5 compatibility + if RUBY_VERSION < '2.6' + s.add_development_dependency 'psych', '< 4.0' + end s.rdoc_options << '--webcvs=http://github.com/outoftime/sunspot/tree/master/%s' << '--title' << 'Sunspot-Rails - Rails integration for the Sunspot Solr search library - API Documentation' <<