thinking-sphinx 3.0.3 → 3.0.4

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 (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -1
  3. data/HISTORY +25 -0
  4. data/lib/thinking_sphinx.rb +1 -0
  5. data/lib/thinking_sphinx/active_record/association_proxy.rb +13 -55
  6. data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +47 -0
  7. data/lib/thinking_sphinx/active_record/base.rb +16 -15
  8. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -12
  9. data/lib/thinking_sphinx/active_record/database_adapters.rb +41 -42
  10. data/lib/thinking_sphinx/active_record/polymorpher.rb +7 -2
  11. data/lib/thinking_sphinx/active_record/property_query.rb +23 -19
  12. data/lib/thinking_sphinx/active_record/sql_builder.rb +108 -129
  13. data/lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb +28 -0
  14. data/lib/thinking_sphinx/active_record/sql_builder/query.rb +43 -0
  15. data/lib/thinking_sphinx/active_record/sql_builder/statement.rb +110 -0
  16. data/lib/thinking_sphinx/active_record/sql_source.rb +143 -138
  17. data/lib/thinking_sphinx/capistrano.rb +11 -8
  18. data/lib/thinking_sphinx/configuration.rb +57 -35
  19. data/lib/thinking_sphinx/connection.rb +15 -6
  20. data/lib/thinking_sphinx/core.rb +1 -0
  21. data/lib/thinking_sphinx/core/index.rb +18 -10
  22. data/lib/thinking_sphinx/core/settings.rb +9 -0
  23. data/lib/thinking_sphinx/deletion.rb +48 -0
  24. data/lib/thinking_sphinx/errors.rb +7 -0
  25. data/lib/thinking_sphinx/excerpter.rb +1 -0
  26. data/lib/thinking_sphinx/facet_search.rb +42 -19
  27. data/lib/thinking_sphinx/masks/scopes_mask.rb +7 -0
  28. data/lib/thinking_sphinx/middlewares.rb +27 -33
  29. data/lib/thinking_sphinx/middlewares/active_record_translator.rb +18 -18
  30. data/lib/thinking_sphinx/middlewares/geographer.rb +49 -16
  31. data/lib/thinking_sphinx/middlewares/sphinxql.rb +128 -58
  32. data/lib/thinking_sphinx/panes/excerpts_pane.rb +7 -3
  33. data/lib/thinking_sphinx/rake_interface.rb +10 -0
  34. data/lib/thinking_sphinx/real_time.rb +7 -1
  35. data/lib/thinking_sphinx/real_time/attribute.rb +4 -0
  36. data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +14 -10
  37. data/lib/thinking_sphinx/real_time/index.rb +20 -12
  38. data/lib/thinking_sphinx/search/glaze.rb +5 -0
  39. data/lib/thinking_sphinx/search/query.rb +4 -8
  40. data/lib/thinking_sphinx/tasks.rb +8 -0
  41. data/spec/acceptance/excerpts_spec.rb +22 -0
  42. data/spec/acceptance/remove_deleted_records_spec.rb +10 -0
  43. data/spec/acceptance/searching_across_models_spec.rb +10 -0
  44. data/spec/acceptance/searching_with_filters_spec.rb +15 -0
  45. data/spec/acceptance/specifying_sql_spec.rb +3 -3
  46. data/spec/acceptance/sphinx_scopes_spec.rb +11 -0
  47. data/spec/internal/app/indices/product_index.rb +2 -0
  48. data/spec/internal/app/models/categorisation.rb +6 -0
  49. data/spec/internal/app/models/category.rb +3 -0
  50. data/spec/internal/app/models/product.rb +4 -1
  51. data/spec/internal/db/schema.rb +10 -0
  52. data/spec/thinking_sphinx/active_record/base_spec.rb +33 -0
  53. data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +4 -35
  54. data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +5 -10
  55. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +4 -3
  56. data/spec/thinking_sphinx/configuration_spec.rb +26 -6
  57. data/spec/thinking_sphinx/connection_spec.rb +4 -1
  58. data/spec/thinking_sphinx/deletion_spec.rb +76 -0
  59. data/spec/thinking_sphinx/facet_search_spec.rb +54 -5
  60. data/spec/thinking_sphinx/panes/excerpts_pane_spec.rb +4 -6
  61. data/spec/thinking_sphinx/rake_interface_spec.rb +35 -0
  62. data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +68 -28
  63. data/spec/thinking_sphinx/search/glaze_spec.rb +19 -0
  64. data/spec/thinking_sphinx/search/query_spec.rb +39 -2
  65. data/thinking-sphinx.gemspec +2 -2
  66. metadata +31 -45
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
1
3
  class ThinkingSphinx::Middlewares::Geographer <
2
4
  ThinkingSphinx::Middlewares::Middleware
3
5
 
@@ -9,6 +11,8 @@ class ThinkingSphinx::Middlewares::Geographer <
9
11
  app.call contexts
10
12
  end
11
13
 
14
+ private
15
+
12
16
  class Inner
13
17
  def initialize(context)
14
18
  @context = context
@@ -25,29 +29,58 @@ class ThinkingSphinx::Middlewares::Geographer <
25
29
 
26
30
  attr_reader :context
27
31
 
28
- def attribute_names
29
- @attribute_names ||= context[:indices].collect(&:unique_attribute_names).
30
- flatten.uniq
31
- end
32
+ delegate :geo, :latitude, :longitude, :to => :geolocation_attributes
32
33
 
33
- def geo
34
- context.search.options[:geo]
34
+ def geolocation_attributes
35
+ @geolocation_attributes ||= GeolocationAttributes.new(context)
35
36
  end
36
37
 
37
38
  def geodist_clause
38
- "GEODIST(#{geo.first}, #{geo.last}, #{latitude_attribute}, #{longitude_attribute}) AS geodist"
39
+ "GEODIST(#{geo.first}, #{geo.last}, #{latitude}, #{longitude}) AS geodist"
39
40
  end
40
41
 
41
- def latitude_attribute
42
- context.search.options[:latitude_attr] ||
43
- attribute_names.detect { |attribute| attribute == 'lat' } ||
44
- attribute_names.detect { |attribute| attribute == 'latitude' } || 'lat'
45
- end
42
+ class GeolocationAttributes
43
+ def initialize(context)
44
+ self.context = context
45
+ self.latitude = latitude_attr if latitude_attr
46
+ self.longitude = longitude_attr if longitude_attr
47
+ end
48
+
49
+ def geo
50
+ search_context_options[:geo]
51
+ end
52
+ attr_accessor :latitude, :longitude
53
+
54
+ def latitude
55
+ @latitude ||= names.detect { |name| %w[lat latitude].include?(name) } || 'lat'
56
+ end
57
+
58
+ def longitude
59
+ @longitude ||= names.detect { |name| %w[lng longitude].include?(name) } || 'lng'
60
+ end
61
+
62
+ private
63
+ attr_accessor :context
64
+
65
+ def latitude_attr
66
+ @latitude_attr ||= search_context_options[:latitude_attr]
67
+ end
68
+
69
+ def longitude_attr
70
+ @longitude_attr ||= search_context_options[:longitude_attr]
71
+ end
72
+
73
+ def indices
74
+ context[:indices]
75
+ end
76
+
77
+ def names
78
+ @names ||= indices.collect(&:unique_attribute_names).flatten.uniq
79
+ end
46
80
 
47
- def longitude_attribute
48
- context.search.options[:longitude_attr] ||
49
- attribute_names.detect { |attribute| attribute == 'lng' } ||
50
- attribute_names.detect { |attribute| attribute == 'longitude' } || 'lng'
81
+ def search_context_options
82
+ @search_context_options ||= context.search.options
83
+ end
51
84
  end
52
85
  end
53
86
  end
@@ -37,11 +37,26 @@ class ThinkingSphinx::Middlewares::SphinxQL <
37
37
  classes + descendants
38
38
  end
39
39
 
40
- def class_condition
41
- class_names = classes_and_descendants.collect(&:name).collect { |name|
42
- name[/:/] ? "\"#{name}\"" : name
40
+ def classes_and_descendants_names
41
+ classes_and_descendants.collect do |klass|
42
+ name = klass.name
43
+ name = %Q{"#{name}"} if name[/:/]
44
+ name
45
+ end
46
+ end
47
+
48
+ def classes_with_inheritance_column
49
+ classes.select { |klass|
50
+ klass.column_names.include?(klass.inheritance_column)
43
51
  }
44
- '(' + class_names.join('|') + ')'
52
+ end
53
+
54
+ def class_condition
55
+ "(#{classes_and_descendants_names.join('|')})"
56
+ end
57
+
58
+ def constantize_inheritance_column(klass)
59
+ klass.connection.select_values(inheritance_column_select(klass)).compact.each(&:constantize)
45
60
  end
46
61
 
47
62
  def descendants
@@ -49,51 +64,45 @@ class ThinkingSphinx::Middlewares::SphinxQL <
49
64
  end
50
65
 
51
66
  def descendants_from_tables
52
- classes.select { |klass|
53
- klass.column_names.include?(klass.inheritance_column)
54
- }.collect { |klass|
55
- klass.connection.select_values(<<-SQL).compact.each(&:constantize)
56
- SELECT DISTINCT #{klass.inheritance_column}
57
- FROM #{klass.table_name}
58
- SQL
67
+ classes_with_inheritance_column.collect do |klass|
68
+ constantize_inheritance_column(klass)
59
69
  klass.descendants
60
- }.flatten
70
+ end.flatten
71
+ end
72
+
73
+ def inheritance_column_select(klass)
74
+ <<-SQL
75
+ SELECT DISTINCT #{klass.inheritance_column}
76
+ FROM #{klass.table_name}
77
+ SQL
61
78
  end
62
79
 
63
80
  def exclusive_filters
64
81
  @exclusive_filters ||= (options[:without] || {}).tap do |without|
65
- if options[:without_ids].present? && options[:without_ids].any?
66
- without[:sphinx_internal_id] = options[:without_ids]
67
- end
82
+ without[:sphinx_internal_id] = options[:without_ids] if options[:without_ids].present?
68
83
  end
69
84
  end
70
85
 
71
86
  def extended_query
72
87
  conditions = options[:conditions] || {}
73
88
  conditions[:sphinx_internal_class_name] = class_condition if classes.any?
74
- @extended_query ||= begin
75
- ThinkingSphinx::Search::Query.new(context.search.query, conditions,
76
- options[:star]).to_s
77
- end
89
+ @extended_query ||= ThinkingSphinx::Search::Query.new(
90
+ context.search.query, conditions, options[:star]
91
+ ).to_s
78
92
  end
79
93
 
80
94
  def group_attribute
81
- options[:group_by] ? options[:group_by].to_s : nil
95
+ options[:group_by].to_s if options[:group_by]
82
96
  end
83
97
 
84
98
  def group_order_clause
85
- case options[:order_group_by]
86
- when Symbol
87
- "#{options[:order_group_by]} ASC"
88
- else
89
- options[:order_group_by]
90
- end
99
+ group_by = options[:order_group_by]
100
+ group_by = "#{group_by} ASC" if group_by.is_a? Symbol
101
+ group_by
91
102
  end
92
103
 
93
104
  def inclusive_filters
94
- @inclusive_filters ||= (options[:with] || {}).tap do |with|
95
- with[:sphinx_deleted] = false
96
- end
105
+ (options[:with] || {}).merge({:sphinx_deleted => false})
97
106
  end
98
107
 
99
108
  def index_names
@@ -108,17 +117,13 @@ class ThinkingSphinx::Middlewares::SphinxQL <
108
117
  @indices ||= ThinkingSphinx::IndexSet.new classes, options[:indices]
109
118
  end
110
119
 
111
- def options
112
- context.search.options
113
- end
120
+ delegate :search, :to => :context
121
+ delegate :options, :to => :search
114
122
 
115
123
  def order_clause
116
- case options[:order]
117
- when Symbol
118
- "#{options[:order]} ASC"
119
- else
120
- options[:order]
121
- end
124
+ order_by = options[:order]
125
+ order_by = "#{order_by} ASC" if order_by.is_a? Symbol
126
+ order_by
122
127
  end
123
128
 
124
129
  def select_options
@@ -130,31 +135,96 @@ class ThinkingSphinx::Middlewares::SphinxQL <
130
135
  end
131
136
  end
132
137
 
133
- def settings
134
- context.configuration.settings
138
+ delegate :configuration, :to => :context
139
+ delegate :settings, :to => :configuration
140
+
141
+ def values
142
+ options[:select] ||= '*, @groupby, @count' if group_attribute.present?
143
+ options[:select]
135
144
  end
136
145
 
137
146
  def statement
138
- Riddle::Query::Select.new.tap do |select|
139
- select.from *index_names.collect { |index| "`#{index}`" }
140
- select.values values if values.present?
141
- select.matching extended_query if extended_query.present?
142
- select.where inclusive_filters if inclusive_filters.any?
143
- select.where_all options[:with_all] if options[:with_all]
144
- select.where_not exclusive_filters if exclusive_filters.any?
145
- select.where_not_all options[:without_all] if options[:without_all]
146
- select.order_by order_clause if order_clause.present?
147
- select.group_by group_attribute if group_attribute.present?
148
- select.order_within_group_by group_order_clause if group_order_clause.present?
149
- select.offset context.search.offset
150
- select.limit context.search.per_page
151
- select.with_options select_options if select_options.keys.any?
152
- end
147
+ Statement.new(self).to_riddle_query_select
153
148
  end
154
149
 
155
- def values
156
- options[:select] ||= '*, @groupby, @count' if group_attribute.present?
157
- options[:select]
150
+ class Statement
151
+ def initialize(report)
152
+ self.report = report
153
+ self.query = Riddle::Query::Select.new
154
+ end
155
+
156
+ def to_riddle_query_select
157
+ filter_by_scopes
158
+
159
+ query
160
+ end
161
+
162
+ protected
163
+ attr_accessor :report, :query
164
+ def filter_by_scopes
165
+ scope_by_from
166
+ scope_by_values
167
+ scope_by_extended_query
168
+ scope_by_inclusive_filters
169
+ scope_by_with_all
170
+ scope_by_exclusive_filters
171
+ scope_by_without_all
172
+
173
+ scope_by_order
174
+ scope_by_group
175
+ scope_by_pagination
176
+ scope_by_options
177
+ end
178
+
179
+ def scope_by_from
180
+ query.from *(index_names.collect { |index| "`#{index}`" })
181
+ end
182
+
183
+ def scope_by_values
184
+ query.values values if values.present?
185
+ end
186
+
187
+ def scope_by_extended_query
188
+ query.matching extended_query if extended_query.present?
189
+ end
190
+
191
+ def scope_by_inclusive_filters
192
+ query.where inclusive_filters if inclusive_filters.any?
193
+ end
194
+
195
+ def scope_by_with_all
196
+ query.where_all options[:with_all] if options[:with_all]
197
+ end
198
+
199
+ def scope_by_exclusive_filters
200
+ query.where_not exclusive_filters if exclusive_filters.any?
201
+ end
202
+
203
+ def scope_by_without_all
204
+ query.where_not_all options[:without_all] if options[:without_all]
205
+ end
206
+
207
+ def scope_by_order
208
+ query.order_by order_clause if order_clause.present?
209
+ end
210
+
211
+ def scope_by_group
212
+ query.group_by group_attribute if group_attribute.present?
213
+ query.order_within_group_by group_order_clause if group_order_clause.present?
214
+ end
215
+
216
+ def scope_by_pagination
217
+ query.offset context.search.offset
218
+ query.limit context.search.per_page
219
+ end
220
+
221
+ def scope_by_options
222
+ query.with_options select_options if select_options.keys.any?
223
+ end
224
+
225
+ def method_missing(*args, &block)
226
+ report.send *args, &block
227
+ end
158
228
  end
159
229
  end
160
230
  end
@@ -18,9 +18,13 @@ class ThinkingSphinx::Panes::ExcerptsPane
18
18
  end
19
19
 
20
20
  def excerpt_words
21
- @excerpt_words ||= @context[:meta].keys.select { |key|
22
- key[/^keyword\[/]
23
- }.sort.collect { |key| @context[:meta][key] }.join(' ')
21
+ @excerpt_words ||= begin
22
+ conditions = @context.search.options[:conditions] || {}
23
+ ThinkingSphinx::Search::Query.new(
24
+ ([@context.search.query] + conditions.values).compact.join(' '),
25
+ {}, @context.search.options[:star]
26
+ ).to_s
27
+ end
24
28
  end
25
29
 
26
30
  class Excerpts
@@ -1,4 +1,13 @@
1
1
  class ThinkingSphinx::RakeInterface
2
+ def clear
3
+ [
4
+ configuration.indices_location,
5
+ configuration.searchd.binlog_path
6
+ ].each do |path|
7
+ FileUtils.rm_r(path) if File.exists?(path)
8
+ end
9
+ end
10
+
2
11
  def configure
3
12
  puts "Generating configuration to #{configuration.configuration_file}"
4
13
  configuration.render_to_file
@@ -37,6 +46,7 @@ class ThinkingSphinx::RakeInterface
37
46
  def start
38
47
  raise RuntimeError, 'searchd is already running' if controller.running?
39
48
 
49
+ FileUtils.mkdir_p configuration.indices_location
40
50
  controller.start
41
51
 
42
52
  if controller.running?
@@ -1,5 +1,11 @@
1
1
  module ThinkingSphinx::RealTime
2
- module Callbacks; end
2
+ module Callbacks
3
+ #
4
+ end
5
+
6
+ def self.callback_for(reference, path = [])
7
+ Callbacks::RealTimeCallbacks.new reference, path
8
+ end
3
9
  end
4
10
 
5
11
  require 'thinking_sphinx/real_time/property'
@@ -1,4 +1,8 @@
1
1
  class ThinkingSphinx::RealTime::Attribute < ThinkingSphinx::RealTime::Property
2
+ def multi?
3
+ @options[:multi]
4
+ end
5
+
2
6
  def type
3
7
  @options[:type]
4
8
  end
@@ -1,18 +1,22 @@
1
- class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks <
2
- ThinkingSphinx::Callbacks
3
-
4
- callbacks :after_save
1
+ class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks
2
+ def initialize(reference, path = [])
3
+ @reference, @path = reference, path
4
+ end
5
5
 
6
- def after_save
6
+ def after_save(instance)
7
7
  return unless real_time_indices?
8
8
 
9
9
  real_time_indices.each do |index|
10
- ThinkingSphinx::RealTime::Transcriber.new(index).copy instance
10
+ objects_for(instance).each do |object|
11
+ ThinkingSphinx::RealTime::Transcriber.new(index).copy object
12
+ end
11
13
  end
12
14
  end
13
15
 
14
16
  private
15
17
 
18
+ attr_reader :reference, :path
19
+
16
20
  def configuration
17
21
  ThinkingSphinx::Configuration.instance
18
22
  end
@@ -21,6 +25,10 @@ class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks <
21
25
  @indices ||= configuration.indices_for_references reference
22
26
  end
23
27
 
28
+ def objects_for(instance)
29
+ Array(path.inject(instance) { |object, method| object.send method })
30
+ end
31
+
24
32
  def real_time_indices?
25
33
  real_time_indices.any?
26
34
  end
@@ -30,8 +38,4 @@ class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks <
30
38
  index.is_a? ThinkingSphinx::RealTime::Index
31
39
  }
32
40
  end
33
-
34
- def reference
35
- instance.class.name.underscore.to_sym
36
- end
37
41
  end
@@ -49,6 +49,25 @@ class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
49
49
 
50
50
  private
51
51
 
52
+ def append_unique_attribute(collection, attribute)
53
+ collection << attribute.name unless collection.include?(attribute.name)
54
+ end
55
+
56
+ def collection_for(attribute)
57
+ case attribute.type
58
+ when :integer, :boolean
59
+ attribute.multi? ? @rt_attr_multi : @rt_attr_uint
60
+ when :string
61
+ @rt_attr_string
62
+ when :timestamp
63
+ @rt_attr_timestamp
64
+ when :float
65
+ @rt_attr_float
66
+ else
67
+ raise "Unknown attribute type '#{attribute.type}'"
68
+ end
69
+ end
70
+
52
71
  def interpreter
53
72
  ThinkingSphinx::RealTime::Interpreter
54
73
  end
@@ -59,18 +78,7 @@ class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
59
78
  @rt_field = fields.collect &:name
60
79
 
61
80
  attributes.each do |attribute|
62
- case attribute.type
63
- when :integer, :boolean
64
- @rt_attr_uint << attribute.name unless @rt_attr_uint.include?(attribute.name)
65
- when :string
66
- @rt_attr_string << attribute.name unless @rt_attr_string.include?(attribute.name)
67
- when :timestamp
68
- @rt_attr_timestamp << attribute.name unless @rt_attr_timestamp.include?(attribute.name)
69
- when :float
70
- @rt_attr_float << attribute.name unless @rt_attr_float.include?(attribute.name)
71
- else
72
- raise "Unknown attribute type '#{attribute.type}'"
73
- end
81
+ append_unique_attribute collection_for(attribute), attribute
74
82
  end
75
83
  end
76
84