sunspot 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 698b438f344a3e391c87d9f755dc227869fefad88555b261ef4dcd9cc9d9c1d1
4
- data.tar.gz: 2ba1b782a43a5eafd6f1bc6397ef221fe94bbbb92f1036505530719a50f07882
3
+ metadata.gz: 385375ab001841dcd220a07df21307f581e5ed8d4c41c6907e4d2f05783259e5
4
+ data.tar.gz: 0cd9f3eb2a5225b46429988a7a9d10f2ecbd488fd2c824448831a5f908a69c75
5
5
  SHA512:
6
- metadata.gz: 8f89e196fd125f6c0b2bd8eda2aecf7c288860ff14ab128d695f61252344fc07b0011ed8c10d8c5578227eae814df368fecf3332d306ae2a79e9b5da593dfc97
7
- data.tar.gz: 780ca66b8d6e569a994670b2e6f990444333dc03f109bd015c7f26924673e801281f611f5d08b14b48afb35b6177099747113e0bbdc10a07aebe4cee5f93c8ea
6
+ metadata.gz: 97c5760b633b1ab3cbfb8a04a3dd49e9c0bd40485151c94b5c8196104a2b0fc47f1fdd97ff2d85b738c4d225820316dc1e42cceb5a19ea8c7f084a9b82e224f0
7
+ data.tar.gz: 6ca3b17c3c28f89f56160ebdbaf2c924103976eb939df6832f0755d30a368c757dd912b5491b9a84caf38498e8320252ad14d59cd86ca4e8d071e3b2d95518e1
data/Appraisals CHANGED
@@ -1,7 +1,7 @@
1
- appraise 'rsolr-1.1.x' do
2
- gem 'rsolr', '~> 1.1.0'
1
+ appraise 'rsolr-1.x' do
2
+ gem 'rsolr', '>= 1', '< 2'
3
3
  end
4
4
 
5
- appraise 'rsolr-2.1.x' do
6
- gem 'rsolr', '~> 2.1.0'
5
+ appraise 'rsolr-2.x' do
6
+ gem 'rsolr', '>= 2', '< 3'
7
7
  end
@@ -5,16 +5,46 @@ module Sunspot
5
5
  # method, which takes an object and returns the value extracted from it.
6
6
  #
7
7
  module DataExtractor #:nodoc: all
8
+ #
9
+ # Abstract extractor to perform common actions on extracted values
10
+ #
11
+ class AbstractExtractor
12
+ BLACKLIST_REGEXP = /[\x0-\x8\xB\xC\xE-\x1F\x7f]/
13
+
14
+ def value_for(object)
15
+ extract_value_from(object)
16
+ end
17
+
18
+ private
19
+
20
+ def extract_value_from(object)
21
+ case object
22
+ when String
23
+ remove_blacklisted_chars(object)
24
+ when Array
25
+ object.map { |o| extract_value_from(o) }
26
+ when Hash
27
+ object.inject({}) { |h, (k, v)| h.merge(extract_value_from(k) => extract_value_from(v)) }
28
+ else
29
+ object
30
+ end
31
+ end
32
+
33
+ def remove_blacklisted_chars(object)
34
+ object.gsub(BLACKLIST_REGEXP, '')
35
+ end
36
+ end
37
+
8
38
  #
9
39
  # AttributeExtractors extract data by simply calling a method on the block.
10
40
  #
11
- class AttributeExtractor
41
+ class AttributeExtractor < AbstractExtractor
12
42
  def initialize(attribute_name)
13
43
  @attribute_name = attribute_name
14
44
  end
15
45
 
16
46
  def value_for(object)
17
- object.send(@attribute_name)
47
+ super object.send(@attribute_name)
18
48
  end
19
49
  end
20
50
 
@@ -24,26 +54,26 @@ module Sunspot
24
54
  # as the argument to the block. Either way, the return value of the block is
25
55
  # the value returned by the extractor.
26
56
  #
27
- class BlockExtractor
57
+ class BlockExtractor < AbstractExtractor
28
58
  def initialize(&block)
29
59
  @block = block
30
60
  end
31
61
 
