thinking-sphinx 1.5.0 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/README.textile +15 -48
  2. data/VERSION +1 -0
  3. data/features/attribute_transformation.feature +7 -7
  4. data/features/attribute_updates.feature +16 -18
  5. data/features/deleting_instances.feature +13 -16
  6. data/features/excerpts.feature +0 -8
  7. data/features/facets.feature +19 -25
  8. data/features/handling_edits.feature +20 -25
  9. data/features/searching_across_models.feature +1 -1
  10. data/features/searching_by_index.feature +5 -6
  11. data/features/searching_by_model.feature +29 -29
  12. data/features/sphinx_scopes.feature +0 -26
  13. data/features/step_definitions/common_steps.rb +6 -18
  14. data/features/step_definitions/scope_steps.rb +0 -4
  15. data/features/step_definitions/search_steps.rb +4 -9
  16. data/features/support/env.rb +10 -3
  17. data/features/thinking_sphinx/db/fixtures/alphas.rb +10 -8
  18. data/features/thinking_sphinx/db/fixtures/cats.rb +1 -1
  19. data/features/thinking_sphinx/db/fixtures/dogs.rb +1 -1
  20. data/features/thinking_sphinx/db/fixtures/foxes.rb +1 -1
  21. data/features/thinking_sphinx/db/fixtures/people.rb +1 -1
  22. data/features/thinking_sphinx/db/fixtures/posts.rb +1 -5
  23. data/features/thinking_sphinx/db/migrations/create_posts.rb +0 -1
  24. data/features/thinking_sphinx/models/alpha.rb +0 -1
  25. data/features/thinking_sphinx/models/beta.rb +0 -5
  26. data/features/thinking_sphinx/models/developer.rb +1 -6
  27. data/features/thinking_sphinx/models/music.rb +1 -3
  28. data/features/thinking_sphinx/models/person.rb +1 -2
  29. data/features/thinking_sphinx/models/post.rb +0 -1
  30. data/lib/cucumber/thinking_sphinx/external_world.rb +4 -8
  31. data/lib/cucumber/thinking_sphinx/internal_world.rb +27 -36
  32. data/lib/thinking_sphinx.rb +60 -132
  33. data/lib/thinking_sphinx/active_record.rb +98 -124
  34. data/lib/thinking_sphinx/active_record/attribute_updates.rb +13 -17
  35. data/lib/thinking_sphinx/active_record/delta.rb +15 -21
  36. data/lib/thinking_sphinx/active_record/has_many_association.rb +23 -16
  37. data/lib/thinking_sphinx/active_record/scopes.rb +0 -18
  38. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +15 -63
  39. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -4
  40. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +24 -65
  41. data/lib/thinking_sphinx/association.rb +11 -36
  42. data/lib/thinking_sphinx/attribute.rb +85 -92
  43. data/lib/thinking_sphinx/auto_version.rb +3 -21
  44. data/lib/thinking_sphinx/class_facet.rb +3 -8
  45. data/lib/thinking_sphinx/configuration.rb +58 -114
  46. data/lib/thinking_sphinx/context.rb +20 -22
  47. data/lib/thinking_sphinx/core/array.rb +13 -0
  48. data/lib/thinking_sphinx/deltas.rb +0 -2
  49. data/lib/thinking_sphinx/deltas/default_delta.rb +22 -18
  50. data/lib/thinking_sphinx/deploy/capistrano.rb +31 -30
  51. data/lib/thinking_sphinx/excerpter.rb +1 -2
  52. data/lib/thinking_sphinx/facet.rb +35 -45
  53. data/lib/thinking_sphinx/facet_search.rb +24 -58
  54. data/lib/thinking_sphinx/field.rb +0 -18
  55. data/lib/thinking_sphinx/index.rb +36 -38
  56. data/lib/thinking_sphinx/index/builder.rb +59 -74
  57. data/lib/thinking_sphinx/property.rb +45 -66
  58. data/lib/thinking_sphinx/railtie.rb +35 -0
  59. data/lib/thinking_sphinx/search.rb +250 -506
  60. data/lib/thinking_sphinx/source.rb +31 -50
  61. data/lib/thinking_sphinx/source/internal_properties.rb +3 -8
  62. data/lib/thinking_sphinx/source/sql.rb +31 -71
  63. data/lib/thinking_sphinx/tasks.rb +27 -48
  64. data/spec/thinking_sphinx/active_record/delta_spec.rb +41 -36
  65. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +0 -96
  66. data/spec/thinking_sphinx/active_record/scopes_spec.rb +29 -29
  67. data/spec/thinking_sphinx/active_record_spec.rb +169 -140
  68. data/spec/thinking_sphinx/association_spec.rb +2 -20
  69. data/spec/thinking_sphinx/attribute_spec.rb +97 -101
  70. data/spec/thinking_sphinx/auto_version_spec.rb +11 -75
  71. data/spec/thinking_sphinx/configuration_spec.rb +62 -63
  72. data/spec/thinking_sphinx/context_spec.rb +66 -66
  73. data/spec/thinking_sphinx/facet_search_spec.rb +99 -99
  74. data/spec/thinking_sphinx/facet_spec.rb +4 -30
  75. data/spec/thinking_sphinx/field_spec.rb +3 -17
  76. data/spec/thinking_sphinx/index/builder_spec.rb +132 -169
  77. data/spec/thinking_sphinx/index_spec.rb +39 -45
  78. data/spec/thinking_sphinx/search_methods_spec.rb +33 -37
  79. data/spec/thinking_sphinx/search_spec.rb +269 -491
  80. data/spec/thinking_sphinx/source_spec.rb +48 -62
  81. data/spec/thinking_sphinx_spec.rb +49 -49
  82. data/tasks/distribution.rb +46 -0
  83. data/tasks/testing.rb +74 -0
  84. metadata +123 -199
  85. data/features/field_sorting.feature +0 -18
  86. data/features/thinking_sphinx/db/.gitignore +0 -1
  87. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +0 -1
  88. data/features/thinking_sphinx/models/andrew.rb +0 -17
  89. data/lib/thinking-sphinx.rb +0 -1
  90. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
  91. data/lib/thinking_sphinx/bundled_search.rb +0 -40
  92. data/lib/thinking_sphinx/connection.rb +0 -71
  93. data/lib/thinking_sphinx/deltas/delete_job.rb +0 -16
  94. data/lib/thinking_sphinx/deltas/index_job.rb +0 -17
  95. data/lib/thinking_sphinx/rails_additions.rb +0 -181
  96. data/spec/fixtures/data.sql +0 -32
  97. data/spec/fixtures/database.yml.default +0 -3
  98. data/spec/fixtures/models.rb +0 -161
  99. data/spec/fixtures/structure.sql +0 -146
  100. data/spec/spec_helper.rb +0 -54
  101. data/spec/sphinx_helper.rb +0 -67
  102. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +0 -163
  103. data/spec/thinking_sphinx/connection_spec.rb +0 -77
  104. data/spec/thinking_sphinx/rails_additions_spec.rb +0 -203
