thinking-sphinx 1.3.4 → 1.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/README.textile +15 -4
  2. data/VERSION +1 -0
  3. data/features/alternate_primary_key.feature +1 -1
  4. data/features/attribute_updates.feature +11 -5
  5. data/features/deleting_instances.feature +3 -0
  6. data/features/searching_by_index.feature +40 -0
  7. data/features/step_definitions/alpha_steps.rb +5 -1
  8. data/features/step_definitions/beta_steps.rb +1 -1
  9. data/features/step_definitions/common_steps.rb +12 -1
  10. data/features/step_definitions/sphinx_steps.rb +8 -4
  11. data/features/support/db/fixtures/tags.rb +1 -1
  12. data/features/support/env.rb +3 -0
  13. data/features/support/models/alpha.rb +11 -0
  14. data/lib/cucumber/thinking_sphinx/internal_world.rb +7 -6
  15. data/lib/thinking_sphinx.rb +40 -31
  16. data/lib/thinking_sphinx/active_record.rb +164 -195
  17. data/lib/thinking_sphinx/active_record/attribute_updates.rb +9 -6
  18. data/lib/thinking_sphinx/configuration.rb +1 -1
  19. data/lib/thinking_sphinx/deltas/default_delta.rb +14 -20
  20. data/lib/thinking_sphinx/index.rb +76 -19
  21. data/lib/thinking_sphinx/index/builder.rb +2 -2
  22. data/lib/thinking_sphinx/search.rb +7 -0
  23. data/lib/thinking_sphinx/search_methods.rb +22 -4
  24. data/lib/thinking_sphinx/source.rb +6 -6
  25. data/lib/thinking_sphinx/source/sql.rb +4 -2
  26. data/spec/{lib/thinking_sphinx → thinking_sphinx}/active_record/delta_spec.rb +4 -6
  27. data/spec/{lib/thinking_sphinx → thinking_sphinx}/active_record/has_many_association_spec.rb +0 -0
  28. data/spec/{lib/thinking_sphinx → thinking_sphinx}/active_record/scopes_spec.rb +0 -0
  29. data/spec/{lib/thinking_sphinx → thinking_sphinx}/active_record_spec.rb +254 -94
  30. data/spec/{lib/thinking_sphinx → thinking_sphinx}/association_spec.rb +0 -0
  31. data/spec/{lib/thinking_sphinx → thinking_sphinx}/attribute_spec.rb +0 -0
  32. data/spec/{lib/thinking_sphinx → thinking_sphinx}/configuration_spec.rb +2 -2
  33. data/spec/{lib/thinking_sphinx → thinking_sphinx}/core/array_spec.rb +0 -0
  34. data/spec/{lib/thinking_sphinx → thinking_sphinx}/core/string_spec.rb +0 -0
  35. data/spec/{lib/thinking_sphinx → thinking_sphinx}/excerpter_spec.rb +0 -0
  36. data/spec/{lib/thinking_sphinx → thinking_sphinx}/facet_search_spec.rb +0 -0
  37. data/spec/{lib/thinking_sphinx → thinking_sphinx}/facet_spec.rb +0 -0
  38. data/spec/{lib/thinking_sphinx → thinking_sphinx}/field_spec.rb +0 -0
  39. data/spec/{lib/thinking_sphinx → thinking_sphinx}/index/builder_spec.rb +10 -0
  40. data/spec/{lib/thinking_sphinx → thinking_sphinx}/index/faux_column_spec.rb +0 -0
  41. data/spec/thinking_sphinx/index_spec.rb +177 -0
  42. data/spec/{lib/thinking_sphinx → thinking_sphinx}/rails_additions_spec.rb +0 -0
  43. data/spec/{lib/thinking_sphinx → thinking_sphinx}/search_methods_spec.rb +0 -0
  44. data/spec/{lib/thinking_sphinx → thinking_sphinx}/search_spec.rb +33 -0
  45. data/spec/{lib/thinking_sphinx → thinking_sphinx}/source_spec.rb +0 -0
  46. data/spec/{lib/thinking_sphinx_spec.rb → thinking_sphinx_spec.rb} +62 -50
  47. data/tasks/distribution.rb +3 -3
  48. metadata +27 -31
  49. data/VERSION.yml +0 -5
  50. data/features/support/db/active_record.rb +0 -40
  51. data/features/support/db/database.yml +0 -5
  52. data/features/support/db/mysql.rb +0 -3
  53. data/features/support/db/postgresql.rb +0 -3
  54. data/features/support/post_database.rb +0 -43
  55. data/spec/lib/thinking_sphinx/index_spec.rb +0 -45
