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,98 +0,0 @@
1
- module ThinkingSphinx
2
- # Fields - holding the string data which Sphinx indexes for your searches.
3
- # This class isn't really useful to you unless you're hacking around with the
4
- # internals of Thinking Sphinx - but hey, don't let that stop you.
5
- #
6
- # One key thing to remember - if you're using the field manually to
7
- # generate SQL statements, you'll need to set the base model, and all the
8
- # associations. Which can get messy. Use Index.link!, it really helps.
9
- #
10
- class Field < ThinkingSphinx::Property
11
- attr_accessor :sortable, :infixes, :prefixes
12
-
13
- # To create a new field, you'll need to pass in either a single Column
14
- # or an array of them, and some (optional) options. The columns are
15
- # references to the data that will make up the field.
16
- #
17
- # Valid options are:
18
- # - :as => :alias_name
19
- # - :sortable => true
20
- # - :infixes => true
21
- # - :prefixes => true
22
- # - :file => true
23
- # - :with => :attribute # or :wordcount
24
- #
25
- # Alias is only required in three circumstances: when there's
26
- # another attribute or field with the same name, when the column name is
27
- # 'id', or when there's more than one column.
28
- #
29
- # Sortable defaults to false - but is quite useful when set to true, as
30
- # it creates an attribute with the same string value (which Sphinx converts
31
- # to an integer value), which can be sorted by. Thinking Sphinx is smart
32
- # enough to realise that when you specify fields in sort statements, you
33
- # mean their respective attributes.
34
- #
35
- # If you have partial matching enabled (ie: enable_star), then you can
36
- # specify certain fields to have their prefixes and infixes indexed. Keep
37
- # in mind, though, that Sphinx's default is _all_ fields - so once you
38
- # highlight a particular field, no other fields in the index will have
39
- # these partial indexes.
40
- #
41
- # Here's some examples:
42
- #
43
- # Field.new(
44
- # Column.new(:name)
45
- # )
46
- #
47
- # Field.new(
48
- # [Column.new(:first_name), Column.new(:last_name)],
49
- # :as => :name, :sortable => true
50
- # )
51
- #
52
- # Field.new(
53
- # [Column.new(:posts, :subject), Column.new(:posts, :content)],
54
- # :as => :posts, :prefixes => true
55
- # )
56
- #
57
- def initialize(source, columns, options = {})
58
- super
59
-
60
- @sortable = options[:sortable] || false
61
- @infixes = options[:infixes] || false
62
- @prefixes = options[:prefixes] || false
63
- @file = options[:file] || false
64
- @with = options[:with]
65
-
66
- source.fields << self
67
- end
68
-
69
- # Get the part of the SELECT clause related to this field. Don't forget
70
- # to set your model and associations first though.
71
- #
72
- # This will concatenate strings if there's more than one data source or
73
- # multiple data values (has_many or has_and_belongs_to_many associations).
74
- #
75
- def to_select_sql
76
- return nil unless available?
77
-
78
- clause = columns_with_prefixes.join(', ')
79
-
80
- clause = adapter.concatenate(clause) if concat_ws?
81
- clause = adapter.group_concatenate(clause) if is_many?
82
-
83
- "#{clause} AS #{quote_column(unique_name)}"
84
- end
85
-
86
- def file?
87
- @file
88
- end
89
-
90
- def with_attribute?
91
- @with == :attribute
92
- end
93
-
94
- def with_wordcount?
95
- @with == :wordcount
96
- end
97
- end
98
- end
@@ -1,157 +0,0 @@
1
- require 'thinking_sphinx/index/builder'
2
- require 'thinking_sphinx/index/faux_column'
3
-
4
- module ThinkingSphinx
5
- class Index
6
- attr_accessor :name, :model, :sources, :delta_object
7
-
8
- # Create a new index instance by passing in the model it is tied to, and
9
- # a block to build it with (optional but recommended). For documentation
10
- # on the syntax for inside the block, the Builder class is what you want.
11
- #
12
- # Quick Example:
13
- #
14
- # Index.new(User) do
15
- # indexes login, email
16
- #
17
- # has created_at
18
- #
19
- # set_property :delta => true
20
- # end
21
- #
22
- def initialize(model, &block)
23
- @name = self.class.name_for model
24
- @model = model
25
- @sources = []
26
- @options = {}
27
- @delta_object = nil
28
- end
29
-
30
- def fields
31
- @sources.collect { |source| source.fields }.flatten
32
- end
33
-
34
- def attributes
35
- @sources.collect { |source| source.attributes }.flatten
36
- end
37
-
38
- def core_name
39
- "#{name}_core"
40
- end
41
-
42
- def delta_name
43
- "#{name}_delta"
44
- end
45
-
46
- def all_names
47
- names = [core_name]
48
- names << delta_name if delta?
49
-
50
- names
51
- end
52
-
53
- def self.name_for(model)
54
- model.name.underscore.tr(':/\\', '_')
55
- end
56
-
57
- def prefix_fields
58
- fields.select { |field| field.prefixes }
59
- end
60
-
61
- def infix_fields
62
- fields.select { |field| field.infixes }
63
- end
64
-
65
- def local_options
66
- @options
67
- end
68
-
69
- def options
70
- all_index_options = config.index_options.clone
71
- @options.keys.select { |key|
72
- ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s) ||
73
- ThinkingSphinx::Configuration::CustomOptions.include?(key.to_s)
74
- }.each { |key| all_index_options[key.to_sym] = @options[key] }
75
- all_index_options
76
- end
77
-
78
- def delta?
79
- !@delta_object.nil?
80
- end
81
-
82
- def to_riddle(offset)
83
- indexes = [to_riddle_for_core(offset)]
84
- indexes << to_riddle_for_delta(offset) if delta?
85
- indexes << to_riddle_for_distributed
86
- end
87
-
88
- private
89
-
90
- def adapter
91
- @adapter ||= @model.sphinx_database_adapter
92
- end
93
-
94
- def utf8?
95
- options[:charset_type] == "utf-8"
96
- end
97
-
98
- def sql_query_pre_for_delta
99
- [""]
100
- end
101
-
102
- def config
103
- @config ||= ThinkingSphinx::Configuration.instance
104
- end
105
-
106
- def to_riddle_for_core(offset)
107
- index = Riddle::Configuration::Index.new core_name
108
- index.path = File.join config.searchd_file_path, index.name
109
-
110
- set_configuration_options_for_indexes index
111
- set_field_settings_for_indexes index
112
-
113
- sources.each_with_index do |source, i|
114
- index.sources << source.to_riddle_for_core(offset, i)
115
- end
116
-
117
- index
118
- end
119
-
120
- def to_riddle_for_delta(offset)
121
- index = Riddle::Configuration::Index.new delta_name
122
- index.parent = core_name
123
- index.path = File.join config.searchd_file_path, index.name
124
-
125
- sources.each_with_index do |source, i|
126
- index.sources << source.to_riddle_for_delta(offset, i)
127
- end
128
-
129
- index
130
- end
131
-
132
- def to_riddle_for_distributed
133
- index = Riddle::Configuration::DistributedIndex.new name
134
- index.local_indexes << core_name
135
- index.local_indexes.unshift delta_name if delta?
136
- index
137
- end
138
-
139
- def set_configuration_options_for_indexes(index)
140
- config.index_options.each do |key, value|
141
- method = "#{key}=".to_sym
142
- index.send(method, value) if index.respond_to?(method)
143
- end
144
-
145
- options.each do |key, value|
146
- index.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s) && !value.nil?
147
- end
148
- end
149
-
150
- def set_field_settings_for_indexes(index)
151
- field_names = lambda { |field| field.unique_name.to_s }
152
-
153
- index.prefix_field_names += prefix_fields.collect(&field_names)
154
- index.infix_field_names += infix_fields.collect(&field_names)
155
- end
156
- end
157
- end
@@ -1,312 +0,0 @@
1
- module ThinkingSphinx
2
- class Index
3
- # The Builder class is the core for the index definition block processing.
4
- # There are four methods you really need to pay attention to:
5
- # - indexes
6
- # - has
7
- # - where
8
- # - set_property/set_properties
9
- #
10
- # The first two of these methods allow you to define what data makes up
11
- # your indexes. #where provides a method to add manual SQL conditions, and
12
- # set_property allows you to set some settings on a per-index basis. Check
13
- # out each method's documentation for better ideas of usage.
14
- #
15
- class Builder
16
- instance_methods.grep(/^[^_]/).each { |method|
17
- next if method.to_s == "instance_eval"
18
- define_method(method) {
19
- caller.grep(/irb.completion/).empty? ? method_missing(method) : super
20
- }
21
- }
22
-
23
- def self.generate(model, name = nil, &block)
24
- index = ThinkingSphinx::Index.new(model)
25
- index.name = name unless name.nil?
26
-
27
- Builder.new(index, &block) if block_given?
28
-
29
- index.delta_object = ThinkingSphinx::Deltas.parse index
30
- index
31
- end
32
-
33
- def initialize(index, &block)
34
- @index = index
35
- @explicit_source = false
36
-
37
- self.instance_eval &block
38
-
39
- if no_fields?
40
- raise "At least one field is necessary for an index"
41
- end
42
- end
43
-
44
- def define_source(&block)
45
- if @explicit_source
46
- @source = ThinkingSphinx::Source.new(@index)
47
- @index.sources << @source
48
- else
49
- @explicit_source = true
50
- end
51
-
52
- self.instance_eval &block
53
- end
54
-
55
- # This is how you add fields - the strings Sphinx looks at - to your
56
- # index. Technically, to use this method, you need to pass in some
57
- # columns and options - but there's some neat method_missing stuff
58
- # happening, so lets stick to the expected syntax within a define_index
59
- # block.
60
- #
61
- # Expected options are :as, which points to a column alias in symbol
62
- # form, and :sortable, which indicates whether you want to sort by this
63
- # field.
64
- #
65
- # Adding Single-Column Fields:
66
- #
67
- # You can use symbols or methods - and can chain methods together to
68
- # get access down the associations tree.
69
- #
70
- # indexes :id, :as => :my_id
71
- # indexes :name, :sortable => true
72
- # indexes first_name, last_name, :sortable => true
73
- # indexes users.posts.content, :as => :post_content
74
- # indexes users(:id), :as => :user_ids
75
- #
76
- # Keep in mind that if any keywords for Ruby methods - such as id or
77
- # name - clash with your column names, you need to use the symbol
78
- # version (see the first, second and last examples above).
79
- #
80
- # If you specify multiple columns (example #2), a field will be created
81
- # for each. Don't use the :as option in this case. If you want to merge
82
- # those columns together, continue reading.
83
- #
84
- # Adding Multi-Column Fields:
85
- #
86
- # indexes [first_name, last_name], :as => :name
87
- # indexes [location, parent.location], :as => :location
88
- #
89
- # To combine multiple columns into a single field, you need to wrap
90
- # them in an Array, as shown by the above examples. There's no
91
- # limitations on whether they're symbols or methods or what level of
92
- # associations they come from.
93
- #
94
- # Adding SQL Fragment Fields
95
- #
96
- # You can also define a field using an SQL fragment, useful for when
97
- # you would like to index a calculated value.
98
- #
99
- # indexes "age < 18", :as => :minor
100
- #
101
- def indexes(*args)
102
- options = args.extract_options!
103
- args.each do |columns|
104
- field = Field.new(source, FauxColumn.coerce(columns), options)
105
-
106
- add_sort_attribute field, options if field.sortable
107
- add_facet_attribute field, options if field.faceted
108
- end
109
- end
110
-
111
- # This is the method to add attributes to your index (hence why it is
112
- # aliased as 'attribute'). The syntax is the same as #indexes, so use
113
- # that as starting point, but keep in mind the following points.
114
- #
115
- # An attribute can have an alias (the :as option), but it is always
116
- # sortable - so you don't need to explicitly request that. You _can_
117
- # specify the data type of the attribute (the :type option), but the
118
- # code's pretty good at figuring that out itself from peering into the
119
- # database.
120
- #
121
- # Attributes are limited to the following types: integers, floats,
122
- # datetimes (converted to timestamps), booleans, strings and MVAs
123
- # (:multi). Don't forget that Sphinx converts string attributes to
124
- # integers, which are useful for sorting, but that's about it.
125
- #
126
- # Collection of integers are known as multi-value attributes (MVAs).
127
- # Generally these would be through a has_many relationship, like in this
128
- # example:
129
- #
130
- # has posts(:id), :as => :post_ids
131
- #
132
- # This allows you to filter on any of the values tied to a specific
133
- # record. Might be best to read through the Sphinx documentation to get
134
- # a better idea of that though.
135
- #
136
- # Adding SQL Fragment Attributes
137
- #
138
- # You can also define an attribute using an SQL fragment, useful for
139
- # when you would like to index a calculated value. Don't forget to set
140
- # the type of the attribute though:
141
- #
142
- # has "age < 18", :as => :minor, :type => :boolean
143
- #
144
- # If you're creating attributes for latitude and longitude, don't
145
- # forget that Sphinx expects these values to be in radians.
146
- #
147
- def has(*args)
148
- options = args.extract_options!
149
- args.each do |columns|
150
- attribute = Attribute.new(source, FauxColumn.coerce(columns), options)
151
-
152
- add_facet_attribute attribute, options if attribute.faceted
153
- end
154
- end
155
-
156
- def facet(*args)
157
- options = args.extract_options!
158
- options[:facet] = true
159
-
160
- args.each do |columns|
161
- attribute = Attribute.new(source, FauxColumn.coerce(columns), options)
162
-
163
- add_facet_attribute attribute, options
164
- end
165
- end
166
-
167
- def join(*args)
168
- args.each do |association|
169
- Join.new(source, association)
170
- end
171
- end
172
-
173
- # Use this method to add some manual SQL conditions for your index
174
- # request. You can pass in as many strings as you like, they'll get
175
- # joined together with ANDs later on.
176
- #
177
- # where "user_id = 10"
178
- # where "parent_type = 'Article'", "created_at < NOW()"
179
- #
180
- def where(*args)
181
- source.conditions += args
182
- end
183
-
184
- # Use this method to add some manual SQL strings to the GROUP BY
185
- # clause. You can pass in as many strings as you'd like, they'll get
186
- # joined together with commas later on.
187
- #
188
- # group_by "lat", "lng"
189
- #
190
- def group_by(*args)
191
- source.groupings += args
192
- end
193
-
194
- # This is what to use to set properties on the index. Chief amongst
195
- # those is the delta property - to allow automatic updates to your
196
- # indexes as new models are added and edited - but also you can
197
- # define search-related properties which will be the defaults for all
198
- # searches on the model.
199
- #
200
- # set_property :delta => true
201
- # set_property :field_weights => {"name" => 100}
202
- # set_property :order => "name ASC"
203
- # set_property :select => 'name'
204
- #
205
- # Also, the following two properties are particularly relevant for
206
- # geo-location searching - latitude_attr and longitude_attr. If your
207
- # attributes for these two values are named something other than
208
- # lat/latitude or lon/long/longitude, you can dictate what they are
209
- # when defining the index, so you don't need to specify them for every
210
- # geo-related search.
211
- #
212
- # set_property :latitude_attr => "lt", :longitude_attr => "lg"
213
- #
214
- # Please don't forget to add a boolean field named 'delta' to your
215
- # model's database table if enabling the delta index for it.
216
- # Valid options for the delta property are:
217
- #
218
- # true
219
- # false
220
- # :default
221
- # :delayed
222
- # :datetime
223
- #
224
- # You can also extend ThinkingSphinx::Deltas::DefaultDelta to implement
225
- # your own handling for delta indexing.
226
- #
227
- def set_property(*args)
228
- options = args.extract_options!
229
- options.each do |key, value|
230
- set_single_property key, value
231
- end
232
-
233
- set_single_property args[0], args[1] if args.length == 2
234
- end
235
- alias_method :set_properties, :set_property
236
-
237
- # Handles the generation of new columns for the field and attribute
238
- # definitions.
239
- #
240
- def method_missing(method, *args)
241
- FauxColumn.new(method, *args)
242
- end
243
-
244
- # A method to allow adding fields from associations which have names
245
- # that clash with method names in the Builder class (ie: properties,
246
- # fields, attributes).
247
- #
248
- # Example: indexes assoc(:properties).column
249
- #
250
- def assoc(assoc, *args)
251
- FauxColumn.new(assoc, *args)
252
- end
253
-
254
- # Use this method to generate SQL for your attributes, conditions, etc.
255
- # You can pass in as whatever ActiveRecord::Base.sanitize_sql accepts.
256
- #
257
- # where sanitize_sql(["active = ?", true])
258
- # #=> WHERE active = 1
259
- #
260
- def sanitize_sql(*args)
261
- @index.model.send(:sanitize_sql, *args)
262
- end
263
-
264
- private
265
-
266
- def source
267
- @source ||= begin
268
- source = ThinkingSphinx::Source.new(@index)
269
- @index.sources << source
270
- source
271
- end
272
- end
273
-
274
- def set_single_property(key, value)
275
- source_options = ThinkingSphinx::Configuration::SourceOptions
276
- if source_options.include?(key.to_s)
277
- source.options.merge! key => value
278
- else
279
- @index.local_options.merge! key => value
280
- end
281
- end
282
-
283
- def add_sort_attribute(field, options)
284
- add_internal_attribute field, options, "_sort"
285
- end
286
-
287
- def add_facet_attribute(property, options)
288
- add_internal_attribute property, options, "_facet", true
289
- @index.model.sphinx_facets << property.to_facet
290
- end
291
-
292
- def add_internal_attribute(property, options, suffix, crc = false)
293
- return unless ThinkingSphinx::Facet.translate?(property)
294
-
295
- Attribute.new(source,
296
- property.columns.collect { |col| col.clone },
297
- options.merge(
298
- :type => property.is_a?(Field) ? :string : options[:type],
299
- :as => property.unique_name.to_s.concat(suffix).to_sym,
300
- :crc => crc
301
- ).except(:facet)
302
- )
303
- end
304
-
305
- def no_fields?
306
- @index.sources.empty? || @index.sources.any? { |source|
307
- source.fields.length == 0
308
- }
309
- end
310
- end
311
- end
312
- end