freelancing-god-thinking-sphinx 0.9.10 → 0.9.11

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