32
62
  def value_for(object)
33
- Util.instance_eval_or_call(object, &@block)
63
+ super Util.instance_eval_or_call(object, &@block)
34
64
  end
35
65
  end
36
66
 
37
67
  #
38
68
  # Constant data extractors simply return the same value for every object.
39
69
  #
40
- class Constant
70
+ class Constant < AbstractExtractor
41
71
  def initialize(value)
42
72
  @value = value
43
73
  end
44
74
 
45
75
  def value_for(object)
46
- @value
76
+ super @value
47
77
  end
48
78
  end
49
79
  end
@@ -59,6 +59,16 @@ module Sunspot
59
59
  @group.truncate = true
60
60
  end
61
61
 
62
+ #
63
+ # The group.ngroups option true return the total number of groups
64
+ # this is expensive and sometimes you don't need it!
65
+ # If ngroups is false paginated_collection last_page? and total_pages wont't work.
66
+ # Defaults to true.
67
+ #
68
+ def ngroups(enabled)
69
+ @group.ngroups = enabled
70
+ end
71
+
62
72
  # Specify the order that results should be returned in. This method can
63
73
  # be called multiple times; precedence will be in the order given.
64
74
  #
@@ -199,25 +199,25 @@ module Sunspot
199
199
 
200
200
  def add_restriction(negated, *args)
201
201
  case args.first
202
- when String, Symbol
203
- raise ArgumentError if args.length > 2
204
- field = @setup.field(args[0].to_sym)
205
- if args.length > 1
206
- value = args[1]
207
- @scope.add_shorthand_restriction(negated, field, value)
208
- else # NONE
209
- DSL::Restriction.new(field, @scope, negated)
210
- end
211
- else # args are instances
212
- @scope.add_restriction(
213
- negated,
214
- IdField.instance,
215
- Sunspot::Query::Restriction::AnyOf,
216
- args.flatten.map { |instance|
217
- Sunspot::Adapters::InstanceAdapter.adapt(instance).index_id }
218
- )
202
+ when String, Symbol
203
+ raise ArgumentError if args.length > 2
204
+ field = @setup.field(args[0].to_sym)
205
+ if args.length > 1
206
+ value = args[1]
207
+ @scope.add_shorthand_restriction(negated, field, value)
208
+ else # NONE
209
+ DSL::Restriction.new(field, @scope, negated)
219
210
  end
211
+ else # args are instances
212
+ @scope.add_restriction(
213
+ negated,
214
+ IdField.instance,
215
+ Sunspot::Query::Restriction::AnyOf,
216
+ args.flatten.map { |instance|
217
+ Sunspot::Adapters::InstanceAdapter.adapt(instance).index_id }
218
+ )
220
219
  end
220
+ end
221
221
  end
222
222
  end
223
223
  end
@@ -4,7 +4,7 @@ module Sunspot
4
4
  # A Group groups by the unique values of a given field, or by given queries.
5
5
  #
6
6
  class Group
7
- attr_accessor :limit, :truncate
7
+ attr_accessor :limit, :truncate, :ngroups
8
8
  attr_reader :fields, :queries
9
9
 
10
10
  def initialize
@@ -30,16 +30,15 @@ module Sunspot
30
30
 
31
31
  def to_params
32
32
  params = {
33
- :group => "true",
34
- :"group.ngroups" => "true",
33
+ :group => 'true',
34
+ :"group.ngroups" => @ngroups.nil? ? 'true' : @ngroups.to_s
35
35
  }
36
36
 
37
- params.merge!(@sort.to_params("group."))
37
+ params.merge!(@sort.to_params('group.'))
38
38
  params[:"group.field"] = @fields.map(&:indexed_name) if @fields.any?
39
39
  params[:"group.query"] = @queries.map(&:to_boolean_phrase) if @queries.any?
40
40
  params[:"group.limit"] = @limit if @limit
41
41
  params[:"group.truncate"] = @truncate if @truncate
42
-
43
42
  params
44
43
  end
45
44
  end
@@ -42,12 +42,10 @@ module Sunspot
42
42
  keywords = escape_quotes(params.delete(:q))