@@ -1,7 +1,7 @@
1
1
  module ThinkingSphinx
2
2
  class Property
3
3
  attr_accessor :alias, :columns, :associations, :model, :faceted, :admin
4
-
4
+
5
5
  def initialize(source, columns, options = {})
6
6
  @source = source
7
7
  @model = source.model
@@ -9,27 +9,25 @@ module ThinkingSphinx
9
9
  @associations = {}
10
10
 
11
11
  raise "Cannot define a field or attribute in #{source.model.name} with no columns. Maybe you are trying to index a field with a reserved name (id, name). You can fix this error by using a symbol rather than a bare name (:id instead of id)." if @columns.empty? || @columns.any? { |column| !column.respond_to?(:__stack) }
12
-
13
- @alias = options[:as]
14
- @faceted = options[:facet]
15
- @admin = options[:admin]
16
- @sortable = options[:sortable] || false
17
- @value_source = options[:value]
18
-
12
+
13
+ @alias = options[:as]
14
+ @faceted = options[:facet]
15
+ @admin = options[:admin]
16
+
19
17
  @alias = @alias.to_sym unless @alias.blank?
20
-
18
+
21
19
  @columns.each { |col|
22
- @associations[col.__stack] = association_stack(col.__stack.clone).each { |assoc|
20
+ @associations[col] = association_stack(col.__stack.clone).each { |assoc|
23
21
  assoc.join_to(source.base)
24
22
  }
25
23
  }
26
24
  end
27
-
25
+
28
26
  # Returns the unique name of the attribute - which is either the alias of
29
27
  # the attribute, or the name of the only column - if there is only one. If
30
28
  # there isn't, there should be an alias. Else things probably won't work.
31
29
  # Consider yourself warned.
32
- #
30
+ #
33
31
  def unique_name
34
32
  if @columns.length == 1
35
33
  @alias || @columns.first.__name
@@ -37,19 +35,19 @@ module ThinkingSphinx
37
35
  @alias
38
36
  end
39
37
  end
40
-
38
+
41
39
  def to_facet
42
40
  return nil unless @faceted
43
-
44
- ThinkingSphinx::Facet.new(self, @value_source)
41
+
42
+ ThinkingSphinx::Facet.new(self)
45
43
  end
46
-
44
+
47
45
  # Get the part of the GROUP BY clause related to this attribute - if one is
48
46
  # needed. If not, all you'll get back is nil. The latter will happen if
49
47
  # there isn't actually a real column to get data from, or if there's
50
48
  # multiple data values (read: a has_many or has_and_belongs_to_many
51
49
  # association).
52
- #
50
+ #
53
51
  def to_group_sql
54
52
  case
55
53
  when is_many?, is_string?, ThinkingSphinx.use_group_by_shortcut?
@@ -60,128 +58,109 @@ module ThinkingSphinx
60
58
  }
61
59
  end
62
60
  end
63
-
61
+
64
62
  def changed?(instance)
65
63
  return true if is_string? || @columns.any? { |col| !col.__stack.empty? }
66
-
67
- !@columns.all? { |col|
68
- instance.respond_to?("#{col.__name.to_s}_changed?") &&
69
- !instance.send("#{col.__name.to_s}_changed?")
64
+
65
+ @columns.any? { |col|
66
+ instance.send("#{col.__name.to_s}_changed?")
70
67
  }
71
68
  end
72
-
69
+
73
70
  def admin?
74
71
  admin
75
72
  end
76
-
73
+
77
74
  def public?
78
75
  !admin
79
76
  end
80
-
81
- def available?
82
- columns.any? { |column| column_available?(column) }
83
- end
84
-
77
+
85
78
  private
86
-
79
+
87
80
  # Could there be more than one value related to the parent record? If so,
88
81
  # then this will return true. If not, false. It's that simple.
89
- #
82
+ #
90
83
  def is_many?
91
84
  associations.values.flatten.any? { |assoc| assoc.is_many? }
92
85
  end
93
-
86
+
94
87
  # Returns true if any of the columns are string values, instead of database
95
88
  # column references.
96
89
  def is_string?
97
90
  columns.all? { |col| col.is_string? }
98
91
  end
99
-
92
+
100
93
  def adapter
101
94
  @adapter ||= @model.sphinx_database_adapter
102
95
  end
103
-
96
+
104
97
  def quote_with_table(table, column)
105
98
  "#{quote_table_name(table)}.#{quote_column(column)}"
106
99
  end
107
-
100
+
108
101
  def quote_column(column)
109
102
  @model.connection.quote_column_name(column)
110
103
  end
111
-
104
+
112
105
  def quote_table_name(table_name)
113
106
  @model.connection.quote_table_name(table_name)
114
107
  end
115
-
108
+
116
109
  # Indication of whether the columns should be concatenated with a space
117
110
  # between each value. True if there's either multiple sources or multiple
118
111
  # associations.
119
- #
112
+ #
120
113
  def concat_ws?
121
114
  multiple_associations? || @columns.length > 1
122
115
  end
123
-
116
+
124
117
  # Checks whether any column requires multiple associations (which only
125
118
  # happens for polymorphic situations).
126
- #
119
+ #
127
120
  def multiple_associations?
128
- associations.values.any? { |assocs| assocs.length > 1 }
121
+ associations.any? { |col,assocs| assocs.length > 1 }
129
122
  end
130
-
123
+
131
124
  # Builds a column reference tied to the appropriate associations. This
132
125
  # dives into the associations hash and their corresponding joins to
133
126
  # figure out how to correctly reference a column in SQL.
134
- #
127
+ #
135
128
  def column_with_prefix(column)
136
- return nil unless column_available?(column)
137
-
138
129
  if column.is_string?
139
130
  column.__name
140
- elsif column.__stack.empty?
131
+ elsif associations[column].empty?
141
132
  "#{@model.quoted_table_name}.#{quote_column(column.__name)}"
142
133
  else
143
- associations[column.__stack].collect { |assoc|
134
+ associations[column].collect { |assoc|
144
135
  assoc.has_column?(column.__name) ?
145
136
  "#{quote_with_table(assoc.join.aliased_table_name, column.__name)}" :
146
137
  nil
147
138
  }.compact
148
139
  end
149
140
  end
150
-
141
+
151
142
  def columns_with_prefixes
152
143
  @columns.collect { |column|
153
144
  column_with_prefix column
154
- }.flatten.compact
155
- end
156
-
157
- def column_available?(column)
158
- if column.is_string?
159
- true
160
- elsif column.__stack.empty?
161
- @model.column_names.include?(column.__name.to_s)
162
- else
163
- associations[column.__stack].any? { |assoc|
164
- assoc.has_column?(column.__name)
165
- }
166
- end
145
+ }.flatten
167
146
  end
168
-
147
+
169
148
  # Gets a stack of associations for a specific path.
170
- #
149
+ #
171
150
  def association_stack(path, parent = nil)
172
151
  assocs = []
173
-
152
+
174
153
  if parent.nil?
175
154
  assocs = @source.association(path.shift)
176
155
  else
177
156
  assocs = parent.children(path.shift)
178
157
  end
179
-
158
+
180
159
  until path.empty?
181
160
  point = path.shift
182
161
  assocs = assocs.collect { |assoc| assoc.children(point) }.flatten
183
162
  end
184
-
163
+
185
164
  assocs
186
165
  end
187
166
  end
@@ -0,0 +1,35 @@
1
+ require 'thinking_sphinx'
2
+ require 'rails'
3
+
4
+ module ThinkingSphinx
5
+ class Railtie < Rails::Railtie
6
+
7
+ initializer "thinking_sphinx.active_record" do
8
+ if defined?(ActiveRecord)
9
+ ::ActiveRecord::Base.send(:include, ThinkingSphinx::ActiveRecord)
10
+ end
11
+ end
12
+
13
+ initializer "thinking_sphinx.set_app_root" do |app|
14
+ ThinkingSphinx::Configuration.instance.reset # Rails has setup app now
15
+ end
16
+
17
+ config.to_prepare do
18
+ I18n.backend.reload!
19
+ I18n.backend.available_locales
20
+
21
+ # ActiveRecord::Base.to_crc32s is dependant on the subclasses being loaded
22
+ # consistently. When the environment is reset, subclasses/descendants will
23
+ # be lost but our context will not reload them for us.
24
+ #
25
+ # We reset the context which causes the subclasses/descendants to be
26
+ # reloaded next time the context is called.
27
+ #
28
+ ThinkingSphinx.reset_context!
29
+ end
30
+
31
+ rake_tasks do
32
+ load File.expand_path('../tasks.rb', __FILE__)
33
+ end
34
+ end
35
+ end
@@ -6,312 +6,215 @@ module ThinkingSphinx
6
6
  # Most times, you will just want a specific model's results - to search and
7
7
  # search_for_ids methods will do the job in exactly the same manner when
8
8
  # called from a model.
9
- #
10
- class Search < Array
9
+ #
10
+ class Search
11
11
  CoreMethods = %w( == class class_eval extend frozen? id instance_eval
12
12
  instance_of? instance_values instance_variable_defined?
13
13
  instance_variable_get instance_variable_set instance_variables is_a?
14
- kind_of? member? method methods nil? object_id respond_to?
15
- respond_to_missing? send should tap type )
14
+ kind_of? member? method methods nil? object_id respond_to? send should
15
+ type )
16
16
  SafeMethods = %w( partition private_methods protected_methods
17
17
  public_methods send class )
