sunspot_stats 0.0.2 → 0.0.3
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.
- data/README.md +5 -0
- data/lib/dsl/field_query.rb +1 -1
- data/lib/query/field_stat.rb +1 -1
- data/lib/sunspot_stats/version.rb +1 -1
- data/spec/api/query/spec_helper.rb +1 -0
- data/spec/api/query/stats_spec.rb +14 -0
- data/spec/api/search/spec_helper.rb +1 -0
- data/spec/api/search/stats_spec.rb +13 -0
- data/spec/api/spec_helper.rb +3 -0
- data/spec/ext.rb +11 -0
- data/spec/helpers/indexer_helper.rb +17 -0
- data/spec/helpers/integration_helper.rb +8 -0
- data/spec/helpers/mock_session_helper.rb +13 -0
- data/spec/helpers/query_helper.rb +26 -0
- data/spec/helpers/search_helper.rb +55 -0
- data/spec/mocks/adapters.rb +32 -0
- data/spec/mocks/connection.rb +126 -0
- data/spec/mocks/content.rb +12 -0
- data/spec/mocks/mock_adapter.rb +30 -0
- data/spec/mocks/mock_record.rb +52 -0
- data/spec/mocks/super_class.rb +2 -0
- data/spec/spec_helper.rb +41 -0
- metadata +40 -5
data/README.md
CHANGED
data/lib/dsl/field_query.rb
CHANGED
@@ -13,7 +13,7 @@ module Sunspot
|
|
13
13
|
|
14
14
|
def stat(field_name, options = {})
|
15
15
|
field = @setup.field(field_name)
|
16
|
-
options[:facet] = @setup.field(options[:facet]) if options[:facet].
|
16
|
+
options[:facet] = @setup.field(options[:facet]) if !options[:facet].nil?
|
17
17
|
stat = @query.add_stat(Sunspot::Query::FieldStat.new(field, options))
|
18
18
|
@search.add_field_stat(field, options)
|
19
19
|
end
|
data/lib/query/field_stat.rb
CHANGED
@@ -23,7 +23,7 @@ module Sunspot
|
|
23
23
|
:stats => 'true',
|
24
24
|
:"stats.field" => @field.indexed_name
|
25
25
|
}
|
26
|
-
params.merge!({:"stats.facet" => @options[:facet].indexed_name}) if
|
26
|
+
params.merge!({:"stats.facet" => @options[:facet].indexed_name}) if !@options[:facet].nil?
|
27
27
|
params
|
28
28
|
end
|
29
29
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.join(File.dirname(__FILE__), '..'))
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe "stats component" do
|
4
|
+
it "sends stats parameters to solr" do
|
5
|
+
session.search Content do
|
6
|
+
stat :visibility, :facet => :published_at
|
7
|
+
end
|
8
|
+
|
9
|
+
connection.should have_last_search_including(:stats, "true")
|
10
|
+
connection.should have_last_search_including(:"stats.field", "visibility_f")
|
11
|
+
connection.should have_last_search_including(:"stats.facet", "published_at_d")
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.join(File.dirname(__FILE__), '..'))
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe 'stats', :type => :search do
|
4
|
+
it 'returns field name for facet' do
|
5
|
+
#stub_stat(:visibility)
|
6
|
+
result = session.search Content do
|
7
|
+
stat :visibility, :facet => :published_at
|
8
|
+
end
|
9
|
+
|
10
|
+
result.stat(:visibility).field_name.should == :visibility
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
data/spec/ext.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module IndexerHelper
|
2
|
+
def post(attrs = {})
|
3
|
+
@post ||= Post.new(attrs)
|
4
|
+
end
|
5
|
+
|
6
|
+
def last_add
|
7
|
+
@connection.adds.last
|
8
|
+
end
|
9
|
+
|
10
|
+
def value_in_last_document_for(field_name)
|
11
|
+
@connection.adds.last.last.field_by_name(field_name).value
|
12
|
+
end
|
13
|
+
|
14
|
+
def values_in_last_document_for(field_name)
|
15
|
+
@connection.adds.last.last.fields_by_name(field_name).map { |field| field.value }
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module QueryHelper
|
2
|
+
def get_filter_tag(boolean_query)
|
3
|
+
connection.searches.last[:fq].each do |fq|
|
4
|
+
if match = fq.match(/^\{!tag=(.+)\}#{Regexp.escape(boolean_query)}$/)
|
5
|
+
return match[1]
|
6
|
+
end
|
7
|
+
end
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def subqueries(param)
|
12
|
+
q = connection.searches.last[:q]
|
13
|
+
subqueries = []
|
14
|
+
subqueries = q.scan(%r(_query_:"\{!dismax (.*?)\}(.*?)"))
|
15
|
+
subqueries.map do |subquery|
|
16
|
+
params = {}
|
17
|
+
subquery[0].scan(%r((\S+?)='(.+?)')) do |key, value|
|
18
|
+
params[key.to_sym] = value
|
19
|
+
end
|
20
|
+
unless subquery[1].empty?
|
21
|
+
params[:v] = subquery[1]
|
22
|
+
end
|
23
|
+
params
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module SearchHelper
|
2
|
+
def stub_nil_results
|
3
|
+
connection.response = { 'response' => nil }
|
4
|
+
end
|
5
|
+
|
6
|
+
def stub_full_results(*results)
|
7
|
+
count =
|
8
|
+
if results.last.is_a?(Integer) then results.pop
|
9
|
+
else results.length
|
10
|
+
end
|
11
|
+
docs = results.map do |result|
|
12
|
+
instance = result.delete('instance')
|
13
|
+
result.merge('id' => "#{instance.class.name} #{instance.id}")
|
14
|
+
end
|
15
|
+
response = {
|
16
|
+
'response' => {
|
17
|
+
'docs' => docs,
|
18
|
+
'numFound' => count
|
19
|
+
}
|
20
|
+
}
|
21
|
+
connection.response = response
|
22
|
+
response
|
23
|
+
end
|
24
|
+
|
25
|
+
def stub_results(*results)
|
26
|
+
stub_full_results(
|
27
|
+
*results.map do |result|
|
28
|
+
if result.is_a?(Integer)
|
29
|
+
result
|
30
|
+
else
|
31
|
+
{ 'instance' => result }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def stub_stat(name, values)
|
38
|
+
connection.response = {
|
39
|
+
'facet_counts' => {
|
40
|
+
'facet_fields' => {
|
41
|
+
name.to_s => values.to_a.sort_by { |value, count| -count }.flatten
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def stat_field_name(result, field_name)
|
48
|
+
result.stat(field_name).rows.map { |row| row.field_name }
|
49
|
+
end
|
50
|
+
|
51
|
+
def values(result, field_name)
|
52
|
+
result.stat(field_name).rows.map { |row| row.value }
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class AbstractModel
|
2
|
+
end
|
3
|
+
|
4
|
+
class Model < AbstractModel
|
5
|
+
end
|
6
|
+
|
7
|
+
class AbstractModelInstanceAdapter < Sunspot::Adapters::InstanceAdapter
|
8
|
+
end
|
9
|
+
|
10
|
+
class AbstractModelDataAccessor < Sunspot::Adapters::DataAccessor
|
11
|
+
end
|
12
|
+
|
13
|
+
Sunspot::Adapters::InstanceAdapter.register(AbstractModelInstanceAdapter, AbstractModel)
|
14
|
+
Sunspot::Adapters::DataAccessor.register(AbstractModelDataAccessor, AbstractModel)
|
15
|
+
|
16
|
+
|
17
|
+
module MixInModel
|
18
|
+
end
|
19
|
+
|
20
|
+
class MixModel
|
21
|
+
include MixInModel
|
22
|
+
end
|
23
|
+
|
24
|
+
class MixInModelInstanceAdapter < Sunspot::Adapters::InstanceAdapter
|
25
|
+
end
|
26
|
+
|
27
|
+
class MixInModelDataAccessor < Sunspot::Adapters::DataAccessor
|
28
|
+
end
|
29
|
+
|
30
|
+
Sunspot::Adapters::InstanceAdapter.register(MixInModelInstanceAdapter, MixInModel)
|
31
|
+
Sunspot::Adapters::DataAccessor.register(MixInModelDataAccessor, MixInModel)
|
32
|
+
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Mock
|
2
|
+
class ConnectionFactory
|
3
|
+
def connect(opts)
|
4
|
+
if @instance
|
5
|
+
raise('Factory can only create an instance once!')
|
6
|
+
else
|
7
|
+
@instance = Connection.new(opts)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def new(url = nil)
|
12
|
+
if @instance
|
13
|
+
raise('Factory can only create an instance once!')
|
14
|
+
else
|
15
|
+
@instance = Connection.new(url)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def instance
|
20
|
+
@instance ||= Connection.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Connection
|
25
|
+
attr_reader :adds, :commits, :optims, :searches, :message, :opts, :deletes_by_query
|
26
|
+
attr_accessor :response
|
27
|
+
attr_writer :expected_handler
|
28
|
+
undef_method :select # annoyingly defined on Object
|
29
|
+
|
30
|
+
def initialize(opts = {})
|
31
|
+
@opts = opts
|
32
|
+
@message = OpenStruct.new
|
33
|
+
@adds, @deletes, @deletes_by_query, @commits, @optims, @searches = Array.new(6) { [] }
|
34
|
+
@expected_handler = :select
|
35
|
+
end
|
36
|
+
|
37
|
+
def add(documents)
|
38
|
+
@adds << Array(documents)
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_by_id(ids)
|
42
|
+
@deletes << Array(ids)
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete_by_query(query)
|
46
|
+
@deletes_by_query << query
|
47
|
+
end
|
48
|
+
|
49
|
+
def commit
|
50
|
+
@commits << Time.now
|
51
|
+
end
|
52
|
+
|
53
|
+
def optimize
|
54
|
+
@optims << Time.now
|
55
|
+
end
|
56
|
+
|
57
|
+
def post(path, params)
|
58
|
+
unless path == "#{@expected_handler}"
|
59
|
+
raise ArgumentError, "Expected request to #{@expected_handler} request handler"
|
60
|
+
end
|
61
|
+
@searches << @last_search = params[:data]
|
62
|
+
@response || {}
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing(method, *args, &block)
|
66
|
+
get("#{method}", *args)
|
67
|
+
end
|
68
|
+
|
69
|
+
def has_add_with?(*documents)
|
70
|
+
@adds.any? do |add|
|
71
|
+
documents.all? do |document|
|
72
|
+
add.any? do |added|
|
73
|
+
if document.is_a?(Hash)
|
74
|
+
document.all? do |field, value|
|
75
|
+
added.fields_by_name(field).map do |field|
|
76
|
+
field.value.to_s
|
77
|
+
end == Array(value).map { |v| v.to_s }
|
78
|
+
end
|
79
|
+
else
|
80
|
+
!added.fields_by_name(document).empty?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def has_delete?(*ids)
|
88
|
+
@deletes.any? do |delete|
|
89
|
+
delete & ids == ids
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def has_delete_by_query?(query)
|
94
|
+
@deletes_by_query.include?(query)
|
95
|
+
end
|
96
|
+
|
97
|
+
def has_last_search_with?(params)
|
98
|
+
with?(@last_search, params) if @last_search
|
99
|
+
end
|
100
|
+
|
101
|
+
def has_last_search_including?(key, *values)
|
102
|
+
return unless @last_search
|
103
|
+
if @last_search.has_key?(key)
|
104
|
+
if @last_search[key].is_a?(Array)
|
105
|
+
(@last_search[key] & values).length == values.length
|
106
|
+
elsif values.length == 1
|
107
|
+
@last_search[key] == values.first
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def with?(request, params)
|
115
|
+
if params.respond_to?(:all?)
|
116
|
+
params.all? do |key, value|
|
117
|
+
if request.has_key?(key)
|
118
|
+
request[key] == value
|
119
|
+
end
|
120
|
+
end
|
121
|
+
else
|
122
|
+
request.has_key?(params)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'content')
|
2
|
+
|
3
|
+
module MockAdapter
|
4
|
+
class InstanceAdapter < Sunspot::Adapters::InstanceAdapter
|
5
|
+
def id
|
6
|
+
@instance.id
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class DataAccessor < Sunspot::Adapters::DataAccessor
|
11
|
+
def load(id)
|
12
|
+
@clazz.get(id.to_i)
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_all(ids)
|
16
|
+
all = @clazz.get_all(ids.map { |id| id.to_i })
|
17
|
+
if @custom_title
|
18
|
+
all.each { |item| item.title = @custom_title }
|
19
|
+
end
|
20
|
+
all
|
21
|
+
end
|
22
|
+
|
23
|
+
def custom_title=(custom_title)
|
24
|
+
@custom_title = custom_title
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Sunspot::Adapters::DataAccessor.register(MockAdapter::DataAccessor, MockRecord)
|
30
|
+
Sunspot::Adapters::InstanceAdapter.register(MockAdapter::InstanceAdapter, MockRecord)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class MockRecord
|
2
|
+
IDS = Hash.new { |h, k| h[k] = 0 }
|
3
|
+
QUERY_COUNTS = Hash.new { |h, k| h[k] = 0 }
|
4
|
+
INSTANCES = Hash.new { |h, k| h[k] = {} }
|
5
|
+
|
6
|
+
attr_reader :id
|
7
|
+
|
8
|
+
class <<self
|
9
|
+
def reset!
|
10
|
+
IDS[name.to_sym] = 0
|
11
|
+
INSTANCES[name.to_sym] = {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(attrs = {})
|
16
|
+
@id = attrs.delete(:id) || IDS[self.class.name.to_sym] += 1
|
17
|
+
INSTANCES[self.class.name.to_sym][@id] = self
|
18
|
+
attrs.each_pair do |name, value|
|
19
|
+
send(:"#{name}=", value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.inherited(base)
|
24
|
+
base.extend(ClassMethods)
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
def get(id)
|
29
|
+
QUERY_COUNTS[self.name.to_sym] += 1
|
30
|
+
get_instance(id)
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_all(ids)
|
34
|
+
QUERY_COUNTS[self.name.to_sym] += 1
|
35
|
+
ids.map { |id| get_instance(id) }.compact.sort_by { |instance| instance.id }
|
36
|
+
end
|
37
|
+
|
38
|
+
def query_count
|
39
|
+
QUERY_COUNTS[self.name.to_sym]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def get_instance(id)
|
45
|
+
INSTANCES[self.name.to_sym][id]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def destroy
|
50
|
+
INSTANCES[self.class.name.to_sym].delete(@id)
|
51
|
+
end
|
52
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require 'sunspot'
|
5
|
+
require 'sunspot_stats'
|
6
|
+
|
7
|
+
require File.join(File.dirname(__FILE__), 'mocks', 'mock_record.rb')
|
8
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'mocks', '**', '*.rb')).each do |file|
|
9
|
+
require file unless File.basename(file) == 'mock_record.rb'
|
10
|
+
end
|
11
|
+
Dir.glob(File.join(File.dirname(__FILE__), "helpers", "*.rb")).each do |file|
|
12
|
+
require file
|
13
|
+
end
|
14
|
+
require File.join(File.dirname(__FILE__), 'ext')
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
# Mock session available to all spec/api tests
|
18
|
+
config.include MockSessionHelper,
|
19
|
+
:type => :api,
|
20
|
+
:example_group => {:file_path => /spec[\\\/]api/}
|
21
|
+
|
22
|
+
# Real Solr instance is available to integration tests
|
23
|
+
config.include IntegrationHelper,
|
24
|
+
:type => :integration,
|
25
|
+
:example_group => {:file_path => /spec[\\\/]integration/}
|
26
|
+
|
27
|
+
# Nested under spec/api
|
28
|
+
[:indexer, :query, :search].each do |spec_type|
|
29
|
+
helper_name = "#{spec_type}_helper"
|
30
|
+
|
31
|
+
config.include Sunspot::Util.full_const_get(Sunspot::Util.camel_case(helper_name)),
|
32
|
+
:type => spec_type,
|
33
|
+
:example_group => {:file_path => /spec[\\\/]api[\\\/]#{spec_type}/}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def without_class(clazz)
|
38
|
+
Object.class_eval { remove_const(clazz.name.to_sym) }
|
39
|
+
yield
|
40
|
+
Object.class_eval { const_set(clazz.name.to_sym, clazz) }
|
41
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 3
|
9
|
+
version: 0.0.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- duccio giovannelli
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-07-
|
17
|
+
date: 2012-07-16 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -69,6 +69,24 @@ files:
|
|
69
69
|
- lib/search/stat_search.rb
|
70
70
|
- lib/sunspot_stats.rb
|
71
71
|
- lib/sunspot_stats/version.rb
|
72
|
+
- spec/api/query/spec_helper.rb
|
73
|
+
- spec/api/query/stats_spec.rb
|
74
|
+
- spec/api/search/spec_helper.rb
|
75
|
+
- spec/api/search/stats_spec.rb
|
76
|
+
- spec/api/spec_helper.rb
|
77
|
+
- spec/ext.rb
|
78
|
+
- spec/helpers/indexer_helper.rb
|
79
|
+
- spec/helpers/integration_helper.rb
|
80
|
+
- spec/helpers/mock_session_helper.rb
|
81
|
+
- spec/helpers/query_helper.rb
|
82
|
+
- spec/helpers/search_helper.rb
|
83
|
+
- spec/mocks/adapters.rb
|
84
|
+
- spec/mocks/connection.rb
|
85
|
+
- spec/mocks/content.rb
|
86
|
+
- spec/mocks/mock_adapter.rb
|
87
|
+
- spec/mocks/mock_record.rb
|
88
|
+
- spec/mocks/super_class.rb
|
89
|
+
- spec/spec_helper.rb
|
72
90
|
- sunspot_stats.gemspec
|
73
91
|
has_rdoc: true
|
74
92
|
homepage: https://github.com/giovannelli/sunspot_stats
|
@@ -104,5 +122,22 @@ rubygems_version: 1.3.6
|
|
104
122
|
signing_key:
|
105
123
|
specification_version: 3
|
106
124
|
summary: Added the statsComponent to sunspot
|
107
|
-
test_files:
|
108
|
-
|
125
|
+
test_files:
|
126
|
+
- spec/api/query/spec_helper.rb
|
127
|
+
- spec/api/query/stats_spec.rb
|
128
|
+
- spec/api/search/spec_helper.rb
|
129
|
+
- spec/api/search/stats_spec.rb
|
130
|
+
- spec/api/spec_helper.rb
|
131
|
+
- spec/ext.rb
|
132
|
+
- spec/helpers/indexer_helper.rb
|
133
|
+
- spec/helpers/integration_helper.rb
|
134
|
+
- spec/helpers/mock_session_helper.rb
|
135
|
+
- spec/helpers/query_helper.rb
|
136
|
+
- spec/helpers/search_helper.rb
|
137
|
+
- spec/mocks/adapters.rb
|
138
|
+
- spec/mocks/connection.rb
|
139
|
+
- spec/mocks/content.rb
|
140
|
+
- spec/mocks/mock_adapter.rb
|
141
|
+
- spec/mocks/mock_record.rb
|
142
|
+
- spec/mocks/super_class.rb
|
143
|
+
- spec/spec_helper.rb
|