warp-thinking-sphinx 1.2.12 → 1.3.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. data/README.textile +21 -4
  2. data/VERSION +1 -0
  3. data/features/abstract_inheritance.feature +10 -0
  4. data/features/alternate_primary_key.feature +1 -1
  5. data/features/attribute_updates.feature +22 -5
  6. data/features/deleting_instances.feature +3 -0
  7. data/features/facets.feature +6 -0
  8. data/features/facets_across_model.feature +2 -2
  9. data/features/searching_across_models.feature +1 -1
  10. data/features/searching_by_index.feature +40 -0
  11. data/features/sphinx_scopes.feature +7 -0
  12. data/features/step_definitions/alpha_steps.rb +14 -1
  13. data/features/step_definitions/beta_steps.rb +1 -1
  14. data/features/step_definitions/common_steps.rb +12 -2
  15. data/features/step_definitions/facet_steps.rb +5 -1
  16. data/features/step_definitions/scope_steps.rb +4 -0
  17. data/features/step_definitions/sphinx_steps.rb +8 -4
  18. data/features/sti_searching.feature +5 -0
  19. data/features/support/{db/database.example.yml → database.example.yml} +0 -0
  20. data/features/support/db/fixtures/foxes.rb +3 -0
  21. data/features/support/db/fixtures/music.rb +4 -0
  22. data/features/support/db/fixtures/robots.rb +1 -1
  23. data/features/support/db/fixtures/tags.rb +1 -1
  24. data/features/support/db/migrations/create_alphas.rb +1 -0
  25. data/features/support/db/migrations/create_genres.rb +3 -0
  26. data/features/support/db/migrations/create_music.rb +6 -0
  27. data/features/support/db/migrations/create_robots.rb +1 -2
  28. data/features/support/env.rb +16 -1
  29. data/features/support/models/alpha.rb +12 -0
  30. data/features/support/models/comment.rb +3 -3
  31. data/features/support/models/fox.rb +5 -0
  32. data/features/support/models/genre.rb +3 -0
  33. data/features/support/models/medium.rb +5 -0
  34. data/features/support/models/music.rb +8 -0
  35. data/features/support/models/post.rb +2 -1
  36. data/features/support/models/robot.rb +4 -0
  37. data/lib/cucumber/thinking_sphinx/external_world.rb +8 -0
  38. data/lib/cucumber/thinking_sphinx/internal_world.rb +126 -0
  39. data/lib/cucumber/thinking_sphinx/sql_logger.rb +20 -0
  40. data/lib/thinking_sphinx.rb +56 -37
  41. data/lib/thinking_sphinx/active_record.rb +257 -192
  42. data/lib/thinking_sphinx/active_record/attribute_updates.rb +10 -12
  43. data/lib/thinking_sphinx/active_record/delta.rb +0 -26
  44. data/lib/thinking_sphinx/active_record/scopes.rb +37 -1
  45. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +1 -1
  46. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +18 -11
  47. data/lib/thinking_sphinx/attribute.rb +19 -4
  48. data/lib/thinking_sphinx/auto_version.rb +22 -0
  49. data/lib/thinking_sphinx/configuration.rb +57 -59
  50. data/lib/thinking_sphinx/context.rb +74 -0
  51. data/lib/thinking_sphinx/deltas.rb +0 -2
  52. data/lib/thinking_sphinx/deltas/default_delta.rb +14 -20
  53. data/lib/thinking_sphinx/deploy/capistrano.rb +1 -1
  54. data/lib/thinking_sphinx/facet_search.rb +3 -1
  55. data/lib/thinking_sphinx/index.rb +77 -19
  56. data/lib/thinking_sphinx/index/builder.rb +2 -2
  57. data/lib/thinking_sphinx/search.rb +47 -9
  58. data/lib/thinking_sphinx/search_methods.rb +22 -4
  59. data/lib/thinking_sphinx/source.rb +9 -8
  60. data/lib/thinking_sphinx/source/sql.rb +5 -3
  61. data/lib/thinking_sphinx/tasks.rb +13 -57
  62. data/lib/thinking_sphinx/test.rb +52 -0
  63. data/rails/init.rb +4 -2
  64. data/spec/{lib/thinking_sphinx → thinking_sphinx}/active_record/delta_spec.rb +4 -6
  65. data/spec/{lib/thinking_sphinx → thinking_sphinx}/active_record/has_many_association_spec.rb +0 -0
  66. data/spec/thinking_sphinx/active_record/scopes_spec.rb +177 -0
  67. data/spec/thinking_sphinx/active_record_spec.rb +622 -0
  68. data/spec/{lib/thinking_sphinx → thinking_sphinx}/association_spec.rb +0 -0
  69. data/spec/{lib/thinking_sphinx → thinking_sphinx}/attribute_spec.rb +39 -0
  70. data/spec/thinking_sphinx/auto_version_spec.rb +39 -0
  71. data/spec/{lib/thinking_sphinx → thinking_sphinx}/configuration_spec.rb +27 -61
  72. data/spec/thinking_sphinx/context_spec.rb +119 -0
  73. data/spec/{lib/thinking_sphinx → thinking_sphinx}/core/array_spec.rb +0 -0
  74. data/spec/{lib/thinking_sphinx → thinking_sphinx}/core/string_spec.rb +0 -0
  75. data/spec/{lib/thinking_sphinx → thinking_sphinx}/excerpter_spec.rb +0 -0
  76. data/spec/{lib/thinking_sphinx → thinking_sphinx}/facet_search_spec.rb +0 -0
  77. data/spec/{lib/thinking_sphinx → thinking_sphinx}/facet_spec.rb +0 -0
  78. data/spec/{lib/thinking_sphinx → thinking_sphinx}/field_spec.rb +0 -0
  79. data/spec/{lib/thinking_sphinx → thinking_sphinx}/index/builder_spec.rb +24 -0
  80. data/spec/{lib/thinking_sphinx → thinking_sphinx}/index/faux_column_spec.rb +0 -0
  81. data/spec/thinking_sphinx/index_spec.rb +183 -0
  82. data/spec/{lib/thinking_sphinx → thinking_sphinx}/rails_additions_spec.rb +0 -0
  83. data/spec/{lib/thinking_sphinx → thinking_sphinx}/search_methods_spec.rb +0 -0
  84. data/spec/{lib/thinking_sphinx → thinking_sphinx}/search_spec.rb +41 -0
  85. data/spec/{lib/thinking_sphinx → thinking_sphinx}/source_spec.rb +1 -1
  86. data/spec/thinking_sphinx_spec.rb +204 -0
  87. data/tasks/distribution.rb +6 -20
  88. data/tasks/testing.rb +8 -19
  89. metadata +117 -142
  90. data/VERSION.yml +0 -4
  91. data/features/a.rb +0 -17
  92. data/features/datetime_deltas.feature +0 -66
  93. data/features/delayed_delta_indexing.feature +0 -37
  94. data/features/step_definitions/datetime_delta_steps.rb +0 -15
  95. data/features/step_definitions/delayed_delta_indexing_steps.rb +0 -7
  96. data/features/support/db/active_record.rb +0 -40
  97. data/features/support/db/fixtures/delayed_betas.rb +0 -10
  98. data/features/support/db/fixtures/thetas.rb +0 -10
  99. data/features/support/db/migrations/create_delayed_betas.rb +0 -17
  100. data/features/support/db/migrations/create_thetas.rb +0 -5
  101. data/features/support/db/mysql.rb +0 -3
  102. data/features/support/db/postgresql.rb +0 -3
  103. data/features/support/models/delayed_beta.rb +0 -7
  104. data/features/support/models/theta.rb +0 -7
  105. data/features/support/post_database.rb +0 -43
  106. data/features/support/z.rb +0 -19
  107. data/lib/thinking_sphinx/deltas/datetime_delta.rb +0 -50
  108. data/lib/thinking_sphinx/deltas/delayed_delta.rb +0 -30
  109. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +0 -24
  110. data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +0 -27
  111. data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +0 -26
  112. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +0 -96
  113. data/spec/lib/thinking_sphinx/active_record_spec.rb +0 -353
  114. data/spec/lib/thinking_sphinx/deltas/job_spec.rb +0 -32
  115. data/spec/lib/thinking_sphinx/index_spec.rb +0 -45
  116. data/spec/lib/thinking_sphinx_spec.rb +0 -162
  117. data/vendor/after_commit/LICENSE +0 -20
  118. data/vendor/after_commit/README +0 -16
  119. data/vendor/after_commit/Rakefile +0 -22
  120. data/vendor/after_commit/init.rb +0 -8
  121. data/vendor/after_commit/lib/after_commit.rb +0 -45
  122. data/vendor/after_commit/lib/after_commit/active_record.rb +0 -114
  123. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +0 -103
  124. data/vendor/after_commit/test/after_commit_test.rb +0 -53
  125. data/vendor/delayed_job/lib/delayed/job.rb +0 -251
  126. data/vendor/delayed_job/lib/delayed/message_sending.rb +0 -7
  127. data/vendor/delayed_job/lib/delayed/performable_method.rb +0 -55
  128. data/vendor/delayed_job/lib/delayed/worker.rb +0 -54
  129. data/vendor/riddle/lib/riddle.rb +0 -30
  130. data/vendor/riddle/lib/riddle/client.rb +0 -635
  131. data/vendor/riddle/lib/riddle/client/filter.rb +0 -53
  132. data/vendor/riddle/lib/riddle/client/message.rb +0 -66
  133. data/vendor/riddle/lib/riddle/client/response.rb +0 -84
  134. data/vendor/riddle/lib/riddle/configuration.rb +0 -33
  135. data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +0 -48
  136. data/vendor/riddle/lib/riddle/configuration/index.rb +0 -142
  137. data/vendor/riddle/lib/riddle/configuration/indexer.rb +0 -19
  138. data/vendor/riddle/lib/riddle/configuration/remote_index.rb +0 -17
  139. data/vendor/riddle/lib/riddle/configuration/searchd.rb +0 -25
  140. data/vendor/riddle/lib/riddle/configuration/section.rb +0 -43
  141. data/vendor/riddle/lib/riddle/configuration/source.rb +0 -23
  142. data/vendor/riddle/lib/riddle/configuration/sql_source.rb +0 -34
  143. data/vendor/riddle/lib/riddle/configuration/xml_source.rb +0 -28
  144. data/vendor/riddle/lib/riddle/controller.rb +0 -53