43
43
  options = params.map { |key, value| escape_param(key, value) }.join(' ')
44
44
  q_name = "q#{@target.name}#{self.object_id}"
45
- fq_name = "f#{q_name}"
46
45
 
47
46
  {
48
- :q => "_query_:\"{!join from=#{@from} to=#{@to} v=$#{q_name} fq=$#{fq_name}}\"",
49
- q_name => "_query_:\"{!edismax #{options}}#{keywords}\"",
50
- fq_name => "type:#{@target.name}"
47
+ :q => "_query_:\"{!join from=#{@from} to=#{@to} v=$#{q_name}}\"",
48
+ q_name => "_query_:\"{!field f=type}#{@target.name}\"+_query_:\"{!edismax #{options}}#{keywords}\""
51
49
  }
52
50
  end
53
51
 
@@ -135,7 +135,7 @@ module Sunspot
135
135
 
136
136
  protected
137
137
 
138
- #
138
+ #
139
139
  # Return escaped Solr API representation of given value
140
140
  #
141
141
  # ==== Parameters
@@ -158,9 +158,13 @@ module Sunspot
158
158
  end
159
159
 
160
160
  class InRadius < Base
161
- def initialize(negated, field, lat, lon, radius)
162
- @lat, @lon, @radius = lat, lon, radius
163
- super negated, field, [lat, lon, radius]
161
+ def initialize(negated, field, *value)
162
+ @lat, @lon, @radius = value
163
+ super negated, field, value
164
+ end
165
+
166
+ def negate
167
+ self.class.new(!@negated, @field, *@value)
164
168
  end
165
169
 
166
170
  private
@@ -16,7 +16,7 @@ module Sunspot
16
16
  # Retrieve all facet objects defined for this search, in order they were
17
17
  # defined. To retrieve an individual facet by name, use #facet()
18
18
  #
19
- attr_reader :facets, :groups, :stats
19
+ attr_reader :facets, :groups
20
20
  attr_reader :query #:nodoc:
21
21
  attr_accessor :request_handler
22
22
 
@@ -1,3 +1,3 @@
1
1
  module Sunspot
2
- VERSION = '2.3.0'
2
+ VERSION = '2.4.0'
3
3
  end
@@ -0,0 +1,39 @@
1
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
2
+
3
+ describe Sunspot::DataExtractor do
4
+ it "removes special characters from strings" do
5
+ extractor = Sunspot::DataExtractor::AttributeExtractor.new(:name)
6
+ blog = Blog.new(:name => "Te\x0\x1\x7\x6\x8st\xB\xC\xE Bl\x1Fo\x7fg")
7
+
8
+ expect(extractor.value_for(blog)).to eq "Test Blog"
9
+ end
10
+
11
+ it "removes special characters from arrays" do
12
+ extractor = Sunspot::DataExtractor::BlockExtractor.new { tags }
13
+ post = Post.new(:tags => ["Te\x0\x1\x7\x6\x8st Ta\x1Fg\x7f 1", "Test\xB\xC\xE Tag 2"])
14
+
15
+ expect(extractor.value_for(post)).to eq ["Test Tag 1", "Test Tag 2"]
16
+ end
17
+
18
+ it "removes special characters from hashes" do
19
+ extractor = Sunspot::DataExtractor::Constant.new({ "Te\x0\x1\x7\x6\x8st" => "Ta\x1Fg\x7f" })
20
+
21
+ expect(extractor.value_for(Post.new)).to eq({ "Test" => "Tag" })
22
+ end
23
+
24
+ it "skips other data types" do
25
+ [
26
+ :"Te\x0\x1\x7\x6\x8st",
27
+ 123,
28
+ 123.0,
29
+ nil,
30
+ false,
31
+ true,
32
+ Sunspot::Util::Coordinates.new(40.7, -73.5)
33
+ ].each do |value|
34
+ extractor = Sunspot::DataExtractor::Constant.new(value)
35
+
36
+ expect(extractor.value_for(Post.new)).to eq value
37
+ end
38
+ end
39
+ end
@@ -418,11 +418,9 @@ shared_examples_for 'fulltext query' do
418
418
 
