sunspot 2.0.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/Appraisals +7 -0
- data/Gemfile +0 -2
- data/History.txt +10 -0
- data/lib/sunspot.rb +55 -17
- data/lib/sunspot/adapters.rb +68 -18
- data/lib/sunspot/batcher.rb +1 -1
- data/lib/sunspot/configuration.rb +4 -2
- data/lib/sunspot/data_extractor.rb +36 -6
- data/lib/sunspot/dsl.rb +4 -3
- data/lib/sunspot/dsl/adjustable.rb +2 -2
- data/lib/sunspot/dsl/field_query.rb +69 -16
- data/lib/sunspot/dsl/field_stats.rb +25 -0
- data/lib/sunspot/dsl/fields.rb +28 -8
- data/lib/sunspot/dsl/fulltext.rb +9 -1
- data/lib/sunspot/dsl/group.rb +118 -0
- data/lib/sunspot/dsl/paginatable.rb +4 -1
- data/lib/sunspot/dsl/scope.rb +19 -10
- data/lib/sunspot/dsl/search.rb +1 -1
- data/lib/sunspot/dsl/spellcheckable.rb +14 -0
- data/lib/sunspot/dsl/standard_query.rb +63 -35
- data/lib/sunspot/field.rb +76 -4
- data/lib/sunspot/field_factory.rb +60 -11
- data/lib/sunspot/indexer.rb +70 -18
- data/lib/sunspot/query.rb +5 -4
- data/lib/sunspot/query/abstract_field_facet.rb +0 -2
- data/lib/sunspot/query/abstract_fulltext.rb +76 -0
- data/lib/sunspot/query/abstract_json_field_facet.rb +70 -0
- data/lib/sunspot/query/bbox.rb +5 -1
- data/lib/sunspot/query/common_query.rb +31 -6
- data/lib/sunspot/query/composite_fulltext.rb +58 -8
- data/lib/sunspot/query/date_field_json_facet.rb +25 -0
- data/lib/sunspot/query/dismax.rb +25 -71
- data/lib/sunspot/query/field_json_facet.rb +19 -0
- data/lib/sunspot/query/field_list.rb +15 -0
- data/lib/sunspot/query/field_stats.rb +61 -0
- data/lib/sunspot/query/function_query.rb +1 -2
- data/lib/sunspot/query/geo.rb +1 -1
- data/lib/sunspot/query/geofilt.rb +8 -3
- data/lib/sunspot/query/group.rb +46 -0
- data/lib/sunspot/query/group_query.rb +17 -0
- data/lib/sunspot/query/join.rb +88 -0
- data/lib/sunspot/query/more_like_this.rb +1 -1
- data/lib/sunspot/query/pagination.rb +12 -4
- data/lib/sunspot/query/range_json_facet.rb +28 -0
- data/lib/sunspot/query/restriction.rb +99 -13
- data/lib/sunspot/query/sort.rb +41 -0
- data/lib/sunspot/query/sort_composite.rb +7 -0
- data/lib/sunspot/query/spellcheck.rb +19 -0
- data/lib/sunspot/query/standard_query.rb +24 -2
- data/lib/sunspot/query/text_field_boost.rb +1 -3
- data/lib/sunspot/schema.rb +12 -3
- data/lib/sunspot/search.rb +4 -2
- data/lib/sunspot/search/abstract_search.rb +93 -43
- data/lib/sunspot/search/cursor_paginated_collection.rb +32 -0
- data/lib/sunspot/search/field_facet.rb +4 -4
- data/lib/sunspot/search/field_json_facet.rb +33 -0
- data/lib/sunspot/search/field_stats.rb +21 -0
- data/lib/sunspot/search/hit.rb +6 -1
- data/lib/sunspot/search/hit_enumerable.rb +4 -1
- data/lib/sunspot/search/json_facet_row.rb +40 -0
- data/lib/sunspot/search/json_facet_stats.rb +23 -0
- data/lib/sunspot/search/paginated_collection.rb +1 -0
- data/lib/sunspot/search/query_group.rb +74 -0
- data/lib/sunspot/search/standard_search.rb +70 -3
- data/lib/sunspot/search/stats_facet.rb +25 -0
- data/lib/sunspot/search/stats_json_row.rb +82 -0
- data/lib/sunspot/search/stats_row.rb +68 -0
- data/lib/sunspot/session.rb +62 -37
- data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +6 -4
- data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +16 -8
- data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +2 -2
- data/lib/sunspot/session_proxy/retry_5xx_session_proxy.rb +1 -1
- data/lib/sunspot/session_proxy/sharding_session_proxy.rb +4 -2
- data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +1 -1
- data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +6 -4
- data/lib/sunspot/setup.rb +42 -0
- data/lib/sunspot/type.rb +20 -0
- data/lib/sunspot/util.rb +78 -14
- data/lib/sunspot/version.rb +1 -1
- data/spec/api/adapters_spec.rb +40 -15
- data/spec/api/batcher_spec.rb +15 -15
- data/spec/api/binding_spec.rb +3 -3
- data/spec/api/class_set_spec.rb +6 -6
- data/spec/api/data_extractor_spec.rb +39 -0
- data/spec/api/hit_enumerable_spec.rb +32 -9
- data/spec/api/indexer/attributes_spec.rb +35 -30
- data/spec/api/indexer/batch_spec.rb +8 -7
- data/spec/api/indexer/dynamic_fields_spec.rb +8 -8
- data/spec/api/indexer/fixed_fields_spec.rb +16 -11
- data/spec/api/indexer/fulltext_spec.rb +8 -8
- data/spec/api/indexer/removal_spec.rb +24 -14
- data/spec/api/indexer_spec.rb +2 -2
- data/spec/api/query/advanced_manipulation_examples.rb +3 -3
- data/spec/api/query/connectives_examples.rb +26 -14
- data/spec/api/query/dsl_spec.rb +24 -6
- data/spec/api/query/dynamic_fields_examples.rb +18 -18
- data/spec/api/query/faceting_examples.rb +80 -61
- data/spec/api/query/fulltext_examples.rb +194 -40
- data/spec/api/query/function_spec.rb +116 -13
- data/spec/api/query/geo_examples.rb +8 -12
- data/spec/api/query/group_spec.rb +27 -5
- data/spec/api/query/highlighting_examples.rb +26 -26
- data/spec/api/query/join_spec.rb +19 -0
- data/spec/api/query/more_like_this_spec.rb +40 -27
- data/spec/api/query/ordering_pagination_examples.rb +37 -23
- data/spec/api/query/scope_examples.rb +39 -39
- data/spec/api/query/spatial_examples.rb +3 -3
- data/spec/api/query/spellcheck_examples.rb +20 -0
- data/spec/api/query/standard_spec.rb +3 -1
- data/spec/api/query/stats_examples.rb +66 -0
- data/spec/api/query/text_field_scoping_examples.rb +5 -5
- data/spec/api/query/types_spec.rb +4 -4
- data/spec/api/search/cursor_paginated_collection_spec.rb +35 -0
- data/spec/api/search/dynamic_fields_spec.rb +4 -4
- data/spec/api/search/faceting_spec.rb +55 -52
- data/spec/api/search/highlighting_spec.rb +7 -7
- data/spec/api/search/hits_spec.rb +43 -29
- data/spec/api/search/paginated_collection_spec.rb +19 -18
- data/spec/api/search/results_spec.rb +13 -13
- data/spec/api/search/search_spec.rb +3 -3
- data/spec/api/search/stats_spec.rb +94 -0
- data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +23 -16
- data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +16 -4
- data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +10 -6
- data/spec/api/session_proxy/retry_5xx_session_proxy_spec.rb +11 -11
- data/spec/api/session_proxy/sharding_session_proxy_spec.rb +15 -14
- data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +3 -3
- data/spec/api/session_proxy/spec_helper.rb +1 -1
- data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +40 -26
- data/spec/api/session_spec.rb +78 -38
- data/spec/api/sunspot_spec.rb +7 -4
- data/spec/helpers/integration_helper.rb +11 -1
- data/spec/helpers/query_helper.rb +1 -1
- data/spec/helpers/search_helper.rb +30 -0
- data/spec/integration/atomic_updates_spec.rb +58 -0
- data/spec/integration/dynamic_fields_spec.rb +31 -20
- data/spec/integration/faceting_spec.rb +252 -39
- data/spec/integration/field_grouping_spec.rb +47 -15
- data/spec/integration/field_lists_spec.rb +57 -0
- data/spec/integration/geospatial_spec.rb +34 -8
- data/spec/integration/highlighting_spec.rb +8 -8
- data/spec/integration/indexing_spec.rb +7 -6
- data/spec/integration/join_spec.rb +45 -0
- data/spec/integration/keyword_search_spec.rb +68 -38
- data/spec/integration/local_search_spec.rb +4 -4
- data/spec/integration/more_like_this_spec.rb +7 -7
- data/spec/integration/scoped_search_spec.rb +193 -74
- data/spec/integration/spellcheck_spec.rb +119 -0
- data/spec/integration/stats_spec.rb +88 -0
- data/spec/integration/stored_fields_spec.rb +1 -1
- data/spec/integration/test_pagination.rb +4 -4
- data/spec/integration/unicode_spec.rb +1 -1
- data/spec/mocks/adapters.rb +36 -0
- data/spec/mocks/connection.rb +5 -3
- data/spec/mocks/photo.rb +32 -1
- data/spec/mocks/post.rb +18 -3
- data/spec/spec_helper.rb +13 -8
- data/sunspot.gemspec +6 -4
- data/tasks/rdoc.rake +22 -14
- metadata +101 -44
- data/lib/sunspot/dsl/field_group.rb +0 -57
- data/lib/sunspot/query/field_group.rb +0 -37
data/lib/sunspot/dsl/search.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module DSL #:nodoc:
|
3
|
+
module Spellcheckable #:nodoc
|
4
|
+
# Ask Solr to suggest alternative spellings for the query
|
5
|
+
#
|
6
|
+
# ==== Options
|
7
|
+
#
|
8
|
+
# The list of options can be found here: http://wiki.apache.org/solr/SpellCheckComponent
|
9
|
+
def spellcheck(options = {})
|
10
|
+
@query.add_spellcheck(options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -9,7 +9,7 @@ module Sunspot
|
|
9
9
|
# See Sunspot.search for usage examples
|
10
10
|
#
|
11
11
|
class StandardQuery < FieldQuery
|
12
|
-
include Paginatable, Adjustable
|
12
|
+
include Paginatable, Adjustable, Spellcheckable
|
13
13
|
|
14
14
|
# Specify a phrase that should be searched as fulltext. Only +text+
|
15
15
|
# fields are searched - see DSL::Fields.text
|
@@ -56,67 +56,95 @@ module Sunspot
|
|
56
56
|
# a pizza" will not. Default behavior is a query phrase slop of zero.
|
57
57
|
#
|
58
58
|
def fulltext(keywords, options = {}, &block)
|
59
|
-
if keywords
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
if minimum_match = options.delete(:minimum_match)
|
69
|
-
fulltext_query.minimum_match = minimum_match.to_i
|
70
|
-
end
|
71
|
-
if tie = options.delete(:tie)
|
72
|
-
fulltext_query.tie = tie.to_f
|
73
|
-
end
|
74
|
-
if query_phrase_slop = options.delete(:query_phrase_slop)
|
75
|
-
fulltext_query.query_phrase_slop = query_phrase_slop.to_i
|
76
|
-
end
|
59
|
+
return if not keywords or keywords.to_s =~ /^\s*$/
|
60
|
+
|
61
|
+
field_names = Util.Array(options.delete(:fields)).compact
|
62
|
+
|
63
|
+
add_fulltext(keywords, field_names) do |query, fields|
|
64
|
+
query.minimum_match = options.delete(:minimum_match).to_i if options.key?(:minimum_match)
|
65
|
+
query.tie = options.delete(:tie).to_f if options.key?(:tie)
|
66
|
+
query.query_phrase_slop = options.delete(:query_phrase_slop).to_i if options.key?(:query_phrase_slop)
|
67
|
+
|
77
68
|
if highlight_field_names = options.delete(:highlight)
|
78
69
|
if highlight_field_names == true
|
79
|
-
|
70
|
+
query.add_highlight
|
80
71
|
else
|
81
72
|
highlight_fields = []
|
82
73
|
Util.Array(highlight_field_names).each do |field_name|
|
83
74
|
highlight_fields.concat(@setup.text_fields(field_name))
|
84
75
|
end
|
85
|
-
|
76
|
+
query.add_highlight(highlight_fields)
|
86
77
|
end
|
87
78
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
79
|
+
|
80
|
+
if block && query
|
81
|
+
fulltext_dsl = Fulltext.new(query, @setup)
|
82
|
+
Util.instance_eval_or_call(fulltext_dsl, &block)
|
83
|
+
else
|
84
|
+
fulltext_dsl = nil
|
94
85
|
end
|
95
|
-
|
86
|
+
|
87
|
+
if fields.empty? && (!fulltext_dsl || !fulltext_dsl.fields_added?)
|
96
88
|
@setup.all_text_fields.each do |field|
|
97
|
-
unless
|
89
|
+
unless query.has_fulltext_field?(field)
|
98
90
|
unless fulltext_dsl && fulltext_dsl.exclude_fields.include?(field.name)
|
99
|
-
|
91
|
+
query.add_fulltext_field(field, field.default_boost)
|
100
92
|
end
|
101
93
|
end
|
102
94
|
end
|
103
95
|
end
|
104
96
|
end
|
105
97
|
end
|
98
|
+
|
106
99
|
alias_method :keywords, :fulltext
|
107
100
|
|
108
101
|
def with(*args)
|
109
102
|
case args.first
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
103
|
+
when String, Symbol
|
104
|
+
if args.length == 1 # NONE
|
105
|
+
field = @setup.field(args[0].to_sym)
|
106
|
+
return DSL::RestrictionWithNear.new(field, @scope, @query, false)
|
107
|
+
end
|
115
108
|
end
|
116
109
|
|
117
110
|
# else
|
118
111
|
super
|
119
112
|
end
|
113
|
+
|
114
|
+
def any(&block)
|
115
|
+
@query.disjunction do
|
116
|
+
Util.instance_eval_or_call(self, &block)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def all(&block)
|
121
|
+
@query.conjunction do
|
122
|
+
Util.instance_eval_or_call(self, &block)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def add_fulltext(keywords, field_names)
|
129
|
+
return yield(@query.add_fulltext(keywords), []) unless field_names.any?
|
130
|
+
|
131
|
+
all_fields = field_names.map { |name| @setup.text_fields(name) }.flatten
|
132
|
+
all_fields -= join_fields = all_fields.find_all(&:joined?)
|
133
|
+
|
134
|
+
if all_fields.any?
|
135
|
+
fulltext_query = @query.add_fulltext(keywords)
|
136
|
+
all_fields.each { |field| fulltext_query.add_fulltext_field(field, field.default_boost) }
|
137
|
+
yield(fulltext_query, all_fields)
|
138
|
+
end
|
139
|
+
|
140
|
+
if join_fields.any?
|
141
|
+
join_fields.group_by { |field| [field.target, field.from, field.to] }.each_pair do |(target, from, to), fields|
|
142
|
+
join_query = @query.add_join(keywords, target, from, to)
|
143
|
+
fields.each { |field| join_query.add_fulltext_field(field, field.default_boost) }
|
144
|
+
yield(join_query, fields)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
120
148
|
end
|
121
149
|
end
|
122
150
|
end
|
data/lib/sunspot/field.rb
CHANGED
@@ -82,17 +82,46 @@ module Sunspot
|
|
82
82
|
!!@more_like_this
|
83
83
|
end
|
84
84
|
|
85
|
+
#
|
86
|
+
# Whether the field was joined from another model.
|
87
|
+
#
|
88
|
+
# ==== Returns
|
89
|
+
#
|
90
|
+
# Boolean:: True if this field was joined from another model
|
91
|
+
#
|
92
|
+
def joined?
|
93
|
+
!!@joined
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Whether the field is stored or not.
|
98
|
+
#
|
99
|
+
# ==== Returns
|
100
|
+
#
|
101
|
+
# Boolean:: True if this field is a stored field
|
102
|
+
#
|
103
|
+
def stored?
|
104
|
+
!!@stored
|
105
|
+
end
|
106
|
+
|
85
107
|
def hash
|
86
108
|
indexed_name.hash
|
87
109
|
end
|
88
110
|
|
89
111
|
def eql?(field)
|
90
|
-
indexed_name == field.indexed_name
|
112
|
+
field.is_a?(self.class) && indexed_name == field.indexed_name
|
91
113
|
end
|
92
114
|
alias_method :==, :eql?
|
93
115
|
|
94
116
|
private
|
95
117
|
|
118
|
+
#
|
119
|
+
# Raise if an unknown option passed
|
120
|
+
#
|
121
|
+
def check_options(options)
|
122
|
+
raise ArgumentError, "Unknown field option #{options.keys.first.inspect} provided for field #{name.inspect}" unless options.empty?
|
123
|
+
end
|
124
|
+
|
96
125
|
#
|
97
126
|
# Determine the indexed name. If the :as option is given use that, otherwise
|
98
127
|
# create the value based on the indexed_name of the type with additional
|
@@ -107,7 +136,8 @@ module Sunspot
|
|
107
136
|
if options[:as]
|
108
137
|
options.delete(:as).to_s
|
109
138
|
else
|
110
|
-
|
139
|
+
name = options[:prefix] ? @name.to_s.sub(/^#{options[:prefix]}_/, '') : @name
|
140
|
+
"#{@type.indexed_name(name)}#{'m' if multiple? }#{'s' if @stored}#{'v' if more_like_this?}"
|
111
141
|
end
|
112
142
|
end
|
113
143
|
|
@@ -129,7 +159,8 @@ module Sunspot
|
|
129
159
|
@multiple = true
|
130
160
|
@boost = options.delete(:boost)
|
131
161
|
@default_boost = options.delete(:default_boost)
|
132
|
-
|
162
|
+
|
163
|
+
check_options(options)
|
133
164
|
end
|
134
165
|
|
135
166
|
def indexed_name
|
@@ -154,9 +185,50 @@ module Sunspot
|
|
154
185
|
elsif reference.respond_to?(:to_sym)
|
155
186
|
reference.to_sym
|
156
187
|
end
|
157
|
-
|
188
|
+
|
189
|
+
check_options(options)
|
158
190
|
end
|
191
|
+
end
|
192
|
+
|
193
|
+
#
|
194
|
+
# JoinField encapsulates attributes from referenced models.
|
195
|
+
# Could be of any type
|
196
|
+
#
|
197
|
+
class JoinField < Field #:nodoc:
|
198
|
+
attr_reader :default_boost, :target
|
199
|
+
|
200
|
+
def initialize(name, type, options = {})
|
201
|
+
@multiple = !!options.delete(:multiple)
|
202
|
+
|
203
|
+
super(name, type, options)
|
159
204
|
|
205
|
+
@prefix = options.delete(:prefix)
|
206
|
+
@join = options.delete(:join)
|
207
|
+
@clazz = options.delete(:clazz)
|
208
|
+
@target = options.delete(:target)
|
209
|
+
@default_boost = options.delete(:default_boost)
|
210
|
+
@joined = true
|
211
|
+
|
212
|
+
check_options(options)
|
213
|
+
end
|
214
|
+
|
215
|
+
def from
|
216
|
+
Sunspot::Setup.for(@target).field(@join[:from]).indexed_name
|
217
|
+
end
|
218
|
+
|
219
|
+
def to
|
220
|
+
Sunspot::Setup.for(@clazz).field(@join[:to]).indexed_name
|
221
|
+
end
|
222
|
+
|
223
|
+
def local_params
|
224
|
+
"{!join from=#{from} to=#{to}}"
|
225
|
+
end
|
226
|
+
|
227
|
+
def eql?(field)
|
228
|
+
super && target == field.target && from == field.from && to == field.to
|
229
|
+
end
|
230
|
+
|
231
|
+
alias_method :==, :eql?
|
160
232
|
end
|
161
233
|
|
162
234
|
class TypeField #:nodoc:
|
@@ -22,6 +22,23 @@ module Sunspot
|
|
22
22
|
DataExtractor::AttributeExtractor.new(options.delete(:using) || name)
|
23
23
|
end
|
24
24
|
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Extract the encapsulated field's data from the given model and add it
|
28
|
+
# into the Solr document for indexing. (noop here for joins)
|
29
|
+
#
|
30
|
+
def populate_document(document, model, options = {}) #:nodoc:
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def extract_value(model, options = {})
|
36
|
+
if options.has_key?(:value)
|
37
|
+
options.delete(:value)
|
38
|
+
else
|
39
|
+
@data_extractor.value_for(model)
|
40
|
+
end
|
41
|
+
end
|
25
42
|
end
|
26
43
|
|
27
44
|
#
|
@@ -54,15 +71,20 @@ module Sunspot
|
|
54
71
|
# Extract the encapsulated field's data from the given model and add it
|
55
72
|
# into the Solr document for indexing.
|
56
73
|
#
|
57
|
-
def populate_document(document, model) #:nodoc:
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
74
|
+
def populate_document(document, model, options = {}) #:nodoc:
|
75
|
+
atomic_operation = options[:update] == :set
|
76
|
+
value = extract_value(model, options)
|
77
|
+
if value != nil || atomic_operation
|
78
|
+
indexed_values = Util.Array(@field.to_indexed(value))
|
79
|
+
indexed_values = [nil] if indexed_values.empty? && atomic_operation
|
80
|
+
indexed_values.each do |scalar_value|
|
81
|
+
field_options = {}
|
82
|
+
field_options[:boost] = @field.boost if @field.boost
|
83
|
+
field_options[:null] = true if scalar_value.nil? && atomic_operation
|
62
84
|
document.add_field(
|
63
85
|
@field.indexed_name.to_sym,
|
64
86
|
scalar_value,
|
65
|
-
options
|
87
|
+
field_options.merge(options)
|
66
88
|
)
|
67
89
|
end
|
68
90
|
end
|
@@ -76,23 +98,48 @@ module Sunspot
|
|
76
98
|
end
|
77
99
|
end
|
78
100
|
|
101
|
+
class Join < Abstract
|
102
|
+
def initialize(name, type, options = {}, &block)
|
103
|
+
super(options[:prefix] ? "#{options[:prefix]}_#{name}" : name, options, &block)
|
104
|
+
unless name.to_s =~ /^\w+$/
|
105
|
+
raise ArgumentError, "Invalid field name #{name}: only letters, numbers, and underscores are allowed."
|
106
|
+
end
|
107
|
+
@field = JoinField.new(self.name, type, options)
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Return the field instance built by this factory
|
112
|
+
#
|
113
|
+
def build
|
114
|
+
@field
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# A unique signature identifying this field by name and type.
|
119
|
+
#
|
120
|
+
def signature
|
121
|
+
['join', @field.name, @field.type]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
79
125
|
#
|
80
126
|
# DynamicFieldFactories create dynamic field instances based on dynamic
|
81
127
|
# configuration.
|
82
128
|
#
|
83
129
|
class Dynamic < Abstract
|
84
|
-
attr_accessor :name, :type
|
130
|
+
attr_accessor :name, :type, :separator
|
85
131
|
|
86
132
|
def initialize(name, type, options = {}, &block)
|
87
133
|
super(name, options, &block)
|
88
134
|
@type, @options = type, options
|
135
|
+
@separator = @options.delete(:separator) || ':'
|
89
136
|
end
|
90
137
|
|
91
138
|
#
|
92
139
|
# Build a field based on the dynamic name given.
|
93
140
|
#
|
94
141
|
def build(dynamic_name)
|
95
|
-
AttributeField.new(
|
142
|
+
AttributeField.new([@name, dynamic_name].join(separator), @type, @options.dup)
|
96
143
|
end
|
97
144
|
#
|
98
145
|
# This alias allows a DynamicFieldFactory to be used in place of a Setup
|
@@ -104,14 +151,16 @@ module Sunspot
|
|
104
151
|
# Generate dynamic fields based on hash returned by data accessor and
|
105
152
|
# add the field data to the document.
|
106
153
|
#
|
107
|
-
def populate_document(document, model)
|
108
|
-
|
154
|
+
def populate_document(document, model, options = {})
|
155
|
+
values = extract_value(model, options)
|
156
|
+
if values
|
109
157
|
values.each_pair do |dynamic_name, value|
|
110
158
|
field_instance = build(dynamic_name)
|
111
159
|
Util.Array(field_instance.to_indexed(value)).each do |scalar_value|
|
112
160
|
document.add_field(
|
113
161
|
field_instance.indexed_name.to_sym,
|
114
|
-
scalar_value
|
162
|
+
scalar_value,
|
163
|
+
options
|
115
164
|
)
|
116
165
|
end
|
117
166
|
end
|
data/lib/sunspot/indexer.rb
CHANGED
@@ -8,7 +8,6 @@ module Sunspot
|
|
8
8
|
# subclasses).
|
9
9
|
#
|
10
10
|
class Indexer #:nodoc:
|
11
|
-
include RSolr::Char
|
12
11
|
|
13
12
|
def initialize(connection)
|
14
13
|
@connection = connection
|
@@ -23,12 +22,23 @@ module Sunspot
|
|
23
22
|
# model<Object>:: the model to index
|
24
23
|
#
|
25
24
|
def add(model)
|
26
|
-
documents = Util.Array(model).map { |m|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
documents = Util.Array(model).map { |m| prepare_full_update(m) }
|
26
|
+
add_batch_documents(documents)
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Construct a representation of the given class instances for atomic properties update
|
31
|
+
# and send it to the connection for indexing
|
32
|
+
#
|
33
|
+
# ==== Parameters
|
34
|
+
#
|
35
|
+
# clazz<Class>:: the class of the models to be updated
|
36
|
+
# updates<Hash>:: hash of updates where keys are model ids
|
37
|
+
# and values are hash with property name/values to be updated
|
38
|
+
#
|
39
|
+
def add_atomic_update(clazz, updates={})
|
40
|
+
documents = updates.map { |id, m| prepare_atomic_update(clazz, id, m) }
|
41
|
+
add_batch_documents(documents)
|
32
42
|
end
|
33
43
|
|
34
44
|
#
|
@@ -44,17 +54,18 @@ module Sunspot
|
|
44
54
|
# Remove the model from the Solr index by specifying the class and ID
|
45
55
|
#
|
46
56
|
def remove_by_id(class_name, *ids)
|
57
|
+
ids.flatten!
|
47
58
|
@connection.delete_by_id(
|
48
59
|
ids.map { |id| Adapters::InstanceAdapter.index_id_for(class_name, id) }
|
49
60
|
)
|
50
61
|
end
|
51
62
|
|
52
|
-
#
|
63
|
+
#
|
53
64
|
# Delete all documents of the class indexed by this indexer from Solr.
|
54
65
|
#
|
55
66
|
def remove_all(clazz = nil)
|
56
67
|
if clazz
|
57
|
-
@connection.delete_by_query("type:#{escape(clazz.name)}")
|
68
|
+
@connection.delete_by_query("type:#{Util.escape(clazz.name)}")
|
58
69
|
else
|
59
70
|
@connection.delete_by_query("*:*")
|
60
71
|
end
|
@@ -90,9 +101,9 @@ module Sunspot
|
|
90
101
|
#
|
91
102
|
# Convert documents into hash of indexed properties
|
92
103
|
#
|
93
|
-
def
|
94
|
-
document =
|
95
|
-
setup =
|
104
|
+
def prepare_full_update(model)
|
105
|
+
document = document_for_full_update(model)
|
106
|
+
setup = setup_for_object(model)
|
96
107
|
if boost = setup.document_boost_for(model)
|
97
108
|
document.attrs[:boost] = boost
|
98
109
|
end
|
@@ -102,22 +113,48 @@ module Sunspot
|
|
102
113
|
document
|
103
114
|
end
|
104
115
|
|
116
|
+
def prepare_atomic_update(clazz, id, updates = {})
|
117
|
+
document = document_for_atomic_update(clazz, id)
|
118
|
+
setup_for_class(clazz).all_field_factories.each do |field_factory|
|
119
|
+
if updates.has_key?(field_factory.name)
|
120
|
+
field_factory.populate_document(document, nil, value: updates[field_factory.name], update: :set)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
document
|
124
|
+
end
|
125
|
+
|
105
126
|
def add_documents(documents)
|
106
127
|
@connection.add(documents)
|
107
128
|
end
|
108
129
|
|
130
|
+
def add_batch_documents(documents)
|
131
|
+
if batcher.batching?
|
132
|
+
batcher.concat(documents)
|
133
|
+
else
|
134
|
+
add_documents(documents)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
109
138
|
#
|
110
139
|
# All indexed documents index and store the +id+ and +type+ fields.
|
111
|
-
#
|
140
|
+
# These methods construct the document hash containing those key-value
|
112
141
|
# pairs.
|
113
142
|
#
|
114
|
-
def
|
143
|
+
def document_for_full_update(model)
|
115
144
|
RSolr::Xml::Document.new(
|
116
|
-
:
|
117
|
-
:
|
145
|
+
id: Adapters::InstanceAdapter.adapt(model).index_id,
|
146
|
+
type: Util.superclasses_for(model.class).map(&:name)
|
118
147
|
)
|
119
148
|
end
|
120
149
|
|
150
|
+
def document_for_atomic_update(clazz, id)
|
151
|
+
if Adapters::InstanceAdapter.for(clazz)
|
152
|
+
RSolr::Xml::Document.new(
|
153
|
+
id: Adapters::InstanceAdapter.index_id_for(clazz.name, id),
|
154
|
+
type: Util.superclasses_for(clazz).map(&:name)
|
155
|
+
)
|
156
|
+
end
|
157
|
+
end
|
121
158
|
#
|
122
159
|
# Get the Setup object for the given object's class.
|
123
160
|
#
|
@@ -129,8 +166,23 @@ module Sunspot
|
|
129
166
|
#
|
130
167
|
# Sunspot::Setup:: The setup for the object's class
|
131
168
|
#
|
132
|
-
def
|
133
|
-
|
169
|
+
def setup_for_object(object)
|
170
|
+
setup_for_class(object.class)
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# Get the Setup object for the given class.
|
175
|
+
#
|
176
|
+
# ==== Parameters
|
177
|
+
#
|
178
|
+
# clazz<Class>:: The class whose setup is to be retrieved
|
179
|
+
#
|
180
|
+
# ==== Returns
|
181
|
+
#
|
182
|
+
# Sunspot::Setup:: The setup for the class
|
183
|
+
#
|
184
|
+
def setup_for_class(clazz)
|
185
|
+
Setup.for(clazz) || raise(NoSetupError, "Sunspot is not configured for #{clazz.inspect}")
|
134
186
|
end
|
135
187
|
end
|
136
188
|
end
|