@@ -1,10 +1,10 @@
1
1
  class Comment < ActiveRecord::Base
2
- belongs_to :post
3
- belongs_to :category
4
-
5
2
  define_index do
6
3
  indexes :content
7
4
 
8
5
  has category.name, :facet => true, :as => :category_name, :type => :string
9
6
  end
7
+
8
+ belongs_to :post
9
+ belongs_to :category
10
10
  end
@@ -0,0 +1,5 @@
1
+ class Fox < Animal
2
+ define_index do
3
+ indexes :name
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ class Genre < ActiveRecord::Base
2
+ #
3
+ end
@@ -0,0 +1,5 @@
1
+ class Medium < ActiveRecord::Base
2
+ self.abstract_class = true
3
+
4
+ belongs_to :genre
5
+ end
@@ -0,0 +1,8 @@
1
+ class Music < Medium
2
+ set_table_name 'music'
3
+
4
+ define_index do
5
+ indexes artist, track, album
6
+ indexes genre(:name), :as => :genre
7
+ end
8
+ end
@@ -12,7 +12,8 @@ class Post < ActiveRecord::Base
12
12
  indexes comments.content, :as => :comments
13
13
  indexes authors.name, :as => :authors
14
14
 
15
- has comments(:id), :as => :comment_ids, :source => :ranged_query
15
+ has comments(:id), :as => :comment_ids, :source => :ranged_query,
16
+ :facet => true
16
17
  has category.name, :facet => true, :as => :category_name, :type => :string
