thinking-sphinx 3.0.3 → 3.0.4

Sign up to get free protection for your applications and to get access to all the features.
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