419
419
  obj_id = find_ob_id(srch)
420
420
  q_name = "qPhoto#{obj_id}"
421
- fq_name = "f#{q_name}"
422
421
 
423
- expect(connection.searches.last[:q]).to eq "(_query_:\"{!join from=photo_container_id_i to=id_i v=$#{q_name} fq=$#{fq_name}}\" OR _query_:\"{!edismax qf='description_text^1.2'}keyword2\")"
424
- expect(connection.searches.last[q_name]).to eq "_query_:\"{!edismax qf='caption_text'}keyword1\""
425
- expect(connection.searches.last[fq_name]).to eq "type:Photo"
422
+ expect(connection.searches.last[:q]).to eq "(_query_:\"{!join from=photo_container_id_i to=id_i v=$#{q_name}}\" OR _query_:\"{!edismax qf='description_text^1.2'}keyword2\")"
423
+ expect(connection.searches.last[q_name]).to eq "_query_:\"{!field f=type}Photo\"+_query_:\"{!edismax qf='caption_text'}keyword1\""
426
424
  end
427
425
 
428
426
  it "should be able to resolve name conflicts with the :prefix option" do
@@ -435,11 +433,9 @@ shared_examples_for 'fulltext query' do
435
433
 
436
434
  obj_id = find_ob_id(srch)
437
435
  q_name = "qPhoto#{obj_id}"
438
- fq_name = "f#{q_name}"
439
436
 
440
- expect(connection.searches.last[:q]).to eq "(_query_:\"{!edismax qf='description_text^1.2'}keyword1\" OR _query_:\"{!join from=photo_container_id_i to=id_i v=$#{q_name} fq=$#{fq_name}}\")"
441
- expect(connection.searches.last[q_name]).to eq "_query_:\"{!edismax qf='description_text'}keyword2\""
442
- expect(connection.searches.last[fq_name]).to eq "type:Photo"
437
+ expect(connection.searches.last[:q]).to eq "(_query_:\"{!edismax qf='description_text^1.2'}keyword1\" OR _query_:\"{!join from=photo_container_id_i to=id_i v=$#{q_name}}\")"
438
+ expect(connection.searches.last[q_name]).to eq "_query_:\"{!field f=type}Photo\"+_query_:\"{!edismax qf='description_text'}keyword2\""
443
439
  end
444
440
 
445
441
  it "should recognize fields when adding from DSL, e.g. when calling boost_fields" do
@@ -453,11 +449,9 @@ shared_examples_for 'fulltext query' do
453
449
 
454
450
  obj_id = find_ob_id(srch)
455
451
  q_name = "qPhoto#{obj_id}"
456
- fq_name = "f#{q_name}"
457
452
 
458
- expect(connection.searches.last[:q]).to eq "(_query_:\"{!edismax qf='description_text^1.5'}keyword1\" OR _query_:\"{!join from=photo_container_id_i to=id_i v=$#{q_name} fq=$#{fq_name}}\")"
459
- expect(connection.searches.last[q_name]).to eq "_query_:\"{!edismax qf='description_text^1.3'}keyword1\""
460
- expect(connection.searches.last[fq_name]).to eq "type:Photo"
453
+ expect(connection.searches.last[:q]).to eq "(_query_:\"{!edismax qf='description_text^1.5'}keyword1\" OR _query_:\"{!join from=photo_container_id_i to=id_i v=$#{q_name}}\")"
454
+ expect(connection.searches.last[q_name]).to eq "_query_:\"{!field f=type}Photo\"+_query_:\"{!edismax qf='description_text^1.3'}keyword1\""
461
455
  end
462
456
 
463
457
  private
@@ -94,6 +94,25 @@ describe "field grouping" do
94
94
  expect(search.group(:title).groups.length).to eql(1)
95
95
  expect(search.group(:title).groups.first.results).to eq([ @posts.last ])
96
96
  end