17
18
  has 'COUNT(DISTINCT comments.id)', :as => :comments_count, :type => :integer
18
19
  has comments.created_at, :as => :comments_created_at
@@ -5,4 +5,8 @@ class Robot < ActiveRecord::Base
5
5
  define_index do
6
6
  indexes :name
7
7
  end
8
+
9
+ def id
10
+ internal_id
11
+ end
8
12
  end
@@ -0,0 +1,8 @@
1
+ require 'thinking_sphinx/test'
2
+
3
+ class Cucumber::ThinkingSphinx::ExternalWorld
4
+ def initialize(suppress_delta_output = true)
5
+ ::ThinkingSphinx::Test.init
6
+ ::ThinkingSphinx::Test.start_with_autostop
7
+ end
8
+ end
@@ -0,0 +1,126 @@
1
+ require 'cucumber/thinking_sphinx/sql_logger'
2
+
3
+ module Cucumber
4
+ module ThinkingSphinx
5
+ class InternalWorld
6
+ attr_accessor :temporary_directory, :migrations_directory,
7
+ :models_directory, :fixtures_directory, :database_file
8
+ attr_accessor :adapter, :database, :username,
9
+ :password, :host
10
+
11
+ def initialize
12
+ @temporary_directory = "#{Dir.pwd}/tmp"
13
+ @migrations_directory = "features/support/db/migrations"
14
+ @models_directory = "features/support/models"
15
+ @fixtures_directory = "features/support/db/fixtures"
16
+ @database_file = "features/support/database.yml"
17
+
18
+ @adapter = ENV['DATABASE'] || 'mysql'
19
+ @database = 'thinking_sphinx'
20
+ @username = 'thinking_sphinx'
21
+ # @password = 'thinking_sphinx'
22
+ @host = 'localhost'
23
+ end
24
+
25
+ def setup
26
+ make_temporary_directory
27
+
28
+ configure_cleanup
29
+ configure_thinking_sphinx
30
+ configure_active_record
31
+
32
+ prepare_data
33
+ setup_sphinx
34
+
35
+ self
36
+ end
37
+
38
+ def configure_database
39
+ ActiveRecord::Base.establish_connection database_settings
40
+ self
41
+ end
42
+
43
+ private
44
+
45
+ def config
46
+ @config ||= ::ThinkingSphinx::Configuration.instance
47
+ end
48
+
49
+ def make_temporary_directory
50
+ FileUtils.mkdir_p temporary_directory
51
+ Dir["#{temporary_directory}/*"].each do |file|
52
+ FileUtils.rm_rf file
53
+ end
54
+ end
55
+
56
+ def configure_thinking_sphinx
57
+ config.config_file = "#{temporary_directory}/sphinx.conf"
58
+ config.searchd_log_file = "#{temporary_directory}/searchd.log"
59
+ config.query_log_file = "#{temporary_directory}/searchd.query.log"
60
+ config.pid_file = "#{temporary_directory}/searchd.pid"
61
+ config.searchd_file_path = "#{temporary_directory}/indexes/"
62
+
63
+ ::ThinkingSphinx.suppress_delta_output = true
64
+ end
65
+
66
+ def configure_cleanup
67
+ Kernel.at_exit do
68
+ ::ThinkingSphinx::Configuration.instance.controller.stop
69
+ sleep(0.5) # Ensure Sphinx has shut down completely
70
+ ActiveRecord::Base.logger.close
71
+ end
72
+ end
73
+
74
+ def yaml_database_settings
75
+ return {} unless File.exist?(@database_file)
76
+
77
+ YAML.load open(@database_file)
78
+ end
79
+
80
+ def database_settings
81
+ {
82
+ 'adapter' => @adapter,
83
+ 'database' => @database,
84
+ 'username' => @username,
85
+ 'password' => @password,
86
+ 'host' => @host
87
+ }.merge yaml_database_settings
88
+ end
89
+
90
+ def configure_active_record
91
+ ActiveRecord::Base.logger = Logger.new(
92
+ open("#{temporary_directory}/active_record.log", "a")
93
+ )
94
+
95
+ ActiveRecord::Base.connection.class.send(
96
+ :include, Cucumber::ThinkingSphinx::SqlLogger
97
+ )
98
+ end
99
+
100
+ def prepare_data
101
+ ::ThinkingSphinx.deltas_enabled = false
102
+
103
+ load_files migrations_directory
104
+ load_files models_directory
105
+ load_files fixtures_directory
106
+
107
+ ::ThinkingSphinx.deltas_enabled = true
108
+ end
109
+
110
+ def load_files(path)
111
+ files = Dir["#{path}/*.rb"].sort!
112
+ files.each do |file|
113
+ require file.gsub(/\.rb$/, '')
114
+ end
115
+ end
116
+
117
+ def setup_sphinx
118
+ FileUtils.mkdir_p config.searchd_file_path
119
+
120
+ config.build
121
+ config.controller.index
122
+ config.controller.start
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,20 @@
1
+ module Cucumber
2
+ module ThinkingSphinx
3
+ module SqlLogger
4
+ def self.included(base)
5
+ base.send :alias_method_chain, :execute, :query_record
6
+ end
7
+
8
+ IGNORED_SQL = [
9
+ /^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/,
10
+ /^SELECT @@ROWCOUNT/, /^SHOW FIELDS/
11
+ ]
12
+
13
+ def execute_with_query_record(sql, name = nil, &block)
14
+ $queries_executed ||= []
15
+ $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
16
+ execute_without_query_record(sql, name, &block)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,13 +1,10 @@
1
- Dir[File.join(File.dirname(__FILE__), '../vendor/*/lib')].each do |path|
2
- $LOAD_PATH.unshift path
3
- end
4
-
5
1
  require 'active_record'