data/README.textile CHANGED
@@ -14,16 +14,20 @@ To quickly see if your system is ready to run the thinking sphinx specs, run the
14
14
 
15
15
  To get the spec suite running, you will need to install the ginger gem:
16
16
 
17
- sudo gem install ginger --source http://gemcutter.org
17
+ <pre><code>sudo gem install ginger --source http://gemcutter.org</code></pre>
18
18
 
19
- Then install the cucumber, yard, jeweler and rspec gems. Make sure you have a git install version 1.6.0.0 or higher, otherwise the jeweler gem won't install.
19
+ Then install the cucumber, yard, jeweler and rspec gems. Make sure you have a git install version 1.6.0.0 or higher, otherwise the jeweler gem won't install. Bluecloth is required for some of the yard documentation.
20
20
 
21
- sudo gem install cucumber yard jeweler rspec
21
+ <pre>
22
+ sudo gem install bluecloth cucumber yard jeweler rspec
23
+ </pre>
22
24
 
23
25
  Then set up your database:
24
26
 
25
- cp spec/fixtures/database.yml.default spec/fixtures/database.yml
27
+ <pre>
28
+ cp spec/fixtures/database.yml.default spec/fixtures/database.yml &&
26
29
  mysqladmin -u root create thinking_sphinx
30
+ </pre>
27
31
 
28
32
  This last step can be done automatically by the contribute.rb script if all dependencies are met.
29
33
 
@@ -32,11 +36,15 @@ in the app root.
32
36
 
33
37
  You should now have a passing test suite from which to build your patch on.
34
38
 
39
+ <pre>
35
40
  rake spec
41
+ </pre>
36
42
 
37
43
  If you get the message "Failed to start searchd daemon", run the spec with sudo:
38
44
 
45
+ <pre>
39
46
  sudo rake spec
47
+ </pre>
40
48
 
41
49
  If you quit the spec suite before it's completed, you may be left with data in the test
42
50
  database, causing the next run to have failures. Let that run complete and then try again.
@@ -145,3 +153,6 @@ Since I first released this library, there's been quite a few people who have su
145
153
  * Steve Madsen
146
154
  * Justin DeWind
147
155
  * Chris Z
156
+ * Chris Roos
157
+ * Andrew Assarattanakul
158
+ * Jonas von Andrian
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.3.6
@@ -23,5 +23,5 @@ Feature: Searching on a single model
23
23
 
24
24
  When I destroy robot Expendable
25
25
  And I wait for Sphinx to catch up
26
- And I search for three
26
+ And I search for Expendable
27
27
  Then I should get 0 results
@@ -9,15 +9,21 @@ Feature: Update attributes directly to Sphinx
9
9
  When I filter by 3 on value
10
10
  Then I should get 1 result
11
11
 
12
- When I change the value of alpha two to 13
13
- And I wait for Sphinx to catch up
12
+ When I change the value of alpha four to 13
13
+ And I wait for Sphinx to catch up
14
14
  And I filter by 13 on value
15
+ And I use index alpha_core
16
+ Then I should get 1 result
17
+ When I use index alternative_core
15
18
  Then I should get 1 result
16
19
 
17
- When I change the value of alpha two to 3
18
- And I wait for Sphinx to catch up
20
+ When I change the value of alpha four to 4
21
+ And I wait for Sphinx to catch up
19
22
  And I filter by 13 on value
23
+ And I use index alpha_core
20
24
  Then I should get 0 results
25
+ When I use index alternative_core
26
+ Then I should get 0 result
21
27
 
22
28
  Scenario: Updating attributes in Sphinx with delta indexes
23
29
  Given Sphinx is running
@@ -30,4 +36,4 @@ Feature: Update attributes directly to Sphinx
30
36
  Then I should get 1 result
31
37
 
32
38
  When I search for the document id of beta eight in the beta_delta index
