freelancing-god-thinking-sphinx 0.9.10 → 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -16,15 +16,32 @@ To get the spec suite running, you will need to install the not-a-mock gem if yo
16
16
  cd not-a-mock
17
17
  rake gem
18
18
  gem install pkg/not_a_mock-1.1.0.gem
19
+
20
+ Then install the ginger gem. The steps are the same, except that you might need to sudo the gem install:
19
21
 
20
- Then set up your database
22
+ git clone git://github.com/freelancing-god/ginger.git
23
+ cd ginger
24
+ rake gem
25
+ sudo gem install pkg/ginger-1.1.0.gem
26
+
27
+ Then set up your database:
21
28
 
22
29
  cp spec/fixtures/database.yml.default spec/fixtures/database.yml
23
30
  mysqladmin -u root create thinking_sphinx
31
+
32
+ Make sure you don't have another Sphinx daemon (searchd) running. If you do, quit it with "rake ts:stop"
33
+ in the app root.
24
34
 
25
35
  You should now have a passing test suite from which to build your patch on.
26
36
 
27
37
  rake spec
38
+
39
+ If you get the message "Failed to start searchd daemon", run the spec with sudo:
40
+
41
+ sudo rake spec
42
+
43
+ If you quit the spec suite before it's completed, you may be left with data in the test
44
+ database, causing the next run to have failures. Let that run complete and then try again.
28
45
 
29
46
  == Contributors
30
47
 
@@ -74,3 +91,4 @@ Since I first released this library, there's been quite a few people who have su
74
91
  - Mike Flester
75
92
  - Jim Remsik
76
93
  - Kennon Ballou
94
+ - Henrik Nyh
@@ -30,7 +30,7 @@ module ThinkingSphinx
30
30
  module Version #:nodoc:
31
31
  Major = 0
32
32
  Minor = 9
33
- Tiny = 10
33
+ Tiny = 11
34
34
 
35
35
  String = [Major, Minor, Tiny].join('.')
36
36
  end
@@ -109,4 +109,15 @@ module ThinkingSphinx
109
109
  "SELECT @@global.sql_mode, @@session.sql_mode;"
110
110
  ).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
111
111
  end
112
+
113
+ def self.sphinx_running?
114
+ pid_file = ThinkingSphinx::Configuration.instance.pid_file
115
+
116
+ if File.exists?(pid_file)
117
+ pid = `cat #{pid_file}`[/\d+/]
118
+ `ps -p #{pid} | wc -l`.to_i > 1
119
+ else
120
+ false
121
+ end
122
+ end
112
123
  end
@@ -103,6 +103,18 @@ module ThinkingSphinx
103
103
  def to_crc32s
104
104
  (subclasses << self).collect { |klass| klass.to_crc32 }
105
105
  end
106
+
107
+ def source_of_sphinx_index
108
+ possible_models = self.sphinx_indexes.collect { |index| index.model }
109
+ return self if possible_models.include?(self)
110
+
111
+ parent = self.superclass
112
+ while !possible_models.include?(parent) && parent != ::ActiveRecord::Base
113
+ parent = parent.superclass
114
+ end
115
+
116
+ return parent
117
+ end
106
118
  end
107
119
  end
108
120
 
@@ -120,12 +132,12 @@ module ThinkingSphinx
120
132
  def in_core_index?
121
133
  self.class.search_for_id(
122
134
  self.sphinx_document_id,
123
- "#{self.class.name.underscore.tr(':/\\', '_')}_core"
135
+ "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_core"
124
136
  )
125
137
  end
126
138
 
127
139
  def toggle_deleted
128
- return unless ThinkingSphinx.updates_enabled?
140
+ return unless ThinkingSphinx.updates_enabled? && ThinkingSphinx.sphinx_running?
129
141
 
130
142
  config = ThinkingSphinx::Configuration.instance
131
143
  client = Riddle::Client.new config.address, config.port
@@ -147,7 +159,7 @@ module ThinkingSphinx
147
159
 
148
160
  def sphinx_document_id
149
161
  (self.id * ThinkingSphinx.indexed_models.size) +