6
- require 'riddle'
7
2
  require 'after_commit'
8
3
  require 'yaml'
9
4
  require 'cgi'
5
+ require 'riddle'
10
6
 
7
+ require 'thinking_sphinx/auto_version'
11
8
  require 'thinking_sphinx/core/array'
12
9
  require 'thinking_sphinx/core/string'
13
10
  require 'thinking_sphinx/property'
@@ -15,6 +12,7 @@ require 'thinking_sphinx/active_record'
15
12
  require 'thinking_sphinx/association'
16
13
  require 'thinking_sphinx/attribute'
17
14
  require 'thinking_sphinx/configuration'
15
+ require 'thinking_sphinx/context'
18
16
  require 'thinking_sphinx/excerpter'
19
17
  require 'thinking_sphinx/facet'
20
18
  require 'thinking_sphinx/class_facet'
@@ -57,26 +55,39 @@ module ThinkingSphinx
57
55
  # @return [String] The version number as a string
58
56
  #
59
57
  def self.version
60
- hash = YAML.load_file File.join(File.dirname(__FILE__), '../VERSION.yml')
61
- [hash[:major], hash[:minor], hash[:patch]].join('.')
58
+ open(File.join(File.dirname(__FILE__), '../VERSION')) { |f|
59
+ f.read.strip
60
+ }
62
61
  end
63
62
 
64
63
  # The collection of indexed models. Keep in mind that Rails lazily loads
65
64
  # its classes, so this may not actually be populated with _all_ the models
66
65
  # that have Sphinx indexes.
67
- def self.indexed_models
68
- @@indexed_models ||= []
66
+ def self.context
67
+ if Thread.current[:thinking_sphinx_context].nil?
68
+ Thread.current[:thinking_sphinx_context] = ThinkingSphinx::Context.new
69
+ Thread.current[:thinking_sphinx_context].prepare
70
+ end
71
+
72
+ Thread.current[:thinking_sphinx_context]
73
+ end
74
+
75
+ def self.reset_context!
76
+ Thread.current[:thinking_sphinx_context] = nil
69
77
  end
70
78
 
71
79
  def self.unique_id_expression(offset = nil)
72
- "* #{ThinkingSphinx.indexed_models.size} + #{offset || 0}"
80
+ "* #{context.indexed_models.size} + #{offset || 0}"
73
81
  end
74
82
 
75
83
  # Check if index definition is disabled.
76
84
  #
77
85
  def self.define_indexes?
78
- @@define_indexes = true unless defined?(@@define_indexes)
79
- @@define_indexes == true
86
+ if Thread.current[:thinking_sphinx_define_indexes].nil?
87
+ Thread.current[:thinking_sphinx_define_indexes] = true
88
+ end
89
+
90
+ Thread.current[:thinking_sphinx_define_indexes]
80
91
  end
81
92
 
82
93
  # Enable/disable indexes - you may want to do this while migrating data.
@@ -84,16 +95,19 @@ module ThinkingSphinx
84
95
  # ThinkingSphinx.define_indexes = false
85
96
  #
86
97
  def self.define_indexes=(value)
87
- @@define_indexes = value
98
+ Thread.current[:thinking_sphinx_define_indexes] = value
88
99
  end
89
100
 
90
- @@deltas_enabled = nil
91
-
92
101
  # Check if delta indexing is enabled.
93
102
  #
94
103
  def self.deltas_enabled?
95
- @@deltas_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@deltas_enabled.nil?
96
- @@deltas_enabled
104
+ if Thread.current[:thinking_sphinx_deltas_enabled].nil?
105
+ Thread.current[:thinking_sphinx_deltas_enabled] = (
106
+ ThinkingSphinx::Configuration.environment != "test"
107
+ )
108
+ end
109
+
110
+ Thread.current[:thinking_sphinx_deltas_enabled]
97
111
  end
98
112
 
99
113
  # Enable/disable all delta indexing.
@@ -101,17 +115,20 @@ module ThinkingSphinx
101
115
  # ThinkingSphinx.deltas_enabled = false
102
116
  #
103
117
  def self.deltas_enabled=(value)
104
- @@deltas_enabled = value
118
+ Thread.current[:thinking_sphinx_deltas_enabled] = value
105
119
  end
106
120
 
