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.
Files changed (61) hide show
  1. data/Gemfile +4 -1
  2. data/HISTORY +16 -0
  3. data/README.textile +41 -23
  4. data/lib/thinking_sphinx.rb +9 -0
  5. data/lib/thinking_sphinx/active_record.rb +1 -0
  6. data/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb +20 -3
  7. data/lib/thinking_sphinx/active_record/base.rb +15 -2
  8. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +2 -2
  9. data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +1 -1
  10. data/lib/thinking_sphinx/active_record/field.rb +5 -0
  11. data/lib/thinking_sphinx/active_record/index.rb +10 -2
  12. data/lib/thinking_sphinx/active_record/interpreter.rb +9 -0
  13. data/lib/thinking_sphinx/active_record/property.rb +4 -0
  14. data/lib/thinking_sphinx/active_record/property_query.rb +112 -0
  15. data/lib/thinking_sphinx/active_record/sql_source.rb +10 -8
  16. data/lib/thinking_sphinx/active_record/sql_source/template.rb +3 -1
  17. data/lib/thinking_sphinx/configuration.rb +21 -24
  18. data/lib/thinking_sphinx/connection.rb +71 -0
  19. data/lib/thinking_sphinx/core.rb +1 -0
  20. data/lib/thinking_sphinx/core/field.rb +9 -0
  21. data/lib/thinking_sphinx/core/index.rb +8 -3
  22. data/lib/thinking_sphinx/deltas.rb +3 -0
  23. data/lib/thinking_sphinx/deltas/default_delta.rb +1 -1
  24. data/lib/thinking_sphinx/excerpter.rb +1 -1
  25. data/lib/thinking_sphinx/frameworks.rb +9 -0
  26. data/lib/thinking_sphinx/frameworks/plain.rb +8 -0
  27. data/lib/thinking_sphinx/frameworks/rails.rb +9 -0
  28. data/lib/thinking_sphinx/index.rb +5 -1
  29. data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +2 -2
  30. data/lib/thinking_sphinx/real_time/field.rb +2 -0
  31. data/lib/thinking_sphinx/real_time/property.rb +2 -0
  32. data/lib/thinking_sphinx/scopes.rb +6 -0
  33. data/lib/thinking_sphinx/search.rb +4 -0
  34. data/lib/thinking_sphinx/search/batch_inquirer.rb +1 -9
  35. data/lib/thinking_sphinx/search/merger.rb +3 -1
  36. data/lib/thinking_sphinx/sinatra.rb +5 -0
  37. data/lib/thinking_sphinx/test.rb +2 -2
  38. data/spec/acceptance/index_options_spec.rb +87 -0
  39. data/spec/acceptance/searching_within_a_model_spec.rb +7 -0
  40. data/spec/acceptance/specifying_sql_spec.rb +277 -0
  41. data/spec/acceptance/support/sphinx_controller.rb +4 -3
  42. data/spec/fixtures/database.yml +4 -0
  43. data/spec/internal/app/indices/admin_person_index.rb +3 -0
  44. data/spec/internal/app/models/admin/person.rb +3 -0
  45. data/spec/internal/app/models/book.rb +2 -0
  46. data/spec/internal/app/models/genre.rb +3 -0
  47. data/spec/internal/db/schema.rb +14 -0
  48. data/spec/thinking_sphinx/active_record/base_spec.rb +20 -14
  49. data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +6 -5
  50. data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +6 -5
  51. data/spec/thinking_sphinx/active_record/index_spec.rb +1 -1
  52. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +35 -27
  53. data/spec/thinking_sphinx/configuration_spec.rb +8 -45
  54. data/spec/thinking_sphinx/deltas/default_delta_spec.rb +4 -5
  55. data/spec/thinking_sphinx/deltas_spec.rb +6 -0
  56. data/spec/thinking_sphinx/excerpter_spec.rb +1 -2
  57. data/spec/thinking_sphinx/index_spec.rb +23 -10
  58. data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +4 -4
  59. data/spec/thinking_sphinx/scopes_spec.rb +7 -0
  60. data/thinking-sphinx.gemspec +2 -3
  61. 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 'pg', '~> 0.11.0', :platform => :ruby
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
- Basic indexing and searching should be fine. There's currently only limited basic delta support. Some settings haven't yet been brought across. Many of the smaller features don't yet exist either. Some may actually not return... we'll see.
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 is not yet supported, but it will be.
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/freelancing-god/thinking-sphinx/contributors.
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.
@@ -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
- "#{sphinx_type} #{@attribute.name} from field"
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
- search = ThinkingSphinx.search query, options
24
- ThinkingSphinx::Search::Merger.new(search).merge! nil, :classes => [self]
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.query Riddle::Query.update(
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 ||= config.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
- configuration.connection.query(sphinxql)
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
- sources.collect(&:attributes).flatten.collect(&:name)
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)
@@ -22,6 +22,10 @@ class ThinkingSphinx::ActiveRecord::Property
22
22
  (options[:as] || columns.first.__name).to_s
23
23
  end
24
24
 
25
+ def source_type
26
+ options[:source]
27
+ end
28
+
25
29
  def type
26
30
  nil
27
31
  end
@@ -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