thinking-sphinx 2.0.6 → 2.0.7

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 (50) hide show
  1. data/HISTORY +157 -0
  2. data/lib/cucumber/thinking_sphinx/external_world.rb +12 -0
  3. data/lib/cucumber/thinking_sphinx/internal_world.rb +127 -0
  4. data/lib/cucumber/thinking_sphinx/sql_logger.rb +20 -0
  5. data/lib/thinking-sphinx.rb +1 -0
  6. data/lib/thinking_sphinx/action_controller.rb +31 -0
  7. data/lib/thinking_sphinx/active_record/attribute_updates.rb +53 -0
  8. data/lib/thinking_sphinx/active_record/collection_proxy.rb +40 -0
  9. data/lib/thinking_sphinx/active_record/collection_proxy_with_scopes.rb +27 -0
  10. data/lib/thinking_sphinx/active_record/delta.rb +65 -0
  11. data/lib/thinking_sphinx/active_record/has_many_association.rb +37 -0
  12. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +21 -0
  13. data/lib/thinking_sphinx/active_record/log_subscriber.rb +61 -0
  14. data/lib/thinking_sphinx/active_record/scopes.rb +110 -0
  15. data/lib/thinking_sphinx/active_record.rb +383 -0
  16. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +87 -0
  17. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +62 -0
  18. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +171 -0
  19. data/lib/thinking_sphinx/association.rb +229 -0
  20. data/lib/thinking_sphinx/attribute.rb +407 -0
  21. data/lib/thinking_sphinx/auto_version.rb +38 -0
  22. data/lib/thinking_sphinx/bundled_search.rb +44 -0
  23. data/lib/thinking_sphinx/class_facet.rb +20 -0
  24. data/lib/thinking_sphinx/configuration.rb +335 -0
  25. data/lib/thinking_sphinx/context.rb +77 -0
  26. data/lib/thinking_sphinx/core/string.rb +15 -0
  27. data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
  28. data/lib/thinking_sphinx/deltas.rb +28 -0
  29. data/lib/thinking_sphinx/deploy/capistrano.rb +99 -0
  30. data/lib/thinking_sphinx/excerpter.rb +23 -0
  31. data/lib/thinking_sphinx/facet.rb +128 -0
  32. data/lib/thinking_sphinx/facet_search.rb +170 -0
  33. data/lib/thinking_sphinx/field.rb +98 -0
  34. data/lib/thinking_sphinx/index/builder.rb +312 -0
  35. data/lib/thinking_sphinx/index/faux_column.rb +118 -0
  36. data/lib/thinking_sphinx/index.rb +157 -0
  37. data/lib/thinking_sphinx/join.rb +37 -0
  38. data/lib/thinking_sphinx/property.rb +185 -0
  39. data/lib/thinking_sphinx/railtie.rb +46 -0
  40. data/lib/thinking_sphinx/search.rb +995 -0
  41. data/lib/thinking_sphinx/search_methods.rb +439 -0
  42. data/lib/thinking_sphinx/sinatra.rb +7 -0
  43. data/lib/thinking_sphinx/source/internal_properties.rb +51 -0
  44. data/lib/thinking_sphinx/source/sql.rb +157 -0
  45. data/lib/thinking_sphinx/source.rb +194 -0
  46. data/lib/thinking_sphinx/tasks.rb +132 -0
  47. data/lib/thinking_sphinx/test.rb +55 -0
  48. data/lib/thinking_sphinx/version.rb +3 -0
  49. data/lib/thinking_sphinx.rb +296 -0
  50. metadata +53 -4