107
- @@updates_enabled = nil
108
-
109
121
  # Check if updates are enabled. True by default, unless within the test
110
122
  # environment.
111
123
  #
112
124
  def self.updates_enabled?
113
- @@updates_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@updates_enabled.nil?
114
- @@updates_enabled
125
+ if Thread.current[:thinking_sphinx_updates_enabled].nil?
126
+ Thread.current[:thinking_sphinx_updates_enabled] = (
127
+ ThinkingSphinx::Configuration.environment != "test"
128
+ )
129
+ end
130
+
131
+ Thread.current[:thinking_sphinx_updates_enabled]
115
132
  end
116
133
 
117
134
  # Enable/disable updates to Sphinx
@@ -119,37 +136,37 @@ module ThinkingSphinx
119
136
  # ThinkingSphinx.updates_enabled = false
120
137
  #
121
138
  def self.updates_enabled=(value)
122
- @@updates_enabled = value
139
+ Thread.current[:thinking_sphinx_updates_enabled] = value
123
140
  end
124
141
 
125
- @@suppress_delta_output = false
126
-
127
142
  def self.suppress_delta_output?
128
- @@suppress_delta_output
143
+ Thread.current[:thinking_sphinx_suppress_delta_output] ||= false
129
144
  end
130
145
 
131
146
  def self.suppress_delta_output=(value)
132
- @@suppress_delta_output = value
147
+ Thread.current[:thinking_sphinx_suppress_delta_output] = value
133
148
  end
134
-
149
+
135
150
  # Checks to see if MySQL will allow simplistic GROUP BY statements. If not,
136
151
  # or if not using MySQL, this will return false.
137
152
  #
138
153
  def self.use_group_by_shortcut?
139
- !!(
140
- mysql? && ::ActiveRecord::Base.connection.select_all(
141
- "SELECT @@global.sql_mode, @@session.sql_mode;"
142
- ).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
143
- )
154
+ if Thread.current[:thinking_sphinx_use_group_by_shortcut].nil?
155
+ Thread.current[:thinking_sphinx_use_group_by_shortcut] = !!(
156
+ mysql? && ::ActiveRecord::Base.connection.select_all(
157
+ "SELECT @@global.sql_mode, @@session.sql_mode;"
158
+ ).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
159
+ )
160
+ end
161
+
162
+ Thread.current[:thinking_sphinx_use_group_by_shortcut]
144
163
  end
145
164
 
146
- @@remote_sphinx = false
147
-
148
165
  # An indication of whether Sphinx is running on a remote machine instead of
149
166
  # the same machine.
150
167
  #
151
168
  def self.remote_sphinx?
152
- @@remote_sphinx
169
+ Thread.current[:thinking_sphinx_remote_sphinx] ||= false
153
170
  end
154
171
 
155
172
  # Tells Thinking Sphinx that Sphinx is running on a different machine, and
@@ -161,7 +178,7 @@ module ThinkingSphinx
161
178
  # ThinkingSphinx.remote_sphinx = true
162
179
  #
163
180
  def self.remote_sphinx=(value)
164
- @@remote_sphinx = value
181
+ Thread.current[:thinking_sphinx_remote_sphinx] = value
165
182
  end
166
183
 
167
184
  # Check if Sphinx is running. If remote_sphinx is set to true (indicating
@@ -210,3 +227,5 @@ module ThinkingSphinx
210
227
 
211
228
  extend ThinkingSphinx::SearchMethods::ClassMethods
212
229
  end
230
+
231
+ ThinkingSphinx::AutoVersion.detect
@@ -12,7 +12,11 @@ module ThinkingSphinx
12
12
  def self.included(base)
13
13
  base.class_eval do
14
14
  class_inheritable_array :sphinx_indexes, :sphinx_facets
15
+
16
+ extend ThinkingSphinx::ActiveRecord::ClassMethods
17
+
15
18
  class << self
19
+ attr_accessor :sphinx_index_blocks
16
20
 
17
21
  def set_sphinx_primary_key(attribute)
18
22
  @sphinx_primary_key_attribute = attribute
@@ -22,93 +26,6 @@ module ThinkingSphinx
22
26
  @sphinx_primary_key_attribute || primary_key
23
27
  end
24
28
 