150
- ThinkingSphinx.indexed_models.index(self.class.name)
162
+ ThinkingSphinx.indexed_models.index(self.class.source_of_sphinx_index.name)
151
163
  end
152
164
  end
153
165
  end
@@ -133,7 +133,7 @@ module ThinkingSphinx
133
133
  # when you would like to index a calculated value. Don't forget to set
134
134
  # the type of the attribute though:
135
135
  #
136
- # indexes "age < 18", :as => :minor, :type => :boolean
136
+ # has "age < 18", :as => :minor, :type => :boolean
137
137
  #
138
138
  # If you're creating attributes for latitude and longitude, don't
139
139
  # forget that Sphinx expects these values to be in radians.
@@ -44,7 +44,7 @@ module ThinkingSphinx
44
44
  #
45
45
  # User.search "pat", :include => :posts
46
46
  #
47
- # == Advanced Searching
47
+ # == Match Modes
48
48
  #
49
49
  # Sphinx supports 5 different matching modes. By default Thinking Sphinx
50
50
  # uses :all, which unsurprisingly requires all the supplied search terms
@@ -62,6 +62,20 @@ module ThinkingSphinx
62
62
  # for more complex query syntax, refer to the sphinx documentation for further
63
63
  # details.
64
64
  #
65
+ # == Weighting
66
+ #
67
+ # Sphinx has support for weighting, where matches in one field can be considered
68
+ # more important than in another. Weights are integers, with 1 as the default.
69
+ # They can be set per-search like this:
70
+ #
71
+ # User.search "pat allan", :field_weights => { :alias => 4, :aka => 2 }
72
+ #
73
+ # If you're searching multiple models, you can set per-index weights:
74
+ #
75
+ # ThinkingSphinx::Search.search "pat", :index_weights => { User => 10 }
76
+ #
77
+ # See http://sphinxsearch.com/doc.html#weighting for further details.
78
+ #
65
79
  # == Searching by Fields
66
80
  #
67
81
  # If you want to step it up a level, you can limit your search terms to
@@ -275,6 +289,16 @@ module ThinkingSphinx
275
289
  klass = options[:class]
276
290
  index_options = klass ? klass.sphinx_indexes.last.options : {}
277
291
 
