jaikoo-thinking-sphinx 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
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