25
- # Allows creation of indexes for Sphinx. If you don't do this, there
26
- # isn't much point trying to search (or using this plugin at all,
27
- # really).
28
- #
29
- # An example or two:
30
- #
31
- # define_index
32
- # indexes :id, :as => :model_id
33
- # indexes name
34
- # end
35
- #
36
- # You can also grab fields from associations - multiple levels deep
37
- # if necessary.
38
- #
39
- # define_index do
40
- # indexes tags.name, :as => :tag
41
- # indexes articles.content
42
- # indexes orders.line_items.product.name, :as => :product
43
- # end
44
- #
45
- # And it will automatically concatenate multiple fields:
46
- #
47
- # define_index do
48
- # indexes [author.first_name, author.last_name], :as => :author
49
- # end
50
- #
51
- # The #indexes method is for fields - if you want attributes, use
52
- # #has instead. All the same rules apply - but keep in mind that
53
- # attributes are for sorting, grouping and filtering, not searching.
54
- #
55
- # define_index do
56
- # # fields ...
57
- #
58
- # has created_at, updated_at
59
- # end
60
- #
61
- # One last feature is the delta index. This requires the model to
62
- # have a boolean field named 'delta', and is enabled as follows:
63
- #
64
- # define_index do
65
- # # fields ...
66
- # # attributes ...
67
- #
68
- # set_property :delta => true
69
- # end
70
- #
71
- # Check out the more detailed documentation for each of these methods
72
- # at ThinkingSphinx::Index::Builder.
73
- #
74
- def define_index(&block)
75
- return unless ThinkingSphinx.define_indexes?
76
-
77
- self.sphinx_indexes ||= []
78
- self.sphinx_facets ||= []
79
- index = ThinkingSphinx::Index::Builder.generate(self, &block)
80
-
81
- self.sphinx_indexes << index
82
- unless ThinkingSphinx.indexed_models.include?(self.name)
83
- ThinkingSphinx.indexed_models << self.name
84
- end
85
-
86
- if index.delta?
87
- before_save :toggle_delta
88
- after_commit :index_delta
89
- end
90
-
91
- after_destroy :toggle_deleted
92
-
93
- include ThinkingSphinx::SearchMethods
94
- include ThinkingSphinx::ActiveRecord::AttributeUpdates
95
- include ThinkingSphinx::ActiveRecord::Scopes
96
-
97
- index
98
-
99
- # We want to make sure that if the database doesn't exist, then Thinking
100
- # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
101
- # and db:migrate). It's a bit hacky, but I can't think of a better way.
102
- rescue StandardError => err
103
- case err.class.name
104
- when "Mysql::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
105
- return
106
- else
107
- raise err
108
- end
109
- end
110
- alias_method :sphinx_index, :define_index
111
-
112
29
  def sphinx_index_options
113
30
  sphinx_indexes.last.options
114
31
  end
@@ -127,26 +44,6 @@ module ThinkingSphinx
127
44
  (subclasses << self).collect { |klass| klass.to_crc32 }
128
45
  end
129
46
 
130
- def source_of_sphinx_index
131
- possible_models = self.sphinx_indexes.collect { |index| index.model }
132
- return self if possible_models.include?(self)
133
-
134
- parent = self.superclass
135
- while !possible_models.include?(parent) && parent != ::ActiveRecord::Base
136
- parent = parent.superclass
137
- end
138
-
139
- return parent
140
- end
141
-
142
- def to_riddle(offset)
143
- sphinx_database_adapter.setup
144
-
145
- indexes = [to_riddle_for_core(offset)]
146
- indexes << to_riddle_for_delta(offset) if sphinx_delta?
147
- indexes << to_riddle_for_distributed
148
- end
149
-
150
47
  def sphinx_database_adapter
151
48
  @sphinx_database_adapter ||=
152
49
  ThinkingSphinx::AbstractAdapter.detect(self)
@@ -156,86 +53,36 @@ module ThinkingSphinx
156
53
  self.name.underscore.tr(':/\\', '_')
157
54
  end
158
55
 
159
- def sphinx_index_names
160
- klass = source_of_sphinx_index
161
- names = ["#{klass.sphinx_name}_core"]
162
- names << "#{klass.sphinx_name}_delta" if sphinx_delta?
163
-
164
- names
56
+ #
57
+ # The above method to_crc32s is dependant on the subclasses being loaded consistently
58
+ # After a reset_subclasses is called (during a Dispatcher.cleanup_application in development)
59
+ # Our subclasses will be lost but our context will not reload them for us.
60
+ #
61
+ # We reset the context which causes the subclasses to be reloaded next time the context is called.
62
+ #
63
+ def reset_subclasses_with_thinking_sphinx
64
+ reset_subclasses_without_thinking_sphinx
65
+ ThinkingSphinx.reset_context!
165
66
  end
166
67
 
167
- private
68
+ alias_method_chain :reset_subclasses, :thinking_sphinx
168
69
 
169
- def sphinx_delta?
170
- self.sphinx_indexes.any? { |index| index.delta? }
171
- end
172
-
173
- def to_riddle_for_core(offset)
174
- index = Riddle::Configuration::Index.new("#{sphinx_name}_core")
175
- index.path = File.join(
176
- ThinkingSphinx::Configuration.instance.searchd_file_path, index.name
177
- )
178
-
179
- set_configuration_options_for_indexes index
180
- set_field_settings_for_indexes index
181
-
182
- self.sphinx_indexes.select { |ts_index|
183
- ts_index.model == self
184
- }.each_with_index do |ts_index, i|
185
- index.sources += ts_index.sources.collect { |source|
186
- source.to_riddle_for_core(offset, i)
187
- }
188
- end
189
-
190
- index
191
- end
192
-
193
- def to_riddle_for_delta(offset)
194
- index = Riddle::Configuration::Index.new("#{sphinx_name}_delta")
195
- index.parent = "#{sphinx_name}_core"
196
- index.path = File.join(ThinkingSphinx::Configuration.instance.searchd_file_path, index.name)
197
-
198
- self.sphinx_indexes.each_with_index do |ts_index, i|
199
- index.sources += ts_index.sources.collect { |source|
200
- source.to_riddle_for_delta(offset, i)
201
- } if ts_index.delta?
202
- end
203
-
204
- index
205
- end
70
+ private
206
71
 
207
- def to_riddle_for_distributed
208
- index = Riddle::Configuration::DistributedIndex.new(sphinx_name)
209
- index.local_indexes << "#{sphinx_name}_core"
210
- index.local_indexes.unshift "#{sphinx_name}_delta" if sphinx_delta?
211
- index
72
+ def defined_indexes?
73
+ @defined_indexes
212
74
  end
213
75
 
