thinking-sphinx 2.0.5 → 2.0.6

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.
Files changed (70) hide show
  1. data/README.textile +7 -1
  2. data/features/searching_by_model.feature +24 -30
  3. data/features/step_definitions/common_steps.rb +5 -5
  4. data/features/thinking_sphinx/db/.gitignore +1 -0
  5. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +1 -0
  6. data/spec/fixtures/data.sql +32 -0
  7. data/spec/fixtures/database.yml.default +3 -0
  8. data/spec/fixtures/models.rb +161 -0
  9. data/spec/fixtures/structure.sql +146 -0
  10. data/spec/spec_helper.rb +62 -0
  11. data/spec/sphinx_helper.rb +61 -0
  12. data/spec/support/rails.rb +18 -0
  13. data/spec/thinking_sphinx/active_record/delta_spec.rb +24 -24
  14. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +27 -0
  15. data/spec/thinking_sphinx/active_record/scopes_spec.rb +25 -25
  16. data/spec/thinking_sphinx/active_record_spec.rb +108 -107
  17. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +38 -38
  18. data/spec/thinking_sphinx/association_spec.rb +69 -35
  19. data/spec/thinking_sphinx/context_spec.rb +61 -64
  20. data/spec/thinking_sphinx/search_spec.rb +7 -0
  21. data/spec/thinking_sphinx_spec.rb +47 -46
  22. metadata +49 -141
  23. data/VERSION +0 -1
  24. data/lib/cucumber/thinking_sphinx/external_world.rb +0 -12
  25. data/lib/cucumber/thinking_sphinx/internal_world.rb +0 -127
  26. data/lib/cucumber/thinking_sphinx/sql_logger.rb +0 -20
  27. data/lib/thinking-sphinx.rb +0 -1
  28. data/lib/thinking_sphinx.rb +0 -301
  29. data/lib/thinking_sphinx/action_controller.rb +0 -31
  30. data/lib/thinking_sphinx/active_record.rb +0 -384
  31. data/lib/thinking_sphinx/active_record/attribute_updates.rb +0 -52
  32. data/lib/thinking_sphinx/active_record/delta.rb +0 -65
  33. data/lib/thinking_sphinx/active_record/has_many_association.rb +0 -36
  34. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
  35. data/lib/thinking_sphinx/active_record/log_subscriber.rb +0 -61
  36. data/lib/thinking_sphinx/active_record/scopes.rb +0 -93
  37. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +0 -87
  38. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -62
  39. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +0 -157
  40. data/lib/thinking_sphinx/association.rb +0 -219
  41. data/lib/thinking_sphinx/attribute.rb +0 -396
  42. data/lib/thinking_sphinx/auto_version.rb +0 -38
  43. data/lib/thinking_sphinx/bundled_search.rb +0 -44
  44. data/lib/thinking_sphinx/class_facet.rb +0 -20
  45. data/lib/thinking_sphinx/configuration.rb +0 -339
  46. data/lib/thinking_sphinx/context.rb +0 -76
  47. data/lib/thinking_sphinx/core/string.rb +0 -15
  48. data/lib/thinking_sphinx/deltas.rb +0 -28
  49. data/lib/thinking_sphinx/deltas/default_delta.rb +0 -62
  50. data/lib/thinking_sphinx/deploy/capistrano.rb +0 -101
  51. data/lib/thinking_sphinx/excerpter.rb +0 -23
  52. data/lib/thinking_sphinx/facet.rb +0 -128
  53. data/lib/thinking_sphinx/facet_search.rb +0 -170
  54. data/lib/thinking_sphinx/field.rb +0 -98
  55. data/lib/thinking_sphinx/index.rb +0 -157
  56. data/lib/thinking_sphinx/index/builder.rb +0 -312
  57. data/lib/thinking_sphinx/index/faux_column.rb +0 -118
  58. data/lib/thinking_sphinx/join.rb +0 -37
  59. data/lib/thinking_sphinx/property.rb +0 -185
  60. data/lib/thinking_sphinx/railtie.rb +0 -46
  61. data/lib/thinking_sphinx/search.rb +0 -972
  62. data/lib/thinking_sphinx/search_methods.rb +0 -439
  63. data/lib/thinking_sphinx/sinatra.rb +0 -7
  64. data/lib/thinking_sphinx/source.rb +0 -194
  65. data/lib/thinking_sphinx/source/internal_properties.rb +0 -51
  66. data/lib/thinking_sphinx/source/sql.rb +0 -157
  67. data/lib/thinking_sphinx/tasks.rb +0 -130
  68. data/lib/thinking_sphinx/test.rb +0 -55
  69. data/tasks/distribution.rb +0 -33
  70. data/tasks/testing.rb +0 -80
