sunspot 2.0.0 → 2.5.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 +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
|