thinking-sphinx 3.0.0.pre → 3.0.0.rc
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -1
- data/HISTORY +16 -0
- data/README.textile +41 -23
- data/lib/thinking_sphinx.rb +9 -0
- data/lib/thinking_sphinx/active_record.rb +1 -0
- data/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb +20 -3
- data/lib/thinking_sphinx/active_record/base.rb +15 -2
- data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +2 -2
- data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +1 -1
- data/lib/thinking_sphinx/active_record/field.rb +5 -0
- data/lib/thinking_sphinx/active_record/index.rb +10 -2
- data/lib/thinking_sphinx/active_record/interpreter.rb +9 -0
- data/lib/thinking_sphinx/active_record/property.rb +4 -0
- data/lib/thinking_sphinx/active_record/property_query.rb +112 -0
- data/lib/thinking_sphinx/active_record/sql_source.rb +10 -8
- data/lib/thinking_sphinx/active_record/sql_source/template.rb +3 -1
- data/lib/thinking_sphinx/configuration.rb +21 -24
- data/lib/thinking_sphinx/connection.rb +71 -0
- data/lib/thinking_sphinx/core.rb +1 -0
- data/lib/thinking_sphinx/core/field.rb +9 -0
- data/lib/thinking_sphinx/core/index.rb +8 -3
- data/lib/thinking_sphinx/deltas.rb +3 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +1 -1
- data/lib/thinking_sphinx/excerpter.rb +1 -1
- data/lib/thinking_sphinx/frameworks.rb +9 -0
- data/lib/thinking_sphinx/frameworks/plain.rb +8 -0
- data/lib/thinking_sphinx/frameworks/rails.rb +9 -0
- data/lib/thinking_sphinx/index.rb +5 -1
- data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +2 -2
- data/lib/thinking_sphinx/real_time/field.rb +2 -0
- data/lib/thinking_sphinx/real_time/property.rb +2 -0
- data/lib/thinking_sphinx/scopes.rb +6 -0
- data/lib/thinking_sphinx/search.rb +4 -0
- data/lib/thinking_sphinx/search/batch_inquirer.rb +1 -9
- data/lib/thinking_sphinx/search/merger.rb +3 -1
- data/lib/thinking_sphinx/sinatra.rb +5 -0
- data/lib/thinking_sphinx/test.rb +2 -2
- data/spec/acceptance/index_options_spec.rb +87 -0
- data/spec/acceptance/searching_within_a_model_spec.rb +7 -0
- data/spec/acceptance/specifying_sql_spec.rb +277 -0
- data/spec/acceptance/support/sphinx_controller.rb +4 -3
- data/spec/fixtures/database.yml +4 -0
- data/spec/internal/app/indices/admin_person_index.rb +3 -0
- data/spec/internal/app/models/admin/person.rb +3 -0
- data/spec/internal/app/models/book.rb +2 -0
- data/spec/internal/app/models/genre.rb +3 -0
- data/spec/internal/db/schema.rb +14 -0
- data/spec/thinking_sphinx/active_record/base_spec.rb +20 -14
- data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +6 -5
- data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +6 -5
- data/spec/thinking_sphinx/active_record/index_spec.rb +1 -1
- data/spec/thinking_sphinx/active_record/sql_source_spec.rb +35 -27
- data/spec/thinking_sphinx/configuration_spec.rb +8 -45
- data/spec/thinking_sphinx/deltas/default_delta_spec.rb +4 -5
- data/spec/thinking_sphinx/deltas_spec.rb +6 -0
- data/spec/thinking_sphinx/excerpter_spec.rb +1 -2
- data/spec/thinking_sphinx/index_spec.rb +23 -10
- data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +4 -4
- data/spec/thinking_sphinx/scopes_spec.rb +7 -0
- data/thinking-sphinx.gemspec +2 -3
- metadata +66 -26
data/Gemfile
CHANGED
@@ -6,5 +6,8 @@ gem 'combustion',
|
|
6
6
|
:git => 'git://github.com/pat/combustion.git',
|
7
7
|
:ref => '45f50e64c3'
|
8
8
|
|
9
|
-
gem '
|
9
|
+
gem 'mysql2', '~> 0.3.12b4', :platform => :ruby
|
10
|
+
gem 'pg', '~> 0.11.0', :platform => :ruby
|
11
|
+
|
12
|
+
gem 'activerecord-jdbcmysql-adapter', '~> 1.1.3', :platform => :jruby
|
10
13
|
gem 'activerecord-jdbcpostgresql-adapter', '~> 1.1.3', :platform => :jruby
|
data/HISTORY
CHANGED
@@ -1,2 +1,18 @@
|
|
1
|
+
2012-12-22: 3.0.0.rc
|
2
|
+
* [FEATURE] Source type support (query and ranged query) for both attributes and fields. Custom SQL strings can be supplied as well.
|
3
|
+
* [FEATURE] Wordcount attributes and fields now supported.
|
4
|
+
* [FEATURE] Support for Sinatra and other non-Rails frameworks.
|
5
|
+
* [FEATURE] A sphinx scope can be defined as the default.
|
6
|
+
* [FEATURE] An index can have multiple sources, by using define_source within the index definition.
|
7
|
+
* [FEATURE] sanitize_sql is available within an index definition.
|
8
|
+
* [FEATURE] Providing :prefixes => true or :infixes => true as an option when declaring a field means just the noted fields have infixes/prefixes applied.
|
9
|
+
* [FEATURE] ThinkingSphinx::Search#query_time returns the time Sphinx took to make the query.
|
10
|
+
* [FEATURE] Namespaced model support.
|
11
|
+
* [FEATURE] Default settings for index definition arguments can be set in config/thinking_sphinx.yml.
|
12
|
+
* [FIX] Correctly escape nulls in inheritance column (Darcy Laycock).
|
13
|
+
* [FIX] Use ThinkingSphinx::Configuration#render_to_file instead of ThinkingSphinx::Configuration#build in test helpers (Darcy Laycock).
|
14
|
+
* [FIX] Suppressing delta output in test helpers now works (Darcy Laycock).
|
15
|
+
* [FEATURE] A custom Riddle/Sphinx controller can be supplied. Useful for Flying Sphinx to have an API layer over Sphinx commands, without needing custom gems for different Thinking Sphinx/Flying Sphinx combinations.
|
16
|
+
|
1
17
|
2012-10-06: 3.0.0.pre
|
2
18
|
* First pre-release. Not quite feature complete, but the important stuff is certainly covered. See the README for more the finer details.
|
data/README.textile
CHANGED
@@ -28,7 +28,16 @@ You'll notice the first argument is the model name downcased and as a symbol, an
|
|
28
28
|
|
29
29
|
Other changes:
|
30
30
|
* SphinxQL is now used instead of the old socket connections (hence the dependency on the @mysql2@ gem).
|
31
|
-
* You'll need to include @ThinkingSphinx::Scopes@ into your models if you want to use Sphinx scopes.
|
31
|
+
* You'll need to include @ThinkingSphinx::Scopes@ into your models if you want to use Sphinx scopes. Default scopes can be set as follows:
|
32
|
+
|
33
|
+
<pre><code>class Person < ActiveRecord::Base
|
34
|
+
include ThinkingSphinx::Scopes
|
35
|
+
|
36
|
+
sphinx_scope(:date_order) { {:order => :created_at} }
|
37
|
+
default_sphinx_scope :date_order
|
38
|
+
# ...
|
39
|
+
end</code></pre>
|
40
|
+
|
32
41
|
* The match mode is always extended - SphinxQL doesn't know any other way.
|
33
42
|
* ActiveRecord::Base.set_sphinx_primary_key is now an option in the index definition (alongside @:with@ in the above example): @:primary_key@ - and therefore is no longer inheritable between models.
|
34
43
|
* If you're explicitly setting a time attribute's type, instead of @:datetime@ it should now be @:timestamp@.
|
@@ -113,6 +122,31 @@ batch.searches #=> [[foo results], [bar results], [baz results]]</code></pre>
|
|
113
122
|
* Automatic updates of non-string attributes are still limited to those from columns on the model in question, and is disabled by default. To enable it, just set attribute_updates to true in your @config/sphinx.yml@.
|
114
123
|
* Search result helper methods are no longer injected into the actual result objects. Read the section below on search results, glazes and panes.
|
115
124
|
* If you're using string facets, make sure they're defined as fields, not strings. There is currently no support for multi-value string facets.
|
125
|
+
* To have fine-grained control over when deltas are invoked, create a sub-class of your chosen delta class (the standard is @ThinkingSphinx::Deltas::DefaultDelta@) and customise the @toggle@ and @toggled?@ methods, both of which accept a single parameter being the ActiveRecord instance.
|
126
|
+
|
127
|
+
<pre><code>class OccasionalDeltas < ThinkingSphinx::Deltas::DefaultDelta
|
128
|
+
# Invoked via a before_save callback. The default behaviour is to set the
|
129
|
+
# delta column to true.
|
130
|
+
def toggle(instance)
|
131
|
+
super unless instance.title_changed?
|
132
|
+
end
|
133
|
+
|
134
|
+
# Invoked via an after_commit callback. The default behaviour is to check
|
135
|
+
# whether the delta column is set to true. If this method returns true, the
|
136
|
+
# indexer is fired.
|
137
|
+
def toggled?(instance)
|
138
|
+
return false unless instance.title_changed?
|
139
|
+
|
140
|
+
super
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# And in your index definition:
|
145
|
+
ThinkingSphinx::Index.define :article, :with => :active_record, :delta => OccasionalDeltas do
|
146
|
+
# ...
|
147
|
+
end</code></pre>
|
148
|
+
|
149
|
+
* When you're defining indices for namespaced models, use a lowercase string with /'s for namespacing as the model reference: @ThinkingSphinx::Index.define 'blog/article', :with => :active_record@.
|
116
150
|
|
117
151
|
h2. Search Middleware
|
118
152
|
|
@@ -144,25 +178,7 @@ If you wish to add your own panes, go ahead. The only requirement is that the in
|
|
144
178
|
|
145
179
|
h2. Limitations
|
146
180
|
|
147
|
-
|
148
|
-
|
149
|
-
Lists of what still needs to be implemented, in no particular order but in groups of vague importance:
|
150
|
-
|
151
|
-
Required for Release Candidate:
|
152
|
-
|
153
|
-
* Query and Ranged Query sources for Attributes and Fields
|
154
|
-
* Using :though association shortcuts in index definitions
|
155
|
-
* Overwritable toggle_delta? method on model
|
156
|
-
* Query times for searches
|
157
|
-
* Namespaced models support
|
158
|
-
* Delayed Deltas
|
159
|
-
* Infixing and Prefixing of specific fields
|
160
|
-
* Default Sphinx scopes
|
161
|
-
* sanitise_sql method in index definitions
|
162
|
-
* Multiple sources for an index
|
163
|
-
* JRuby support
|
164
|
-
* Sinatra support
|
165
|
-
* Wordcount fields and attributes
|
181
|
+
Almost all functionality from v2 releases are implemented, though it's worth noting that some settings haven't yet been brought across, and a handful of the smaller features don't yet exist either. Some may actually not return... we'll see.
|
166
182
|
|
167
183
|
May or may not be added:
|
168
184
|
|
@@ -175,11 +191,11 @@ May or may not be added:
|
|
175
191
|
|
176
192
|
h3. Sphinx Versions
|
177
193
|
|
178
|
-
TS 3 is built for Sphinx 2.x only. You cannot use 1.10-beta, 0.9.9 or anything earlier than that.
|
194
|
+
TS 3 is built for Sphinx 2.x only. You cannot use 1.10-beta, 0.9.9 or anything earlier than that. 2.0.5 or newer is recommended.
|
179
195
|
|
180
196
|
h3. Rails Versions
|
181
197
|
|
182
|
-
Currently TS 3 is built to support Rails/ActiveRecord 3.1 or newer. Sinatra
|
198
|
+
Currently TS 3 is built to support Rails/ActiveRecord 3.1 or newer. If you're using Sinatra and ActiveRecord instead of Rails, that's fine - just make sure you add the @:require => 'thinking_sphinx/sinatra'@ option when listing @thinking-sphinx@ in your Gemfile.
|
183
199
|
|
184
200
|
TS 3 does not support Rails 3.0, Rails 2.x or earlier, or Merb - please refer to the TS 1.x and 2.x releases in those situations.
|
185
201
|
|
@@ -187,6 +203,8 @@ h3. Ruby Versions
|
|
187
203
|
|
188
204
|
Built on MRI 1.9.3 and tested against MRI 1.9.2 as well. No plans to support MRI 1.8, but would like to support Rubinius and JRuby (the one catch with the latter is the different MySQL interfaces).
|
189
205
|
|
206
|
+
There's also the complication that Sphinx 2.0.x releases don't work with JDBC (as JDBC sends several MySQL-specific comamnds through when initializing a connection). So, JRuby support won't appear until there's a stable Sphinx release that can interact with JDBC.
|
207
|
+
|
190
208
|
h3. Database Versions
|
191
209
|
|
192
210
|
MySQL 5.x and Postgres 8.4 or better are supported.
|
@@ -201,4 +219,4 @@ For some ideas behind my current approach, have a look through @sketchpad.rb@ in
|
|
201
219
|
|
202
220
|
h2. Licence
|
203
221
|
|
204
|
-
Copyright (c) 2007-2012, Thinking Sphinx is developed and maintained by Pat Allan, and is released under the open MIT Licence. Many thanks to "all who have contributed patches":https://github.com/
|
222
|
+
Copyright (c) 2007-2012, Thinking Sphinx is developed and maintained by Pat Allan, and is released under the open MIT Licence. Many thanks to "all who have contributed patches":https://github.com/pat/thinking-sphinx/contributors.
|
data/lib/thinking_sphinx.rb
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
if RUBY_PLATFORM == 'java'
|
2
|
+
require 'java'
|
3
|
+
require 'jdbc/mysql'
|
4
|
+
else
|
5
|
+
require 'mysql2'
|
6
|
+
end
|
7
|
+
|
1
8
|
require 'riddle'
|
2
9
|
require 'middleware'
|
3
10
|
require 'active_record'
|
@@ -26,9 +33,11 @@ require 'thinking_sphinx/batched_search'
|
|
26
33
|
require 'thinking_sphinx/callbacks'
|
27
34
|
require 'thinking_sphinx/core'
|
28
35
|
require 'thinking_sphinx/configuration'
|
36
|
+
require 'thinking_sphinx/connection'
|
29
37
|
require 'thinking_sphinx/excerpter'
|
30
38
|
require 'thinking_sphinx/facet'
|
31
39
|
require 'thinking_sphinx/facet_search'
|
40
|
+
require 'thinking_sphinx/frameworks'
|
32
41
|
require 'thinking_sphinx/index'
|
33
42
|
require 'thinking_sphinx/index_set'
|
34
43
|
require 'thinking_sphinx/masks'
|
@@ -16,6 +16,7 @@ require 'thinking_sphinx/active_record/field'
|
|
16
16
|
require 'thinking_sphinx/active_record/index'
|
17
17
|
require 'thinking_sphinx/active_record/interpreter'
|
18
18
|
require 'thinking_sphinx/active_record/log_subscriber'
|
19
|
+
require 'thinking_sphinx/active_record/property_query'
|
19
20
|
require 'thinking_sphinx/active_record/property_sql_presenter'
|
20
21
|
require 'thinking_sphinx/active_record/sql_builder'
|
21
22
|
require 'thinking_sphinx/active_record/sql_source'
|
@@ -10,8 +10,8 @@ class ThinkingSphinx::ActiveRecord::Attribute::SphinxPresenter
|
|
10
10
|
:wordcount => :str2wordcount
|
11
11
|
}
|
12
12
|
|
13
|
-
def initialize(attribute)
|
14
|
-
@attribute = attribute
|
13
|
+
def initialize(attribute, source)
|
14
|
+
@attribute, @source = attribute, source
|
15
15
|
end
|
16
16
|
|
17
17
|
def collection_type
|
@@ -20,7 +20,7 @@ class ThinkingSphinx::ActiveRecord::Attribute::SphinxPresenter
|
|
20
20
|
|
21
21
|
def declaration
|
22
22
|
if @attribute.multi?
|
23
|
-
|
23
|
+
multi_declaration
|
24
24
|
else
|
25
25
|
@attribute.name
|
26
26
|
end
|
@@ -29,4 +29,21 @@ class ThinkingSphinx::ActiveRecord::Attribute::SphinxPresenter
|
|
29
29
|
def sphinx_type
|
30
30
|
SPHINX_TYPES[@attribute.type]
|
31
31
|
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def multi_declaration
|
36
|
+
case @attribute.source_type
|
37
|
+
when :query, :ranged_query
|
38
|
+
query
|
39
|
+
else
|
40
|
+
"#{sphinx_type} #{@attribute.name} from field"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def query
|
45
|
+
ThinkingSphinx::ActiveRecord::PropertyQuery.new(
|
46
|
+
@attribute, @source, sphinx_type
|
47
|
+
).to_s
|
48
|
+
end
|
32
49
|
end
|
@@ -20,8 +20,11 @@ module ThinkingSphinx::ActiveRecord::Base
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def search(query = nil, options = {})
|
23
|
-
|
24
|
-
|
23
|
+
merger = ThinkingSphinx::Search::Merger.new ThinkingSphinx.search
|
24
|
+
|
25
|
+
merger.merge! *default_sphinx_scope_response if default_sphinx_scope?
|
26
|
+
merger.merge! query, options
|
27
|
+
merger.merge! nil, :classes => [self]
|
25
28
|
end
|
26
29
|
|
27
30
|
def search_count(query = nil, options = {})
|
@@ -32,5 +35,15 @@ module ThinkingSphinx::ActiveRecord::Base
|
|
32
35
|
search = search query, options
|
33
36
|
ThinkingSphinx::Search::Merger.new(search).merge! nil, :ids_only => true
|
34
37
|
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def default_sphinx_scope?
|
42
|
+
respond_to?(:default_sphinx_scope) && default_sphinx_scope
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_sphinx_scope_response
|
46
|
+
[sphinx_scopes[default_sphinx_scope].call].flatten
|
47
|
+
end
|
35
48
|
end
|
36
49
|
end
|
@@ -5,7 +5,7 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks <
|
|
5
5
|
|
6
6
|
def after_destroy
|
7
7
|
indices.each do |index|
|
8
|
-
connection.
|
8
|
+
connection.execute Riddle::Query.update(
|
9
9
|
index.name, index.document_id_for_key(instance.id),
|
10
10
|
:sphinx_deleted => true
|
11
11
|
)
|
@@ -21,7 +21,7 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks <
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def connection
|
24
|
-
@connection ||=
|
24
|
+
@connection ||= ThinkingSphinx::Connection.new
|
25
25
|
end
|
26
26
|
|
27
27
|
def indices
|
@@ -42,7 +42,7 @@ class ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks <
|
|
42
42
|
sphinxql = Riddle::Query.update(
|
43
43
|
index.name, index.document_id_for_key(instance.id), attributes
|
44
44
|
)
|
45
|
-
|
45
|
+
ThinkingSphinx::Connection.new.execute(sphinxql)
|
46
46
|
rescue Mysql2::Error => error
|
47
47
|
# This isn't vital, so don't raise the error.
|
48
48
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
class ThinkingSphinx::ActiveRecord::Field <
|
2
2
|
ThinkingSphinx::ActiveRecord::Property
|
3
|
+
include ThinkingSphinx::Core::Field
|
3
4
|
|
4
5
|
def file?
|
5
6
|
options[:file]
|
@@ -8,4 +9,8 @@ class ThinkingSphinx::ActiveRecord::Field <
|
|
8
9
|
def with_attribute?
|
9
10
|
options[:sortable] || options[:facet]
|
10
11
|
end
|
12
|
+
|
13
|
+
def wordcount?
|
14
|
+
options[:wordcount]
|
15
|
+
end
|
11
16
|
end
|
@@ -6,7 +6,7 @@ class ThinkingSphinx::ActiveRecord::Index < Riddle::Configuration::Index
|
|
6
6
|
|
7
7
|
def append_source
|
8
8
|
ThinkingSphinx::ActiveRecord::SQLSource.new(
|
9
|
-
model, source_options
|
9
|
+
model, source_options.merge(:position => sources.length)
|
10
10
|
).tap do |source|
|
11
11
|
sources << source
|
12
12
|
end
|
@@ -25,7 +25,7 @@ class ThinkingSphinx::ActiveRecord::Index < Riddle::Configuration::Index
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def unique_attribute_names
|
28
|
-
|
28
|
+
attributes.collect(&:name)
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
@@ -35,6 +35,14 @@ class ThinkingSphinx::ActiveRecord::Index < Riddle::Configuration::Index
|
|
35
35
|
adapter_for(model)
|
36
36
|
end
|
37
37
|
|
38
|
+
def attributes
|
39
|
+
sources.collect(&:attributes).flatten
|
40
|
+
end
|
41
|
+
|
42
|
+
def fields
|
43
|
+
sources.collect(&:fields).flatten
|
44
|
+
end
|
45
|
+
|
38
46
|
def interpreter
|
39
47
|
ThinkingSphinx::ActiveRecord::Interpreter
|
40
48
|
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
class ThinkingSphinx::ActiveRecord::Interpreter <
|
2
2
|
::ThinkingSphinx::Core::Interpreter
|
3
3
|
|
4
|
+
def define_source(&block)
|
5
|
+
@source = @index.append_source
|
6
|
+
instance_eval &block
|
7
|
+
end
|
8
|
+
|
4
9
|
def group_by(*columns)
|
5
10
|
__source.groupings += columns
|
6
11
|
end
|
@@ -23,6 +28,10 @@ class ThinkingSphinx::ActiveRecord::Interpreter <
|
|
23
28
|
}
|
24
29
|
end
|
25
30
|
|
31
|
+
def sanitize_sql(*arguments)
|
32
|
+
__source.model.send :sanitize_sql, *arguments
|
33
|
+
end
|
34
|
+
|
26
35
|
def set_property(properties)
|
27
36
|
properties.each do |key, value|
|
28
37
|
@index.send("#{key}=", value) if @index.class.settings.include?(key)
|
@@ -0,0 +1,112 @@
|
|
1
|
+
class ThinkingSphinx::ActiveRecord::PropertyQuery
|
2
|
+
def initialize(property, source, type = nil)
|
3
|
+
@property, @source, @type = property, source, type
|
4
|
+
end
|
5
|
+
|
6
|
+
def to_s
|
7
|
+
identifier = [type, property.name].compact.join(' ')
|
8
|
+
queries = []
|
9
|
+
if column.string?
|
10
|
+
queries << column.__name
|
11
|
+
else
|
12
|
+
queries << to_sql
|
13
|
+
queries << range_sql if ranged?
|
14
|
+
end
|
15
|
+
|
16
|
+
"#{identifier} from #{source_type}; #{queries.join('; ')}"
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :property, :source, :type
|
22
|
+
|
23
|
+
def base_association
|
24
|
+
reflections.first
|
25
|
+
end
|
26
|
+
|
27
|
+
def column
|
28
|
+
@column ||= property.columns.first
|
29
|
+
end
|
30
|
+
|
31
|
+
def extend_reflection(reflection)
|
32
|
+
return [reflection] unless reflection.through_reflection
|
33
|
+
|
34
|
+
[reflection.through_reflection, reflection.source_reflection]
|
35
|
+
end
|
36
|
+
|
37
|
+
def reflections
|
38
|
+
@reflections ||= begin
|
39
|
+
base = source.model
|
40
|
+
|
41
|
+
column.__stack.collect { |key|
|
42
|
+
reflection = base.reflections[key]
|
43
|
+
base = reflection.klass
|
44
|
+
|
45
|
+
extend_reflection reflection
|
46
|
+
}.flatten
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def joins
|
51
|
+
@joins ||= begin
|
52
|
+
remainder = reflections.collect(&:name)[1..-1]
|
53
|
+
return nil if remainder.empty?
|
54
|
+
return remainder.first if remainder.length == 1
|
55
|
+
|
56
|
+
remainder[0..-2].reverse.inject(remainder.last) { |value, key|
|
57
|
+
{key => value}
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def offset
|
63
|
+
"* #{ThinkingSphinx::Configuration.instance.indices.count} + #{source.offset}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def quoted_foreign_key
|
67
|
+
quote_with_table base_association.klass.table_name,
|
68
|
+
base_association.foreign_key
|
69
|
+
end
|
70
|
+
|
71
|
+
def quoted_primary_key
|
72
|
+
quote_with_table reflections.last.klass.table_name, column.__name
|
73
|
+
end
|
74
|
+
|
75
|
+
def quote_with_table(table, column)
|
76
|
+
"#{quote_column(table)}.#{quote_column(column)}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def quote_column(column)
|
80
|
+
ActiveRecord::Base.connection.quote_column_name(column)
|
81
|
+
end
|
82
|
+
|
83
|
+
def ranged?
|
84
|
+
property.source_type == :ranged_query
|
85
|
+
end
|
86
|
+
|
87
|
+
def range_sql
|
88
|
+
base_association.klass.unscoped.
|
89
|
+
select("MIN(#{quoted_foreign_key}), MAX(#{quoted_foreign_key})").to_sql
|
90
|
+
end
|
91
|
+
|
92
|
+
def source_type
|
93
|
+
property.source_type.to_s.dasherize
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_sql
|
97
|
+
raise "Could not determine SQL for MVA" if reflections.empty?
|
98
|
+
|
99
|
+
relation = base_association.klass.unscoped
|
100
|
+
relation = relation.joins joins unless joins.blank?
|
101
|
+
relation = relation.select "#{quoted_foreign_key} #{offset} AS #{quote_column('id')}, #{quoted_primary_key} AS #{quote_column(property.name)}"
|
102
|
+
|
103
|
+
if ranged?
|
104
|
+
relation = relation.where("#{quoted_foreign_key} >= $start")
|
105
|
+
relation = relation.where("#{quoted_foreign_key} <= $end")
|
106
|
+
end
|
107
|
+
|
108
|
+
relation = relation.order("#{quoted_foreign_key} ASC") if type.nil?
|
109
|
+
|
110
|
+
relation.to_sql
|
111
|
+
end
|
112
|
+
end
|