UnderpantsGnome-sunspot 0.9.1.1
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/History.txt +39 -0
- data/LICENSE +18 -0
- data/README.rdoc +154 -0
- data/Rakefile +9 -0
- data/TODO +4 -0
- data/VERSION.yml +4 -0
- data/bin/sunspot-configure-solr +46 -0
- data/bin/sunspot-solr +62 -0
- data/lib/light_config.rb +40 -0
- data/lib/sunspot.rb +470 -0
- data/lib/sunspot/adapters.rb +265 -0
- data/lib/sunspot/composite_setup.rb +186 -0
- data/lib/sunspot/configuration.rb +38 -0
- data/lib/sunspot/data_extractor.rb +47 -0
- data/lib/sunspot/date_facet.rb +36 -0
- data/lib/sunspot/date_facet_row.rb +17 -0
- data/lib/sunspot/dsl.rb +3 -0
- data/lib/sunspot/dsl/field_query.rb +72 -0
- data/lib/sunspot/dsl/fields.rb +86 -0
- data/lib/sunspot/dsl/query.rb +59 -0
- data/lib/sunspot/dsl/query_facet.rb +31 -0
- data/lib/sunspot/dsl/restriction.rb +25 -0
- data/lib/sunspot/dsl/scope.rb +193 -0
- data/lib/sunspot/dsl/search.rb +30 -0
- data/lib/sunspot/facet.rb +51 -0
- data/lib/sunspot/facet_row.rb +34 -0
- data/lib/sunspot/field.rb +157 -0
- data/lib/sunspot/field_factory.rb +126 -0
- data/lib/sunspot/indexer.rb +127 -0
- data/lib/sunspot/instantiated_facet.rb +38 -0
- data/lib/sunspot/instantiated_facet_row.rb +12 -0
- data/lib/sunspot/query.rb +190 -0
- data/lib/sunspot/query/base_query.rb +90 -0
- data/lib/sunspot/query/connective.rb +77 -0
- data/lib/sunspot/query/dynamic_query.rb +69 -0
- data/lib/sunspot/query/field_facet.rb +149 -0
- data/lib/sunspot/query/field_query.rb +57 -0
- data/lib/sunspot/query/pagination.rb +39 -0
- data/lib/sunspot/query/query_facet.rb +72 -0
- data/lib/sunspot/query/query_facet_row.rb +19 -0
- data/lib/sunspot/query/restriction.rb +225 -0
- data/lib/sunspot/query/scope.rb +165 -0
- data/lib/sunspot/query/sort.rb +36 -0
- data/lib/sunspot/query/sort_composite.rb +33 -0
- data/lib/sunspot/query_facet.rb +33 -0
- data/lib/sunspot/query_facet_row.rb +21 -0
- data/lib/sunspot/schema.rb +165 -0
- data/lib/sunspot/search.rb +222 -0
- data/lib/sunspot/search/hit.rb +62 -0
- data/lib/sunspot/session.rb +201 -0
- data/lib/sunspot/setup.rb +271 -0
- data/lib/sunspot/type.rb +200 -0
- data/lib/sunspot/util.rb +164 -0
- data/solr/etc/jetty.xml +212 -0
- data/solr/etc/webdefault.xml +379 -0
- data/solr/lib/jetty-6.1.3.jar +0 -0
- data/solr/lib/jetty-util-6.1.3.jar +0 -0
- data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
- data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
- data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
- data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
- data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
- data/solr/solr/conf/elevate.xml +36 -0
- data/solr/solr/conf/protwords.txt +21 -0
- data/solr/solr/conf/schema.xml +50 -0
- data/solr/solr/conf/solrconfig.xml +696 -0
- data/solr/solr/conf/stopwords.txt +57 -0
- data/solr/solr/conf/synonyms.txt +31 -0
- data/solr/start.jar +0 -0
- data/solr/webapps/solr.war +0 -0
- data/spec/api/adapters_spec.rb +33 -0
- data/spec/api/build_search_spec.rb +918 -0
- data/spec/api/indexer_spec.rb +311 -0
- data/spec/api/query_spec.rb +153 -0
- data/spec/api/search_retrieval_spec.rb +325 -0
- data/spec/api/session_spec.rb +157 -0
- data/spec/api/spec_helper.rb +1 -0
- data/spec/api/sunspot_spec.rb +18 -0
- data/spec/integration/dynamic_fields_spec.rb +55 -0
- data/spec/integration/faceting_spec.rb +169 -0
- data/spec/integration/keyword_search_spec.rb +83 -0
- data/spec/integration/scoped_search_spec.rb +188 -0
- data/spec/integration/spec_helper.rb +1 -0
- data/spec/integration/stored_fields_spec.rb +10 -0
- data/spec/integration/test_pagination.rb +32 -0
- data/spec/mocks/adapters.rb +32 -0
- data/spec/mocks/blog.rb +3 -0
- data/spec/mocks/comment.rb +19 -0
- data/spec/mocks/connection.rb +84 -0
- data/spec/mocks/mock_adapter.rb +30 -0
- data/spec/mocks/mock_record.rb +41 -0
- data/spec/mocks/photo.rb +8 -0
- data/spec/mocks/post.rb +70 -0
- data/spec/mocks/user.rb +8 -0
- data/spec/spec_helper.rb +47 -0
- data/tasks/gemspec.rake +25 -0
- data/tasks/rcov.rake +28 -0
- data/tasks/rdoc.rake +21 -0
- data/tasks/schema.rake +19 -0
- data/tasks/spec.rake +24 -0
- data/tasks/todo.rake +4 -0
- data/templates/schema.xml.haml +24 -0
- metadata +245 -0
@@ -0,0 +1,271 @@
|
|
1
|
+
module Sunspot
|
2
|
+
#
|
3
|
+
# This class encapsulates the search/indexing setup for a given class. Its
|
4
|
+
# contents are built using the Sunspot.setup method.
|
5
|
+
#
|
6
|
+
class Setup #:nodoc:
|
7
|
+
def initialize(clazz)
|
8
|
+
@clazz = clazz
|
9
|
+
@class_name = clazz.name
|
10
|
+
@field_factories, @text_field_factories, @dynamic_field_factories,
|
11
|
+
@field_factories_cache, @text_field_factories_cache,
|
12
|
+
@dynamic_field_factories_cache = *Array.new(6) { Hash.new }
|
13
|
+
@dsl = DSL::Fields.new(self)
|
14
|
+
add_field_factory(:class, Type::ClassType)
|
15
|
+
end
|
16
|
+
|
17
|
+
def type_names
|
18
|
+
[@class_name]
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Add field_factories for scope/ordering
|
23
|
+
#
|
24
|
+
# ==== Parameters
|
25
|
+
#
|
26
|
+
# field_factories<Array>:: Array of Sunspot::Field objects
|
27
|
+
#
|
28
|
+
def add_field_factory(name, type, options = {}, &block)
|
29
|
+
field_factory = FieldFactory::Static.new(name, type, options, &block)
|
30
|
+
@field_factories[field_factory.signature] = field_factory
|
31
|
+
@field_factories_cache[field_factory.name] = field_factory
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Add field_factories for fulltext search
|
36
|
+
#
|
37
|
+
# ==== Parameters
|
38
|
+
#
|
39
|
+
# field_factories<Array>:: Array of Sunspot::Field objects
|
40
|
+
#
|
41
|
+
def add_text_field_factory(name, options = {}, &block)
|
42
|
+
field_factory = FieldFactory::Static.new(name, Type::TextType, options, &block)
|
43
|
+
@text_field_factories[name] = field_factory
|
44
|
+
@text_field_factories_cache[field_factory.name] = field_factory
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Add dynamic field_factories
|
49
|
+
#
|
50
|
+
# ==== Parameters
|
51
|
+
#
|
52
|
+
# field_factories<Array>:: Array of dynamic field objects
|
53
|
+
#
|
54
|
+
def add_dynamic_field_factory(name, type, options = {}, &block)
|
55
|
+
field_factory = FieldFactory::Dynamic.new(name, type, options, &block)
|
56
|
+
@dynamic_field_factories[field_factory.signature] = field_factory
|
57
|
+
@dynamic_field_factories_cache[field_factory.name] = field_factory
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_document_boost(attr_name, &block)
|
61
|
+
@document_boost_extractor =
|
62
|
+
if attr_name
|
63
|
+
if attr_name.respond_to?(:to_f)
|
64
|
+
DataExtractor::Constant.new(attr_name)
|
65
|
+
else
|
66
|
+
DataExtractor::AttributeExtractor.new(attr_name)
|
67
|
+
end
|
68
|
+
else
|
69
|
+
DataExtractor::BlockExtractor.new(&block)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Builder method for evaluating the setup DSL
|
75
|
+
#
|
76
|
+
def setup(&block)
|
77
|
+
@dsl.instance_eval(&block)
|
78
|
+
end
|
79
|
+
|
80
|
+
def field(field_name)
|
81
|
+
if field_factory = @field_factories_cache[field_name.to_sym]
|
82
|
+
field_factory.build
|
83
|
+
else
|
84
|
+
raise(
|
85
|
+
UnrecognizedFieldError,
|
86
|
+
"No field configured for #{@clazz.name} with name '#{field_name}'"
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def text_field(field_name)
|
92
|
+
if field_factory = @text_field_factories_cache[field_name.to_sym]
|
93
|
+
field_factory.build
|
94
|
+
else
|
95
|
+
raise(
|
96
|
+
UnrecognizedFieldError,
|
97
|
+
"No text field configured for #{@clazz.name} with name '#{field_name}'"
|
98
|
+
)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def dynamic_field_factory(field_name)
|
103
|
+
@dynamic_field_factories_cache[field_name.to_sym] || raise(
|
104
|
+
UnrecognizedFieldError,
|
105
|
+
"No dynamic field configured for #{@clazz.name} with name '#{field_name}'"
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
def fields
|
110
|
+
field_factories.map { |field_factory| field_factory.build }
|
111
|
+
end
|
112
|
+
|
113
|
+
def text_fields
|
114
|
+
text_field_factories.map { |text_field_factory| text_field_factory.build }
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# Get the field_factories associated with this setup as well as all inherited field_factories
|
119
|
+
#
|
120
|
+
# ==== Returns
|
121
|
+
#
|
122
|
+
# Array:: Collection of all field_factories associated with this setup
|
123
|
+
#
|
124
|
+
def field_factories
|
125
|
+
collection_from_inheritable_hash(:field_factories)
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Get the text field_factories associated with this setup as well as all inherited
|
130
|
+
# text field_factories
|
131
|
+
#
|
132
|
+
# ==== Returns
|
133
|
+
#
|
134
|
+
# Array:: Collection of all text field_factories associated with this setup
|
135
|
+
#
|
136
|
+
def text_field_factories
|
137
|
+
collection_from_inheritable_hash(:text_field_factories)
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Get all static, dynamic, and text field_factories associated with this setup as
|
142
|
+
# well as all inherited field_factories
|
143
|
+
#
|
144
|
+
# ==== Returns
|
145
|
+
#
|
146
|
+
# Array:: Collection of all text and scope field_factories associated with this setup
|
147
|
+
#
|
148
|
+
def all_field_factories
|
149
|
+
all_field_factories = []
|
150
|
+
all_field_factories.concat(field_factories).concat(text_field_factories).concat(dynamic_field_factories)
|
151
|
+
all_field_factories
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# Get all dynamic field_factories for this and parent setups
|
156
|
+
#
|
157
|
+
# ==== Returns
|
158
|
+
#
|
159
|
+
# Array:: Dynamic field_factories
|
160
|
+
#
|
161
|
+
def dynamic_field_factories
|
162
|
+
collection_from_inheritable_hash(:dynamic_field_factories)
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# Return the class associated with this setup.
|
167
|
+
#
|
168
|
+
# ==== Returns
|
169
|
+
#
|
170
|
+
# clazz<Class>:: Class setup is configured for
|
171
|
+
#
|
172
|
+
def clazz
|
173
|
+
Util.full_const_get(@class_name)
|
174
|
+
end
|
175
|
+
|
176
|
+
def document_boost_for(model)
|
177
|
+
if @document_boost_extractor
|
178
|
+
@document_boost_extractor.value_for(model)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
protected
|
183
|
+
|
184
|
+
#
|
185
|
+
# Get the nearest inherited setup, if any
|
186
|
+
#
|
187
|
+
# ==== Returns
|
188
|
+
#
|
189
|
+
# Sunspot::Setup:: Setup for the nearest ancestor of this setup's class
|
190
|
+
#
|
191
|
+
def parent
|
192
|
+
Setup.for(clazz.superclass)
|
193
|
+
end
|
194
|
+
|
195
|
+
def get_inheritable_hash(name)
|
196
|
+
hash = instance_variable_get(:"@#{name}")
|
197
|
+
parent.get_inheritable_hash(name).each_pair do |key, value|
|
198
|
+
hash[key] = value unless hash.has_key?(key)
|
199
|
+
end if parent
|
200
|
+
hash
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
def collection_from_inheritable_hash(name)
|
206
|
+
get_inheritable_hash(name).values
|
207
|
+
end
|
208
|
+
|
209
|
+
class <<self
|
210
|
+
#
|
211
|
+
# Retrieve or create the Setup instance for the given class, evaluating
|
212
|
+
# the given block to add to the setup's configuration
|
213
|
+
#
|
214
|
+
def setup(clazz, &block) #:nodoc:
|
215
|
+
self.for!(clazz).setup(&block)
|
216
|
+
end
|
217
|
+
|
218
|
+
#
|
219
|
+
# Retrieve the setup instance for the given class, or for the nearest
|
220
|
+
# ancestor that has a setup, if any.
|
221
|
+
#
|
222
|
+
# ==== Parameters
|
223
|
+
#
|
224
|
+
# clazz<Class>:: Class for which to retrieve a setup
|
225
|
+
#
|
226
|
+
# ==== Returns
|
227
|
+
#
|
228
|
+
# Sunspot::Setup::
|
229
|
+
# Setup instance associated with the given class or its nearest ancestor
|
230
|
+
#
|
231
|
+
def for(clazz) #:nodoc:
|
232
|
+
class_name =
|
233
|
+
if clazz.respond_to?(:name)
|
234
|
+
clazz.name
|
235
|
+
else
|
236
|
+
clazz
|
237
|
+
end
|
238
|
+
setups[class_name.to_sym] || self.for(clazz.superclass) if clazz
|
239
|
+
end
|
240
|
+
|
241
|
+
protected
|
242
|
+
|
243
|
+
#
|
244
|
+
# Retrieve or create a Setup instance for this class
|
245
|
+
#
|
246
|
+
# ==== Parameters
|
247
|
+
#
|
248
|
+
# clazz<Class>:: Class for which to retrieve a setup
|
249
|
+
#
|
250
|
+
# ==== Returns
|
251
|
+
#
|
252
|
+
# Sunspot::Setup:: New or existing setup for this class
|
253
|
+
#
|
254
|
+
def for!(clazz) #:nodoc:
|
255
|
+
setups[clazz.name.to_sym] ||= new(clazz)
|
256
|
+
end
|
257
|
+
|
258
|
+
private
|
259
|
+
|
260
|
+
# Singleton hash of class names to Setup instances
|
261
|
+
#
|
262
|
+
# ==== Returns
|
263
|
+
#
|
264
|
+
# Hash:: Class names keyed to Setup instances
|
265
|
+
#
|
266
|
+
def setups
|
267
|
+
@setups ||= {}
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
data/lib/sunspot/type.rb
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
module Sunspot
|
2
|
+
#
|
3
|
+
# This module contains singleton objects that represent the types that can be
|
4
|
+
# indexed and searched using Sunspot. Plugin developers should be able to
|
5
|
+
# add new constants to the Type module; as long as they implement the
|
6
|
+
# appropriate methods, Sunspot should be able to integrate them (note that
|
7
|
+
# this capability is untested at the moment). The required methods are:
|
8
|
+
#
|
9
|
+
# +indexed_name+::
|
10
|
+
# Convert a given field name into its form as stored in Solr. This
|
11
|
+
# generally means adding a suffix to match a Solr dynamicField definition.
|
12
|
+
# +to_indexed+::
|
13
|
+
# Convert a value of this type into the appropriate Solr string
|
14
|
+
# representation.
|
15
|
+
# +cast+::
|
16
|
+
# Convert a Solr string representation of a value into the appropriate
|
17
|
+
# Ruby type.
|
18
|
+
#
|
19
|
+
module Type
|
20
|
+
#
|
21
|
+
# Text is a special type that stores data for fulltext search. Unlike other
|
22
|
+
# types, Text fields are tokenized and are made available to the keyword
|
23
|
+
# search phrase. Text fields cannot be faceted, ordered upon, or used in
|
24
|
+
# restrictions. Similarly, text fields are the only fields that are made
|
25
|
+
# available to keyword search.
|
26
|
+
#
|
27
|
+
module TextType
|
28
|
+
class <<self
|
29
|
+
def indexed_name(name) #:nodoc:
|
30
|
+
"#{name}_text"
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_indexed(value) #:nodoc:
|
34
|
+
value.to_s if value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# The String type represents string data.
|
41
|
+
#
|
42
|
+
module StringType
|
43
|
+
class <<self
|
44
|
+
def indexed_name(name) #:nodoc:
|
45
|
+
"#{name}_s"
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_indexed(value) #:nodoc:
|
49
|
+
value.to_s if value
|
50
|
+
end
|
51
|
+
|
52
|
+
def cast(string) #:nodoc:
|
53
|
+
string
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# The Integer type represents integers.
|
60
|
+
#
|
61
|
+
module IntegerType
|
62
|
+
class <<self
|
63
|
+
def indexed_name(name) #:nodoc:
|
64
|
+
"#{name}_i"
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_indexed(value) #:nodoc:
|
68
|
+
value.to_i.to_s if value
|
69
|
+
end
|
70
|
+
|
71
|
+
def cast(string) #:nodoc:
|
72
|
+
string.to_i
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# The Float type represents floating-point numbers.
|
79
|
+
#
|
80
|
+
module FloatType
|
81
|
+
class <<self
|
82
|
+
def indexed_name(name) #:nodoc:
|
83
|
+
"#{name}_f"
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_indexed(value) #:nodoc:
|
87
|
+
value.to_f.to_s if value
|
88
|
+
end
|
89
|
+
|
90
|
+
def cast(string) #:nodoc:
|
91
|
+
string.to_f
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# The time type represents times. Note that times are always converted to
|
98
|
+
# UTC before indexing, and facets of Time fields always return times in UTC.
|
99
|
+
#
|
100
|
+
module TimeType
|
101
|
+
|
102
|
+
class <<self
|
103
|
+
def indexed_name(name) #:nodoc:
|
104
|
+
"#{name}_d"
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_indexed(value) #:nodoc:
|
108
|
+
if value
|
109
|
+
time =
|
110
|
+
if value.respond_to?(:utc)
|
111
|
+
value
|
112
|
+
else
|
113
|
+
Time.parse(value.to_s)
|
114
|
+
end
|
115
|
+
time.utc.xmlschema
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def cast(string) #:nodoc:
|
120
|
+
Time.xmlschema(string)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# The DateType encapsulates dates (without time information). Internally,
|
127
|
+
# Solr does not have a date-only type, so this type indexes data using
|
128
|
+
# Solr's DateField type (which is actually date/time), midnight UTC of the
|
129
|
+
# indexed date.
|
130
|
+
#
|
131
|
+
module DateType
|
132
|
+
class <<self
|
133
|
+
def indexed_name(name) #:nodoc:
|
134
|
+
"#{name}_d"
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_indexed(value) #:nodoc:
|
138
|
+
if value
|
139
|
+
time =
|
140
|
+
if %w(year mon mday).all? { |method| value.respond_to?(method) }
|
141
|
+
Time.utc(value.year, value.mon, value.mday)
|
142
|
+
else
|
143
|
+
date = Date.parse(value.to_s)
|
144
|
+
Time.utc(date.year, date.mon, date.mday)
|
145
|
+
end
|
146
|
+
time.utc.xmlschema
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def cast(string)
|
151
|
+
time = Time.xmlschema(string)
|
152
|
+
Date.civil(time.year, time.mon, time.mday)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
#
|
158
|
+
# The boolean type represents true/false values. Note that +nil+ will not be
|
159
|
+
# indexed at all; only +false+ will be indexed with a false value.
|
160
|
+
#
|
161
|
+
module BooleanType
|
162
|
+
class <<self
|
163
|
+
def indexed_name(name) #:nodoc:
|
164
|
+
"#{name}_b"
|
165
|
+
end
|
166
|
+
|
167
|
+
def to_indexed(value) #:nodoc:
|
168
|
+
unless value.nil?
|
169
|
+
value ? 'true' : 'false'
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def cast(string) #:nodoc:
|
174
|
+
case string
|
175
|
+
when 'true'
|
176
|
+
true
|
177
|
+
when 'false'
|
178
|
+
false
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
module ClassType
|
185
|
+
class <<self
|
186
|
+
def indexed_name(name)
|
187
|
+
'class_name'
|
188
|
+
end
|
189
|
+
|
190
|
+
def to_indexed(value)
|
191
|
+
value.name
|
192
|
+
end
|
193
|
+
|
194
|
+
def cast(string)
|
195
|
+
Sunspot::Util.full_const_get(string)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|