33
- Then it should not exist
39
+ Then it should not exist
@@ -56,6 +56,9 @@ Feature: Keeping Sphinx in line with deleted model instances
56
56
  And I am searching on betas
57
57
  When I create a new beta named thirteen
58
58
  And I wait for Sphinx to catch up
59
+ And I search for thirteen
60
+ Then I should get 1 result
61
+
59
62
  And I disable delta updates
60
63
  And I destroy beta thirteen
61
64
  And I wait for Sphinx to catch up
@@ -0,0 +1,40 @@
1
+ Feature: Searching within a single index
2
+ In order to use Thinking Sphinx's core functionality
3
+ A developer
4
+ Should be able to search on a single index
5
+
6
+ Scenario: Searching with alternative index
7
+ Given Sphinx is running
8
+ And I am searching on alphas
9
+ When I order by value
10
+ And I use index alternative_core
11
+ Then I should get 7 results
12
+
13
+ Scenario: Searching with default index
14
+ Given Sphinx is running
15
+ And I am searching on alphas
16
+ When I order by value
17
+ And I use index alpha_core
18
+ Then I should get 10 results
19
+
20
+ Scenario: Searching without specified index
21
+ Given Sphinx is running
22
+ And I am searching on alphas
23
+ When I order by value
24
+ Then I should get 10 results
25
+
26
+ Scenario: Deleting instances from the core index
27
+ Given Sphinx is running
28
+ And I am searching on alphas
29
+
30
+ When I create a new alpha named eleven
31
+ And I process the alpha_core index
32
+ And I process the alternative_core index
33
+ And I wait for Sphinx to catch up
34
+ And I search for eleven
35
+ Then I should get 1 result
36
+
37
+ When I destroy alpha eleven
38
+ And I wait for Sphinx to catch up
39
+ And I search for eleven
40
+ Then I should get 0 results
@@ -1,3 +1,7 @@
1
+ When /^I create a new alpha named (\w+)$/ do |name|
2
+ Alpha.create!(:name => name, :value => 101)
3
+ end
4
+
1
5
  When /^I change the (\w+) of alpha (\w+) to (\w+)$/ do |column, name, replacement|
2
6
  Alpha.find_by_name(name).update_attributes(column.to_sym => replacement)
3
- end
7
+ end
@@ -1,5 +1,5 @@
1
1
  When /^I create a new beta named (\w+)$/ do |name|
2
- Beta.create(:name => name, :value => 101)
2
+ Beta.create!(:name => name, :value => 101)
3
3
  end
4
4
 
5
5
  When /^I change the (\w+) of beta (\w+) to (\w+)$/ do |column, name, replacement|
@@ -10,6 +10,8 @@ Before do
10
10
  @with_all = {}
11
11
  @options = {}
12
12
  @results = nil
13
+
14
+ Given "updates are enabled"
13
15
  end
14
16
 
15
17
  Given /^I am searching on (.+)$/ do |model|
@@ -25,6 +27,11 @@ When /^I am searching for ids$/ do
25
27
  @method = :search_for_ids
26
28
  end
27
29
 
30
+ When /^I use index (.+)$/ do |index|
31
+ @results = nil
32
+ @options[:index] = index
33
+ end
34
+
28
35
  When /^I am retrieving the result count$/ do
29
36
  @result = nil
30
37
  @method = @model ? :search_count : :count
@@ -49,6 +56,10 @@ When /^I search for (\w+) on (\w+)$/ do |query, field|
49
56
  @conditions[field.to_sym] = query
50
57
  end
51
58
 
59
+ When /^I output the raw result data$/ do
60
+ puts results.results.inspect
61
+ end
62
+
52
63
  When /^I clear existing filters$/ do
53
64
  @with = {}
54
65
  @without = {}
@@ -143,7 +154,7 @@ Then /^I can iterate by result and (\w+)$/ do |attribute|
143
154
  iteration = lambda { |result, attr_value|
144
155
  result.should be_kind_of(@model)
145
156
  unless attribute == "group" && attr_value.nil?
146
- attr_value.should be_kind_of(Integer)
157
+ attr_value.should be_kind_of(Integer)
147
158
  end
148
159
  }
149
160
 
@@ -18,6 +18,14 @@ When "I stop Sphinx" do
18
18
  ThinkingSphinx::Configuration.instance.controller.stop