data/HISTORY ADDED
@@ -0,0 +1,157 @@
1
+ 2.0.6 - August 28th 2011
2
+ * Don't change superclass index definitions (otherwise sibling subclasses can end up with each others' index definitions).
3
+ * Improved scope support (Andrew White).
4
+ * Fixed association-filtered searches for Rails 3.1 (Andrew White).
5
+ * Fixed polymorphic association support for Rails 3.1 (Jonathan Viney).
6
+ * 1.4.7 changes.
7
+
8
+ 2.0.5 - May 25th 2011
9
+ * Railtie equivalent for Sinatra.
10
+ * Rails 3.1 support (Florent Piteau).
11
+ * 1.4.6 changes.
12
+
13
+ 2.0.4 - May 12th 2011
14
+ * Only determine Sphinx version when initializing Thinking Sphinx, instead of upon require.
15
+ * Still execute logged blocks for searching even when logging isn't enabled.
16
+ * 1.4.5 changes.
17
+
18
+ 2.0.3 - April 3rd 2011
19
+ * 1.4.4 changes.
20
+
21
+ 2.0.2 - January 13th 2011
22
+ * Improvement to loading the environment via Rake (Brenton Fletcher).
23
+ * 1.4.2 changes.
24
+ * 1.4.1 changes.
25
+
26
+ 2.0.1 - November 19th 2011
27
+ * Intermittent as_json bug fix.
28
+ * Using Rails 3 logging system (Stephen Celis).
29
+ * Fixes for Rails 3.0.2 (Ivan Ukhov).
30
+
31
+ 2.0.0 - November 14th 2011
32
+ * Handle has_many conditions defined as a hash (Artem Orlov).
33
+ * Deprecation fixes for logging (Anton Sozontov).
34
+ * Only load Thinking Sphinx if ActiveRecord is loaded (Matthew Higgins).
35
+ * Rails 3 support.
36
+ * 1.4.0 changes.
37
+
38
+ 1.4.7 - August 28th 2011
39
+ * Don't search for objects when updating attributes on every save - if the object isn't there, then the error will be caught anyway.
40
+ * Extra flexibility for association attribute references (Andrew White).
41
+ * Setting up test suite for Travis, and fixing tests across many rubies.
42
+ * Removing test for filtering on wordcount attribute - seems Sphinx doesn't like that.
43
+ * If many indices are searched on, only use the first for excerpts.
44
+ * Allow MVA facets from SQL strings that are all integers to skip object translation.
45
+ * Adding total_count method for better Kaminari support (Dan Pickett).
46
+ * Switching from Jeweler to Bundler for gem management.
47
+ * Cleaning up Context - re-using camelized class names.
48
+ * Simplified auto-star regex for 1.9 - no need to set the unicode flag.
49
+ * Don't update sphinx_internal_id attribute when updating a record's attributes.
50
+ * Ignore empty arrays for :without_ids (Jason Rust).
51
+ * Now catching Riddle::ResponseError when we aren't fussed about the responses from Sphinx.
52
+ * Distinguishing between Ruby 1.8 and 1.9 for auto-star regex (Andrew White).
53
+ * Don't re-index at the end of suspended_delta calls if requested.
54
+ * Adding stop_timeout setting to configure how long ts:stop should wait while checking whether Sphinx has stopped (Justin Tanner).
55
+ * Speed improvement for types_to_crcs (Josh Goebel).
56
+ * Adding a touched_reindex_file option, for a file that will be touched after indices are rotated (Rémi Prévost).
57
+ * Better support for the Mysql2 adapter (Andrew White).
58
+ * More pagination methods - next_page? and first_page?.
59
+ * Adding use_64_bit option to ensure all timestamp attributes are treated as 64 bit integers (Andrey Deryabin).
60
+ * Simpler, less brittle support for all Sphinx settings via Riddle (Clemens Kofler).
61
+ * Optimised start/stop behaviour in Capistrano recipe (Lars Weiler).
62
+ * Catch and ignore timeout errors for low priority requests.
63
+ * Don't double up on de-polymorphised associations for generated Sphinx source SQL queries.
64
+
65
+ 1.4.6 - May 25th 2011
66
+ * Require Riddle 1.3.3 or better.
67
+ * Cast Sphinx document ids to 32bit integers to keep PostgreSQL happy when Sphinx is compiled for id64 support (Bruno Santschi).
68
+ * Making sure suspended_delta always exists on indexed models, whether indices have been defined or not.
69
+ * Workaround for ActsAsTaggableOn to ensure association joins work.
70
+ * Use Sphinx string attributes instead of class_crc integers to determine which class each search result is from (Sphinx 2.x or newer).
71
+ * String attribute support - and so, using this instead of str2ordinal for string sorting.
72
+ * Support for fields with paired string attributes and wordcount attributes (Sphinx 1.10-beta or newer).
73
+ * Support for file fields (Sphinx 1.10-beta or newer).
74
+ * Wordcount attribute support (Sphinx 1.10-beta or newer).
75
+ * Sinatra support is only used when Rails isn't loaded.
76
+ * Sphinx 2.0.x (including 2.0.2-dev) support.
77
+ * :without_any filtering option (the reverse of :with_all).
78
+
79
+ 1.4.5 - May 12th 2011
80
+ * Require Riddle 1.3.2 or better.
81
+ * Improved 1.9.2 support
82
+ * Don't hack Array - make ThinkingSphinx::Search a subclass of Array instead.
83
+ * Distinguishing between suspended deltas and disabled deltas.
84
+ * Removing most Thread references, opting for Mutexes for better multi-threading support.
85
+ * Added support for client_key to allow for authenticated Sphinx communication.
86
+ * Sphinx 2.0.1 support.
87
+ * Don't try to translate nil values (Alex Chee).
88
+ * Performance fix for facets (Clemens Kofler).
89
+ * Use Sphinx 0.9.9 in Capistrano recipe (Konstantin Shabanov).
90
+ * Sinatra support (Patrick Tulskie).
91
+ * Offer sanitize_sql method within define_index blocks (Matt Todd).
92
+ * Performance fix: caching primary_key_for_sphinx value.
93
+
94
+ 1.4.4 - April 3 2011
95
+ * Being consistent with === comparisons for Array monkeypatch.
96
+ * Kaminari pagination support.
97
+ * Ensure deltas are supported across multiple indices for a given model.
98
+ * Use custom index names for excerpts calls if that's what exists (Javier Ramírez).
99
+ * Allow for Sphinx versions compiled from source (Greg Weber).
100
+ * Ensuring thread is being required to allow Mutexes to work.
101
+ * Ensure primary_key_for_sphinx is inherited to subclasses (Robert Stern).
102
+ * Don't complain if there's an error when checking for documents in a given index.
103
+
104
+ 1.4.3 - January 29th 2011
105
+ * Don't memoize the database adapter.
106
+ * Allow queries to just return specific attributes via the :only option, instead of ActiveRecord objects (Hans Hasselberg).
107
+ * Performance fix: Only add sphinx scopes to has_many associations if there are scopes in play (Kirill Maximov).
108
+ * Fixing CRC32 function for PostgreSQL to handle UTF characters that use more than one byte.
109
+
110
+ 1.4.2 - January 13th 2011
111
+ * Ignore Sphinx errors when updating attributes (it's a low priority request).
112
+ * Don't use the CRC32 function within the generated SQL queries for class types.
113
+ * Direct facets to use a method to translate values using :value.
114
+ * Ensure that if one sphinx_internal_id attribute is a bigint, they all are.
115
+ * Ignore Sphinx errors when marking documents as deleted (it's a low priority request).
116
+ * Don't select columns from polymorphic joins if they're not available.
117
+ * Don't query Sphinx for a facet request if zero facets are requested.
118
+ * Ensure we're only using local_options when it's available (Paul Schyska).
119
+ * Don't presume the environment for Capistrano is production (Robert Glaser).
120
+ * Allow for custom database adapters.
121
+ * Fix for custom delta columns (Marcin Stecki).
122
+
123
+ 1.4.1 - December 21st 2010
124
+ * No longer looking for attributes in :conditions option when searching.
125
+ * Copy and then modify sort arguments.
126
+ * Allow the generation of Sphinx configuration objects without necessarily writing to the configuration file.
127
+ * Allow Sphinx 1.10-beta versions that have been compiled with id64 support.
128
+ * Raise Sphinx errors when searching - which can be ignored using the :ignore_errors option (Matt Todd).
129
+ * Handle complex :include arguments, including hashes within arrays (Paco Guzmán).
130
+ * Handle hashes passed through to :include when searching.
131
+ * Can now require either 'thinking_sphinx' or 'thinking-sphinx'.
132
+ * Excerpts are less fussy about inputs.
133
+ * No longer hard-coding RSpec colour setting - that's up to the developer.
134
+ * Require Riddle 1.2.0 or better.
135
+ * Use searchd to stop Sphinx (via --stop or --stopwait arguments) (Matt Todd).
136
+
137
+ 1.4.0 - November 14th 2010
138
+ * No longer supporting attributes in :conditions option.
139
+ * Case insensitive field sorting if :sortable is set to :insensitive.
140
+ * Using Bundler for development
141
+ * Can use make facet requests on existing searches and Sphinx scopes.
142
+ * Don't star field markers in search queries.
143
+ * Can use search_for_ids on Sphinx scopes.
144
+ * Ensure the CRC32 function for PostgreSQL handles empty strings and NULLs.
145
+ * Allow custom determination for database adapters.
146
+ * :include search option now limits itself to the relevant classes when searching across more than one class.
147
+ * Don't memoize primary_key_for_sphinx, which was causing trouble when objects are deleted or frozen.
148
+ * Allow for excerpts options when searching (using :excerpt_options) (Lee Capps).
149
+ * Wrap UTC in quotes when setting the timezone in PostgreSQL (Keith Pitt).
150
+ * Don't colourize logs unless ActiveRecord allows for it.
151
+ * Consistent contribution documentation (Sam Goldstein).
152
+ * Allow the setting of Riddle's timeout value via sphinx.yml (Sam Goldstein).
153
+ * 1.10-beta support.
154
+ * Can set the riddle client for the search request using :client.
155
+ * Fixing default sphinx scope support.
156
+ * Make a reasonable attempt to check that Sphinx has stopped (Matt Todd).
157
+ * Be more verbose if a model can't be loaded by Thinking Sphinx.
@@ -0,0 +1,12 @@
1
+ require 'thinking_sphinx/test'
2
+
3
+ module Cucumber
4
+ module ThinkingSphinx
5
+ class ExternalWorld
6
+ def initialize(suppress_delta_output = true)
7
+ ::ThinkingSphinx::Test.init
8
+ ::ThinkingSphinx::Test.start_with_autostop
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,127 @@
1
+ require 'cucumber/thinking_sphinx/sql_logger'
2
+
3
+ module Cucumber
4
+ module ThinkingSphinx
5
+ class InternalWorld
6
+ attr_accessor :temporary_directory, :migrations_directory,
7
+ :models_directory, :fixtures_directory, :database_file
8
+ attr_accessor :adapter, :database, :username,
9
+ :password, :host
10
+
11
+ def initialize
12
+ pwd = Dir.pwd
13
+ @temporary_directory = "#{pwd}/tmp"
14
+ @migrations_directory = "#{pwd}/features/thinking_sphinx/db/migrations"
15
+ @models_directory = "#{pwd}/features/thinking_sphinx/models"
16
+ @fixtures_directory = "#{pwd}/features/thinking_sphinx/db/fixtures"
17
+ @database_file = "#{pwd}/features/thinking_sphinx/database.yml"
18
+
19
+ @adapter = (ENV['DATABASE'] || 'mysql').gsub /^mysql$/, 'mysql2'
20
+ @database = 'thinking_sphinx'
21
+ @username = @adapter[/mysql/] ? 'root' : 'postgres'
22
+ # @password = 'thinking_sphinx'
23
+ @host = 'localhost'
24
+ end
25
+
26
+ def setup
27
+ make_temporary_directory
28
+
29
+ configure_cleanup
30
+ configure_thinking_sphinx
31
+ configure_active_record
32
+
33
+ prepare_data
34
+ setup_sphinx
35
+
36
+ self
37
+ end
38
+
39
+ def configure_database
40
+ ActiveRecord::Base.establish_connection database_settings
41
+ self
42
+ end
43
+
44
+ private
45
+
46
+ def config
47
+ @config ||= ::ThinkingSphinx::Configuration.instance
48
+ end
49
+
50
+ def make_temporary_directory
51
+ FileUtils.mkdir_p temporary_directory
52
+ Dir["#{temporary_directory}/*"].each do |file|
53
+ FileUtils.rm_rf file
54
+ end
55
+ end
56
+
57
+ def configure_thinking_sphinx
58
+ config.config_file = "#{temporary_directory}/sphinx.conf"
59
+ config.searchd_log_file = "#{temporary_directory}/searchd.log"
60
+ config.query_log_file = "#{temporary_directory}/searchd.query.log"
61
+ config.pid_file = "#{temporary_directory}/searchd.pid"
62
+ config.searchd_file_path = "#{temporary_directory}/indexes/"
63
+
64
+ ::ThinkingSphinx.suppress_delta_output = true
65
+ end
66
+
67
+ def configure_cleanup
68
+ Kernel.at_exit do
69
+ ::ThinkingSphinx::Configuration.instance.controller.stop
70
+ sleep(0.5) # Ensure Sphinx has shut down completely
71
+ ::ThinkingSphinx::ActiveRecord::LogSubscriber.logger.close
72
+ end
73
+ end
74
+
75
+ def yaml_database_settings
76
+ return {} unless File.exist?(@database_file)
77
+
78
+ YAML.load open(@database_file)
79
+ end
80
+
81
+ def database_settings
82
+ {
83
+ 'adapter' => @adapter,
84
+ 'database' => @database,
85
+ 'username' => @username,
86
+ 'password' => @password,
87
+ 'host' => @host
88
+ }.merge yaml_database_settings
89
+ end
90
+
91
+ def configure_active_record
92
+ ::ThinkingSphinx::ActiveRecord::LogSubscriber.logger = Logger.new(
93
+ open("#{temporary_directory}/active_record.log", "a")
94
+ )
95
+
96
+ ActiveRecord::Base.connection.class.send(
97
+ :include, Cucumber::ThinkingSphinx::SqlLogger
98
+ )
99
+ end
100
+
101
+ def prepare_data
102
+ ::ThinkingSphinx.deltas_enabled = false
103
+
104
+ load_files migrations_directory
105
+ load_files models_directory
106
+ load_files fixtures_directory
107
+
108
+ ::ThinkingSphinx.deltas_enabled = true
109
+ end
110
+
111
+ def load_files(path)
112
+ files = Dir["#{path}/*.rb"].sort!
113
+ files.each do |file|
114
+ require file.gsub(/\.rb$/, '')
115
+ end
116
+ end
117
+
118
+ def setup_sphinx
119
+ FileUtils.mkdir_p config.searchd_file_path
120
+
121
+ config.build
122
+ config.controller.index
123
+ config.controller.start
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,20 @@
1
+ module Cucumber
2
+ module ThinkingSphinx
3
+ module SqlLogger
4
+ def self.included(base)
5
+ base.send :alias_method_chain, :execute, :query_record
6
+ end
7
+
8
+ IGNORED_SQL = [
9
+ /^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/,
10
+ /^SELECT @@ROWCOUNT/, /^SHOW FIELDS/
11
+ ]
12
+
13
+ def execute_with_query_record(sql, name = nil, &block)
14
+ $queries_executed ||= []
15
+ $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
16
+ execute_without_query_record(sql, name, &block)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1 @@
1
+ require 'thinking_sphinx'
@@ -0,0 +1,31 @@
1
+ module ThinkingSphinx
2
+ module ActionController
3
+ extend ActiveSupport::Concern
4
+
5
+ protected
6
+
7
+ attr_internal :query_runtime
8
+
9
+ def cleanup_view_runtime
10
+ log_subscriber = ThinkingSphinx::ActiveRecord::LogSubscriber
11
+ query_runtime_pre_render = log_subscriber.reset_runtime
12
+ runtime = super
13
+ query_runtime_post_render = log_subscriber.reset_runtime
14
+ self.query_runtime = query_runtime_pre_render + query_runtime_post_render
15
+ runtime - query_runtime_post_render
16
+ end
17
+
18
+ def append_info_to_payload(payload)
19
+ super
20
+ payload[:query_runtime] = query_runtime
21
+ end
22
+
23
+ module ClassMethods
24
+ def log_process_action(payload)
25
+ messages, query_runtime = super, payload[:query_runtime]
26
+ messages << ("Sphinx: %.1fms" % query_runtime.to_f) if query_runtime
27
+ messages
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,53 @@
1
+ module ThinkingSphinx
2
+ module ActiveRecord
3
+ module AttributeUpdates
4
+ def self.included(base)
5
+ base.class_eval do
6
+ after_save :update_attribute_values
7
+ end
8
+ end
9
+
10
+ private
11
+
12
+ def update_attribute_values
13
+ return true unless ThinkingSphinx.updates_enabled? &&
14
+ ThinkingSphinx.sphinx_running?
15
+
16
+ self.class.sphinx_indexes.each do |index|
17
+ attribute_pairs = attribute_values_for_index(index)
18
+ attribute_names = attribute_pairs.keys
19
+ attribute_values = attribute_names.collect { |key|
20
+ attribute_pairs[key]
21
+ }
22
+
23
+ update_index index.core_name, attribute_names, attribute_values
24
+ next unless index.delta?
25
+ update_index index.delta_name, attribute_names, attribute_values
26
+ end
27
+
28
+ true
29
+ end
30
+
31
+ def updatable_attributes(index)
32
+ index.attributes.select { |attrib| attrib.updatable? }
33
+ end
34
+
35
+ def attribute_values_for_index(index)
36
+ updatable_attributes(index).inject({}) { |hash, attrib|
37
+ hash[attrib.unique_name.to_s] = attrib.live_value self
38
+ hash
39
+ }
40
+ end
41
+
42
+ def update_index(index_name, attribute_names, attribute_values)
43
+ config = ThinkingSphinx::Configuration.instance
44
+ config.client.update index_name, attribute_names, {
45
+ sphinx_document_id => attribute_values
46
+ }
47
+ rescue Riddle::ConnectionError, Riddle::ResponseError,
48
+ ThinkingSphinx::SphinxError, Errno::ETIMEDOUT
49
+ # Not the end of the world if Sphinx isn't running.
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,40 @@
1
+ module ThinkingSphinx
2
+ module ActiveRecord
3
+ module CollectionProxy
4
+ def search(*args)
5
+ options = args.extract_options!
6
+ options[:with] ||= {}
7
+ options[:with].merge! default_filter
8
+
9
+ args << options
10
+ proxy_association.klass.search(*args)
11
+ end
12
+
13
+ private
14
+
15
+ def attribute_for_foreign_key
16
+ if proxy_association.reflection.through_reflection
17
+ foreign_key = proxy_association.reflection.through_reflection.foreign_key
18
+ else
19
+ foreign_key = proxy_association.reflection.foreign_key
20
+ end
21
+
22
+ proxy_association.klass.define_indexes
23
+ (proxy_association.klass.sphinx_indexes || []).each do |index|
24
+ attribute = index.attributes.detect { |attrib|
25
+ attrib.columns.length == 1 &&
26
+ attrib.columns.first.__name == foreign_key.to_sym ||
27
+ attrib.alias == foreign_key.to_sym
28
+ }
29
+ return attribute unless attribute.nil?
30
+ end
31
+
32
+ raise "Missing Attribute for Foreign Key #{foreign_key}"
33
+ end
34
+
35
+ def default_filter
36
+ {attribute_for_foreign_key.unique_name => proxy_association.owner.id}
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ module ThinkingSphinx
2
+ module ActiveRecord
3
+ module CollectionProxyWithScopes
4
+ def self.included(base)
5
+ base.class_eval do
6
+ alias_method_chain :method_missing, :sphinx_scopes
7
+ alias_method_chain :respond_to?, :sphinx_scopes
8
+ end
9
+ end
10
+
11
+ def method_missing_with_sphinx_scopes(method, *args, &block)
12
+ klass = proxy_association.klass
13
+ if klass.respond_to?(:sphinx_scopes) && klass.sphinx_scopes.include?(method)
14
+ klass.search(:with => default_filter).send(method, *args, &block)
15
+ else
16
+ method_missing_without_sphinx_scopes(method, *args, &block)
17
+ end
18
+ end
19
+
20
+ def respond_to_with_sphinx_scopes?(method)
21
+ proxy_association.klass.respond_to?(:sphinx_scopes) &&
22
+ proxy_association.klass.sphinx_scopes.include?(scope) ||
23
+ respond_to_without_sphinx_scopes?(method)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,65 @@
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
+ # Build the delta index for the related model. This won't be called
16
+ # if running in the test environment.
17
+ #
18
+ def index_delta(instance = nil)
19
+ delta_objects.each { |obj| obj.index(self, instance) }
20
+ end
21
+
22
+ def delta_objects
23
+ self.sphinx_indexes.collect(&:delta_object).compact
24
+ end
25
+ end
26
+
27
+ def toggled_delta?
28
+ self.class.delta_objects.any? { |obj| obj.toggled(self) }
29
+ end
30
+
31
+ private
32
+
33
+ # Set the delta value for the model to be true.
34
+ def toggle_delta
35
+ self.class.delta_objects.each { |obj|
36
+ obj.toggle(self)
37
+ } if should_toggle_delta?
38
+ end
39
+
40
+ # Build the delta index for the related model. This won't be called
41
+ # if running in the test environment.
42
+ #
43
+ def index_delta
44
+ self.class.index_delta(self) if self.class.delta_objects.any? { |obj|
45
+ obj.toggled(self)
46
+ }
47
+ end
48
+
49
+ def should_toggle_delta?
50
+ self.new_record? || indexed_data_changed?
51
+ end
52
+
53
+ def indexed_data_changed?
54
+ sphinx_indexes.any? { |index|
55
+ index.fields.any? { |field| field.changed?(self) } ||
56
+ index.attributes.any? { |attrib|
57
+ attrib.public? && attrib.changed?(self) && !attrib.updatable?
58
+ }
59
+ }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,37 @@
1
+ module ThinkingSphinx
2
+ module ActiveRecord
3
+ module HasManyAssociation
4
+ def search(*args)
5
+ options = args.extract_options!
6
+ options[:with] ||= {}
7
+ options[:with].merge! default_filter
8
+
9
+ args << options
10
+ @reflection.klass.search(*args)
11
+ end
12
+
13
+ private
14
+
15
+ def attribute_for_foreign_key
16
+ foreign_key = @reflection.primary_key_name
17
+ stack = [@reflection.options[:through]].compact
18
+
19
+ @reflection.klass.define_indexes
20
+ (@reflection.klass.sphinx_indexes || []).each do |index|
21
+ attribute = index.attributes.detect { |attrib|
22
+ attrib.columns.length == 1 &&
23
+ attrib.columns.first.__name == foreign_key.to_sym ||
24
+ attrib.alias == foreign_key.to_sym
25
+ }
26
+ return attribute unless attribute.nil?
27
+ end
28
+
29
+ raise "Missing Attribute for Foreign Key #{foreign_key}"
30
+ end
31
+
32
+ def default_filter
33
+ {attribute_for_foreign_key.unique_name => @owner.id}
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ module ThinkingSphinx
2
+ module ActiveRecord
3
+ module HasManyAssociationWithScopes
4
+ def method_missing(method, *args, &block)
5
+ if responds_to_scope(method)
6
+ @reflection.klass.
7
+ search(:with => default_filter).
8
+ send(method, *args, &block)
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ private
15
+ def responds_to_scope(scope)
16
+ @reflection.klass.respond_to?(:sphinx_scopes) &&
17
+ @reflection.klass.sphinx_scopes.include?(scope)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,61 @@
1
+ require 'active_support/log_subscriber'
2
+
3
+ module ThinkingSphinx
4
+ module ActiveRecord
5
+ class LogSubscriber < ActiveSupport::LogSubscriber
6
+ def self.runtime=(value)
7
+ Thread.current['thinking_sphinx_query_runtime'] = value
8
+ end
9
+
10
+ def self.runtime
11
+ Thread.current['thinking_sphinx_query_runtime'] ||= 0
12
+ end
13
+
14
+ def self.reset_runtime
15
+ rt, self.runtime = runtime, 0
16
+ rt
17
+ end
18
+
19
+ def initialize
20
+ super
21
+ @odd_or_even = false
22
+ end
23
+
24
+ def query(event)
25
+ self.class.runtime += event.duration
26
+ return unless logger.debug?
27
+
28
+ identifier = color('Sphinx Query (%.1fms)' % event.duration, GREEN, true)
29
+ query = event.payload[:query]
30
+ query = color query, nil, true if odd?
31
+
32
+ debug " #{identifier} #{query}"
33
+ end
34
+
35
+ def message(event)
36
+ return unless logger.debug?
37
+
38
+ identifier = color 'Sphinx', GREEN, true
39
+ message = event.payload[:message]
40
+ message = color message, nil, true if odd?
41
+
42
+ debug " #{identifier} #{message}"
43
+ end
44
+
45
+ def odd?
46
+ @odd_or_even = !@odd_or_even
47
+ end
48
+
49
+ def logger
50
+ return @logger if defined? @logger
51
+ self.logger = ::ActiveRecord::Base.logger
52
+ end
53
+
54
+ def logger=(logger)
55
+ @logger = logger
56
+ end
57
+
58
+ attach_to :thinking_sphinx
59
+ end
60
+ end
61
+ end