thinking-sphinx 1.4.11 → 1.4.12
Sign up to get free protection for your applications and to get access to all the features.
- 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
|