@@ -1,219 +0,0 @@
1
- module ThinkingSphinx
2
- # Association tracks a specific reflection and join to reference data that
3
- # isn't in the base model. Very much an internal class for Thinking Sphinx -
4
- # perhaps because I feel it's not as strong (or simple) as most of the rest.
5
- #
6
- class Association
7
- attr_accessor :parent, :reflection, :join
8
-
9
- # Create a new association by passing in the parent association, and the
10
- # corresponding reflection instance. If there is no parent, pass in nil.
11
- #
12
- # top = Association.new nil, top_reflection
13
- # child = Association.new top, child_reflection
14
- #
15
- def initialize(parent, reflection)
16
- @parent, @reflection = parent, reflection
17
- @children = {}
18
- end
19
-
20
- # Get the children associations for a given association name. The only time
21
- # that there'll actually be more than one association is when the
22
- # relationship is polymorphic. To keep things simple though, it will always
23
- # be an Array that gets returned (an empty one if no matches).
24
- #
25
- # # where pages is an association on the class tied to the reflection.
26
- # association.children(:pages)
27
- #
28
- def children(assoc)
29
- @children[assoc] ||= Association.children(@reflection.klass, assoc, self)
30
- end
31
-
32
- # Get the children associations for a given class, association name and
33
- # parent association. Much like the instance method of the same name, it
34
- # will return an empty array if no associations have the name, and only
35
- # have multiple association instances if the underlying relationship is
36
- # polymorphic.
37
- #
38
- # Association.children(User, :pages, user_association)
39
- #
40
- def self.children(klass, assoc, parent=nil)
41
- ref = klass.reflect_on_association(assoc)
42
-
43
- return [] if ref.nil?
44
- return [Association.new(parent, ref)] unless ref.options[:polymorphic]
45
-
46
- # association is polymorphic - create associations for each
47
- # non-polymorphic reflection.
48
- polymorphic_classes(ref).collect { |poly_class|
49
- reflection = depolymorphic_reflection(ref, poly_class)
50
- klass.reflections[reflection.name] = reflection
51
- Association.new parent, reflection
52
- }
53
- end
54
-
55
- # Link up the join for this model from a base join - and set parent
56
- # associations' joins recursively.
57
- #
58
- def join_to(base_join)
59
- parent.join_to(base_join) if parent && parent.join.nil?
60
-
61
- @join ||= join_association_class.new(
62
- @reflection, base_join, parent ? parent.join : join_parent(base_join)
63
- )
64
- end
65
-
66
- def arel_join
67
- @join.join_type = Arel::OuterJoin
68
- rewrite_conditions
69
-
70
- @join
71
- end
72
-
73
- # Returns true if the association - or a parent - is a has_many or
74
- # has_and_belongs_to_many.
75
- #
76
- def is_many?
77
- case @reflection.macro
78
- when :has_many, :has_and_belongs_to_many
79
- true
80
- else
81
- @parent ? @parent.is_many? : false
82
- end
83
- end
84
-
85
- # Returns an array of all the associations that lead to this one - starting
86
- # with the top level all the way to the current association object.
87
- #
88
- def ancestors
89
- (parent ? parent.ancestors : []) << self
90
- end
91
-
92
- def has_column?(column)
93
- @reflection.klass.column_names.include?(column.to_s)
94
- end
95
-
96
- def primary_key_from_reflection
97
- if @reflection.options[:through]
98
- @reflection.source_reflection.options[:foreign_key] ||
99
- @reflection.source_reflection.primary_key_name
100
- elsif @reflection.macro == :has_and_belongs_to_many
101
- @reflection.association_foreign_key
102
- else
103
- nil
104
- end
105
- end
106
-
107
- def table
108
- if @reflection.options[:through] ||
109
- @reflection.macro == :has_and_belongs_to_many
110
- @join.aliased_join_table_name
111
- else
112
- @join.aliased_table_name
113
- end
114
- end
115
-
116
- private
117
-
118
- def self.depolymorphic_reflection(reflection, klass)
119
- ::ActiveRecord::Reflection::AssociationReflection.new(
120
- reflection.macro,
121
- "#{reflection.name}_#{klass.name}".to_sym,
122
- casted_options(klass, reflection),
123
- reflection.active_record
124
- )
125
- end
126
-
127
- # Returns all the objects that could be currently instantiated from a
128
- # polymorphic association. This is pretty damn fast if there's an index on
129
- # the foreign type column - but if there isn't, it can take a while if you
130
- # have a lot of data.
131
- #
132
- def self.polymorphic_classes(ref)
133
- ref.active_record.connection.select_all(
134
- "SELECT DISTINCT #{ref.options[:foreign_type]} " +
135
- "FROM #{ref.active_record.table_name} " +
136
- "WHERE #{ref.options[:foreign_type]} IS NOT NULL"
137
- ).collect { |row|
138
- row[ref.options[:foreign_type]].constantize
139
- }
140
- end
141
-
142
- # Returns a new set of options for an association that mimics an existing
143
- # polymorphic relationship for a specific class. It adds a condition to
144
- # filter by the appropriate object.
145
- #
146
- def self.casted_options(klass, ref)
147
- options = ref.options.clone
148
- options[:polymorphic] = nil
149
- options[:class_name] = klass.name
150
- options[:foreign_key] ||= "#{ref.name}_id"
151
-
152
- quoted_foreign_type = klass.connection.quote_column_name ref.options[:foreign_type]
153
- case options[:conditions]
154
- when nil
155
- options[:conditions] = "::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'"
156
- when Array
157
- options[:conditions] << "::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'"
158
- when Hash
159
- options[:conditions].merge!(ref.options[:foreign_type] => klass.name)
160
- else
161
- options[:conditions] << " AND ::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'"
162
- end
163
-
164
- options
165
- end
166
-
167
- def join_association_class
168
- if rails_3_1?
169
- ::ActiveRecord::Associations::JoinDependency::JoinAssociation
170
- else
171
- ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
172
- end
173
- end
174
-
175
- def join_parent(join)
176
- if rails_3_1?
177
- join.join_parts.first
178
- else
179
- join.joins.first
180
- end
181
- end
182
-
183
- def rails_3_1?
184
- ::ActiveRecord::Associations.constants.include?(:JoinDependency) ||
185
- ::ActiveRecord::Associations.constants.include?('JoinDependency')
186
- end
187
-
188
- def rewrite_conditions
189
- @join.options[:conditions] = case @join.options[:conditions]
190
- when String
191
- rewrite_condition @join.options[:conditions]
192
- when Array
193
- @join.options[:conditions].collect { |condition|
194
- rewrite_condition condition
195
- }
196
- else
197
- @join.options[:conditions]
198
- end
199
- end
200
-
201
- def rewrite_condition(condition)
202
- return condition unless condition.is_a?(String)
203
-
204
- if defined?(ActsAsTaggableOn) &&
205
- @reflection.klass == ActsAsTaggableOn::Tagging &&
206
- @reflection.name.to_s[/_taggings$/]
207
- condition = condition.gsub /taggings\./, "#{quoted_alias @join}."
208
- end
209
-
210
- condition.gsub /::ts_join_alias::/, quoted_alias(@join.parent)
211
- end
212
-
213
- def quoted_alias(join)
214
- @reflection.klass.connection.quote_table_name(
215
- join.aliased_table_name
216
- )
217
- end
218
- end
219
- end
@@ -1,396 +0,0 @@
1
- module ThinkingSphinx
2
- # Attributes - eternally useful when it comes to filtering, sorting or
3
- # grouping. This class isn't really useful to you unless you're hacking
4
- # around with the internals of Thinking Sphinx - but hey, don't let that
5
- # stop you.
6
- #
7
- # One key thing to remember - if you're using the attribute manually to
8
- # generate SQL statements, you'll need to set the base model, and all the
9
- # associations. Which can get messy. Use Index.link!, it really helps.
10
- #
11
- class Attribute < ThinkingSphinx::Property
12
- attr_accessor :query_source
13
-
14
- SphinxTypeMappings = {
15
- :multi => :sql_attr_multi,
16
- :datetime => :sql_attr_timestamp,
17
- :string => :sql_attr_str2ordinal,
18
- :float => :sql_attr_float,
19
- :boolean => :sql_attr_bool,
20
- :integer => :sql_attr_uint,
21
- :bigint => :sql_attr_bigint,
22
- :wordcount => :sql_attr_str2wordcount
23
- }
24
-
25
- if Riddle.loaded_version.to_i > 1
26
- SphinxTypeMappings[:string] = :sql_attr_string
27
- end
28
-
29
- # To create a new attribute, you'll need to pass in either a single Column
30
- # or an array of them, and some (optional) options.
31
- #
32
- # Valid options are:
33
- # - :as => :alias_name
34
- # - :type => :attribute_type
35
- # - :source => :field, :query, :ranged_query
36
- #
37
- # Alias is only required in three circumstances: when there's
38
- # another attribute or field with the same name, when the column name is
39
- # 'id', or when there's more than one column.
40
- #
41
- # Type is not required, unless you want to force a column to be a certain
42
- # type (but keep in mind the value will not be CASTed in the SQL
43
- # statements). The only time you really need to use this is when the type
44
- # can't be figured out by the column - ie: when not actually using a
45
- # database column as your source.
46
- #
47
- # Source is only used for multi-value attributes (MVA). By default this will
48
- # use a left-join and a group_concat to obtain the values. For better performance
49
- # during indexing it can be beneficial to let Sphinx use a separate query to retrieve
50
- # all document,value-pairs.
51
- # Either :query or :ranged_query will enable this feature, where :ranged_query will cause
52
- # the query to be executed incremental.
53
- #
54
- # Example usage:
55
- #
56
- # Attribute.new(
57
- # Column.new(:created_at)
58
- # )
59
- #
60
- # Attribute.new(
61
- # Column.new(:posts, :id),
62
- # :as => :post_ids
63
- # )
64
- #
65
- # Attribute.new(
66
- # Column.new(:posts, :id),
67
- # :as => :post_ids,
68
- # :source => :ranged_query
69
- # )
70
- #
71
- # Attribute.new(
72
- # [Column.new(:pages, :id), Column.new(:articles, :id)],
73
- # :as => :content_ids
74
- # )
75
- #
76
- # Attribute.new(
77
- # Column.new("NOW()"),
78
- # :as => :indexed_at,
79
- # :type => :datetime
80
- # )
81
- #
82
- # If you're creating attributes for latitude and longitude, don't forget
83
- # that Sphinx expects these values to be in radians.
84
- #
85
- def initialize(source, columns, options = {})
86
- super
87
-
88
- @type = options[:type]
89
- @query_source = options[:source]
90
- @crc = options[:crc]
91
-
92
- @type ||= :multi unless @query_source.nil?
93
- if @type == :string && @crc
94
- @type = is_many? ? :multi : :integer
95
- end
96
-
97
- source.attributes << self
98
- end
99
-
100
- # Get the part of the SELECT clause related to this attribute. Don't forget
101
- # to set your model and associations first though.
102
- #
103
- # This will concatenate strings and arrays of integers, and convert
104
- # datetimes to timestamps, as needed.
105
- #
106
- def to_select_sql
107
- return nil unless include_as_association? && available?
108
-
109
- separator = all_ints? || all_datetimes? || @crc ? ',' : ' '
110
-
111
- clause = columns_with_prefixes.collect { |column|
112
- case type
113
- when :string
114
- adapter.convert_nulls(column)
115
- when :datetime
116
- adapter.cast_to_datetime(column)
117
- when :multi
118
- column = adapter.cast_to_datetime(column) if is_many_datetimes?
119
- column = adapter.convert_nulls(column, '0') if is_many_ints?
120
- column
121
- else
122
- column
123
- end
124
- }.join(', ')
125
-
126
- clause = adapter.crc(clause) if @crc
127
- clause = adapter.concatenate(clause, separator) if concat_ws?
128
- clause = adapter.group_concatenate(clause, separator) if is_many?
129
- clause = adapter.downcase(clause) if insensitive?
130
-
131
- "#{clause} AS #{quote_column(unique_name)}"
132
- end
133
-
134
- def type_to_config
135
- SphinxTypeMappings[type]
136
- end
137
-
138
- def include_as_association?
139
- ! (type == :multi && (query_source == :query || query_source == :ranged_query))
140
- end
141
-
142
- # Returns the configuration value that should be used for
143
- # the attribute.
144
- # Special case is the multi-valued attribute that needs some
145
- # extra configuration.
146
- #
147
- def config_value(offset = nil, delta = false)
148
- if type == :multi
149
- multi_config = include_as_association? ? "field" :
150
- source_value(offset, delta).gsub(/\s+/m, " ").strip
151
- "uint #{unique_name} from #{multi_config}"
152
- else
153
- unique_name
154
- end
155
- end
156
-
157
- # Returns the type of the column. If that's not already set, it returns
158
- # :multi if there's the possibility of more than one value, :string if
159
- # there's more than one association, otherwise it figures out what the
160
- # actual column's datatype is and returns that.
161
- #
162
- def type
163
- @type ||= begin
164
- base_type = case
165
- when is_many?, is_many_ints?
166
- :multi
167
- when @associations.values.flatten.length > 1
168
- :string
169
- else
170
- translated_type_from_database
171
- end
172
-
173
- if base_type == :string && @crc
174
- base_type = :integer
175
- else
176
- @crc = false unless base_type == :multi && is_many_strings? && @crc
177
- end
178
-
179
- base_type
180
- end
181
- end
182
-
183
- def updatable?
184
- [:integer, :datetime, :boolean].include?(type) && !is_string?
185
- end
186
-
187
- def live_value(instance)
188
- object = instance
189
- column = @columns.first
190
- column.__stack.each { |method|
191
- object = object.send(method)
192
- return sphinx_value(nil) if object.nil?
193
- }
194
-
195
- sphinx_value object.send(column.__name)
196
- end
197
-
198
- def all_ints?
199
- all_of_type?(:integer)
200
- end
201
-
202
- def all_datetimes?
203
- all_of_type?(:datetime, :date, :timestamp)
204
- end
205
-
206
- def all_strings?
207
- all_of_type?(:string, :text)
208
- end
209
-
210
- private
211
-
212
- def source_value(offset, delta)
213
- if is_string?
214
- return "#{query_source.to_s.dasherize}; #{columns.first.__name}"
215
- end
216
-
217
- query = query(offset)
218
-
219
- if query_source == :ranged_query
220
- query += query_clause
221
- query += " AND #{query_delta.strip}" if delta
222
- "ranged-query; #{query}; #{range_query}"
223
- else
224
- query += " WHERE #{query_delta.strip}" if delta
225
- "query; #{query}"
226
- end
227
- end
228
-
229
- def query(offset)
230
- base_assoc = base_association_for_mva
231
- end_assoc = end_association_for_mva
232
- raise "Could not determine SQL for MVA" if base_assoc.nil?
233
-
234
- relation = Arel::Table.new(base_assoc.table)
235
-
236
- association_joins.each do |join|
237
- relation = relation.join(join.relation, Arel::OuterJoin).
238
- on(*join.association_join)
239
- end
240
-
241
- relation = relation.project "#{foreign_key_for_mva base_assoc} #{ThinkingSphinx.unique_id_expression(adapter, offset)} AS #{quote_column('id')}, #{primary_key_for_mva(end_assoc)} AS #{quote_column(unique_name)}"
242
-
243
- relation.to_sql
244
- end
245
-
246
- def query_clause
247
- foreign_key = foreign_key_for_mva base_association_for_mva
248
- " WHERE #{foreign_key} >= $start AND #{foreign_key} <= $end"
249
- end
250
-
251
- def query_delta
252
- foreign_key = foreign_key_for_mva base_association_for_mva
253
- <<-SQL
254
- #{foreign_key} IN (SELECT #{quote_column model.primary_key}
255
- FROM #{model.quoted_table_name}
256
- WHERE #{@source.index.delta_object.clause(model, true)})
257
- SQL
258
- end
259
-
260
- def range_query
261
- assoc = base_association_for_mva
262
- foreign_key = foreign_key_for_mva assoc
263
- "SELECT MIN(#{foreign_key}), MAX(#{foreign_key}) FROM #{quote_table_name assoc.table}"
264
- end
265
-
266
- def primary_key_for_mva(assoc)
267
- quote_with_table(
268
- assoc.table, assoc.primary_key_from_reflection || columns.first.__name
269
- )
270
- end
271
-
272
- def foreign_key_for_mva(assoc)
273
- quote_with_table assoc.table, assoc.reflection.primary_key_name
274
- end
275
-
276
- def end_association_for_mva
277
- @association_for_mva ||= associations[columns.first].detect { |assoc|
278
- assoc.has_column?(columns.first.__name)
279
- }
280
- end
281
-
282
- def base_association_for_mva
283
- @first_association_for_mva ||= begin
284
- assoc = end_association_for_mva
285
- while !assoc.parent.nil?
286
- assoc = assoc.parent
287
- end
288
-
289
- assoc
290
- end
291
- end
292
-
293
- def association_joins
294
- joins = []
295
- assoc = end_association_for_mva
296
- while assoc != base_association_for_mva
297
- joins << assoc.join
298
- assoc = assoc.parent
299
- end
300
-
301
- joins
302
- end
303
-
304
- def is_many_ints?
305
- concat_ws? && all_ints?
306
- end
307
-
308
- def is_many_datetimes?
309
- is_many? && all_datetimes?
310
- end
311
-
312
- def is_many_strings?
313
- is_many? && all_strings?
314
- end
315
-
316
- def translated_type_from_database
317
- case type_from_db = type_from_database
318
- when :integer
319
- integer_type_from_db
320
- when :datetime, :string, :float, :boolean
321
- type_from_db
322
- when :decimal
323
- :float
324
- when :timestamp, :date
325
- :datetime
326
- else
327
- raise <<-MESSAGE
328
-
329
- Cannot automatically map attribute #{unique_name} in #{@model.name} to an
330
- equivalent Sphinx type (integer, float, boolean, datetime, string as ordinal).
331
- You could try to explicitly convert the column's value in your define_index
332
- block:
333
- has "CAST(column AS INT)", :type => :integer, :as => :column
334
- MESSAGE
335
- end
336
- end
337
-
338
- def type_from_database
339
- column = column_from_db
340
- column.nil? ? nil : column.type
341
- end
342
-
343
- def integer_type_from_db
344
- column = column_from_db
345
- return nil if column.nil?
346
-
347
- case column.sql_type
348
- when adapter.bigint_pattern
349
- :bigint
350
- else
351
- :integer
352
- end
353
- end
354
-
355
- def column_from_db
356
- klass = @associations.values.flatten.first ?
357
- @associations.values.flatten.first.reflection.klass : @model
358
-
359
- klass.columns.detect { |col|
360
- @columns.collect { |c| c.__name.to_s }.include? col.name
361
- }
362
- end
363
-
364
- def all_of_type?(*column_types)
365
- @columns.all? { |col|
366
- klasses = @associations[col].empty? ? [@model] :
367
- @associations[col].collect { |assoc| assoc.reflection.klass }
368
- klasses.all? { |klass|
369
- column = klass.columns.detect { |column| column.name == col.__name.to_s }
370
- !column.nil? && column_types.include?(column.type)
371
- }
372
- }
373
- end
374
-
375
- def sphinx_value(value)
376
- case value
377
- when TrueClass
378
- 1
379
- when FalseClass, NilClass
380
- 0
381
- when Time
382
- value.to_i
383
- when Date
384
- value.to_time.to_i
385
- when String
386
- value.to_crc32
387
- else
388
- value
389
- end
390
- end
391
-
392
- def insensitive?
393
- @sortable == :insensitive
394
- end
395
- end
396
- end