18
-
18
+
19
19
  instance_methods.select { |method|
20
20
  method.to_s[/^__/].nil? && !CoreMethods.include?(method.to_s)
21
21
  }.each { |method|
22
22
  undef_method method
23
23
  }
24
-
25
- HashOptions = [:conditions, :with, :without, :with_all, :without_any]
24
+
25
+ HashOptions = [:conditions, :with, :without, :with_all]
26
26
  ArrayOptions = [:classes, :without_ids]
27
-
27
+
28
28
  attr_reader :args, :options
29
-
29
+
30
30
  # Deprecated. Use ThinkingSphinx.search
31
31
  def self.search(*args)
32
- warn 'ThinkingSphinx::Search.search is deprecated. Please use ThinkingSphinx.search instead.'
33
- ThinkingSphinx.search(*args)
32
+ log 'ThinkingSphinx::Search.search is deprecated. Please use ThinkingSphinx.search instead.'
33
+ ThinkingSphinx.search *args
34
34
  end
35
-
35
+
36
36
  # Deprecated. Use ThinkingSphinx.search_for_ids
37
37
  def self.search_for_ids(*args)
38
- warn 'ThinkingSphinx::Search.search_for_ids is deprecated. Please use ThinkingSphinx.search_for_ids instead.'
39
- ThinkingSphinx.search_for_ids(*args)
38
+ log 'ThinkingSphinx::Search.search_for_ids is deprecated. Please use ThinkingSphinx.search_for_ids instead.'
39
+ ThinkingSphinx.search_for_ids *args
40
40
  end
41
-
41
+
42
42
  # Deprecated. Use ThinkingSphinx.search_for_ids
43
43
  def self.search_for_id(*args)
44
- warn 'ThinkingSphinx::Search.search_for_id is deprecated. Please use ThinkingSphinx.search_for_id instead.'
45
- ThinkingSphinx.search_for_id(*args)
44
+ log 'ThinkingSphinx::Search.search_for_id is deprecated. Please use ThinkingSphinx.search_for_id instead.'
45
+ ThinkingSphinx.search_for_id *args
46
46
  end
47
-
47
+
48
48
  # Deprecated. Use ThinkingSphinx.count
49
49
  def self.count(*args)
50
- warn 'ThinkingSphinx::Search.count is deprecated. Please use ThinkingSphinx.count instead.'
51
- ThinkingSphinx.count(*args)
50
+ log 'ThinkingSphinx::Search.count is deprecated. Please use ThinkingSphinx.count instead.'
51
+ ThinkingSphinx.count *args
52
52
  end
53
-
53
+
54
54
  # Deprecated. Use ThinkingSphinx.facets
55
55
  def self.facets(*args)
56
- warn 'ThinkingSphinx::Search.facets is deprecated. Please use ThinkingSphinx.facets instead.'
57
- ThinkingSphinx.facets(*args)
56
+ log 'ThinkingSphinx::Search.facets is deprecated. Please use ThinkingSphinx.facets instead.'
57
+ ThinkingSphinx.facets *args
58
58
  end
59
-
60
- def self.bundle_searches(enum = nil)
61
- bundle = ThinkingSphinx::BundledSearch.new
62
-
63
- if enum.nil?
64
- yield bundle
65
- else
66
- enum.each { |item| yield bundle, item }
67
- end
68
-
69
- bundle.searches
70
- end
71
-
59
+
72
60
  def self.matching_fields(fields, bitmask)
73
61
  matches = []
74
62
  bitstring = bitmask.to_s(2).rjust(32, '0').reverse
75
-
63
+
76
64
  fields.each_with_index do |field, index|
77
65
  matches << field if bitstring[index, 1] == '1'
78
66
  end
79
67
  matches
80
68
  end
81
-
69
+
82
70
  def initialize(*args)
83
71
  ThinkingSphinx.context.define_indexes
84
-
72
+
85
73
  @array = []
86
74
  @options = args.extract_options!
87
75
  @args = args
88
-
89
- add_default_scope unless options[:ignore_default]
90
-
76
+
91
77
  populate if @options[:populate]
92
78
  end
93
-
94
- def ==(object)
95
- populate
96
- super
97
- end
98
-
79
+
99
80
  def to_a
100
81
  populate
101
82
  @array
102
83
  end
103
-
84
+
104
85
  def freeze
105
86
  populate
106
87
  @array.freeze
107
88
  self
108
89
  end
109
-
90
+
110
91
  # Indication of whether the request has been made to Sphinx for the search
111
92
  # query.