214
- def set_configuration_options_for_indexes(index)
215
- ThinkingSphinx::Configuration.instance.index_options.each do |key, value|
216
- index.send("#{key}=".to_sym, value)
217
- end
218
-
219
- self.sphinx_indexes.each do |ts_index|
220
- ts_index.options.each do |key, value|
221
- index.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s) && !value.nil?
222
- end
223
- end
76
+ def defined_indexes=(value)
77
+ @defined_indexes = value
224
78
  end
225
79
 
226
- def set_field_settings_for_indexes(index)
227
- field_names = lambda { |field| field.unique_name.to_s }
228
-
229
- self.sphinx_indexes.each do |ts_index|
230
- index.prefix_field_names += ts_index.prefix_fields.collect(&field_names)
231
- index.infix_field_names += ts_index.infix_fields.collect(&field_names)
232
- end
80
+ def sphinx_delta?
81
+ self.sphinx_indexes.any? { |index| index.delta? }
233
82
  end
234
83
  end
235
84
  end
236
85
 
237
- base.send(:include, ThinkingSphinx::ActiveRecord::Delta)
238
-
239
86
  ::ActiveRecord::Associations::HasManyAssociation.send(
240
87
  :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
241
88
  )
@@ -244,6 +91,228 @@ module ThinkingSphinx
244
91
  )
245
92
  end
246
93
 
94
+ module ClassMethods
95
+ # Allows creation of indexes for Sphinx. If you don't do this, there
96
+ # isn't much point trying to search (or using this plugin at all,
97
+ # really).
98
+ #
99
+ # An example or two:
100
+ #
101
+ # define_index
102
+ # indexes :id, :as => :model_id
103
+ # indexes name
104
+ # end
105
+ #
106
+ # You can also grab fields from associations - multiple levels deep
107
+ # if necessary.
108
+ #
109
+ # define_index do
110
+ # indexes tags.name, :as => :tag
111
+ # indexes articles.content
112
+ # indexes orders.line_items.product.name, :as => :product
113
+ # end
114
+ #
115
+ # And it will automatically concatenate multiple fields:
116
+ #
117
+ # define_index do
118
+ # indexes [author.first_name, author.last_name], :as => :author
119
+ # end
120
+ #
121
+ # The #indexes method is for fields - if you want attributes, use
122
+ # #has instead. All the same rules apply - but keep in mind that
123
+ # attributes are for sorting, grouping and filtering, not searching.
124
+ #
125
+ # define_index do
126
+ # # fields ...
127
+ #
128
+ # has created_at, updated_at
129
+ # end
130
+ #
131
+ # One last feature is the delta index. This requires the model to
132
+ # have a boolean field named 'delta', and is enabled as follows:
133
+ #
134
+ # define_index do
135
+ # # fields ...
136
+ # # attributes ...
137
+ #
138
+ # set_property :delta => true
139
+ # end
140
+ #
141
+ # Check out the more detailed documentation for each of these methods
142
+ # at ThinkingSphinx::Index::Builder.
143
+ #
144
+ def define_index(name = nil, &block)
145
+ self.sphinx_index_blocks ||= []
146
+ self.sphinx_indexes ||= []
147
+ self.sphinx_facets ||= []
148
+
149
+ ThinkingSphinx.context.add_indexed_model self
150
+
151
+ if sphinx_index_blocks.empty?
152
+ before_validation :define_indexes
153
+ before_destroy :define_indexes
154
+ end
155
+
156
+ self.sphinx_index_blocks << lambda {
157
+ index = ThinkingSphinx::Index::Builder.generate self, name, &block
158
+ add_sphinx_callbacks_and_extend(index.delta?)
159
+ add_sphinx_index index
160
+ }
161
+
162
+ include ThinkingSphinx::ActiveRecord::Scopes
163
+ include ThinkingSphinx::SearchMethods
164
+ end
165
+
166
+ def define_indexes
167
+ return if sphinx_index_blocks.nil? ||
168
+ defined_indexes? ||
169
+ !ThinkingSphinx.define_indexes?
170
+
171
+ sphinx_index_blocks.each do |block|
172
+ block.call
173
+ end
174
+
175
+ self.defined_indexes = true
176
+
177
+ # We want to make sure that if the database doesn't exist, then Thinking
178
+ # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
179
+ # and db:migrate). It's a bit hacky, but I can't think of a better way.
180
+ rescue StandardError => err
181
+ case err.class.name
182
+ when "Mysql::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
183
+ return
184
+ else
185
+ raise err
186
+ end
187
+ end
188
+
189
+ def add_sphinx_index(index)
190
+ self.sphinx_indexes << index
191
+ subclasses.each { |klass| klass.add_sphinx_index(index) }
192
+ end
193
+
194
+ def has_sphinx_indexes?
195
+ sphinx_indexes &&
196
+ sphinx_index_blocks &&
197
+ (sphinx_indexes.length > 0 || sphinx_index_blocks.length > 0)
198
+ end
199
+
200
+ def indexed_by_sphinx?
201
+ sphinx_indexes && sphinx_indexes.length > 0
202
+ end
203
+
204
+ def delta_indexed_by_sphinx?
205
+ sphinx_indexes && sphinx_indexes.any? { |index| index.delta? }
206
+ end
207
+
208
+ def sphinx_index_names
209
+ define_indexes
210
+ sphinx_indexes.collect(&:all_names).flatten
211
+ end
212
+
213
+ def core_index_names
214
+ define_indexes
215
+ sphinx_indexes.collect(&:core_name)
216
+ end
217
+
218
+ def delta_index_names
219
+ define_indexes
220
+ sphinx_indexes.select(&:delta?).collect(&:delta_name)
221
+ end
222
+
223
+ def to_riddle
224
+ define_indexes
225
+ sphinx_database_adapter.setup
226
+
227
+ local_sphinx_indexes.collect { |index|
228
+ index.to_riddle(sphinx_offset)
229
+ }.flatten
230
+ end
231
+
232
+ def source_of_sphinx_index
233
+ define_indexes
234
+ possible_models = self.sphinx_indexes.collect { |index| index.model }
235
+ return self if possible_models.include?(self)
236
+
237
+ parent = self.superclass
238
+ while !possible_models.include?(parent) && parent != ::ActiveRecord::Base
239
+ parent = parent.superclass
240
+ end
241
+
242
+ return parent
243
+ end
244
+
245
+ def delete_in_index(index, document_id)
246
+ return unless ThinkingSphinx.sphinx_running? &&
247
+ search_for_id(document_id, index)
248
+
249
+ ThinkingSphinx::Configuration.instance.client.update(
250
+ index, ['sphinx_deleted'], {document_id => [1]}
251
+ )
252
+ end
253
+
254
+ def sphinx_offset
255
+ ThinkingSphinx.context.superclass_indexed_models.
256
+ index eldest_indexed_ancestor
257
+ end
258
+
259
+ # Temporarily disable delta indexing inside a block, then perform a single
260
+ # rebuild of index at the end.
261
+ #
262
+ # Useful when performing updates to batches of models to prevent
263
+ # the delta index being rebuilt after each individual update.
264
+ #
265
+ # In the following example, the delta index will only be rebuilt once,
266
+ # not 10 times.
267
+ #
268
+ # SomeModel.suspended_delta do
269
+ # 10.times do
270
+ # SomeModel.create( ... )
271
+ # end
272
+ # end
273
+ #
274
+ def suspended_delta(reindex_after = true, &block)
275
+ define_indexes
276
+ original_setting = ThinkingSphinx.deltas_enabled?
277
+ ThinkingSphinx.deltas_enabled = false
278
+ begin
279
+ yield
280
+ ensure
281
+ ThinkingSphinx.deltas_enabled = original_setting
282
+ self.index_delta if reindex_after
283
+ end
284
+ end
285
+
286
+ private
287
+
288
+ def local_sphinx_indexes
289
+ sphinx_indexes.select { |index|
290
+ index.model == self
291
+ }
292
+ end
293
+
294
+ def add_sphinx_callbacks_and_extend(delta = false)
295
+ unless indexed_by_sphinx?
296
+ after_destroy :toggle_deleted
297
+
298
+ include ThinkingSphinx::ActiveRecord::AttributeUpdates
299
+ end
300
+
301
+ if delta && !delta_indexed_by_sphinx?
302
+ include ThinkingSphinx::ActiveRecord::Delta
303
+
304
+ before_save :toggle_delta
305
+ after_commit :index_delta
306
+ end
307
+ end
308
+
309
+ def eldest_indexed_ancestor
310
+ ancestors.reverse.detect { |ancestor|
311
+ ThinkingSphinx.context.indexed_models.include?(ancestor.name)
312
+ }.name
313
+ end
314
+ end
315
+
247
316
  def in_index?(suffix)