97
+
98
+ context "returns a not paginated collection" do
99
+ subject do
100
+ search = Sunspot.search(Post) do
101
+ group :title do
102
+ ngroups false
103
+ end
104
+ paginate :per_page => 1, :page => 2
105
+
106
+ end
107
+ search.group(:title).groups
108
+ end
109
+
110
+ it { expect(subject.per_page).to eql(1) }
111
+ it { expect(subject.total_pages).to eql(0) }
112
+ it { expect(subject.current_page).to eql(2) }
113
+ it { expect(subject.first_page?).to be(false) }
114
+ it { expect(subject.last_page?).to be(true) }
115
+ end
97
116
 
98
117
  context "returns a paginated collection" do
99
118
  subject do
@@ -26,15 +26,30 @@ describe "geospatial search" do
26
26
  expect(results).not_to include(@post)
27
27
  end
28
28
 
29
+ it "filters out posts in the radius" do
30
+ results = Sunspot.search(Post) {
31
+ without(:coordinates_new).in_radius(32, -68, 1)
32
+ }.results
33
+
34
+ expect(results).not_to include(@post)
35
+ end
36
+
29
37
  it "allows conjunction queries with radius" do
38
+ post = Post.new(:title => "Howdy",
39
+ :coordinates => Sunspot::Util::Coordinates.new(35, -68))
40
+
41
+ Sunspot.index!(post)
42
+
30
43
  results = Sunspot.search(Post) {
31
44
  any_of do
32
45
  with(:coordinates_new).in_radius(32, -68, 1)
33
46
  with(:coordinates_new).in_radius(35, 68, 1)
47
+ without(:coordinates_new).in_radius(35, -68, 1)
34
48
  end
35
49
  }.results
36
50
 
37
51
  expect(results).to include(@post)
52
+ expect(results).not_to include(post)
38
53
  end
39
54
 
40
55
  it "allows conjunction queries with bounding box" do
@@ -0,0 +1,45 @@
1
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
+
3
+ describe "searching by joined fields" do
4
+ before :each do
5
+ Sunspot.remove_all!
6
+
7
+ @container1 = PhotoContainer.new(:id => 1)
8
+ @container2 = PhotoContainer.new(:id => 2).tap { |c| allow(c).to receive(:id).and_return(2) }
9
+ @container3 = PhotoContainer.new(:id => 3).tap { |c| allow(c).to receive(:id).and_return(3) }
10
+
11
+ @picture = Picture.new(:photo_container_id => @container1.id, :description => "one")
12
+ @photo1 = Photo.new(:photo_container_id => @container1.id, :description => "two")
13
+ @photo2 = Photo.new(:photo_container_id => @container2.id, :description => "three")
14
+
15
+ Sunspot.index!(@container1, @container2, @photo1, @photo2, @picture)
16
+ end
17
+
18
+ it "matches by joined fields" do
19
+ {
20
+ "one" => [],
21
+ "two" => [@container1],
22
+ "three" => [@container2]
23
+ }.each do |key, res|
24
+ results = Sunspot.search(PhotoContainer) {
25
+ fulltext(key, :fields => [:photo_description])
26
+ }.results
27
+
28
+ expect(results).to eq res
29
+ end
30
+ end
31
+
32
+ it "doesn't match by joined fields with the same name from other collections" do
33
+ {
34
+ "one" => [@container1],
35
+ "two" => [],
36
+ "three" => []
37
+ }.each do |key, res|
38
+ results = Sunspot.search(PhotoContainer) {
39
+ fulltext(key, :fields => [:picture_description])
40
+ }.results
41
+
42
+ expect(results).to eq res
43
+ end
44
+ end
45
+ end
@@ -13,6 +13,15 @@ Sunspot.setup(Photo) do
13
13
  time :created_at, :trie => true
14
14
  end
15
15
 
16
+ class Picture < MockRecord
17
+ attr_accessor :description, :photo_container_id
18
+ end
19
+
20
+ Sunspot.setup(Picture) do
21
+ text :description
22
+ integer :photo_container_id
23
+ end
24
+
16
25
  class PhotoContainer < MockRecord
17
26
  attr_accessor :description
18
27
 
@@ -25,8 +34,9 @@ Sunspot.setup(PhotoContainer) do
25
34
  integer :id
