thinking-sphinx 1.4.11 → 1.4.12
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.
- data/README.textile +5 -0
- data/features/attribute_transformation.feature +4 -4
- data/features/thinking_sphinx/db/fixtures/alphas.rb +8 -10
- data/lib/cucumber/thinking_sphinx/internal_world.rb +12 -3
- data/lib/thinking_sphinx/active_record.rb +5 -1
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +14 -5
- data/lib/thinking_sphinx/attribute.rb +3 -3
- data/lib/thinking_sphinx/auto_version.rb +2 -2
- data/lib/thinking_sphinx/configuration.rb +8 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +1 -1
- data/lib/thinking_sphinx/facet.rb +20 -20
- data/lib/thinking_sphinx/index.rb +3 -1
- data/lib/thinking_sphinx/index/builder.rb +5 -0
- data/lib/thinking_sphinx/property.rb +43 -41
- data/lib/thinking_sphinx/search.rb +22 -3
- data/lib/thinking_sphinx/source.rb +5 -0
- data/lib/thinking_sphinx/source/sql.rb +33 -16
- data/lib/thinking_sphinx/version.rb +1 -1
- data/spec/sphinx_helper.rb +7 -1
- data/spec/thinking_sphinx/active_record_spec.rb +15 -0
- data/spec/thinking_sphinx/auto_version_spec.rb +8 -0
- data/spec/thinking_sphinx/index/builder_spec.rb +126 -105
- data/spec/thinking_sphinx/index_spec.rb +6 -0
- data/spec/thinking_sphinx/search_spec.rb +269 -248
- data/spec/thinking_sphinx/source_spec.rb +4 -0
- metadata +59 -60
data/README.textile
CHANGED
|
@@ -12,11 +12,11 @@ Feature: Handle not-quite-supported column types as attributes
|
|
|
12
12
|
Scenario: Dates as Datetimes
|
|
13
13
|
Given Sphinx is running
|
|
14
14
|
And I am searching on alphas
|
|
15
|
-
When I filter between
|
|
16
|
-
Then I should get
|
|
15
|
+
When I filter between 2 and 4 days ago on created_on by date
|
|
16
|
+
Then I should get 3 results
|
|
17
17
|
|
|
18
18
|
Scenario: Timestamps as Datetimes
|
|
19
19
|
Given Sphinx is running
|
|
20
20
|
And I am searching on alphas
|
|
21
|
-
When I filter between
|
|
22
|
-
Then I should get 2 results
|
|
21
|
+
When I filter between 2 and 4 days ago on created_at
|
|
22
|
+
Then I should get 2 results
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Alpha.create :name =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Alpha.create :name => "nine", :value => 9, :cost => 9.59, :created_on => 9.day.ago.to_date, :created_at => 9.day.ago
|
|
10
|
-
Alpha.create :name => "ten", :value => 10, :cost => 10.50, :created_on => 10.day.ago.to_date, :created_at => 10.day.ago
|
|
1
|
+
%w(
|
|
2
|
+
one two three four five six seven eight nine ten
|
|
3
|
+
).each_with_index do |number, index|
|
|
4
|
+
value = index + 1
|
|
5
|
+
cost = value.to_f + 0.5 + (value * 0.01)
|
|
6
|
+
Alpha.create :name => number, :value => value, :cost => cost,
|
|
7
|
+
:created_on => value.days.ago.to_date, :created_at => value.days.ago
|
|
8
|
+
end
|
|
@@ -16,11 +16,20 @@ module Cucumber
|
|
|
16
16
|
@fixtures_directory = "#{pwd}/features/thinking_sphinx/db/fixtures"
|
|
17
17
|
@database_file = "#{pwd}/features/thinking_sphinx/database.yml"
|
|
18
18
|
|
|
19
|
-
@adapter = (ENV['DATABASE'] || 'mysql')
|
|
19
|
+
@adapter = (ENV['DATABASE'] || 'mysql')
|
|
20
20
|
@database = 'thinking_sphinx'
|
|
21
|
-
@username =
|
|
22
|
-
# @password = 'thinking_sphinx'
|
|
21
|
+
@username = ENV['USER']
|
|
23
22
|
@host = 'localhost'
|
|
23
|
+
|
|
24
|
+
if ActiveRecord.constants.include?('VERSION')
|
|
25
|
+
@adapter = @adapter.gsub /^mysql$/, 'mysql2'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if @adapter[/mysql/]
|
|
29
|
+
@username = 'root'
|
|
30
|
+
elsif ENV['TRAVIS']
|
|
31
|
+
@username = 'postgres'
|
|
32
|
+
end
|
|
24
33
|
end
|
|
25
34
|
|
|
26
35
|
def setup
|
|
@@ -17,7 +17,7 @@ module ThinkingSphinx
|
|
|
17
17
|
extend ThinkingSphinx::ActiveRecord::ClassMethods
|
|
18
18
|
|
|
19
19
|
class << self
|
|
20
|
-
attr_accessor :sphinx_index_blocks
|
|
20
|
+
attr_accessor :sphinx_index_blocks, :sphinx_types
|
|
21
21
|
|
|
22
22
|
def set_sphinx_primary_key(attribute)
|
|
23
23
|
@sphinx_primary_key_attribute = attribute
|
|
@@ -49,6 +49,10 @@ module ThinkingSphinx
|
|
|
49
49
|
sphinx_indexes.last.options
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
def set_sphinx_types(types)
|
|
53
|
+
@sphinx_types = types
|
|
54
|
+
end
|
|
55
|
+
|
|
52
56
|
# Generate a unique CRC value for the model's name, to use to
|
|
53
57
|
# determine which Sphinx documents belong to which AR records.
|
|
54
58
|
#
|
|
@@ -20,7 +20,7 @@ module ThinkingSphinx
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def group_concatenate(clause, separator = ' ')
|
|
23
|
-
if
|
|
23
|
+
if server_version >= 80400
|
|
24
24
|
"array_to_string(array_agg(COALESCE(#{clause}, '0')), '#{separator}')"
|
|
25
25
|
else
|
|
26
26
|
"array_to_string(array_accum(COALESCE(#{clause}, '0')), '#{separator}')"
|
|
@@ -109,11 +109,9 @@ module ThinkingSphinx
|
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
def create_array_accum_function
|
|
112
|
-
if
|
|
113
|
-
connection.raw_connection.server_version >= 80400
|
|
112
|
+
if server_version >= 80400
|
|
114
113
|
return
|
|
115
|
-
elsif
|
|
116
|
-
connection.raw_connection.server_version > 80200
|
|
114
|
+
elsif server_version > 80200
|
|
117
115
|
execute <<-SQL
|
|
118
116
|
CREATE AGGREGATE array_accum (anyelement)
|
|
119
117
|
(
|
|
@@ -175,5 +173,16 @@ module ThinkingSphinx
|
|
|
175
173
|
SQL
|
|
176
174
|
execute function, true
|
|
177
175
|
end
|
|
176
|
+
|
|
177
|
+
def server_version
|
|
178
|
+
if RUBY_PLATFORM == 'java'
|
|
179
|
+
(connection.raw_connection.connection.server_major_version * 10000) +
|
|
180
|
+
(connection.raw_connection.connection.server_minor_version * 100)
|
|
181
|
+
elsif connection.raw_connection.respond_to?(:server_version)
|
|
182
|
+
connection.raw_connection.server_version
|
|
183
|
+
else
|
|
184
|
+
0
|
|
185
|
+
end
|
|
186
|
+
end
|
|
178
187
|
end
|
|
179
188
|
end
|
|
@@ -269,7 +269,7 @@ WHERE #{@source.index.delta_object.clause(model, true)})
|
|
|
269
269
|
end
|
|
270
270
|
|
|
271
271
|
def end_association_for_mva
|
|
272
|
-
@association_for_mva ||= associations[columns.first].detect { |assoc|
|
|
272
|
+
@association_for_mva ||= associations[columns.first.__stack].detect { |assoc|
|
|
273
273
|
assoc.has_column?(columns.first.__name)
|
|
274
274
|
}
|
|
275
275
|
end
|
|
@@ -358,8 +358,8 @@ block:
|
|
|
358
358
|
|
|
359
359
|
def all_of_type?(*column_types)
|
|
360
360
|
@columns.all? { |col|
|
|
361
|
-
klasses = @associations[col].empty? ? [@model] :
|
|
362
|
-
@associations[col].collect { |assoc| assoc.reflection.klass }
|
|
361
|
+
klasses = @associations[col.__stack].empty? ? [@model] :
|
|
362
|
+
@associations[col.__stack].collect { |assoc| assoc.reflection.klass }
|
|
363
363
|
klasses.all? { |klass|
|
|
364
364
|
column = klass.columns.detect { |column| column.name == col.__name.to_s }
|
|
365
365
|
!column.nil? && column_types.include?(column.type)
|
|
@@ -7,9 +7,9 @@ module ThinkingSphinx
|
|
|
7
7
|
require "riddle/#{version}"
|
|
8
8
|
when /1.10/
|
|
9
9
|
require 'riddle/1.10'
|
|
10
|
-
when /2.0
|
|
10
|
+
when /2.0.[12]/
|
|
11
11
|
require 'riddle/2.0.1'
|
|
12
|
-
when /2.1.\d/
|
|
12
|
+
when /2.0.3/, /2.0.4/, /2.1.\d/
|
|
13
13
|
require 'riddle/2.1.0'
|
|
14
14
|
else
|
|
15
15
|
documentation_link = %Q{
|
|
@@ -195,6 +195,14 @@ module ThinkingSphinx
|
|
|
195
195
|
@configuration.searchd.port = port
|
|
196
196
|
end
|
|
197
197
|
|
|
198
|
+
def use_socket=(use_socket)
|
|
199
|
+
if use_socket
|
|
200
|
+
socket = "#{app_root}/tmp/sockets/searchd.#{self.environment}.sock"
|
|
201
|
+
@configuration.searchd.listen = socket
|
|
202
|
+
self.address = socket
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
198
206
|
def pid_file
|
|
199
207
|
@configuration.searchd.pid_file
|
|
200
208
|
end
|
|
@@ -92,7 +92,7 @@ DESC
|
|
|
92
92
|
rails_env = fetch(:rails_env, "production")
|
|
93
93
|
rake = fetch(:rake, "rake")
|
|
94
94
|
tasks.each do |t|
|
|
95
|
-
run "if [ -d #{release_path} ]; then cd #{release_path}; else cd #{current_path}; fi; #{rake} RAILS_ENV=#{rails_env} #{t}"
|
|
95
|
+
run "if [ -d #{release_path} ]; then cd #{release_path}; else cd #{current_path}; fi; if [ -f Rakefile ]; then #{rake} RAILS_ENV=#{rails_env} #{t}; fi;"
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
end
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
module ThinkingSphinx
|
|
2
2
|
class Facet
|
|
3
3
|
attr_reader :property, :value_source
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
def initialize(property, value_source = nil)
|
|
6
6
|
@property = property
|
|
7
7
|
@value_source = value_source
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
if property.columns.length != 1
|
|
10
10
|
raise "Can't translate Facets on multiple-column field or attribute"
|
|
11
11
|
end
|
|
@@ -20,11 +20,11 @@ module ThinkingSphinx
|
|
|
20
20
|
facet.to_s.gsub(/(_facet|_crc)$/,'').to_sym
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
def self.attribute_name_for(name)
|
|
25
25
|
name.to_s == 'class' ? 'class_crc' : "#{name}_facet"
|
|
26
26
|
end
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
def self.attribute_name_from_value(name, value)
|
|
29
29
|
case value
|
|
30
30
|
when String
|
|
@@ -39,10 +39,10 @@ module ThinkingSphinx
|
|
|
39
39
|
name
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
def self.translate?(property)
|
|
44
44
|
return true if property.is_a?(Field)
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
case property.type
|
|
47
47
|
when :string
|
|
48
48
|
true
|
|
@@ -52,11 +52,11 @@ module ThinkingSphinx
|
|
|
52
52
|
!property.all_ints?
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
def name
|
|
57
57
|
property.unique_name
|
|
58
58
|
end
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
def attribute_name
|
|
61
61
|
if translate?
|
|
62
62
|
Facet.attribute_name_for(@property.unique_name)
|
|
@@ -64,23 +64,23 @@ module ThinkingSphinx
|
|
|
64
64
|
@property.unique_name.to_s
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
def translate?
|
|
69
69
|
Facet.translate?(@property)
|
|
70
70
|
end
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
def type
|
|
73
73
|
@property.is_a?(Field) ? :string : @property.type
|
|
74
74
|
end
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
def float?
|
|
77
77
|
@property.type == :float
|
|
78
78
|
end
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
def value(object, attribute_hash)
|
|
81
81
|
attribute_value = attribute_hash['@groupby']
|
|
82
82
|
return translate(object, attribute_value) if translate? || float?
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
case @property.type
|
|
85
85
|
when :datetime
|
|
86
86
|
Time.at(attribute_value)
|
|
@@ -90,13 +90,13 @@ module ThinkingSphinx
|
|
|
90
90
|
attribute_value
|
|
91
91
|
end
|
|
92
92
|
end
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
def to_s
|
|
95
95
|
name
|
|
96
96
|
end
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
private
|
|
99
|
-
|
|
99
|
+
|
|
100
100
|
def translate(object, attribute_value)
|
|
101
101
|
objects = source_objects(object)
|
|
102
102
|
return if objects.blank?
|
|
@@ -107,20 +107,20 @@ module ThinkingSphinx
|
|
|
107
107
|
result && result.to_crc32 == attribute_value
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
object.
|
|
110
|
+
object ? object.send(method) : nil
|
|
111
111
|
end
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
def source_objects(object)
|
|
114
114
|
column.__stack.each { |method|
|
|
115
115
|
object = Array(object).collect { |item|
|
|
116
116
|
item.send(method)
|
|
117
117
|
}.flatten.compact
|
|
118
|
-
|
|
118
|
+
|
|
119
119
|
return nil if object.empty?
|
|
120
120
|
}
|
|
121
121
|
Array(object)
|
|
122
122
|
end
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
def column
|
|
125
125
|
@property.columns.first
|
|
126
126
|
end
|
|
@@ -3,7 +3,7 @@ require 'thinking_sphinx/index/faux_column'
|
|
|
3
3
|
|
|
4
4
|
module ThinkingSphinx
|
|
5
5
|
class Index
|
|
6
|
-
attr_accessor :name, :model, :sources, :delta_object
|
|
6
|
+
attr_accessor :name, :model, :sources, :delta_object, :additional_indices
|
|
7
7
|
|
|
8
8
|
# Create a new index instance by passing in the model it is tied to, and
|
|
9
9
|
# a block to build it with (optional but recommended). For documentation
|
|
@@ -25,6 +25,7 @@ module ThinkingSphinx
|
|
|
25
25
|
@sources = []
|
|
26
26
|
@options = {}
|
|
27
27
|
@delta_object = nil
|
|
28
|
+
@additional_indices = []
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def fields
|
|
@@ -132,6 +133,7 @@ module ThinkingSphinx
|
|
|
132
133
|
def to_riddle_for_distributed
|
|
133
134
|
index = Riddle::Configuration::DistributedIndex.new name
|
|
134
135
|
index.local_indices << core_name
|
|
136
|
+
index.local_indices += additional_indices
|
|
135
137
|
index.local_indices.unshift delta_name if delta?
|
|
136
138
|
index
|
|
137
139
|
end
|
|
@@ -52,6 +52,11 @@ module ThinkingSphinx
|
|
|
52
52
|
self.instance_eval &block
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
def use_local_indices(*indexes)
|
|
56
|
+
@index.additional_indices += indexes.map {|index_name| "#{index_name.to_s}_core"}
|
|
57
|
+
end
|
|
58
|
+
alias_method :use_local_index, :use_local_indices
|
|
59
|
+
|
|
55
60
|
# This is how you add fields - the strings Sphinx looks at - to your
|
|
56
61
|
# index. Technically, to use this method, you need to pass in some
|
|
57
62
|
# columns and options - but there's some neat method_missing stuff
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module ThinkingSphinx
|
|
2
2
|
class Property
|
|
3
3
|
attr_accessor :alias, :columns, :associations, :model, :faceted, :admin
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
def initialize(source, columns, options = {})
|
|
6
6
|
@source = source
|
|
7
7
|
@model = source.model
|
|
@@ -9,27 +9,27 @@ module ThinkingSphinx
|
|
|
9
9
|
@associations = {}
|
|
10
10
|
|
|
11
11
|
raise "Cannot define a field or attribute in #{source.model.name} with no columns. Maybe you are trying to index a field with a reserved name (id, name). You can fix this error by using a symbol rather than a bare name (:id instead of id)." if @columns.empty? || @columns.any? { |column| !column.respond_to?(:__stack) }
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
@alias = options[:as]
|
|
14
14
|
@faceted = options[:facet]
|
|
15
15
|
@admin = options[:admin]
|
|
16
16
|
@sortable = options[:sortable] || false
|
|
17
17
|
@value_source = options[:value]
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
@alias = @alias.to_sym unless @alias.blank?
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
@columns.each { |col|
|
|
22
|
-
@associations[col] = association_stack(col.__stack.clone).each { |assoc|
|
|
22
|
+
@associations[col.__stack] = association_stack(col.__stack.clone).each { |assoc|
|
|
23
23
|
assoc.join_to(source.base)
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
end
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
# Returns the unique name of the attribute - which is either the alias of
|
|
29
29
|
# the attribute, or the name of the only column - if there is only one. If
|
|
30
30
|
# there isn't, there should be an alias. Else things probably won't work.
|
|
31
31
|
# Consider yourself warned.
|
|
32
|
-
#
|
|
32
|
+
#
|
|
33
33
|
def unique_name
|
|
34
34
|
if @columns.length == 1
|
|
35
35
|
@alias || @columns.first.__name
|
|
@@ -37,19 +37,19 @@ module ThinkingSphinx
|
|
|
37
37
|
@alias
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
def to_facet
|
|
42
42
|
return nil unless @faceted
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
ThinkingSphinx::Facet.new(self, @value_source)
|
|
45
45
|
end
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
# Get the part of the GROUP BY clause related to this attribute - if one is
|
|
48
48
|
# needed. If not, all you'll get back is nil. The latter will happen if
|
|
49
49
|
# there isn't actually a real column to get data from, or if there's
|
|
50
50
|
# multiple data values (read: a has_many or has_and_belongs_to_many
|
|
51
51
|
# association).
|
|
52
|
-
#
|
|
52
|
+
#
|
|
53
53
|
def to_group_sql
|
|
54
54
|
case
|
|
55
55
|
when is_many?, is_string?, ThinkingSphinx.use_group_by_shortcut?
|
|
@@ -60,126 +60,128 @@ module ThinkingSphinx
|
|
|
60
60
|
}
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
def changed?(instance)
|
|
65
65
|
return true if is_string? || @columns.any? { |col| !col.__stack.empty? }
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
!@columns.all? { |col|
|
|
68
68
|
instance.respond_to?("#{col.__name.to_s}_changed?") &&
|
|
69
69
|
!instance.send("#{col.__name.to_s}_changed?")
|
|
70
70
|
}
|
|
71
71
|
end
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
def admin?
|
|
74
74
|
admin
|
|
75
75
|
end
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
def public?
|
|
78
78
|
!admin
|
|
79
79
|
end
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
def available?
|
|
82
82
|
columns.any? { |column| column_available?(column) }
|
|
83
83
|
end
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
private
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
# Could there be more than one value related to the parent record? If so,
|
|
88
88
|
# then this will return true. If not, false. It's that simple.
|
|
89
|
-
#
|
|
89
|
+
#
|
|
90
90
|
def is_many?
|
|
91
91
|
associations.values.flatten.any? { |assoc| assoc.is_many? }
|
|
92
92
|
end
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
# Returns true if any of the columns are string values, instead of database
|
|
95
95
|
# column references.
|
|
96
96
|
def is_string?
|
|
97
97
|
columns.all? { |col| col.is_string? }
|
|
98
98
|
end
|
|
99
|
-
|
|
99
|
+
|
|
100
100
|
def adapter
|
|
101
101
|
@adapter ||= @model.sphinx_database_adapter
|
|
102
102
|
end
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
def quote_with_table(table, column)
|
|
105
105
|
"#{quote_table_name(table)}.#{quote_column(column)}"
|
|
106
106
|
end
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
def quote_column(column)
|
|
109
109
|
@model.connection.quote_column_name(column)
|
|
110
110
|
end
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
def quote_table_name(table_name)
|
|
113
113
|
@model.connection.quote_table_name(table_name)
|
|
114
114
|
end
|
|
115
|
-
|
|
115
|
+
|
|
116
116
|
# Indication of whether the columns should be concatenated with a space
|
|
117
117
|
# between each value. True if there's either multiple sources or multiple
|
|
118
118
|
# associations.
|
|
119
|
-
#
|
|
119
|
+
#
|
|
120
120
|
def concat_ws?
|
|
121
121
|
multiple_associations? || @columns.length > 1
|
|
122
122
|
end
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
# Checks whether any column requires multiple associations (which only
|
|
125
125
|
# happens for polymorphic situations).
|
|
126
|
-
#
|
|
126
|
+
#
|
|
127
127
|
def multiple_associations?
|
|
128
|
-
associations.any? { |
|
|
128
|
+
associations.values.any? { |assocs| assocs.length > 1 }
|
|
129
129
|
end
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
# Builds a column reference tied to the appropriate associations. This
|
|
132
132
|
# dives into the associations hash and their corresponding joins to
|
|
133
133
|
# figure out how to correctly reference a column in SQL.
|
|
134
|
-
#
|
|
134
|
+
#
|
|
135
135
|
def column_with_prefix(column)
|
|
136
136
|
return nil unless column_available?(column)
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
if column.is_string?
|
|
139
139
|
column.__name
|
|
140
140
|
elsif column.__stack.empty?
|
|
141
141
|
"#{@model.quoted_table_name}.#{quote_column(column.__name)}"
|
|
142
142
|
else
|
|
143
|
-
associations[column].collect { |assoc|
|
|
143
|
+
associations[column.__stack].collect { |assoc|
|
|
144
144
|
assoc.has_column?(column.__name) ?
|
|
145
145
|
"#{quote_with_table(assoc.join.aliased_table_name, column.__name)}" :
|
|
146
146
|
nil
|
|
147
147
|
}.compact
|
|
148
148
|
end
|
|
149
149
|
end
|
|
150
|
-
|
|
150
|
+
|
|
151
151
|
def columns_with_prefixes
|
|
152
152
|
@columns.collect { |column|
|
|
153
153
|
column_with_prefix column
|
|
154
154
|
}.flatten.compact
|
|
155
155
|
end
|
|
156
|
-
|
|
156
|
+
|
|
157
157
|
def column_available?(column)
|
|
158
158
|
if column.is_string?
|
|
159
159
|
true
|
|
160
160
|
elsif column.__stack.empty?
|
|
161
161
|
@model.column_names.include?(column.__name.to_s)
|
|
162
162
|
else
|
|
163
|
-
associations[column].any? { |assoc|
|
|
163
|
+
associations[column.__stack].any? { |assoc|
|
|
164
|
+
assoc.has_column?(column.__name)
|
|
165
|
+
}
|
|
164
166
|
end
|
|
165
167
|
end
|
|
166
|
-
|
|
168
|
+
|
|
167
169
|
# Gets a stack of associations for a specific path.
|
|
168
|
-
#
|
|
170
|
+
#
|
|
169
171
|
def association_stack(path, parent = nil)
|
|
170
172
|
assocs = []
|
|
171
|
-
|
|
173
|
+
|
|
172
174
|
if parent.nil?
|
|
173
175
|
assocs = @source.association(path.shift)
|
|
174
176
|
else
|
|
175
177
|
assocs = parent.children(path.shift)
|
|
176
178
|
end
|
|
177
|
-
|
|
179
|
+
|
|
178
180
|
until path.empty?
|
|
179
181
|
point = path.shift
|
|
180
182
|
assocs = assocs.collect { |assoc| assoc.children(point) }.flatten
|
|
181
183
|
end
|
|
182
|
-
|
|
184
|
+
|
|
183
185
|
assocs
|
|
184
186
|
end
|
|
185
187
|
end
|