112
- #
93
+ #
113
94
  # @return [Boolean] true if the results have been requested.
114
- #
95
+ #
115
96
  def populated?
116
97
  !!@populated
117
98
  end
118
-
119
- # Indication of whether the request resulted in an error from Sphinx.
120
- #
121
- # @return [Boolean] true if Sphinx reports query error
122
- #
123
- def error?
124
- !!error
125
- end
126
-
127
- # The Sphinx-reported error, if any.
128
- #
129
- # @return [String, nil]
130
- #
131
- def error
132
- populate
133
- @results[:error]
134
- end
135
-
136
- # Indication of whether the request resulted in a warning from Sphinx.
137
- #
138
- # @return [Boolean] true if Sphinx reports query warning
139
- #
140
- def warning?
141
- !!warning
142
- end
143
-
144
- # The Sphinx-reported warning, if any.
145
- #
146
- # @return [String, nil]
147
- #
148
- def warning
149
- populate
150
- @results[:warning]
151
- end
152
-
99
+
153
100
  # The query result hash from Riddle.
154
- #
101
+ #
155
102
  # @return [Hash] Raw Sphinx results
156
- #
103
+ #
157
104
  def results
158
105
  populate
159
106
  @results
160
107
  end
161
-
108
+
162
109
  def method_missing(method, *args, &block)
163
110
  if is_scope?(method)
164
111
  add_scope(method, *args, &block)
165
112
  return self
166
113
  elsif method == :search_count
167
- merge_search one_class.search(*args), self.args, options
168
114
  return scoped_count
169
115
  elsif method.to_s[/^each_with_.*/].nil? && !@array.respond_to?(method)
170
116
  super
171
117
  elsif !SafeMethods.include?(method.to_s)
172
118
  populate
173
119
  end
174
-
120
+
175
121
  if method.to_s[/^each_with_.*/] && !@array.respond_to?(method)
176
122
  each_with_attribute method.to_s.gsub(/^each_with_/, ''), &block
177
123
  else
178
124
  @array.send(method, *args, &block)
179
125
  end
180
126
  end
181
-
127
+
182
128
  # Returns true if the Search object or the underlying Array object respond
183
129
  # to the requested method.
184
- #
130
+ #
185
131
  # @param [Symbol] method The method name
186
132
  # @return [Boolean] true if either Search or Array responds to the method.
187
- #
133
+ #
188
134
  def respond_to?(method, include_private = false)
189
135
  super || @array.respond_to?(method, include_private)
190
136
  end
191
-
137
+
192
138
  # The current page number of the result set. Defaults to 1 if no page was
193
139
  # explicitly requested.
194
- #
140
+ #
195
141
  # @return [Integer]
196
- #
142
+ #
197
143
  def current_page
198
144
  @options[:page].blank? ? 1 : @options[:page].to_i
199
145
  end
200
-
201
- def first_page?
202
- current_page == 1
203
- end
204
-
205
- # Kaminari support
206
- def page(page_number)
207
- @options[:page] = page_number
208
- self
209
- end
210
-
146
+
211
147
  # The next page number of the result set. If there are no more pages
212
148
  # available, nil is returned.
213
- #
149
+ #
214
150
  # @return [Integer, nil]
215
- #
151
+ #
216
152
  def next_page
217
153
  current_page >= total_pages ? nil : current_page + 1
218
154
  end
219
-
220
- def next_page?
221
- !next_page.nil?
222
- end
223
-
224
- def last_page?
225
- next_page.nil?
226
- end
227
-
155
+
228
156
  # The previous page number of the result set. If this is the first page,
229
157
  # then nil is returned.
230
- #
158
+ #
231
159
  # @return [Integer, nil]
232
- #
160
+ #
233
161
  def previous_page
234
162
  current_page == 1 ? nil : current_page - 1
235
163
  end
236
-
164
+
237
165
  # The amount of records per set of paged results. Defaults to 20 unless a
238
166
  # specific page size is requested.
239
- #
167
+ #
240
168
  # @return [Integer]
241
- #
169
+ #
242
170
  def per_page
243
171
  @options[:limit] ||= @options[:per_page]
244
172
  @options[:limit] ||= 20
245
173
  @options[:limit].to_i
246
174
  end
247
- # Kaminari support
248
- alias_method :limit_value, :per_page
249
-
250
- # Kaminari support
251
- def per(limit)
252
- @options[:limit] = limit
253
- self
254
- end
255
-
175
+
256
176
  # The total number of pages available if the results are paginated.
257
- #
177
+ #
258
178
  # @return [Integer]
259
- #
179
+ #
260
180
  def total_pages
261
181
  populate
262
- return 0 if @results.nil? || @results[:total].nil?
263
-
182
+ return 0 if @results[:total].nil?
183
+
264
184
  @total_pages ||= (@results[:total] / per_page.to_f).ceil
265
185
  end
266
- # Compatibility with kaminari and older versions of will_paginate
186
+ # Compatibility with older versions of will_paginate
267
187
  alias_method :page_count, :total_pages
268
- alias_method :num_pages, :total_pages
269
-
270
- # Query time taken
271
- #
272
- # @return [Integer]
273
- #
274
- def query_time
275
- populate
276
- return 0 if @results[:time].nil?
277
-
278
- @query_time ||= @results[:time]
279
- end
280
-
188
+
281
189
  # The total number of search results available.
282
- #
190
+ #
283
191
  # @return [Integer]
284
- #
192
+ #
285
193
  def total_entries
286
194
  populate
287
- return 0 if @results.nil? || @results[:total_found].nil?
288
-
195
+ return 0 if @results[:total_found].nil?
196
+
289
197
  @total_entries ||= @results[:total_found]
290
198
  end
291
-
292
- # Compatibility with kaminari
293
- alias_method :total_count, :total_entries
294
-
199
+
295
200
  # The current page's offset, based on the number of records per page.
296
- # Or explicit :offset if given.
297
- #
201
+ # Or explicit :offset if given.
202
+ #
298
203
  # @return [Integer]
299
- #
204
+ #
300
205
  def offset
301
206
  @options[:offset] || ((current_page - 1) * per_page)
302
207
  end
303
-
304
- alias_method :offset_value, :offset
305
-
208
+
306
209
  def indexes
307
210
  return options[:index] if options[:index]
308
211
  return '*' if classes.empty?
309
-
212
+
310
213
  classes.collect { |klass|
311
214
  klass.sphinx_index_names
312
215
  }.flatten.uniq.join(',')
313
216
  end
314
-
217
+
315
218
  def each_with_groupby_and_count(&block)
316
219
  populate
317
220
  results[:matches].each_with_index do |match, index|
@@ -321,257 +224,141 @@ module ThinkingSphinx
321
224
  end
322
225
  end
323
226
  alias_method :each_with_group_and_count, :each_with_groupby_and_count
324
-
227
+
325
228
  def each_with_weighting(&block)
326
229
  populate
327
230
  results[:matches].each_with_index do |match, index|
328
231
  yield self[index], match[:weight]