26
35
  text :description, :default_boost => 1.2
27
36
 
28
- join(:caption, :target => Photo, :type => :string, :join => { :from => :photo_container_id, :to => :id })
29
- join(:photo_rating, :target => Photo, :type => :trie_float, :join => { :from => :photo_container_id, :to => :id }, :as => 'average_rating_ft')
30
- join(:caption, :target => Photo, :type => :text, :join => { :from => :photo_container_id, :to => :id })
31
- join(:description, :prefix => "photo", :target => Photo, :type => :text, :join => { :from => :photo_container_id, :to => :id })
37
+ join(:caption, :target => Photo, :type => :string, :join => { :from => :photo_container_id, :to => :id })
38
+ join(:photo_rating, :target => Photo, :type => :trie_float, :join => { :from => :photo_container_id, :to => :id }, :as => 'average_rating_ft')
39
+ join(:caption, :target => Photo, :type => :text, :join => { :from => :photo_container_id, :to => :id })
40
+ join(:description, :target => Photo, :type => :text, :join => { :from => :photo_container_id, :to => :id }, :prefix => "photo")
41
+ join(:description, :target => Picture, :type => :text, :join => { :from => :photo_container_id, :to => :id }, :prefix => "picture")
32
42
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sunspot
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mat Brown
@@ -30,7 +30,7 @@ authors:
30
30
  autorequire:
31
31
  bindir: bin
32
32
  cert_chain: []
33
- date: 2018-04-08 00:00:00.000000000 Z
33
+ date: 2019-07-05 00:00:00.000000000 Z
34
34
  dependencies:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: rsolr
@@ -126,7 +126,6 @@ files:
126
126
  - LICENSE
127
127
  - Rakefile
128
128
  - TODO
129
- - gemfiles/.gitkeep
130
129
  - lib/light_config.rb
131
130
  - lib/sunspot.rb
132
131
  - lib/sunspot/adapters.rb
@@ -240,6 +239,7 @@ files:
240
239
  - spec/api/batcher_spec.rb
241
240
  - spec/api/binding_spec.rb
242
241
  - spec/api/class_set_spec.rb
242
+ - spec/api/data_extractor_spec.rb
243
243
  - spec/api/hit_enumerable_spec.rb
244
244
  - spec/api/indexer/attributes_spec.rb
245
245
  - spec/api/indexer/batch_spec.rb
@@ -305,6 +305,7 @@ files:
305
305
  - spec/integration/geospatial_spec.rb
306
306
  - spec/integration/highlighting_spec.rb
307
307
  - spec/integration/indexing_spec.rb
308
+ - spec/integration/join_spec.rb
308
309
  - spec/integration/keyword_search_spec.rb
309
310
  - spec/integration/local_search_spec.rb
310
311
  - spec/integration/more_like_this_spec.rb
@@ -355,8 +356,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
355
356
  - !ruby/object:Gem::Version
356
357
  version: '0'
357
358
  requirements: []
358
- rubyforge_project: sunspot
359
- rubygems_version: 2.7.6
359
+ rubygems_version: 3.0.2
360
360
  signing_key:
361
361
  specification_version: 4
362
362
  summary: Library for expressive, powerful interaction with the Solr search engine
@@ -365,6 +365,7 @@ test_files:
365
365
  - spec/api/batcher_spec.rb
366
366
  - spec/api/binding_spec.rb
367
367
  - spec/api/class_set_spec.rb
368
+ - spec/api/data_extractor_spec.rb
368
369
  - spec/api/hit_enumerable_spec.rb
369
370
  - spec/api/indexer/attributes_spec.rb
370
371
  - spec/api/indexer/batch_spec.rb
@@ -430,6 +431,7 @@ test_files:
430
431
  - spec/integration/geospatial_spec.rb
431
432
  - spec/integration/highlighting_spec.rb
432
433
  - spec/integration/indexing_spec.rb
434
+ - spec/integration/join_spec.rb
433
435
  - spec/integration/keyword_search_spec.rb
434
436
  - spec/integration/local_search_spec.rb
435
437
  - spec/integration/more_like_this_spec.rb
File without changes