thinking-sphinx 3.0.3 → 3.0.4

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 (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -1
  3. data/HISTORY +25 -0
  4. data/lib/thinking_sphinx.rb +1 -0
  5. data/lib/thinking_sphinx/active_record/association_proxy.rb +13 -55
  6. data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +47 -0
  7. data/lib/thinking_sphinx/active_record/base.rb +16 -15
  8. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -12
  9. data/lib/thinking_sphinx/active_record/database_adapters.rb +41 -42
  10. data/lib/thinking_sphinx/active_record/polymorpher.rb +7 -2
  11. data/lib/thinking_sphinx/active_record/property_query.rb +23 -19
  12. data/lib/thinking_sphinx/active_record/sql_builder.rb +108 -129
  13. data/lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb +28 -0
  14. data/lib/thinking_sphinx/active_record/sql_builder/query.rb +43 -0
  15. data/lib/thinking_sphinx/active_record/sql_builder/statement.rb +110 -0
  16. data/lib/thinking_sphinx/active_record/sql_source.rb +143 -138
  17. data/lib/thinking_sphinx/capistrano.rb +11 -8
  18. data/lib/thinking_sphinx/configuration.rb +57 -35
  19. data/lib/thinking_sphinx/connection.rb +15 -6
  20. data/lib/thinking_sphinx/core.rb +1 -0
  21. data/lib/thinking_sphinx/core/index.rb +18 -10
  22. data/lib/thinking_sphinx/core/settings.rb +9 -0
  23. data/lib/thinking_sphinx/deletion.rb +48 -0
  24. data/lib/thinking_sphinx/errors.rb +7 -0
  25. data/lib/thinking_sphinx/excerpter.rb +1 -0
  26. data/lib/thinking_sphinx/facet_search.rb +42 -19
  27. data/lib/thinking_sphinx/masks/scopes_mask.rb +7 -0
  28. data/lib/thinking_sphinx/middlewares.rb +27 -33
  29. data/lib/thinking_sphinx/middlewares/active_record_translator.rb +18 -18
  30. data/lib/thinking_sphinx/middlewares/geographer.rb +49 -16
  31. data/lib/thinking_sphinx/middlewares/sphinxql.rb +128 -58
  32. data/lib/thinking_sphinx/panes/excerpts_pane.rb +7 -3
  33. data/lib/thinking_sphinx/rake_interface.rb +10 -0
  34. data/lib/thinking_sphinx/real_time.rb +7 -1
  35. data/lib/thinking_sphinx/real_time/attribute.rb +4 -0
  36. data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +14 -10
  37. data/lib/thinking_sphinx/real_time/index.rb +20 -12
  38. data/lib/thinking_sphinx/search/glaze.rb +5 -0
  39. data/lib/thinking_sphinx/search/query.rb +4 -8
  40. data/lib/thinking_sphinx/tasks.rb +8 -0
  41. data/spec/acceptance/excerpts_spec.rb +22 -0
  42. data/spec/acceptance/remove_deleted_records_spec.rb +10 -0
  43. data/spec/acceptance/searching_across_models_spec.rb +10 -0
  44. data/spec/acceptance/searching_with_filters_spec.rb +15 -0
  45. data/spec/acceptance/specifying_sql_spec.rb +3 -3
  46. data/spec/acceptance/sphinx_scopes_spec.rb +11 -0
  47. data/spec/internal/app/indices/product_index.rb +2 -0
  48. data/spec/internal/app/models/categorisation.rb +6 -0
  49. data/spec/internal/app/models/category.rb +3 -0
  50. data/spec/internal/app/models/product.rb +4 -1
  51. data/spec/internal/db/schema.rb +10 -0
  52. data/spec/thinking_sphinx/active_record/base_spec.rb +33 -0
  53. data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +4 -35
  54. data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +5 -10
  55. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +4 -3
  56. data/spec/thinking_sphinx/configuration_spec.rb +26 -6
  57. data/spec/thinking_sphinx/connection_spec.rb +4 -1
  58. data/spec/thinking_sphinx/deletion_spec.rb +76 -0
  59. data/spec/thinking_sphinx/facet_search_spec.rb +54 -5
  60. data/spec/thinking_sphinx/panes/excerpts_pane_spec.rb +4 -6
  61. data/spec/thinking_sphinx/rake_interface_spec.rb +35 -0
  62. data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +68 -28
  63. data/spec/thinking_sphinx/search/glaze_spec.rb +19 -0
  64. data/spec/thinking_sphinx/search/query_spec.rb +39 -2
  65. data/thinking-sphinx.gemspec +2 -2
  66. metadata +31 -45
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e4abfc7a0ce650e92e51a842336ec1028f12e9ea
4
+ data.tar.gz: 2c51520fcd002486f51e127f981f5e7c60000706
5
+ SHA512:
6
+ metadata.gz: 384ba739298c2dc12431cf9c74e79a3f15191098d84b41afab0e94383ac9403384e9290e38c834ef17a1675eedf5835477f63c12aeeb440144421dab97e82959
7
+ data.tar.gz: 787f2a640087f91d7ffd7d39d1b092fb027551e6df58d2ab19062a78bfebe3b7bbbfe8ad4c65858d84fc590403eb0515296c6a5192fc3aaf6937e2f595b1ec3a
data/.gitignore CHANGED
@@ -5,4 +5,5 @@ Gemfile.lock
5
5
  *.sublime-*
6
6
  pkg/*
7
7
  spec/internal/config/test.sphinx.conf
8
- spec/internal/db/sphinx
8
+ spec/internal/db/sphinx
9
+ _site
data/HISTORY CHANGED
@@ -1,3 +1,28 @@
1
+ 2013-07-09: 3.0.4
2
+ * [CHANGE] Updating Riddle dependency to be >= 1.5.7.
3
+ * [FEATURE] ts:regenerate rake task for rebuilding Sphinx when realtime indices are involved.
4
+ * [FEATURE] ts:clear task removes all Sphinx index and binlog files.
5
+ * [CHANGE] Glaze now responds to respond_to? (@groe).
6
+ * [FEATURE] Facet search calls now respect the limit option (which otherwise defaults to max_matches) (Demian Ferreiro).
7
+ * [FEATURE] Excerpts words can be overwritten with the words option (@groe).
8
+ * [FIX] Empty queries with the star option set to true are handled gracefully.
9
+ * [CHANGE] Deleted ActiveRecord objects are deleted in realtime indices as well.
10
+ * [CHANGE] Realtime callbacks are no longer automatically added, but they're now more flexible (for association situations).
11
+ * [CHANGE] Cleaning and refactoring so Code Climate ranks this as A-level code (Philip Arndt, Shevaun Coker, Garrett Heinlen).
12
+ * [FIX] Excerpts are now wildcard-friendly.
13
+ * [FIX] Facet searches now use max_matches value (with a default of 1000) to ensure as many results as possible are returned.
14
+ * [CHANGE] Exceptions raised when communicating with Sphinx are now mentioned in the logs when queries are retried (instead of STDOUT).
15
+ * [CHANGE] Excerpts now use just the query and standard conditions, instead of parsing Sphinx's keyword metadata (which had model names in it).
16
+ * [FIX] The settings cache is now cleared when the configuration singleton is reset (Pedro Cunha).
17
+ * [FEATURE] The :facets option can be used in facet searches to limit which facets are queried.
18
+ * [FIX] Escaped @'s in queries are considered part of each word, instead of word separators.
19
+ * [FIX] Internal class name conditions are ignored with auto-starred queries.
20
+ * [FEATURE] A separate role can be set for Sphinx actions with Capistrano (Andrey Chernih).
21
+ * [FIX] RDoc doesn't like constant hierarchies split over multiple lines.
22
+ * [CHANGE] Get database connection details from ActiveRecord::Base, not each model, as this is where changes are reflected.
23
+ * [CHANGE] Default Sphinx scopes are applied to new facet searches.
24
+ * [FEATURE] Facet searches can now be called from Sphinx scopes.
25
+
1
26
  2013-05-07: 3.0.3
2
27
  * [CHANGE] Updating Riddle dependency to be >= 1.5.6
3
28
  * [FEATURE] INDEX_ONLY environment flag is passed through when invoked through Capistrano (Demian Ferreiro).
@@ -42,6 +42,7 @@ require 'thinking_sphinx/callbacks'
42
42
  require 'thinking_sphinx/core'
43
43
  require 'thinking_sphinx/configuration'
44
44
  require 'thinking_sphinx/connection'
45
+ require 'thinking_sphinx/deletion'
45
46
  require 'thinking_sphinx/errors'
46
47
  require 'thinking_sphinx/excerpter'
47
48
  require 'thinking_sphinx/facet'
@@ -2,73 +2,31 @@ module ThinkingSphinx::ActiveRecord::AssociationProxy
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def search(query = nil, options = {})
5
- query, options = nil, query if query.is_a?(Hash)
6
- options[:ignore_scopes] = true
7
-
8
- ThinkingSphinx::Search::Merger.new(super).merge! nil,
9
- :with => association_filter
5
+ perform_search super(*normalise_search_arguments(query, options))
10
6
  end
11
7
 
12
8
  def search_for_ids(query = nil, options = {})
9
+ perform_search super(*normalise_search_arguments(query, options))
10
+ end
11
+
12
+ private
13
+ def normalise_search_arguments(query, options)
13
14
  query, options = nil, query if query.is_a?(Hash)
14
15
  options[:ignore_scopes] = true
15
16
 
16
- ThinkingSphinx::Search::Merger.new(super).merge! nil,
17
- :with => association_filter
17
+ [query, options]
18
18
  end
19
19
 
20
- private
20
+ def perform_search(searcher)
21
+ ThinkingSphinx::Search::Merger.new(searcher).merge! nil,
22
+ :with => association_filter
23
+ end
21
24
 
22
25
  def association_filter
23
26
  attribute = AttributeFinder.new(proxy_association).attribute
24
27
 
25
28
  {attribute.name.to_sym => proxy_association.owner.id}
26
29
  end
27
-
28
- class AttributeFinder
29
- def initialize(association)
30
- @association = association
31
- end
32
-
33
- def attribute
34
- attributes.detect { |attribute|
35
- # Don't bother with attributes built from multiple columns
36
- next unless attribute.columns.length == 1
37
-
38
- attribute.columns.first.__name == foreign_key.to_sym ||
39
- attribute.name == foreign_key.to_s
40
- } or raise "Missing Attribute for Foreign Key #{foreign_key}"
41
- end
42
-
43
- private
44
-
45
- def attributes
46
- sources.collect(&:attributes).flatten
47
- end
48
-
49
- def configuration
50
- ThinkingSphinx::Configuration.instance
51
- end
52
-
53
- def foreign_key
54
- @foreign_key ||= if @association.reflection.through_reflection
55
- @association.reflection.through_reflection.foreign_key
56
- else
57
- @association.reflection.foreign_key
58
- end
59
- end
60
-
61
- def indices
62
- @indices ||= begin
63
- configuration.preload_indices
64
- configuration.indices_for_references(
65
- *@association.klass.name.underscore.to_sym
66
- )
67
- end
68
- end
69
-
70
- def sources
71
- indices.collect(&:sources).flatten
72
- end
73
- end
74
30
  end
31
+
32
+ require 'thinking_sphinx/active_record/association_proxy/attribute_finder'
@@ -0,0 +1,47 @@
1
+ class ThinkingSphinx::ActiveRecord::AssociationProxy::AttributeFinder
2
+ def initialize(association)
3
+ @association = association
4
+ end
5
+
6
+ def attribute
7
+ attributes.detect { |attribute|
8
+ # Don't bother with attributes built from multiple columns
9
+ next if attribute.columns.many?
10
+
11
+ attribute.columns.first.__name == foreign_key.to_sym ||
12
+ attribute.name == foreign_key.to_s
13
+ } or raise "Missing Attribute for Foreign Key #{foreign_key}"
14
+ end
15
+
16
+ private
17
+ def attributes
18
+ sources.collect(&:attributes).flatten
19
+ end
20
+
21
+ def configuration
22
+ ThinkingSphinx::Configuration.instance
23
+ end
24
+
25
+ def foreign_key
26
+ @foreign_key ||= reflection_target.foreign_key
27
+ end
28
+
29
+ def indices
30
+ @indices ||= begin
31
+ configuration.preload_indices
32
+ configuration.indices_for_references(
33
+ *@association.klass.name.underscore.to_sym
34
+ )
35
+ end
36
+ end
37
+
38
+ def reflection_target
39
+ target = @association.reflection
40
+ target = target.through_reflection if target.through_reflection
41
+ target
42
+ end
43
+
44
+ def sources
45
+ indices.collect(&:sources).flatten
46
+ end
47
+ end
@@ -7,30 +7,17 @@ module ThinkingSphinx::ActiveRecord::Base
7
7
  after_update ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks
8
8
  after_commit ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks
9
9
 
10
- after_save ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks
11
-
12
10
  ::ActiveRecord::Associations::CollectionProxy.send :include,
13
11
  ThinkingSphinx::ActiveRecord::AssociationProxy
14
12
  end
15
13
 
16
14
  module ClassMethods
17
15
  def facets(query = nil, options = {})
18
- search = ThinkingSphinx.facets query, options
19
- ThinkingSphinx::Search::Merger.new(search).merge! nil, :classes => [self]
16
+ merge_search ThinkingSphinx.facets, query, options
20
17
  end
21
18
 
22
19
  def search(query = nil, options = {})
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
-
28
- if current_scope && !merger.search.options[:ignore_scopes]
29
- raise ThinkingSphinx::MixedScopesError,
30
- 'You cannot search with Sphinx through ActiveRecord scopes'
31
- end
32
-
33
- merger.merge! nil, :classes => [self]
20
+ merge_search ThinkingSphinx.search, query, options
34
21
  end
35
22
 
36
23
  def search_count(query = nil, options = {})
@@ -51,5 +38,19 @@ module ThinkingSphinx::ActiveRecord::Base
51
38
  def default_sphinx_scope_response
52
39
  [sphinx_scopes[default_sphinx_scope].call].flatten
53
40
  end
41
+
42
+ def merge_search(search, query, options)
43
+ merger = ThinkingSphinx::Search::Merger.new search
44
+
45
+ merger.merge! *default_sphinx_scope_response if default_sphinx_scope?
46
+ merger.merge! query, options
47
+
48
+ if current_scope && !merger.search.options[:ignore_scopes]
49
+ raise ThinkingSphinx::MixedScopesError,
50
+ 'You cannot search with Sphinx through ActiveRecord scopes'
51
+ end
52
+
53
+ merger.merge! nil, :classes => [self]
54
+ end
54
55
  end
55
56
  end
@@ -4,14 +4,7 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks <
4
4
  callbacks :after_destroy
5
5
 
6
6
  def after_destroy
7
- indices.each do |index|
8
- connection.execute Riddle::Query.update(
9
- index.name, index.document_id_for_key(instance.id),
10
- :sphinx_deleted => true
11
- )
12
- end
13
- rescue Mysql2::Error => error
14
- # This isn't vital, so don't raise the error.
7
+ indices.each { |index| ThinkingSphinx::Deletion.perform index, instance }
15
8
  end
16
9
 
17
10
  private
@@ -20,10 +13,6 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks <
20
13
  ThinkingSphinx::Configuration.instance
21
14
  end
22
15
 
23
- def connection
24
- @connection ||= ThinkingSphinx::Connection.new
25
- end
26
-
27
16
  def indices
28
17
  config.preload_indices
29
18
  config.indices_for_references instance.class.name.underscore.to_sym
@@ -1,55 +1,54 @@
1
1
  module ThinkingSphinx::ActiveRecord::DatabaseAdapters
2
- def self.adapter_for(model)
3
- return default.new(model) unless default.nil?
4
-
5
- adapter = adapter_type_for(model)
6
- klass = case adapter
7
- when :mysql
8
- ThinkingSphinx::ActiveRecord::DatabaseAdapters::MySQLAdapter
9
- when :postgresql
10
- ThinkingSphinx::ActiveRecord::DatabaseAdapters::PostgreSQLAdapter
11
- else
12
- raise "Invalid Database Adapter '#{adapter}': Thinking Sphinx only supports MySQL and PostgreSQL."
13
- end
2
+ class << self
3
+ attr_accessor :default
14
4
 
15
- klass.new model
16
- end
5
+ def adapter_for(model)
6
+ return default.new(model) if default
7
+
8
+ adapter = adapter_type_for(model)
9
+ klass = case adapter
10
+ when :mysql
11
+ MySQLAdapter
12
+ when :postgresql
13
+ PostgreSQLAdapter
14
+ else
15
+ raise "Invalid Database Adapter '#{adapter}': Thinking Sphinx only supports MySQL and PostgreSQL."
16
+ end
17
+
18
+ klass.new model
19
+ end
17
20
 
18
- def self.adapter_type_for(model)
19
- case model.connection.class.name
20
- when "ActiveRecord::ConnectionAdapters::MysqlAdapter",
21
- "ActiveRecord::ConnectionAdapters::Mysql2Adapter"
22
- :mysql
23
- when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
24
- :postgresql
25
- when "ActiveRecord::ConnectionAdapters::JdbcAdapter"
26
- case model.connection.config[:adapter]
27
- when "jdbcmysql"
21
+ def adapter_type_for(model)
22
+ class_name = model.connection.class.name
23
+ case class_name.split('::').last
24
+ when 'MysqlAdapter', 'Mysql2Adapter'
28
25
  :mysql
29
- when "jdbcpostgresql"
26
+ when 'PostgreSQLAdapter'
30
27
  :postgresql
31
- when "jdbc"
32
- match = /^jdbc:(?<adapter>mysql|postgresql):\/\//.match(model.connection.config[:url])
33
- if match
34
- match[:adapter].to_sym
35
- else
36
- model.connection.config[:adapter]
37
- end
28
+ when 'JdbcAdapter'
29
+ adapter_type_for_jdbc(model)
38
30
  else
39
- model.connection.config[:adapter]
31
+ class_name
40
32
  end
41
- else
42
- model.connection.class.name
43
33
  end
44
- end
45
34
 
46
- @default = nil
47
- def self.default
48
- @default
49
- end
35
+ def adapter_type_for_jdbc(model)
36
+ case adapter = model.connection.config[:adapter]
37
+ when 'jdbcmysql'
38
+ :mysql
39
+ when 'jdbcpostgresql'
40
+ :postgresql
41
+ when 'jdbc'
42
+ adapter_type_for_jdbc_plain(adapter, model.connection.config[:url])
43
+ else adapter
44
+ end
45
+ end
46
+
47
+ def adapter_type_for_jdbc_plain(adapter, url)
48
+ return adapter unless match = /^jdbc:(?<adapter>mysql|postgresql):\/\//.match(url)
50
49
 
51
- def self.default=(default)
52
- @default = default
50
+ match[:adapter].to_sym
51
+ end
53
52
  end
54
53
  end
55
54
 
@@ -14,11 +14,16 @@ class ThinkingSphinx::ActiveRecord::Polymorpher
14
14
 
15
15
  def append_reflections
16
16
  mappings.each do |class_name, name|
17
- klass.reflections[name] ||= ThinkingSphinx::ActiveRecord::
18
- FilteredReflection.clone_with_filter(reflection, name, class_name)
17
+ klass.reflections[name] ||= clone_with name, class_name
19
18
  end
20
19
  end
21
20
 
21
+ def clone_with(name, class_name)
22
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
23
+ reflection, name, class_name
24
+ )
25
+ end
26
+
22
27
  def mappings
23
28
  @mappings ||= class_names.inject({}) do |hash, class_name|
24
29
  hash[class_name] = "#{column.__name}_#{class_name.downcase}".to_sym
@@ -5,6 +5,13 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
5
5
 
6
6
  def to_s
7
7
  identifier = [type, property.name].compact.join(' ')
8
+
9
+ "#{identifier} from #{source_type}; #{queries.join('; ')}"
10
+ end
11
+
12
+ private
13
+
14
+ def queries
8
15
  queries = []
9
16
  if column.string?
10
17
  queries << column.__name.strip.gsub(/\n/, "\\\n")
@@ -12,18 +19,20 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
12
19
  queries << to_sql
13
20
  queries << range_sql if ranged?
14
21
  end
15
-
16
- "#{identifier} from #{source_type}; #{queries.join('; ')}"
22
+ queries
17
23
  end
18
24
 
19
- private
20
-
21
25
  attr_reader :property, :source, :type
22
26
 
23
27
  def base_association
24
28
  reflections.first
25
29
  end
26
30
 
31
+ def base_association_class
32
+ base_association.klass
33
+ end
34
+ delegate :relation, :to => :base_association_class, :prefix => true
35
+
27
36
  def column
28
37
  @column ||= property.columns.first
29
38
  end
@@ -40,7 +49,7 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
40
49
 
41
50
  column.__stack.collect { |key|
42
51
  reflection = base.reflections[key]
43
- base = reflection.klass
52
+ base = reflection.klass
44
53
 
45
54
  extend_reflection reflection
46
55
  }.flatten
@@ -64,12 +73,11 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
64
73
  end
65
74
 
66
75
  def quoted_foreign_key
67
- quote_with_table base_association.klass.table_name,
68
- base_association.foreign_key
76
+ quote_with_table(base_association_class.table_name, base_association.foreign_key)
69
77
  end
70
78
 
71
79
  def quoted_primary_key
72
- quote_with_table reflections.last.klass.table_name, column.__name
80
+ quote_with_table(reflections.last.klass.table_name, column.__name)
73
81
  end
74
82
 
75
83
  def quote_with_table(table, column)
@@ -85,8 +93,9 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
85
93
  end
86
94
 
87
95
  def range_sql
88
- base_association.klass.unscoped.
89
- select("MIN(#{quoted_foreign_key}), MAX(#{quoted_foreign_key})").to_sql
96
+ base_association_class_relation.select(
97
+ "MIN(#{quoted_foreign_key}), MAX(#{quoted_foreign_key})"
98
+ ).to_sql
90
99
  end
91
100
 
92
101
  def source_type
@@ -96,15 +105,10 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
96
105
  def to_sql
97
106
  raise "Could not determine SQL for MVA" if reflections.empty?
98
107
 
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 = base_association_class_relation.select("#{quoted_foreign_key} #{offset} AS #{quote_column('id')}, #{quoted_primary_key} AS #{quote_column(property.name)}"
109
+ )
110
+ relation = relation.joins(joins) if joins.present?
111
+ relation = relation.where("#{quoted_foreign_key} BETWEEN $start AND $end") if ranged?
108
112
  relation = relation.order("#{quoted_foreign_key} ASC") if type.nil?
109
113
 
110
114
  relation.to_sql