248
317
  self.class.search_for_id self.sphinx_document_id, sphinx_index_name(suffix)
249
318
  end
@@ -261,23 +330,15 @@ module ThinkingSphinx
261
330
  end
262
331
 
263
332
  def toggle_deleted
264
- return unless ThinkingSphinx.updates_enabled? && ThinkingSphinx.sphinx_running?
265
-
266
- config = ThinkingSphinx::Configuration.instance
267
- client = Riddle::Client.new config.address, config.port
333
+ return unless ThinkingSphinx.updates_enabled?
268
334
 
269
- client.update(
270
- "#{self.class.sphinx_indexes.first.name}_core",
271
- ['sphinx_deleted'],
272
- {self.sphinx_document_id => 1}
273
- ) if self.in_core_index?
335
+ self.class.core_index_names.each do |index_name|
336
+ self.class.delete_in_index index_name, self.sphinx_document_id
337
+ end
338
+ self.class.delta_index_names.each do |index_name|
339
+ self.class.delete_in_index index_name, self.sphinx_document_id
340
+ end if self.class.delta_indexed_by_sphinx? && toggled_delta?
274
341
 
275
- client.update(
276
- "#{self.class.sphinx_indexes.first.name}_delta",
277
- ['sphinx_deleted'],
278
- {self.sphinx_document_id => 1}
279
- ) if self.class.sphinx_indexes.any? { |index| index.delta? } &&
280
- self.toggled_delta?
281
342
  rescue ::ThinkingSphinx::ConnectionError
282
343
  # nothing
283
344
  end
@@ -293,8 +354,8 @@ module ThinkingSphinx
293
354
  end
294
355
 
295
356
  def sphinx_document_id
296
- primary_key_for_sphinx * ThinkingSphinx.indexed_models.size +
297
- ThinkingSphinx.indexed_models.index(self.class.source_of_sphinx_index.name)
357
+ primary_key_for_sphinx * ThinkingSphinx.context.indexed_models.size +
358
+ self.class.sphinx_offset
298
359
  end
299
360
 
300
361
  private
@@ -302,5 +363,9 @@ module ThinkingSphinx
302
363
  def sphinx_index_name(suffix)
303
364
  "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
304
365
  end
366
+
367
+ def define_indexes
368
+ self.class.define_indexes
369
+ end
305
370
  end
306
371
  end