jaikoo-thinking-sphinx 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/LICENCE +20 -0
  2. data/README +76 -0
  3. data/lib/thinking_sphinx.rb +112 -0
  4. data/lib/thinking_sphinx/active_record.rb +153 -0
  5. data/lib/thinking_sphinx/active_record/delta.rb +80 -0
  6. data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
  7. data/lib/thinking_sphinx/active_record/search.rb +50 -0
  8. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +27 -0
  9. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +9 -0
  10. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +84 -0
  11. data/lib/thinking_sphinx/association.rb +144 -0
  12. data/lib/thinking_sphinx/attribute.rb +284 -0
  13. data/lib/thinking_sphinx/collection.rb +105 -0
  14. data/lib/thinking_sphinx/configuration.rb +314 -0
  15. data/lib/thinking_sphinx/field.rb +206 -0
  16. data/lib/thinking_sphinx/index.rb +432 -0
  17. data/lib/thinking_sphinx/index/builder.rb +220 -0
  18. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  19. data/lib/thinking_sphinx/rails_additions.rb +68 -0
  20. data/lib/thinking_sphinx/search.rb +436 -0
  21. data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +132 -0
  22. data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
  23. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
  24. data/spec/unit/thinking_sphinx/active_record_spec.rb +295 -0
  25. data/spec/unit/thinking_sphinx/association_spec.rb +247 -0
  26. data/spec/unit/thinking_sphinx/attribute_spec.rb +360 -0
  27. data/spec/unit/thinking_sphinx/collection_spec.rb +71 -0
  28. data/spec/unit/thinking_sphinx/configuration_spec.rb +512 -0
  29. data/spec/unit/thinking_sphinx/field_spec.rb +224 -0
  30. data/spec/unit/thinking_sphinx/index/builder_spec.rb +34 -0
  31. data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +68 -0
  32. data/spec/unit/thinking_sphinx/index_spec.rb +317 -0
  33. data/spec/unit/thinking_sphinx/search_spec.rb +203 -0
  34. data/spec/unit/thinking_sphinx_spec.rb +129 -0
  35. data/tasks/thinking_sphinx_tasks.rake +1 -0
  36. data/tasks/thinking_sphinx_tasks.rb +100 -0
  37. metadata +103 -0