292
+ # Turn :index_weights => { "foo" => 2, User => 1 }
293
+ # into :index_weights => { "foo" => 2, "user_core" => 1 }
294
+ if iw = options[:index_weights]
295
+ options[:index_weights] = iw.inject({}) do |hash, (index,weight)|
296
+ key = index.is_a?(Class) ? "#{ThinkingSphinx::Index.name(index)}_core" : index
297
+ hash[key] = weight
298
+ hash
299
+ end
300
+ end
301
+
278
302
  [
279
303
  :max_matches, :match_mode, :sort_mode, :sort_by, :id_range,
280
304
  :group_by, :group_function, :group_clause, :group_distinct, :cut_off,
@@ -121,6 +121,8 @@ describe "ThinkingSphinx::ActiveRecord" do
121
121
 
122
122
  describe "toggle_deleted method" do
123
123
  before :each do
124
+ ThinkingSphinx.stub_method(:sphinx_running? => true)
125
+
124
126
  @configuration = ThinkingSphinx::Configuration.instance
125
127
  @configuration.stub_methods(
126
128
  :address => "an address",
@@ -160,6 +162,15 @@ describe "ThinkingSphinx::ActiveRecord" do
160
162
  )
161
163
  end
162
164
 
165
+ it "shouldn't attempt to update the deleted flag if sphinx isn't running" do
166
+ ThinkingSphinx.stub_method(:sphinx_running? => false)
167
+
168
+ @person.toggle_deleted
169
+
170
+ @person.should_not have_received(:in_core_index?)
171
+ @client.should_not have_received(:update)
172
+ end
173
+
163
174
  it "should update the delta index's deleted flag if delta indexes are enabled and the instance's delta is true" do
164
175
  ThinkingSphinx.stub_method(:deltas_enabled? => true)
165
176
  Person.sphinx_indexes.each { |index| index.stub_method(:delta? => true) }
@@ -270,6 +281,16 @@ describe "ThinkingSphinx::ActiveRecord" do
270
281
  Beta.search("three").should be_empty
271
282
  end
272
283
 
284
+ it "should remove subclass instances from the core index if they're in it" do
285
+ Cat.search("moggy").should_not be_empty
286
+
287
+ cat = Cat.find(:first, :conditions => {:name => "moggy"})
288
+ cat.destroy
289
+ sleep(1)
290
+
291
+ Cat.search("moggy").should be_empty
292
+ end
293
+
273
294
  it "should remove destroyed new instances from the delta index if they're in it" do
274
295
  beta = Beta.create!(:name => "eleven")
275
296
  sleep(1) # wait for Sphinx to catch up
@@ -508,5 +508,5 @@ describe ThinkingSphinx::Configuration do
508
508
 
509
509
  config.source_options[option.to_sym] = nil
510
510
  end
511
- end
511
+ end
512
512
  end
@@ -81,7 +81,7 @@ describe ThinkingSphinx::Index do
81
81
  conf = @index.to_config(Person, 0, @database, 0)
82
82
  conf.should_not match(/sql_port/)
83
83
  end
84
-
84
+
85
85
  it "should have a pre query 'SET NAMES utf8' if using mysql and utf8 charset" do
86
86
  @index.options[:charset_type] = "utf-8"
87
87
  @index.to_config(Person, 0, @database, 0).should match(
@@ -126,4 +126,19 @@ describe ThinkingSphinx do
126
126
  end
127
127
  end
128
128
  end
129
+
130
+ it "should detect if sphinx is running" do
131
+ ThinkingSphinx.sphinx_running?.should be_false
132
+
133
+ @sphinx.setup_sphinx
134
+ @sphinx.start
135
+ sleep(1)
136
+
137
+ ThinkingSphinx.sphinx_running?.should be_true
138
+
139
+ @sphinx.stop
140
+ sleep(1)
141
+
142
+ ThinkingSphinx.sphinx_running?.should be_false
143
+ end
129
144
  end
@@ -82,6 +82,8 @@ namespace :ts do
82
82
  desc "Restart Sphinx"
83
83
  task :restart => "thinking_sphinx:restart"
84
84
  desc "Generate the Sphinx configuration file using Thinking Sphinx's settings"
85
+ task :conf => "thinking_sphinx:configure"
86
+ desc "Generate the Sphinx configuration file using Thinking Sphinx's settings"
85
87
  task :config => "thinking_sphinx:configure"
86
88
  end
87
89
 
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Nick Muerdter
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,16 @@
1
+ after_commit
2
+ ===========
3
+
4
+ A Ruby on Rails plugin to add after_commit callbacks. The callbacks that are provided can be used
5
+ to trigger events that run only after the entire transaction is complete. This is beneficial
6
+ in situations where you are doing asynchronous processing and need committed objects.
7
+
8
+ The following callbacks are provided:
9
+
10
+ * (1) after_commit
11
+ * (2) after_commit_on_create
12
+ * (3) after_commit_on_update
13
+ * (4) after_commit_on_destroy
14
+
15
+ The after_commit callback is run for any object that has just been committed. You can obtain finer
16
+ callback control by using the additional <tt>after_commit_on_*</tt> callbacks.
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the after_commit plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the after_commit plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'AfterCommit'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,5 @@
1
+ ActiveRecord::Base.send(:include, AfterCommit::ActiveRecord)
2
+
3
+ Object.subclasses_of(ActiveRecord::ConnectionAdapters::AbstractAdapter).each do |klass|
4
+ klass.send(:include, AfterCommit::ConnectionAdapters)
5
+ end
@@ -0,0 +1,42 @@
1
+ require 'after_commit/active_record'
2
+ require 'after_commit/connection_adapters'
3
+
4
+ module AfterCommit
5
+ def self.committed_records
6
+ @@committed_records ||= []
7
+ end
8
+
9
+ def self.committed_records=(committed_records)
10
+ @@committed_records = committed_records
11
+ end
12
+
13
+ def self.committed_records_on_create
14
+ @@committed_records_on_create ||= []
15
+ end
16
+
17
+ def self.committed_records_on_create=(committed_records)
18
+ @@committed_records_on_create = committed_records
19
+ end
20
+
21
+ def self.committed_records_on_update
22
+ @@committed_records_on_update ||= []
23
+ end
24
+
25
+ def self.committed_records_on_update=(committed_records)
26
+ @@committed_records_on_update = committed_records
27
+ end
28
+
29
+ def self.committed_records_on_destroy
30
+ @@committed_records_on_destroy ||= []
31
+ end
32
+
33
+ def self.committed_records_on_destroy=(committed_records)
34
+ @@committed_records_on_destroy = committed_records
35
+ end
36
+ end
37
+
38
+ ActiveRecord::Base.send(:include, AfterCommit::ActiveRecord)
39
+
40
+ Object.subclasses_of(ActiveRecord::ConnectionAdapters::AbstractAdapter).each do |klass|
41
+ klass.send(:include, AfterCommit::ConnectionAdapters)
42
+ end
@@ -0,0 +1,91 @@
1
+ module AfterCommit
2
+ module ActiveRecord
3
+ # Based on the code found in Thinking Sphinx:
4
+ # http://ts.freelancing-gods.com/ which was based on code written by Eli
5
+ # Miller:
6
+ # http://elimiller.blogspot.com/2007/06/proper-cache-expiry-with-aftercommit.html
7
+ # with slight modification from Joost Hietbrink. And now me! Whew.
8
+ def self.included(base)
9
+ base.class_eval do
10
+ # The define_callbacks method was added post Rails 2.0.2 - if it
11
+ # doesn't exist, we define the callback manually
12
+ if respond_to?(:define_callbacks)
13
+ define_callbacks :after_commit,
14
+ :after_commit_on_create,
15
+ :after_commit_on_update,
16
+ :after_commit_on_destroy
17
+ else
18
+ class << self
19
+ # Handle after_commit callbacks - call all the registered callbacks.
20
+ def after_commit(*callbacks, &block)
21
+ callbacks << block if block_given?
22
+ write_inheritable_array(:after_commit, callbacks)
23
+ end
24
+
25
+ def after_commit_on_create(*callbacks, &block)
26
+ callbacks << block if block_given?
27
+ write_inheritable_array(:after_commit_on_create, callbacks)
28
+ end
29
+
30
+ def after_commit_on_update(*callbacks, &block)
31
+ callbacks << block if block_given?
32
+ write_inheritable_array(:after_commit_on_update, callbacks)
33
+ end
34
+
35
+ def after_commit_on_destroy(*callbacks, &block)
36
+ callbacks << block if block_given?
37
+ write_inheritable_array(:after_commit_on_destroy, callbacks)
38
+ end
39
+ end
40
+ end
41
+
42
+ after_save :add_committed_record
43
+ after_create :add_committed_record_on_create
44
+ after_update :add_committed_record_on_update
45
+ after_destroy :add_committed_record_on_destroy
46
+
47
+ # We need to keep track of records that have been saved or destroyed
48
+ # within this transaction.
49
+ def add_committed_record
50
+ AfterCommit.committed_records << self
51
+ end
52
+
53
+ def add_committed_record_on_create
54
+ AfterCommit.committed_records_on_create << self
55
+ end
56
+
57
+ def add_committed_record_on_update
58
+ AfterCommit.committed_records_on_update << self
59
+ end
60
+
61
+ def add_committed_record_on_destroy
62
+ AfterCommit.committed_records << self
63
+ AfterCommit.committed_records_on_destroy << self
64
+ end
65
+
66
+ def after_commit
67
+ # Deliberately blank.
68
+ end
69
+
70
+ # Wraps a call to the private callback method so that the the
71
+ # after_commit callback can be made from the ConnectionAdapters when
72
+ # the commit for the transaction has finally succeeded.
73
+ def after_commit_callback
74
+ callback(:after_commit)
75
+ end
76
+
77
+ def after_commit_on_create_callback
78
+ callback(:after_commit_on_create)
79
+ end
80
+
81
+ def after_commit_on_update_callback
82
+ callback(:after_commit_on_update)
83
+ end
84
+
85
+ def after_commit_on_destroy_callback
86
+ callback(:after_commit_on_destroy)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end