19
19
  end
20
20
 
21
+ When /^I (enable|disable) delta updates$/ do |mode|
22
+ ThinkingSphinx.deltas_enabled = (mode == 'enable')
23
+ end
24
+
25
+ When /^I process the (\w+) index$/ do |index|
26
+ ThinkingSphinx::Configuration.instance.controller.index index
27
+ end
28
+
21
29
  Then /^Sphinx should be running/ do
22
30
  ThinkingSphinx.sphinx_running?.should be_true
23
31
  end
@@ -25,7 +33,3 @@ end
25
33
  Then "Sphinx should not be running" do
26
34
  ThinkingSphinx.sphinx_running?.should be_false
27
35
  end
28
-
29
- When /^I (enable|disable) delta updates$/ do |mode|
30
- ThinkingSphinx.deltas_enabled = (mode == 'enable')
31
- end
@@ -24,4 +24,4 @@ Developer.find(:all).each do |developer|
24
24
  end
25
25
  end
26
26
 
27
- Tagging.create(:taggable => Developer.last)
27
+ Tagging.create(:taggable => Developer.find(:all).last)
@@ -7,6 +7,9 @@ require 'will_paginate'
7
7
  require 'active_record'
8
8
 
9
9
  $:.unshift File.dirname(__FILE__) + '/../../lib'
10
+ Dir[File.join(File.dirname(__FILE__), '../../vendor/*/lib')].each do |path|
11
+ $:.unshift path
12
+ end
10
13
 
11
14
  require 'cucumber/thinking_sphinx/internal_world'
12
15
 
@@ -7,4 +7,15 @@ class Alpha < ActiveRecord::Base
7
7
 
8
8
  set_property :field_weights => {"name" => 10}
9
9
  end
10
+
11
+ define_index 'alternative' do
12
+ indexes :name, :as => :alternative_name, :sortable => true
13
+
14
+ has value, created_at, created_on
15
+ has cost, :facet => true
16
+
17
+ set_property :field_weights => {'alternative_name' => 10}
18
+
19
+ where "value > 3"
20
+ end
10
21
  end
@@ -79,11 +79,11 @@ module Cucumber
79
79
 
80
80
  def database_settings
81
81
  {
82
- :adapter => @adapter,
83
- :database => @database,
84
- :username => @username,
85
- :password => @password,
86
- :host => @host
82
+ 'adapter' => @adapter,
83
+ 'database' => @database,
84
+ 'username' => @username,
85
+ 'password' => @password,
86
+ 'host' => @host
87
87
  }.merge yaml_database_settings
88
88
  end
89
89
 
@@ -108,7 +108,8 @@ module Cucumber
108
108
  end
109
109
 
110
110
  def load_files(path)
111
- Dir["#{path}/*.rb"].each do |file|
111
+ files = Dir["#{path}/*.rb"].sort!
112
+ files.each do |file|
112
113
  require file.gsub(/\.rb$/, '')
113
114
  end
114
115
  end
@@ -52,26 +52,30 @@ module ThinkingSphinx
52
52
  # @return [String] The version number as a string
53
53
  #
54
54
  def self.version
55
- hash = YAML.load_file File.join(File.dirname(__FILE__), '../VERSION.yml')
56
- [hash[:major], hash[:minor], hash[:patch]].join('.')
55
+ open(File.join(File.dirname(__FILE__), '../VERSION')) { |f|
56
+ f.read.strip
57
+ }
57
58
  end
58
59
 
59
60
  # The collection of indexed models. Keep in mind that Rails lazily loads
60
61
  # its classes, so this may not actually be populated with _all_ the models
61
62
  # that have Sphinx indexes.
62
63
  def self.indexed_models
63
- @@indexed_models ||= []
64
+ Thread.current[:thinking_sphinx_indexed_models] ||= []
64
65
  end
65
66
 
66
67
  def self.unique_id_expression(offset = nil)
67
- "* #{ThinkingSphinx.indexed_models.size} + #{offset || 0}"
68
+ "* #{indexed_models.size} + #{offset || 0}"
68
69
  end
69
70
 
70
71
  # Check if index definition is disabled.
71
72
  #
72
73
  def self.define_indexes?
