thinking-sphinx 1.5.0 → 2.0.0.rc1

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 (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