sunspot 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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