data/LICENCE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Pat Allan
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.
data/README ADDED
@@ -0,0 +1,76 @@
1
+ = Thinking Sphinx
2
+
3
+ == Usage
4
+
5
+ First, if you haven't done so already, check out the main usage[http://ts.freelancing-gods.com/usage.html] page. Once you've done that, the next place to look for information is the specific method docs - ThinkingSphinx::Search and ThinkingSphinx::Index::Builder in particular.
6
+
7
+ Keep in mind that while Thinking Sphinx works for ActiveRecord with Merb, it doesn't yet support DataMapper (although that is planned).
8
+
9
+ == Contributing
10
+
11
+ Fork on GitHub and after you've committed tested patches, send a pull request.
12
+
13
+ To get the spec suite running, you will need to install the not-a-mock gem if you don't already have it:
14
+
15
+ git clone git://github.com/freelancing-god/not-a-mock.git
16
+ cd not-a-mock
17
+ rake gem
18
+ gem install pkg/not_a_mock-1.1.0.gem
19
+
20
+ Then set up your database
21
+
22
+ cp spec/fixtures/database.yml.default spec/fixtures/database.yml
23
+ mysqladmin -u root create thinking_sphinx
24
+
25
+ You should now have a passing test suite from which to build your patch on.
26
+
27
+ rake spec
28
+
29
+ == Contributors
30
+
31
+ Since I first released this library, there's been quite a few people who have submitted patches, to my immense gratitude. Others have suggested syntax changes and general improvements. So my thanks to the following people:
32
+
33
+ - Joost Hietbrink
34
+ - Jonathan Conway
35
+ - Gregory Mirzayantz
36
+ - Tung Nguyen
37
+ - Sean Cribbs
38
+ - Benoit Caccinolo
39
+ - John Barton
40
+ - Oliver Beddows
41
+ - Arthur Zapparoli
42
+ - Dusty Doris
43
+ - Marcus Crafter
44
+ - Patrick Lenz
45
+ - Björn Andreasson
46
+ - James Healy
47
+ - Jae-Jun Hwang
48
+ - Xavier Shay
49
+ - Jason Rust
50
+ - Gopal Patel
51
+ - Chris Heald
52
+ - Peter Vandenberk
53
+ - Josh French
54
+ - Andrew Bennett
55
+ - Jordan Fowler
56
+ - Seth Walker
57
+ - Joe Noon
58
+ - Wolfgang Postler
59
+ - Rick Olson
60
+ - Killian Murphy
61
+ - Morten Primdahl
62
+ - Ryan Bates
63
+ - David Eisinger
64
+ - Shay Arnett
65
+ - Minh Tran
66
+ - Jeremy Durham
67
+ - Piotr Sarnacki
68
+ - Matt Johnson
69
+ - Nicolas Blanco
70
+ - Max Lapshin
71
+ - Josh Natanson
72
+ - Philip Hallstrom
73
+ - Christian Rishøj
74
+ - Mike Flester
75
+ - Jim Remsik
76
+ - Kennon Ballou
@@ -0,0 +1,112 @@
1
+ Dir[File.join(File.dirname(__FILE__), '../vendor/*/lib')].each do |path|
2
+ $LOAD_PATH.unshift path
3
+ end
4
+
5
+ require 'active_record'
6
+ require 'riddle'
7
+ require 'after_commit'
8
+
9
+ require 'thinking_sphinx/active_record'
10
+ require 'thinking_sphinx/association'
11
+ require 'thinking_sphinx/attribute'
12
+ require 'thinking_sphinx/collection'
13
+ require 'thinking_sphinx/configuration'
14
+ require 'thinking_sphinx/field'
15
+ require 'thinking_sphinx/index'
16
+ require 'thinking_sphinx/rails_additions'
17
+ require 'thinking_sphinx/search'
18
+
19
+ require 'thinking_sphinx/adapters/abstract_adapter'
20
+ require 'thinking_sphinx/adapters/mysql_adapter'
21
+ require 'thinking_sphinx/adapters/postgresql_adapter'
22
+
23
+ ActiveRecord::Base.send(:include, ThinkingSphinx::ActiveRecord)
24
+
25
+ Merb::Plugins.add_rakefiles(
26
+ File.join(File.dirname(__FILE__), "..", "tasks", "thinking_sphinx_tasks")
27
+ ) if defined?(Merb)
28
+
29
+ module ThinkingSphinx
30
+ module Version #:nodoc:
31
+ Major = 0
32
+ Minor = 9
33
+ Tiny = 10
34
+
35
+ String = [Major, Minor, Tiny].join('.')
36
+ end
37
+
38
+ # A ConnectionError will get thrown when a connection to Sphinx can't be
39
+ # made.
40
+ class ConnectionError < StandardError
41
+ end
42
+
43
+ # The collection of indexed models. Keep in mind that Rails lazily loads
44
+ # its classes, so this may not actually be populated with _all_ the models
45
+ # that have Sphinx indexes.
46
+ def self.indexed_models
47
+ @@indexed_models ||= []
48
+ end
49
+
50
+ # Check if index definition is disabled.
51
+ #
52
+ def self.define_indexes?
53
+ @@define_indexes = true unless defined?(@@define_indexes)
54
+ @@define_indexes == true
55
+ end
56
+
57
+ # Enable/disable indexes - you may want to do this while migrating data.
58
+ #
59
+ # ThinkingSphinx.define_indexes = false
60
+ #
61
+ def self.define_indexes=(value)
62
+ @@define_indexes = value
63
+ end
64
+
65
+ @@deltas_enabled = nil
66
+
67
+ # Check if delta indexing is enabled.
68
+ #
69
+ def self.deltas_enabled?
70
+ @@deltas_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@deltas_enabled.nil?
71
+ @@deltas_enabled
72
+ end
73
+
74
+ # Enable/disable all delta indexing.
75
+ #
76
+ # ThinkingSphinx.deltas_enabled = false
77
+ #
78
+ def self.deltas_enabled=(value)
79
+ @@deltas_enabled = value
80
+ end
81
+
82
+ @@updates_enabled = nil
83
+
84
+ # Check if updates are enabled. True by default, unless within the test
85
+ # environment.
86
+ #
87
+ def self.updates_enabled?
88
+ @@updates_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@updates_enabled.nil?
89
+ @@updates_enabled
90
+ end
91
+
92
+ # Enable/disable updates to Sphinx
93
+ #
94
+ # ThinkingSphinx.updates_enabled = false
95
+ #
96
+ def self.updates_enabled=(value)
97
+ @@updates_enabled = value
98
+ end
99
+
100
+ # Checks to see if MySQL will allow simplistic GROUP BY statements. If not,
101
+ # or if not using MySQL, this will return false.
102
+ #
103
+ def self.use_group_by_shortcut?
104
+ ::ActiveRecord::ConnectionAdapters.constants.include?("MysqlAdapter") &&
105
+ ::ActiveRecord::Base.connection.is_a?(
106
+ ::ActiveRecord::ConnectionAdapters::MysqlAdapter
107
+ ) &&
108
+ ::ActiveRecord::Base.connection.select_all(
109
+ "SELECT @@global.sql_mode, @@session.sql_mode;"
110
+ ).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
111
+ end
112
+ end
@@ -0,0 +1,153 @@
1
+ require 'thinking_sphinx/active_record/delta'
2
+ require 'thinking_sphinx/active_record/search'
3
+ require 'thinking_sphinx/active_record/has_many_association'
4
+
5
+ module ThinkingSphinx
6
+ # Core additions to ActiveRecord models - define_index for creating indexes
7
+ # for models. If you want to interrogate the index objects created for the
8
+ # model, you can use the class-level accessor :sphinx_indexes.
9
+ #
10
+ module ActiveRecord
11
+ def self.included(base)
12
+ base.class_eval do
13
+ class_inheritable_array :sphinx_indexes
14
+ class << self
15
+ # Allows creation of indexes for Sphinx. If you don't do this, there
16
+ # isn't much point trying to search (or using this plugin at all,
17
+ # really).
18
+ #
19
+ # An example or two:
20
+ #
21
+ # define_index
22
+ # indexes :id, :as => :model_id
23
+ # indexes name
24
+ # end
25
+ #
26
+ # You can also grab fields from associations - multiple levels deep
27
+ # if necessary.
28
+ #
29
+ # define_index do
30
+ # indexes tags.name, :as => :tag
31
+ # indexes articles.content
32
+ # indexes orders.line_items.product.name, :as => :product
33
+ # end
34
+ #
35
+ # And it will automatically concatenate multiple fields:
36
+ #
37
+ # define_index do
38
+ # indexes [author.first_name, author.last_name], :as => :author
39
+ # end
40
+ #
41
+ # The #indexes method is for fields - if you want attributes, use
42
+ # #has instead. All the same rules apply - but keep in mind that
43
+ # attributes are for sorting, grouping and filtering, not searching.
44
+ #
45
+ # define_index do
46
+ # # fields ...
47
+ #
48
+ # has created_at, updated_at
49
+ # end
50
+ #
51
+ # One last feature is the delta index. This requires the model to
52
+ # have a boolean field named 'delta', and is enabled as follows:
53
+ #
54
+ # define_index do
55
+ # # fields ...
56
+ # # attributes ...
57
+ #
58
+ # set_property :delta => true
59
+ # end
60
+ #
61
+ # Check out the more detailed documentation for each of these methods
62
+ # at ThinkingSphinx::Index::Builder.
63
+ #
64
+ def define_index(&block)
65
+ return unless ThinkingSphinx.define_indexes?
66
+
67
+ self.sphinx_indexes ||= []
68
+ index = Index.new(self, &block)
69
+
70
+ self.sphinx_indexes << index
71
+ unless ThinkingSphinx.indexed_models.include?(self.name)
72
+ ThinkingSphinx.indexed_models << self.name
73
+ end
74
+
75
+ if index.delta?
76
+ before_save :toggle_delta
77
+ after_commit :index_delta
78
+ end
79
+
80
+ after_destroy :toggle_deleted
81
+
82
+ index
83
+ end
84
+ alias_method :sphinx_index, :define_index
85
+
86
+ # Generate a unique CRC value for the model's name, to use to
87
+ # determine which Sphinx documents belong to which AR records.
88
+ #
89
+ # Really only written for internal use - but hey, if it's useful to
90
+ # you in some other way, awesome.
91
+ #
92
+ def to_crc32
93
+ result = 0xFFFFFFFF
94
+ self.name.each_byte do |byte|
95
+ result ^= byte
96
+ 8.times do
97
+ result = (result >> 1) ^ (0xEDB88320 * (result & 1))
98
+ end
99
+ end
100
+ result ^ 0xFFFFFFFF
101
+ end
102
+
103
+ def to_crc32s
104
+ (subclasses << self).collect { |klass| klass.to_crc32 }
105
+ end
106
+ end
107
+ end
108
+
109
+ base.send(:include, ThinkingSphinx::ActiveRecord::Delta)
110
+ base.send(:include, ThinkingSphinx::ActiveRecord::Search)
111
+
112
+ ::ActiveRecord::Associations::HasManyAssociation.send(
113
+ :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
114
+ )
115
+ ::ActiveRecord::Associations::HasManyThroughAssociation.send(
116
+ :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
117
+ )
118
+ end
119
+
120
+ def in_core_index?
121
+ self.class.search_for_id(
122
+ self.sphinx_document_id,
123
+ "#{self.class.name.underscore.tr(':/\\', '_')}_core"
124
+ )
125
+ end
126
+
127
+ def toggle_deleted
128
+ return unless ThinkingSphinx.updates_enabled?
129
+
130
+ config = ThinkingSphinx::Configuration.instance
131
+ client = Riddle::Client.new config.address, config.port
132
+
133
+ client.update(
134
+ "#{self.class.sphinx_indexes.first.name}_core",
135
+ ['sphinx_deleted'],
136
+ {self.sphinx_document_id => 1}
137
+ ) if self.in_core_index?
138
+
139
+ client.update(
140
+ "#{self.class.sphinx_indexes.first.name}_delta",
141
+ ['sphinx_deleted'],
142
+ {self.sphinx_document_id => 1}
143
+ ) if ThinkingSphinx.deltas_enabled? &&
144
+ self.class.sphinx_indexes.any? { |index| index.delta? } &&
145
+ self.delta?
146
+ end
147
+
148
+ def sphinx_document_id
149
+ (self.id * ThinkingSphinx.indexed_models.size) +
150
+ ThinkingSphinx.indexed_models.index(self.class.name)
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,80 @@
1
+ module ThinkingSphinx
2
+ module ActiveRecord
3
+ # This module contains all the delta-related code for models. There isn't
4
+ # really anything you need to call manually in here - except perhaps
5
+ # index_delta, but not sure what reason why.
6
+ #
7
+ module Delta
8
+ # Code for after_commit callback is written by Eli Miller:
9
+ # http://elimiller.blogspot.com/2007/06/proper-cache-expiry-with-aftercommit.html
10
+ # with slight modification from Joost Hietbrink.
11
+ #
12
+ def self.included(base)
13
+ base.class_eval do
14
+ class << self
15
+ # Temporarily disable delta indexing inside a block, then perform a single
16
+ # rebuild of index at the end.
17
+ #
18
+ # Useful when performing updates to batches of models to prevent
19
+ # the delta index being rebuilt after each individual update.
20
+ #
21
+ # In the following example, the delta index will only be rebuilt once,
22
+ # not 10 times.
23
+ #
24
+ # SomeModel.suspended_delta do
25
+ # 10.times do
26
+ # SomeModel.create( ... )
27
+ # end
28
+ # end
29
+ #
30
+ def suspended_delta(reindex_after = true, &block)
31
+ original_setting = ThinkingSphinx.deltas_enabled?
32
+ ThinkingSphinx.deltas_enabled = false
33
+ begin
34
+ yield
35
+ ensure
36
+ ThinkingSphinx.deltas_enabled = original_setting
37
+ self.index_delta if reindex_after
38
+ end
39
+ end
40
+
41
+ # Build the delta index for the related model. This won't be called
42
+ # if running in the test environment.
43
+ #
44
+ def index_delta(instance = nil)
45
+ return true unless ThinkingSphinx.updates_enabled? &&
46
+ ThinkingSphinx.deltas_enabled?
47
+
48
+ config = ThinkingSphinx::Configuration.instance
49
+ client = Riddle::Client.new config.address, config.port
50
+
51
+ client.update(
52
+ "#{self.sphinx_indexes.first.name}_core",
53
+ ['sphinx_deleted'],
54
+ {instance.sphinx_document_id => 1}
55
+ ) if instance && instance.in_core_index?
56
+
57
+ system "#{config.bin_path}indexer --config #{config.config_file} --rotate #{self.sphinx_indexes.first.name}_delta"
58
+
59
+ true
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ # Set the delta value for the model to be true.
66
+ def toggle_delta
67
+ self.delta = true
68
+ end
69
+
70
+ # Build the delta index for the related model. This won't be called
71
+ # if running in the test environment.
72
+ #
73
+ def index_delta
74
+ self.class.index_delta(self)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,29 @@
1
+ module ThinkingSphinx
2
+ module ActiveRecord
3
+ module HasManyAssociation
4
+ def search(*args)
5
+ foreign_key = @reflection.primary_key_name
6
+ stack = [@reflection.options[:through]].compact
7
+
8
+ attribute = nil
9
+ (@reflection.klass.sphinx_indexes || []).each do |index|
10
+ attribute = index.attributes.detect { |attrib|
11
+ attrib.columns.length == 1 &&
12
+ attrib.columns.first.__name == foreign_key.to_sym &&
13
+ attrib.columns.first.__stack == stack
14
+ }
15
+ break if attribute
16
+ end
17
+
18
+ raise "Missing Attribute for Foreign Key #{foreign_key}" unless attribute
19
+
20
+ options = args.extract_options!
21
+ options[:with] ||= {}
22
+ options[:with][attribute.unique_name] = @owner.id
23
+
24
+ args << options
25
+ @reflection.klass.search(*args)
26
+ end
27
+ end
28
+ end
29
+ end