thinking-sphinx 2.0.6 → 2.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +157 -0
- data/lib/cucumber/thinking_sphinx/external_world.rb +12 -0
- data/lib/cucumber/thinking_sphinx/internal_world.rb +127 -0
- data/lib/cucumber/thinking_sphinx/sql_logger.rb +20 -0
- data/lib/thinking-sphinx.rb +1 -0
- data/lib/thinking_sphinx/action_controller.rb +31 -0
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +53 -0
- data/lib/thinking_sphinx/active_record/collection_proxy.rb +40 -0
- data/lib/thinking_sphinx/active_record/collection_proxy_with_scopes.rb +27 -0
- data/lib/thinking_sphinx/active_record/delta.rb +65 -0
- data/lib/thinking_sphinx/active_record/has_many_association.rb +37 -0
- data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +21 -0
- data/lib/thinking_sphinx/active_record/log_subscriber.rb +61 -0
- data/lib/thinking_sphinx/active_record/scopes.rb +110 -0
- data/lib/thinking_sphinx/active_record.rb +383 -0
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +87 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +62 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +171 -0
- data/lib/thinking_sphinx/association.rb +229 -0
- data/lib/thinking_sphinx/attribute.rb +407 -0
- data/lib/thinking_sphinx/auto_version.rb +38 -0
- data/lib/thinking_sphinx/bundled_search.rb +44 -0
- data/lib/thinking_sphinx/class_facet.rb +20 -0
- data/lib/thinking_sphinx/configuration.rb +335 -0
- data/lib/thinking_sphinx/context.rb +77 -0
- data/lib/thinking_sphinx/core/string.rb +15 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
- data/lib/thinking_sphinx/deltas.rb +28 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +99 -0
- data/lib/thinking_sphinx/excerpter.rb +23 -0
- data/lib/thinking_sphinx/facet.rb +128 -0
- data/lib/thinking_sphinx/facet_search.rb +170 -0
- data/lib/thinking_sphinx/field.rb +98 -0
- data/lib/thinking_sphinx/index/builder.rb +312 -0
- data/lib/thinking_sphinx/index/faux_column.rb +118 -0
- data/lib/thinking_sphinx/index.rb +157 -0
- data/lib/thinking_sphinx/join.rb +37 -0
- data/lib/thinking_sphinx/property.rb +185 -0
- data/lib/thinking_sphinx/railtie.rb +46 -0
- data/lib/thinking_sphinx/search.rb +995 -0
- data/lib/thinking_sphinx/search_methods.rb +439 -0
- data/lib/thinking_sphinx/sinatra.rb +7 -0
- data/lib/thinking_sphinx/source/internal_properties.rb +51 -0
- data/lib/thinking_sphinx/source/sql.rb +157 -0
- data/lib/thinking_sphinx/source.rb +194 -0
- data/lib/thinking_sphinx/tasks.rb +132 -0
- data/lib/thinking_sphinx/test.rb +55 -0
- data/lib/thinking_sphinx/version.rb +3 -0
- data/lib/thinking_sphinx.rb +296 -0
- 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,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
|