73
- @@define_indexes = true unless defined?(@@define_indexes)
74
- @@define_indexes == true
74
+ if Thread.current[:thinking_sphinx_define_indexes].nil?
75
+ Thread.current[:thinking_sphinx_define_indexes] = true
76
+ end
77
+
78
+ Thread.current[:thinking_sphinx_define_indexes]
75
79
  end
76
80
 
77
81
  # Enable/disable indexes - you may want to do this while migrating data.
@@ -79,16 +83,19 @@ module ThinkingSphinx
79
83
  # ThinkingSphinx.define_indexes = false
80
84
  #
81
85
  def self.define_indexes=(value)
82
- @@define_indexes = value
86
+ Thread.current[:thinking_sphinx_define_indexes] = value
83
87
  end
84
88
 
85
- @@deltas_enabled = nil
86
-
87
89
  # Check if delta indexing is enabled.
88
90
  #
89
91
  def self.deltas_enabled?
90
- @@deltas_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@deltas_enabled.nil?
91
- @@deltas_enabled
92
+ if Thread.current[:thinking_sphinx_deltas_enabled].nil?
93
+ Thread.current[:thinking_sphinx_deltas_enabled] = (
94
+ ThinkingSphinx::Configuration.environment != "test"
95
+ )
96
+ end
97
+
98
+ Thread.current[:thinking_sphinx_deltas_enabled]
92
99
  end
93
100
 
94
101
  # Enable/disable all delta indexing.
@@ -96,17 +103,20 @@ module ThinkingSphinx
96
103
  # ThinkingSphinx.deltas_enabled = false
97
104
  #
98
105
  def self.deltas_enabled=(value)
99
- @@deltas_enabled = value
106
+ Thread.current[:thinking_sphinx_deltas_enabled] = value
100
107
  end
101
108
 
102
- @@updates_enabled = nil
103
-
104
109
  # Check if updates are enabled. True by default, unless within the test
105
110
  # environment.
106
111
  #
107
112
  def self.updates_enabled?
108
- @@updates_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@updates_enabled.nil?
109
- @@updates_enabled
113
+ if Thread.current[:thinking_sphinx_updates_enabled].nil?
114
+ Thread.current[:thinking_sphinx_updates_enabled] = (
115
+ ThinkingSphinx::Configuration.environment != "test"
116
+ )
117
+ end
118
+
119
+ Thread.current[:thinking_sphinx_updates_enabled]
110
120
  end
111
121
 
112
122
  # Enable/disable updates to Sphinx
@@ -114,38 +124,37 @@ module ThinkingSphinx
114
124
  # ThinkingSphinx.updates_enabled = false
115
125
  #
116
126
  def self.updates_enabled=(value)
117
- @@updates_enabled = value
127
+ Thread.current[:thinking_sphinx_updates_enabled] = value
118
128
  end
119
129
 
120
- @@suppress_delta_output = false
121
-
122
130
  def self.suppress_delta_output?
123
- @@suppress_delta_output
131
+ Thread.current[:thinking_sphinx_suppress_delta_output] ||= false
124
132
  end
125
133
 
126
134
  def self.suppress_delta_output=(value)
127
- @@suppress_delta_output = value
135
+ Thread.current[:thinking_sphinx_suppress_delta_output] = value
128
136
  end
129
137
 
130
- @@use_group_by_shortcut = nil
131
138
  # Checks to see if MySQL will allow simplistic GROUP BY statements. If not,
132
139
  # or if not using MySQL, this will return false.
133
140
  #
134
141
  def self.use_group_by_shortcut?
135
- @@use_group_by_shortcut ||= !!(
136
- mysql? && ::ActiveRecord::Base.connection.select_all(
137
- "SELECT @@global.sql_mode, @@session.sql_mode;"
138
- ).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
139
- )
142
+ if Thread.current[:thinking_sphinx_use_group_by_shortcut].nil?
143
+ Thread.current[:thinking_sphinx_use_group_by_shortcut] = !!(
144
+ mysql? && ::ActiveRecord::Base.connection.select_all(
145
+ "SELECT @@global.sql_mode, @@session.sql_mode;"
146
+ ).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
147
+ )
148
+ end
149
+
150
+ Thread.current[:thinking_sphinx_use_group_by_shortcut]
140
151
  end