329
232
  end
330
233
  end
331
-
332
- def each_with_match(&block)
333
- populate
334
- results[:matches].each_with_index do |match, index|
335
- yield self[index], match
336
- end
337
- end
338
-
234
+
339
235
  def excerpt_for(string, model = nil)
340
236
  if model.nil? && one_class
341
237
  model ||= one_class
342
238
  end
343
-
239
+
344
240
  populate
345
-
346
- index = options[:index] || "#{model.core_index_names.first}"
347
- take_client do |client|
348
- client.excerpts(
349
- {
350
- :docs => [string.to_s],
351
- :words => query,
352
- :index => index.split(',').first.strip
353
- }.merge(options[:excerpt_options] || {})
354
- ).first
355
- end
241
+ client.excerpts(
242
+ :docs => [string],
243
+ :words => results[:words].keys.join(' '),
244
+ :index => "#{model.source_of_sphinx_index.sphinx_name}_core"
245
+ ).first
356
246
  end
357
-
247
+
358
248
  def search(*args)
359
- args << args.extract_options!.merge(:ignore_default => true)
360
- merge_search ThinkingSphinx::Search.new(*args), self.args, options
249
+ add_default_scope
250
+ merge_search ThinkingSphinx::Search.new(*args)
361
251
  self
362
252
  end
363
-
364
- def search_for_ids(*args)
365
- args << args.extract_options!.merge(
366
- :ignore_default => true,
367
- :ids_only => true
368
- )
369
- merge_search ThinkingSphinx::Search.new(*args), self.args, options
370
- self
371
- end
372
-
373
- def facets(*args)
374
- options = args.extract_options!
375
- merge_search self, args, options
376
- args << options
377
-
378
- ThinkingSphinx::FacetSearch.new(*args)
379
- end
380
-
381
- def take_client
382
- if options[:client]
383
- prepare options[:client]
384
- yield options[:client]
385
- else
386
- ThinkingSphinx::Connection.take do |client|
387
- prepare client
388
- yield client
389
- end
390
- end
391
- end
392
-
393
- def append_to(client)
394
- prepare client
395
- client.append_query query, indexes, comment
396
- client.reset
397
- end
398
-
399
- def populate_from_queue(results)
400
- return if @populated
401
- @populated = true
402
- @results = results
403
-
404
- compose_results
405
- end
406
-
253
+
407
254
  private
408
-
255
+
409
256
  def config
410
257
  ThinkingSphinx::Configuration.instance
411
258
  end
412
-
259
+
413
260
  def populate
414
261
  return if @populated
415
262
  @populated = true
416
- retries = hard_retries
417
-
418
- begin
419
- retry_on_stale_index do
420
- begin
421
- log "Querying: '#{query}'"
422
- runtime = Benchmark.realtime {
423
- @results = nil
424
- take_client do |client|
425
- @results = client.query query, indexes, comment
426
- end
427
- }
428
- log "Found #{@results[:total_found]} results", :debug,
429
- "Sphinx (#{sprintf("%f", runtime)}s)"
430
-
431
- log "Sphinx Daemon returned warning: #{warning}", :error if warning?
432
-
433
- if error?
434
- log "Sphinx Daemon returned error: #{error}", :error
435
- raise SphinxError.new(error, @results) unless options[:ignore_errors]
436
- end
437
- rescue Errno::ECONNREFUSED => err
438
- raise ThinkingSphinx::ConnectionError,
439
- 'Connection to Sphinx Daemon (searchd) failed.'
440
- end
441
-
442
- compose_results
263
+
264
+ retry_on_stale_index do
265
+ begin
266
+ log "Querying: '#{query}'"
267
+ runtime = Benchmark.realtime {
268
+ @results = client.query query, indexes, comment
269
+ }
270
+ log "Found #{@results[:total_found]} results", :debug,
271
+ "Sphinx (#{sprintf("%f", runtime)}s)"
272
+ rescue Errno::ECONNREFUSED => err
273
+ raise ThinkingSphinx::ConnectionError,
274
+ 'Connection to Sphinx Daemon (searchd) failed.'
443
275
  end
444
- rescue => e
445
- log 'Caught Sphinx exception: %s (%s %s left)' % [
446
- e.message, retries, (retries == 1 ? 'try' : 'tries')
447
- ]
448
- retries -= 1
449
- if retries >= 0
450
- retry
276
+
277
+ if options[:ids_only]
278
+ replace @results[:matches].collect { |match|
279
+ match[:attributes]["sphinx_internal_id"]
280
+ }
451
281
  else
452
- raise e
282
+ replace instances_from_matches
283
+ add_excerpter
284
+ add_sphinx_attributes
285
+ add_matching_fields if client.rank_mode == :fieldmask
453
286
  end
454
287
  end
455
288
  end
456
-
457
- def compose_results
458
- if options[:ids_only]
459
- compose_ids_results
460
- elsif options[:attributes_only]
461
- compose_attributes_results
462
- elsif options[:only]
463
- compose_only_results
464
- else
465
- replace instances_from_matches
466
- add_excerpter
467
- add_sphinx_attributes
468
- add_matching_fields if options[:rank_mode] == :fieldmask
469
- end
470
- end
471
-
472
- def compose_ids_results
473
- replace @results[:matches].collect { |match|
474
- match[:attributes]['sphinx_internal_id']
475
- }
476
- end
477
-
478
- def compose_attributes_results
479
- replace @results[:matches].collect { |match|
480
- attributes = {}
481
- match[:attributes].each do |name, value|
482
- attributes[name.to_sym] = match[:attributes][name]
483
- end
484
- attributes
485
- }
486
- end
487
-
488
- def compose_only_results
489
- replace @results[:matches].collect { |match|
490
- case only = options[:only]
491
- when String, Symbol
492
- match[:attributes][only.to_s]
493
- when Array
494
- only.inject({}) do |hash, attribute|
495
- hash[attribute.to_sym] = match[:attributes][attribute.to_s]
496
- hash
497
- end
498
- else
499
- raise "Unexpected object for :only argument. String or Array is expected, #{only.class} was received."
500
- end
501
- }
502
- end
503
-
289
+
504
290
  def add_excerpter
505
291
  each do |object|
506
- next if object.nil?
507
-
508
- object.excerpts = ThinkingSphinx::Excerpter.new self, object
292
+ next if object.respond_to?(:excerpts)
293
+
294
+ excerpter = ThinkingSphinx::Excerpter.new self, object
295
+ block = lambda { excerpter }
296
+
297
+ object.singleton_class.instance_eval do
298
+ define_method(:excerpts, &block)
299
+ end
509
300
  end
510
301
  end
511
-
302
+
512
303
  def add_sphinx_attributes
513
304
  each do |object|
514
- next if object.nil?
515
-
305
+ next if object.nil? || object.respond_to?(:sphinx_attributes)
306
+
516
307
  match = match_hash object
517
308
  next if match.nil?