141
152
 
142
- @@remote_sphinx = false
143
-
144
153
  # An indication of whether Sphinx is running on a remote machine instead of
145
154
  # the same machine.
146
155
  #
147
156
  def self.remote_sphinx?
148
- @@remote_sphinx
157
+ Thread.current[:thinking_sphinx_remote_sphinx] ||= false
149
158
  end
150
159
 
151
160
  # Tells Thinking Sphinx that Sphinx is running on a different machine, and
@@ -157,7 +166,7 @@ module ThinkingSphinx
157
166
  # ThinkingSphinx.remote_sphinx = true
158
167
  #
159
168
  def self.remote_sphinx=(value)
160
- @@remote_sphinx = value
169
+ Thread.current[:thinking_sphinx_remote_sphinx] = value
161
170
  end
162
171
 
163
172
  # Check if Sphinx is running. If remote_sphinx is set to true (indicating
@@ -12,8 +12,10 @@ 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
16
-
17
19
  def set_sphinx_primary_key(attribute)
18
20
  @sphinx_primary_key_attribute = attribute
19
21
  end
@@ -22,93 +24,6 @@ module ThinkingSphinx
22
24
  @sphinx_primary_key_attribute || primary_key
23
25
  end
24
26
 
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
27
  def sphinx_index_options
113
28
  sphinx_indexes.last.options
114
29
  end
@@ -127,26 +42,6 @@ module ThinkingSphinx
127
42
  (subclasses << self).collect { |klass| klass.to_crc32 }
128
43
  end
129
44
 
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
45
  def sphinx_database_adapter
151
46
  @sphinx_database_adapter ||=
152
47
  ThinkingSphinx::AbstractAdapter.detect(self)
@@ -156,86 +51,14 @@ module ThinkingSphinx
156
51
  self.name.underscore.tr(':/\\', '_')
157
52
  end
158
53
 
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
165
- end
166
-
167
54
  private
168
55
 
169
56
  def sphinx_delta?
170
57
  self.sphinx_indexes.any? { |index| index.delta? }
171
58
  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
206
-
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
212
- end
213
-
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
224
- end
225
-
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
233
- end
234
59
  end
235
60
  end
236
61
 
237
- base.send(:include, ThinkingSphinx::ActiveRecord::Delta)
238
-
239
62
  ::ActiveRecord::Associations::HasManyAssociation.send(
240
63
  :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
241
64
  )
@@ -244,6 +67,160 @@ module ThinkingSphinx
244
67
  )
245
68
  end
246
69
 
70
+ module ClassMethods
71
+ # Allows creation of indexes for Sphinx. If you don't do this, there
72
+ # isn't much point trying to search (or using this plugin at all,
73
+ # really).
74
+ #
75
+ # An example or two:
76
+ #
77
+ # define_index
78
+ # indexes :id, :as => :model_id
79
+ # indexes name
80
+ # end
81
+ #
82
+ # You can also grab fields from associations - multiple levels deep
83
+ # if necessary.
84
+ #
85
+ # define_index do
86
+ # indexes tags.name, :as => :tag
87
+ # indexes articles.content
88
+ # indexes orders.line_items.product.name, :as => :product
89
+ # end
90
+ #
91
+ # And it will automatically concatenate multiple fields:
92
+ #
93
+ # define_index do
94
+ # indexes [author.first_name, author.last_name], :as => :author
95
+ # end
96
+ #
97
+ # The #indexes method is for fields - if you want attributes, use
98
+ # #has instead. All the same rules apply - but keep in mind that
99
+ # attributes are for sorting, grouping and filtering, not searching.
100
+ #
101
+ # define_index do
102
+ # # fields ...
103
+ #
104
+ # has created_at, updated_at
105
+ # end
106
+ #
107
+ # One last feature is the delta index. This requires the model to
108
+ # have a boolean field named 'delta', and is enabled as follows:
109
+ #
110
+ # define_index do
111
+ # # fields ...
112
+ # # attributes ...
113
+ #
114
+ # set_property :delta => true
115
+ # end
116
+ #
117
+ # Check out the more detailed documentation for each of these methods
118
+ # at ThinkingSphinx::Index::Builder.
119
+ #
120
+ def define_index(name = nil, &block)
121
+ return unless ThinkingSphinx.define_indexes?
122
+
123
+ self.sphinx_indexes ||= []
124
+ self.sphinx_facets ||= []
125
+
126
+ index = ThinkingSphinx::Index::Builder.generate self, name, &block
127
+
128
+ unless ThinkingSphinx.indexed_models.include?(self.name)
129
+ ThinkingSphinx.indexed_models << self.name
130
+ end
131
+
132
+ add_sphinx_callbacks_and_extend(index.delta?)
133
+ self.sphinx_indexes << index
134
+
135
+ index
136
+
137
+ # We want to make sure that if the database doesn't exist, then Thinking
138
+ # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
139
+ # and db:migrate). It's a bit hacky, but I can't think of a better way.
140
+ rescue StandardError => err
141
+ case err.class.name
142
+ when "Mysql::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
143
+ return
144
+ else
145
+ raise err
146
+ end
147
+ end
148
+
149
+ def indexed_by_sphinx?
150
+ sphinx_indexes && sphinx_indexes.length > 0
151
+ end
152
+
153
+ def delta_indexed_by_sphinx?
154
+ sphinx_indexes && sphinx_indexes.any? { |index| index.delta? }
155
+ end
156
+
157
+ def sphinx_index_names
158
+ sphinx_indexes.collect(&:all_names).flatten
159
+ end
160
+
161
+ def core_index_names
162
+ sphinx_indexes.collect(&:core_name)
163
+ end
164
+
165
+ def delta_index_names
166
+ sphinx_indexes.select(&:delta?).collect(&:delta_name)
167
+ end
168
+
169
+ def to_riddle(offset)
170
+ sphinx_database_adapter.setup
171
+
172
+ local_sphinx_indexes.collect { |index|
173
+ index.to_riddle(offset)
174
+ }.flatten
175
+ end
176
+
177
+ def source_of_sphinx_index
178
+ possible_models = self.sphinx_indexes.collect { |index| index.model }
179
+ return self if possible_models.include?(self)
180
+
181
+ parent = self.superclass
182
+ while !possible_models.include?(parent) && parent != ::ActiveRecord::Base
183
+ parent = parent.superclass
184
+ end
185
+
186
+ return parent
187
+ end
188
+
189
+ def delete_in_index(index, document_id)
190
+ return unless ThinkingSphinx.sphinx_running? &&
191
+ search_for_id(document_id, index)
192
+
193
+ ThinkingSphinx::Configuration.instance.client.update(
194
+ index, ['sphinx_deleted'], {document_id => [1]}
195
+ )
196
+ end
197
+
198
+ private
199
+
200
+ def local_sphinx_indexes
201
+ sphinx_indexes.select { |index|
202
+ index.model == self
203
+ }
204
+ end
205
+
206
+ def add_sphinx_callbacks_and_extend(delta = false)
207
+ unless indexed_by_sphinx?
208
+ after_destroy :toggle_deleted
209
+
210
+ include ThinkingSphinx::SearchMethods
211
+ include ThinkingSphinx::ActiveRecord::AttributeUpdates
212
+ include ThinkingSphinx::ActiveRecord::Scopes
213
+ end
214
+
215
+ if delta && !delta_indexed_by_sphinx?
216
+ include ThinkingSphinx::ActiveRecord::Delta
217
+
218
+ before_save :toggle_delta
219
+ after_commit :index_delta
220
+ end
221
+ end
222
+ end
223
+
247
224
  def in_index?(suffix)
248
225
  self.class.search_for_id self.sphinx_document_id, sphinx_index_name(suffix)
249
226
  end
@@ -261,23 +238,15 @@ module ThinkingSphinx
261
238
  end
262
239
 
263
240
  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
241
+ return unless ThinkingSphinx.updates_enabled?
268
242
 
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?
243
+ self.class.core_index_names.each do |index_name|
244
+ self.class.delete_in_index index_name, self.sphinx_document_id
245
+ end
246
+ self.class.delta_index_names.each do |index_name|
247
+ self.class.delete_in_index index_name, self.sphinx_document_id
248
+ end if self.class.delta_indexed_by_sphinx? && toggled_delta?
274
249
 
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
250
  rescue ::ThinkingSphinx::ConnectionError
282
251
  # nothing
283
252
  end