518
-
519
- object.sphinx_attributes = match[:attributes]
309
+
310
+ object.singleton_class.instance_eval do
311
+ define_method(:sphinx_attributes) { match[:attributes] }
312
+ end
520
313
  end
521
314
  end
522
-
315
+
523
316
  def add_matching_fields
524
317
  each do |object|
525
- next if object.nil?
526
-
318
+ next if object.nil? || object.respond_to?(:matching_fields)
319
+
527
320
  match = match_hash object
528
321
  next if match.nil?
529
- object.matching_fields = ThinkingSphinx::Search.matching_fields(
322
+ fields = ThinkingSphinx::Search.matching_fields(
530
323
  @results[:fields], match[:weight]
531
324
  )
325
+
326
+ object.singleton_class.instance_eval do
327
+ define_method(:matching_fields) { fields }
328
+ end
532
329
  end
533
330
  end
534
-
331
+
535
332
  def match_hash(object)
536
333
  @results[:matches].detect { |match|
537
- class_crc = object.class.name
538
- class_crc = object.class.to_crc32 if Riddle.loaded_version.to_i < 2
539
-
540
334
  match[:attributes]['sphinx_internal_id'] == object.
541
335
  primary_key_for_sphinx &&
542
- match[:attributes][crc_attribute] == class_crc
336
+ match[:attributes]['class_crc'] == object.class.to_crc32
543
337
  }
544
338
  end
545
-
339
+
546
340
  def self.log(message, method = :debug, identifier = 'Sphinx')
547
341
  return if ::ActiveRecord::Base.logger.nil?
548
-
549
- info = ''
550
- if ::ActiveRecord::Base.colorize_logging
551
- identifier_color, message_color = "4;32;1", "0" # 0;1 = Bold
552
- info << " \e[#{identifier_color}m#{identifier}\e[0m "
553
- info << "\e[#{message_color}m#{message}\e[0m"
554
- else
555
- info = "#{identifier} #{message}"
556
- end
557
-
342
+ identifier_color, message_color = "4;32;1", "0" # 0;1 = Bold
343
+ info = " \e[#{identifier_color}m#{identifier}\e[0m "
344
+ info << "\e[#{message_color}m#{message}\e[0m"
558
345
  ::ActiveRecord::Base.logger.send method, info
559
346
  end
560
-
347
+
561
348
  def log(*args)
562
349
  self.class.log(*args)
563
350
  end
564
-
565
- def prepare(client)
566
- index_options = {}
567
- if one_class && one_class.sphinx_indexes && one_class.sphinx_indexes.first
568
- index_options = one_class.sphinx_indexes.first.local_options
569
- end
570
-
351
+
352
+ def client
353
+ client = config.client
354
+
355
+ index_options = one_class ?
356
+ one_class.sphinx_indexes.first.local_options : {}
357
+
571
358
  [
572
359
  :max_matches, :group_by, :group_function, :group_clause,
573
360
  :group_distinct, :id_range, :cut_off, :retry_count, :retry_delay,
574
- :rank_mode, :rank_expr, :max_query_time, :field_weights
361
+ :rank_mode, :max_query_time, :field_weights
575
362
  ].each do |key|
576
363
  value = options[key] || index_options[key]
577
364
  client.send("#{key}=", value) if value
@@ -579,7 +366,7 @@ module ThinkingSphinx
579
366
 
580
367
  # treated non-standard as :select is already used for AR queries
581
368
  client.select = options[:sphinx_select] || '*'
582
-
369
+
583
370
  client.limit = per_page
584
371
  client.offset = offset
585
372
  client.match_mode = match_mode
@@ -590,69 +377,72 @@ module ThinkingSphinx
590
377
  client.group_function = group_function if group_function
591
378
  client.index_weights = index_weights
592
379
  client.anchor = anchor
593
-
380
+
594
381
  client
595
382
  end
596
-
383
+
597
384
  def retry_on_stale_index(&block)
598
385
  stale_ids = []
599
386
  retries = stale_retries
600
-
387
+
601
388
  begin
602
389
  options[:raise_on_stale] = retries > 0
603
390
  block.call
604
-
391
+
605
392
  # If ThinkingSphinx::Search#instances_from_matches found records in
606
393
  # Sphinx but not in the DB and the :raise_on_stale option is set, this
607
394
  # exception is raised. We retry a limited number of times, excluding the
608
395
  # stale ids from the search.
609
396
  rescue StaleIdsException => err
610
397
  retries -= 1
611
-
398
+
612
399
  # For logging
613
400
  stale_ids |= err.ids
614
401
  # ID exclusion
615
402
  options[:without_ids] = Array(options[:without_ids]) | err.ids
616
-
403
+
617
404
  log 'Sphinx Stale Ids (%s %s left): %s' % [
618
405
  retries, (retries == 1 ? 'try' : 'tries'), stale_ids.join(', ')
619
406
  ]
620
407
  retry
621
408
  end
622
409
  end
623
-
410
+
624
411
  def classes
625
412
  @classes ||= options[:classes] || []
626
413
  end
627
-
414
+
628
415
  def one_class
629
416
  @one_class ||= classes.length != 1 ? nil : classes.first
630
417
  end
631
-
418
+
632
419
  def query
633
420
  @query ||= begin
634
421
  q = @args.join(' ') << conditions_as_query
635
422
  (options[:star] ? star_query(q) : q).strip
636
423
  end
637
424
  end
638
-
425
+
639
426
  def conditions_as_query
640
427
  return '' if @options[:conditions].blank?
641
-
642
- ' ' + @options[:conditions].keys.collect { |key|
643
- search_key = key.is_a?(::Array) ? "(#{key.join(',')})" : key
644
- "@#{search_key} #{options[:conditions][key]}"
428
+
429
+ # Soon to be deprecated.
430
+ keys = @options[:conditions].keys.reject { |key|
431
+ attributes.include?(key.to_sym)
432
+ }
433
+
434
+ ' ' + keys.collect { |key|
435
+ "@#{key} #{options[:conditions][key]}"
645
436
  }.join(' ')
646
437
  end
647
-
438
+
648
439
  def star_query(query)
649
- token = options[:star].is_a?(Regexp) ? options[:star] : default_star_token
440
+ token = options[:star].is_a?(Regexp) ? options[:star] : /\w+/u
650
441
 
651
442
  query.gsub(/("#{token}(.*?#{token})?"|(?![!-])#{token})/u) do
652
443
  pre, proper, post = $`, $&, $'
653
444
  # E.g. "@foo", "/2", "~3", but not as part of a token
654
- is_operator = pre.match(%r{(\W|^)[@~/]\Z}) ||
655
- pre.match(%r{(\W|^)@\([^\)]*$})
445
+ is_operator = pre.match(%r{(\W|^)[@~/]\Z})
656
446
  # E.g. "foo bar", with quotes
657
447
  is_quote = proper.starts_with?('"') && proper.ends_with?('"')
658
448
  has_star = pre.ends_with?("*") || post.starts_with?("*")
@@ -663,25 +453,15 @@ module ThinkingSphinx
663
453
  end
664
454
  end
665
455
  end
666
-
667
- if Regexp.instance_methods.include?(:encoding)
668
- DefaultStarToken = Regexp.new('\p{Word}+')
669
- else
670
- DefaultStarToken = Regexp.new('\w+', nil, 'u')
671
- end
672
-
673
- def default_star_token
674
- DefaultStarToken
675
- end
676
-
456
+
677
457
  def comment
678
458
  options[:comment] || ''
679
459
  end
680
-
460
+
681
461
  def match_mode
682
462
  options[:match_mode] || (options[:conditions].blank? ? :all : :extended)
683
463
  end
684
-
464
+
685
465
  def sort_mode
686
466
  @sort_mode ||= case options[:sort_mode]
687
467
  when :asc
@@ -701,11 +481,11 @@ module ThinkingSphinx
701
481
  options[:sort_mode]
702
482
  end
703
483
  end
704
-
484
+
705
485
  def sort_by
706
486
  case @sort_by = (options[:sort_by] || options[:order])
707
487
  when String
708
- sorted_fields_to_attributes(@sort_by.clone)
488
+ sorted_fields_to_attributes(@sort_by)
709
489
  when Symbol
710
490
  field_names.include?(@sort_by) ?
711
491
  @sort_by.to_s.concat('_sort') : @sort_by.to_s
@@ -713,28 +493,28 @@ module ThinkingSphinx
713
493
  ''
714
494
  end
715
495
  end
716
-
496
+
717
497
  def field_names
718
498
  return [] unless one_class
719
-
499
+
720
500
  one_class.sphinx_indexes.collect { |index|
721
501
  index.fields.collect { |field| field.unique_name }
722
502
  }.flatten
723
503
  end
724
-
504
+
725
505
  def sorted_fields_to_attributes(order_string)
726
506
  field_names.each { |field|
727
507
  order_string.gsub!(/(^|\s)#{field}(,?\s|$)/) { |match|
728
508
  match.gsub field.to_s, field.to_s.concat("_sort")
729
509
  }
730
510
  }
731
-
511
+
732
512
  order_string
733
513
  end
734
-
514
+
735
515
  # Turn :index_weights => { "foo" => 2, User => 1 } into :index_weights =>
736
516
  # { "foo" => 2, "user_core" => 1, "user_delta" => 1 }
737
- #
517
+ #
738
518
  def index_weights
739
519
  weights = options[:index_weights] || {}
740
520
  weights.keys.inject({}) do |hash, key|
@@ -745,39 +525,55 @@ module ThinkingSphinx
745
525
  else
746
526
  hash[key] = weights[key]
747
527
  end
748
-
528
+
749
529
  hash
750
530
  end
751
531
  end
752
-
532
+
753
533
  def group_by
754
534
  options[:group] ? options[:group].to_s : nil
755
535
  end
756
-
536
+
757
537
  def group_function
758
538
  options[:group] ? :attr : nil
759
539
  end
760
-
540
+
761
541
  def internal_filters
762
542
  filters = [Riddle::Client::Filter.new('sphinx_deleted', [0])]
763
-
543
+
764
544
  class_crcs = classes.collect { |klass|
765
545
  klass.to_crc32s
766
546
  }.flatten
767
-
547
+
768
548
  unless class_crcs.empty?
769
549
  filters << Riddle::Client::Filter.new('class_crc', class_crcs)
770
550
  end
771
-
551
+
772
552
  filters << Riddle::Client::Filter.new(
773
553
  'sphinx_internal_id', filter_value(options[:without_ids]), true
774
- ) unless options[:without_ids].nil? || options[:without_ids].empty?
775
-
554
+ ) if options[:without_ids]
555
+
776
556
  filters
777
557
  end
778
-
558
+
559
+ def condition_filters
560
+ (options[:conditions] || {}).collect { |attrib, value|
561
+ if attributes.include?(attrib.to_sym)
562
+ puts <<-MSG
563
+ Deprecation Warning: filters on attributes should be done using the :with
564
+ option, not :conditions. For example:
565
+ :with => {:#{attrib} => #{value.inspect}}
566
+ MSG
567
+ Riddle::Client::Filter.new attrib.to_s, filter_value(value)
568
+ else
569
+ nil
570
+ end
571
+ }.compact
572
+ end
573
+
779
574
  def filters
780
575
  internal_filters +
576
+ condition_filters +
781
577
  (options[:with] || {}).collect { |attrib, value|
782
578
  Riddle::Client::Filter.new attrib.to_s, filter_value(value)
783
579
  } +
@@ -788,15 +584,18 @@ module ThinkingSphinx
788
584
  Array(values).collect { |value|
789
585
  Riddle::Client::Filter.new attrib.to_s, filter_value(value)
790
586
  }
791
- }.flatten +
792
- (options[:without_any] || {}).collect { |attrib, values|
793
- Array(values).collect { |value|
794
- Riddle::Client::Filter.new attrib.to_s, filter_value(value), true
795
- }
796
587
  }.flatten
797
588
  end
798
-
589
+
799
590
  # When passed a Time instance, returns the integer timestamp.
591
+ #
592
+ # If using Rails 2.1+, need to handle timezones to translate them back to
593
+ # UTC, as that's what datetimes will be stored as by MySQL.
594
+ #
595
+ # in_time_zone is a method that was added for the timezone support in
596
+ # Rails 2.1, which is why it's used for testing. I'm sure there's better
597
+ # ways, but this does the job.
598
+ #
800
599
  def filter_value(value)
801
600
  case value
802
601
  when Range
@@ -804,19 +603,17 @@ module ThinkingSphinx
804
603
  when Array
805
604
  value.collect { |v| filter_value(v) }.flatten
806
605
  when Time
807
- [value.to_i]
808
- when Date
809
- [Time.utc(value.year, value.month, value.day).to_i]
606
+ value.respond_to?(:in_time_zone) ? [value.utc.to_i] : [value.to_i]
810
607
  when NilClass
811
608
  0
812
609
  else
813
610
  Array(value)
814
611
  end
815
612
  end
816
-
613
+
817
614
  def anchor
818
615
  return {} unless options[:geo] || (options[:lat] && options[:lng])
819
-
616
+
820
617
  {
821
618
  :latitude => options[:geo] ? options[:geo].first : options[:lat],
822
619
  :longitude => options[:geo] ? options[:geo].last : options[:lng],
@@ -824,43 +621,43 @@ module ThinkingSphinx
824
621
  :longitude_attribute => longitude_attr.to_s
825
622
  }
826
623
  end
827
-
624
+
828
625
  def latitude_attr
829
626
  options[:latitude_attr] ||
830
627
  index_option(:latitude_attr) ||
831
628
  attribute(:lat, :latitude)
832
629
  end
833
-
630
+
834
631
  def longitude_attr
835
632
  options[:longitude_attr] ||
836
633
  index_option(:longitude_attr) ||
837
634
  attribute(:lon, :lng, :longitude)
838
635
  end
839
-
636
+
840
637
  def index_option(key)
841
638
  return nil unless one_class
842
-
639
+
843
640
  one_class.sphinx_indexes.collect { |index|
844
641
  index.local_options[key]
845
642
  }.compact.first
846
643
  end
847
-
644
+
848
645
  def attribute(*keys)
849
646
  return nil unless one_class
850
-
647
+
851
648
  keys.detect { |key|
852
649
  attributes.include?(key)
853
650
  }
854
651
  end
855
-
652
+
856
653
  def attributes
857
654
  return [] unless one_class
858
-
655
+
859
656
  attributes = one_class.sphinx_indexes.collect { |index|
860
657
  index.attributes.collect { |attrib| attrib.unique_name }
861
658
  }.flatten
862
659
  end
863
-
660
+
864
661
  def stale_retries
865
662
  case options[:retry_stale]
866
663
  when TrueClass
@@ -871,50 +668,7 @@ module ThinkingSphinx
871
668
  options[:retry_stale].to_i
872
669
  end
873
670
  end
874
-
875
- def hard_retries
876
- options[:hard_retry_count] || config.hard_retry_count
877
- end
878
-
879
- def include_for_class(klass)
880
- includes = options[:include] || klass.sphinx_index_options[:include]
881
-
882
- case includes
883
- when NilClass
884
- nil
885
- when Array
886
- include_from_array includes, klass
887
- when Symbol
888
- klass.reflections[includes].nil? ? nil : includes
889
- when Hash
890
- include_from_hash includes, klass
891
- else
892
- includes
893
- end
894
- end
895
-
896
- def include_from_array(array, klass)
897
- scoped_array = []
898
- array.each do |value|
899
- case value
900
- when Hash
901
- scoped_hash = include_from_hash(value, klass)
902
- scoped_array << scoped_hash unless scoped_hash.nil?
903
- else
904
- scoped_array << value unless klass.reflections[value].nil?
905
- end
906
- end
907
- scoped_array.empty? ? nil : scoped_array
908
- end
909
-
910
- def include_from_hash(hash, klass)
911
- scoped_hash = {}
912
- hash.keys.each do |key|
913
- scoped_hash[key] = hash[key] unless klass.reflections[key].nil?
914
- end
915
- scoped_hash.empty? ? nil : scoped_hash
916
- end
917
-
671
+
918
672
  def instances_from_class(klass, matches)
919
673
  index_options = klass.sphinx_index_options
920
674
 
@@ -923,13 +677,13 @@ module ThinkingSphinx
923
677
  :all,
924
678
  :joins => options[:joins],
925
679
  :conditions => {klass.primary_key_for_sphinx.to_sym => ids},
926
- :include => include_for_class(klass),
680
+ :include => (options[:include] || index_options[:include]),
927
681
  :select => (options[:select] || index_options[:select]),
928
682
  :order => (options[:sql_order] || index_options[:sql_order])
929
683
  ) : []
930
684
 
931
685
  # Raise an exception if we find records in Sphinx but not in the DB, so
932
- # the search method can retry without them. See
686
+ # the search method can retry without them. See
933
687
  # ThinkingSphinx::Search.retry_search_on_stale_index.
934
688
  if options[:raise_on_stale] && instances.length < ids.length
935
689
  stale_ids = ids - instances.map { |i| i.id }
@@ -946,43 +700,39 @@ module ThinkingSphinx
946
700
  end
947
701
  }
948
702
  end
949
-
703
+
950
704
  # Group results by class and call #find(:all) once for each group to reduce
951
705
  # the number of #find's in multi-model searches.
952
- #
706
+ #
953
707
  def instances_from_matches
954
708
  return single_class_results if one_class
955
-
709
+
956
710
  groups = results[:matches].group_by { |match|
957
- match[:attributes][crc_attribute]
711
+ match[:attributes]["class_crc"]
958
712
  }
959
713
  groups.each do |crc, group|
960
714
  group.replace(
961
715
  instances_from_class(class_from_crc(crc), group)
962
716
  )
963
717
  end
964
-
718
+
965
719
  results[:matches].collect do |match|
966
720
  groups.detect { |crc, group|
967
- crc == match[:attributes][crc_attribute]
721
+ crc == match[:attributes]["class_crc"]
968
722
  }[1].compact.detect { |obj|
969
723
  obj.primary_key_for_sphinx == match[:attributes]["sphinx_internal_id"]
970
724
  }
971
725
  end
972
726
  end
973
-
727
+
974
728
  def single_class_results
975
729
  instances_from_class one_class, results[:matches]
976
730
  end
977
-
731
+
978
732
  def class_from_crc(crc)
979
- if Riddle.loaded_version.to_i < 2
980
- config.models_by_crc[crc].constantize
981
- else
982
- crc.constantize
983
- end
733
+ config.models_by_crc[crc].constantize
984
734
  end
985
-
735
+
986
736
  def each_with_attribute(attribute, &block)
987
737
  populate
988
738
  results[:matches].each_with_index do |match, index|
@@ -990,25 +740,23 @@ module ThinkingSphinx
990
740
  (match[:attributes][attribute] || match[:attributes]["@#{attribute}"])
991
741
  end
992
742
  end
993
-
743
+
994
744
  def is_scope?(method)
995
745
  one_class && one_class.sphinx_scopes.include?(method)
996
746
  end
997
-
747
+
998
748
  # Adds the default_sphinx_scope if set.
999
749
  def add_default_scope
1000
- return unless one_class && one_class.has_default_sphinx_scope?
1001
- add_scope(one_class.get_default_sphinx_scope.to_sym)
750
+ add_scope(one_class.get_default_sphinx_scope) if one_class && one_class.has_default_sphinx_scope?
1002
751
  end
1003
-
752
+
1004
753
  def add_scope(method, *args, &block)
1005
- method = "#{method}_without_default".to_sym
1006
- merge_search one_class.send(method, *args, &block), self.args, options
754
+ merge_search one_class.send(method, *args, &block)
1007
755
  end
1008
-
1009
- def merge_search(search, args, options)
756
+
757
+ def merge_search(search)
1010
758
  search.args.each { |arg| args << arg }
1011
-
759
+
1012
760
  search.options.keys.each do |key|
1013
761
  if HashOptions.include?(key)
1014
762
  options[key] ||= {}
@@ -1022,20 +770,16 @@ module ThinkingSphinx
1022
770
  end
1023
771
  end
1024
772
  end
1025
-
773
+
1026
774
  def scoped_count
1027
- return self.total_entries if(@options[:ids_only] || @options[:only])
1028
-
775
+ return self.total_entries if @options[:ids_only]
776
+
1029
777
  @options[:ids_only] = true
1030
778
  results_count = self.total_entries
1031
779
  @options[:ids_only] = false
1032
780
  @populated = false
1033
-
781
+
1034
782
  results_count
1035
783
  end
1036
-
1037
- def crc_attribute
1038
- Riddle.loaded_version.to_i < 2 ? 'class_crc' : 'sphinx_internal_class'
1039
- end
1040
784
  end
1041
785
  end