composite_primary_keys 1.0.8 → 1.0.10
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/History.txt +10 -0
- data/lib/composite_primary_keys/association_preload.rb +67 -61
- data/lib/composite_primary_keys/associations.rb +428 -409
- data/lib/composite_primary_keys/attribute_methods.rb +3 -3
- data/lib/composite_primary_keys/base.rb +128 -110
- data/lib/composite_primary_keys/calculations.rb +8 -8
- data/lib/composite_primary_keys/composite_arrays.rb +6 -5
- data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +44 -2
- data/lib/composite_primary_keys/fixtures.rb +2 -3
- data/lib/composite_primary_keys/version.rb +1 -1
- data/scripts/console.rb +34 -11
- data/test/fixtures/db_definitions/db2-create-tables.sql +2 -2
- data/test/fixtures/db_definitions/mysql.sql +156 -155
- data/test/fixtures/db_definitions/oracle.drop.sql +4 -1
- data/test/fixtures/db_definitions/oracle.sql +117 -119
- data/test/fixtures/db_definitions/postgresql.sql +164 -98
- data/test/fixtures/db_definitions/sqlite.sql +74 -73
- data/test/fixtures/street.rb +2 -2
- data/test/fixtures/streets.yml +14 -14
- data/test/test_associations.rb +0 -5
- data/test/test_attribute_methods.rb +2 -2
- data/test/test_create.rb +68 -68
- data/test/test_delete.rb +19 -10
- data/test/test_ids.rb +7 -0
- data/tmp/test.db +0 -0
- data/website/template.js +3 -3
- metadata +2 -2
data/History.txt
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
== 1.0.10 2008-10-22
|
2
|
+
|
3
|
+
* add composite key where clause creator method [timurv]
|
4
|
+
|
5
|
+
== 1.0.9 2008-09-08
|
6
|
+
|
7
|
+
* fix postgres tests
|
8
|
+
* fix for delete_records when has_many association has composite keys [darxriggs]
|
9
|
+
* more consistent table/column name quoting [pbrant]
|
10
|
+
|
1
11
|
== 1.0.8 2008-08-27
|
2
12
|
|
3
13
|
* fix has_many :through for non composite models [thx rcarver]
|
@@ -5,43 +5,43 @@ module CompositePrimaryKeys
|
|
5
5
|
super
|
6
6
|
base.send(:extend, ClassMethods)
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
# Composite key versions of Association functions
|
10
|
-
module ClassMethods
|
10
|
+
module ClassMethods
|
11
11
|
def preload_has_and_belongs_to_many_association(records, reflection, preload_options={})
|
12
12
|
table_name = reflection.klass.quoted_table_name
|
13
13
|
id_to_record_map, ids = construct_id_map(records)
|
14
14
|
records.each {|record| record.send(reflection.name).loaded}
|
15
15
|
options = reflection.options
|
16
16
|
|
17
|
-
if
|
17
|
+
if composite?
|
18
18
|
primary_key = reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP)
|
19
19
|
where = (primary_key * ids.size).in_groups_of(primary_key.size).map do |keys|
|
20
|
-
"(" + keys.map{|key| "t0.#{key} = ?"}.join(" AND ") + ")"
|
20
|
+
"(" + keys.map{|key| "t0.#{connection.quote_column_name(key)} = ?"}.join(" AND ") + ")"
|
21
21
|
end.join(" OR ")
|
22
|
-
|
22
|
+
|
23
23
|
conditions = [where, ids].flatten
|
24
|
-
joins = "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{full_composite_join_clause(reflection.klass.
|
25
|
-
parent_primary_keys = reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP).map{|k| "t0.#{k}"}
|
26
|
-
parent_record_id = connection.concat(parent_primary_keys.zip(["','"] * (parent_primary_keys.size - 1)).flatten.compact)
|
24
|
+
joins = "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{full_composite_join_clause(reflection, reflection.klass.table_name, reflection.klass.primary_key, 't0', reflection.association_foreign_key)}"
|
25
|
+
parent_primary_keys = reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP).map{|k| "t0.#{connection.quote_column_name(k)}"}
|
26
|
+
parent_record_id = connection.concat(*parent_primary_keys.zip(["','"] * (parent_primary_keys.size - 1)).flatten.compact)
|
27
27
|
else
|
28
|
-
conditions = ["t0.#{reflection.primary_key_name} IN (?)", ids]
|
29
|
-
joins = "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}"
|
28
|
+
conditions = ["t0.#{connection.quote_column_name(reflection.primary_key_name)} IN (?)", ids]
|
29
|
+
joins = "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{connection.quote_column_name(reflection.klass.primary_key)} = t0.#{connection.quote_column_name(reflection.association_foreign_key)})"
|
30
30
|
parent_record_id = reflection.primary_key_name
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
conditions.first << append_conditions(options, preload_options)
|
34
34
|
|
35
|
-
associated_records = reflection.klass.find(:all,
|
35
|
+
associated_records = reflection.klass.find(:all,
|
36
36
|
:conditions => conditions,
|
37
|
-
:include
|
38
|
-
:joins
|
39
|
-
:select
|
40
|
-
:order
|
41
|
-
|
37
|
+
:include => options[:include],
|
38
|
+
:joins => joins,
|
39
|
+
:select => "#{options[:select] || table_name+'.*'}, #{parent_record_id} as parent_record_id_",
|
40
|
+
:order => options[:order])
|
41
|
+
|
42
42
|
set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'parent_record_id_')
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
def preload_has_many_association(records, reflection, preload_options={})
|
46
46
|
id_to_record_map, ids = construct_id_map(records)
|
47
47
|
records.each {|record| record.send(reflection.name).loaded}
|
@@ -51,7 +51,7 @@ module CompositePrimaryKeys
|
|
51
51
|
through_records = preload_through_records(records, reflection, options[:through])
|
52
52
|
through_reflection = reflections[options[:through]]
|
53
53
|
through_primary_key = through_reflection.primary_key_name
|
54
|
-
|
54
|
+
|
55
55
|
unless through_records.empty?
|
56
56
|
source = reflection.source_reflection.name
|
57
57
|
#add conditions from reflection!
|
@@ -61,7 +61,7 @@ module CompositePrimaryKeys
|
|
61
61
|
add_preloaded_records_to_collection(id_to_record_map[key], reflection.name, through_record.send(source))
|
62
62
|
end
|
63
63
|
end
|
64
|
-
else
|
64
|
+
else
|
65
65
|
associated_records = find_associated_records(ids, reflection, preload_options)
|
66
66
|
set_association_collection_records(id_to_record_map, reflection.name, associated_records, reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP))
|
67
67
|
end
|
@@ -95,7 +95,7 @@ module CompositePrimaryKeys
|
|
95
95
|
records.first.class.preload_associations(records, through_association)
|
96
96
|
through_records = records.map {|record| record.send(through_association)}.flatten
|
97
97
|
end
|
98
|
-
|
98
|
+
|
99
99
|
through_records.compact!
|
100
100
|
through_records
|
101
101
|
end
|
@@ -107,23 +107,23 @@ module CompositePrimaryKeys
|
|
107
107
|
if options[:polymorphic]
|
108
108
|
raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
|
109
109
|
else
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
110
|
+
# I need to keep the original ids for each record (as opposed to the stringified) so
|
111
|
+
# that they get properly converted for each db so the id_map ends up looking like:
|
112
|
+
#
|
113
|
+
# { '1,2' => {:id => [1,2], :records => [...records...]}}
|
114
114
|
id_map = {}
|
115
|
-
|
115
|
+
|
116
116
|
records.each do |record|
|
117
117
|
key = primary_key_name.map{|k| record.send(k)}
|
118
118
|
key_as_string = key.join(CompositePrimaryKeys::ID_SEP)
|
119
|
-
|
119
|
+
|
120
120
|
if key_as_string
|
121
121
|
mapped_records = (id_map[key_as_string] ||= {:id => key, :records => []})
|
122
122
|
mapped_records[:records] << record
|
123
123
|
end
|
124
124
|
end
|
125
|
-
|
126
|
-
|
125
|
+
|
126
|
+
|
127
127
|
klasses_and_ids = [[reflection.klass.name, id_map]]
|
128
128
|
end
|
129
129
|
|
@@ -131,28 +131,30 @@ module CompositePrimaryKeys
|
|
131
131
|
klass_name, id_map = *klass_and_id
|
132
132
|
klass = klass_name.constantize
|
133
133
|
table_name = klass.quoted_table_name
|
134
|
-
|
135
|
-
|
134
|
+
connection = reflection.active_record.connection
|
135
|
+
|
136
|
+
if composite?
|
136
137
|
primary_key = klass.primary_key.to_s.split(CompositePrimaryKeys::ID_SEP)
|
137
138
|
ids = id_map.keys.uniq.map {|id| id_map[id][:id]}
|
138
139
|
|
139
140
|
where = (primary_key * ids.size).in_groups_of(primary_key.size).map do |keys|
|
140
|
-
"(" + keys.map{|key| "#{table_name}.#{key} = ?"}.join(" AND ") + ")"
|
141
|
+
"(" + keys.map{|key| "#{table_name}.#{connection.quote_column_name(key)} = ?"}.join(" AND ") + ")"
|
141
142
|
end.join(" OR ")
|
142
|
-
|
143
|
+
|
143
144
|
conditions = [where, ids].flatten
|
144
145
|
else
|
145
|
-
conditions = ["#{table_name}.#{primary_key} IN (?)", id_map.keys.uniq]
|
146
|
+
conditions = ["#{table_name}.#{connection.quote_column_name(primary_key)} IN (?)", id_map.keys.uniq]
|
146
147
|
end
|
147
|
-
|
148
|
+
|
148
149
|
conditions.first << append_conditions(options, preload_options)
|
149
|
-
|
150
|
-
associated_records = klass.find(:all,
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
150
|
+
|
151
|
+
associated_records = klass.find(:all,
|
152
|
+
:conditions => conditions,
|
153
|
+
:include => options[:include],
|
154
|
+
:select => options[:select],
|
155
|
+
:joins => options[:joins],
|
156
|
+
:order => options[:order])
|
157
|
+
|
156
158
|
set_association_single_records(id_map, reflection.name, associated_records, primary_key)
|
157
159
|
end
|
158
160
|
end
|
@@ -165,13 +167,13 @@ module CompositePrimaryKeys
|
|
165
167
|
add_preloaded_records_to_collection(mapped_records, reflection_name, associated_record)
|
166
168
|
end
|
167
169
|
end
|
168
|
-
|
170
|
+
|
169
171
|
def set_association_single_records(id_to_record_map, reflection_name, associated_records, key)
|
170
172
|
seen_keys = {}
|
171
173
|
associated_records.each do |associated_record|
|
172
174
|
associated_record_key = associated_record[key]
|
173
175
|
associated_record_key = associated_record_key.is_a?(Array) ? associated_record_key.join(CompositePrimaryKeys::ID_SEP) : associated_record_key.to_s
|
174
|
-
|
176
|
+
|
175
177
|
#this is a has_one or belongs_to: there should only be one record.
|
176
178
|
#Unfortunately we can't (in portable way) ask the database for 'all records where foo_id in (x,y,z), but please
|
177
179
|
# only one row per distinct foo_id' so this where we enforce that
|
@@ -183,7 +185,7 @@ module CompositePrimaryKeys
|
|
183
185
|
end
|
184
186
|
end
|
185
187
|
end
|
186
|
-
|
188
|
+
|
187
189
|
def find_associated_records(ids, reflection, preload_options)
|
188
190
|
options = reflection.options
|
189
191
|
table_name = reflection.klass.quoted_table_name
|
@@ -191,40 +193,44 @@ module CompositePrimaryKeys
|
|
191
193
|
if interface = reflection.options[:as]
|
192
194
|
raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
|
193
195
|
else
|
196
|
+
connection = reflection.active_record.connection
|
194
197
|
foreign_key = reflection.primary_key_name
|
195
|
-
conditions = ["#{
|
198
|
+
conditions = ["#{table_name}.#{connection.quote_column_name(foreign_key)} IN (?)", ids]
|
196
199
|
|
197
|
-
if
|
200
|
+
if composite?
|
198
201
|
foreign_keys = foreign_key.to_s.split(CompositePrimaryKeys::ID_SEP)
|
199
202
|
|
200
|
-
|
201
|
-
|
203
|
+
where = (foreign_keys * ids.size).in_groups_of(foreign_keys.size).map do |keys|
|
204
|
+
"(" + keys.map{|key| "#{table_name}.#{connection.quote_column_name(key)} = ?"}.join(" AND ") + ")"
|
202
205
|
end.join(" OR ")
|
203
|
-
|
206
|
+
|
204
207
|
conditions = [where, ids].flatten
|
208
|
+
end
|
205
209
|
end
|
206
|
-
|
207
|
-
|
210
|
+
|
208
211
|
conditions.first << append_conditions(options, preload_options)
|
209
212
|
|
210
213
|
reflection.klass.find(:all,
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
214
|
+
:select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
|
215
|
+
:include => preload_options[:include] || options[:include],
|
216
|
+
:conditions => conditions,
|
217
|
+
:joins => options[:joins],
|
218
|
+
:group => preload_options[:group] || options[:group],
|
219
|
+
:order => preload_options[:order] || options[:order])
|
217
220
|
end
|
218
221
|
|
219
|
-
def full_composite_join_clause(table1, full_keys1, table2, full_keys2)
|
222
|
+
def full_composite_join_clause(reflection, table1, full_keys1, table2, full_keys2)
|
223
|
+
connection = reflection.active_record.connection
|
220
224
|
full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String)
|
221
225
|
full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String)
|
222
226
|
where_clause = [full_keys1, full_keys2].transpose.map do |key_pair|
|
223
|
-
|
227
|
+
quoted1 = connection.quote_table_name(table1)
|
228
|
+
quoted2 = connection.quote_table_name(table2)
|
229
|
+
"#{quoted1}.#{connection.quote_column_name(key_pair.first)}=#{quoted2}.#{connection.quote_column_name(key_pair.last)}"
|
224
230
|
end.join(" AND ")
|
225
231
|
"(#{where_clause})"
|
226
232
|
end
|
227
233
|
end
|
228
234
|
end
|
229
235
|
end
|
230
|
-
end
|
236
|
+
end
|
@@ -1,409 +1,428 @@
|
|
1
|
-
module CompositePrimaryKeys
|
2
|
-
module ActiveRecord
|
3
|
-
module Associations
|
4
|
-
def self.append_features(base)
|
5
|
-
super
|
6
|
-
base.send(:extend, ClassMethods)
|
7
|
-
end
|
8
|
-
|
9
|
-
# Composite key versions of Association functions
|
10
|
-
module ClassMethods
|
11
|
-
|
12
|
-
def construct_counter_sql_with_included_associations(options, join_dependency)
|
13
|
-
scope = scope(:find)
|
14
|
-
sql = "SELECT COUNT(DISTINCT #{quoted_table_columns(primary_key)})"
|
15
|
-
|
16
|
-
# A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
|
17
|
-
if !self.connection.supports_count_distinct?
|
18
|
-
sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{quoted_table_columns(primary_key)}"
|
19
|
-
end
|
20
|
-
|
21
|
-
sql << " FROM #{
|
22
|
-
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
23
|
-
|
24
|
-
add_joins!(sql, options, scope)
|
25
|
-
add_conditions!(sql, options[:conditions], scope)
|
26
|
-
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
27
|
-
|
28
|
-
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
29
|
-
|
30
|
-
if !self.connection.supports_count_distinct?
|
31
|
-
sql << ")"
|
32
|
-
end
|
33
|
-
|
34
|
-
return sanitize_sql(sql)
|
35
|
-
end
|
36
|
-
|
37
|
-
def construct_finder_sql_with_included_associations(options, join_dependency)
|
38
|
-
scope = scope(:find)
|
39
|
-
sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] ||
|
40
|
-
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
41
|
-
|
42
|
-
add_joins!(sql, options, scope)
|
43
|
-
add_conditions!(sql, options[:conditions], scope)
|
44
|
-
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]
|
45
|
-
|
46
|
-
sql << "ORDER BY #{options[:order]} " if options[:order]
|
47
|
-
|
48
|
-
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
49
|
-
|
50
|
-
return sanitize_sql(sql)
|
51
|
-
end
|
52
|
-
|
53
|
-
def table_columns(columns)
|
54
|
-
columns.collect {|column| "#{self.
|
55
|
-
end
|
56
|
-
|
57
|
-
def quoted_table_columns(columns)
|
58
|
-
table_columns(columns).join(ID_SEP)
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
module ActiveRecord::Associations::ClassMethods
|
68
|
-
class JoinDependency
|
69
|
-
def construct_association(record, join, row)
|
70
|
-
case join.reflection.macro
|
71
|
-
when :has_many, :has_and_belongs_to_many
|
72
|
-
collection = record.send(join.reflection.name)
|
73
|
-
collection.loaded
|
74
|
-
|
75
|
-
join_aliased_primary_keys = join.active_record.composite? ?
|
76
|
-
join.aliased_primary_key : [join.aliased_primary_key]
|
77
|
-
return nil if
|
78
|
-
record.id.to_s != join.parent.record_id(row).to_s or not
|
79
|
-
join_aliased_primary_keys.select {|key| row[key].nil?}.blank?
|
80
|
-
association = join.instantiate(row)
|
81
|
-
collection.target.push(association) unless collection.target.include?(association)
|
82
|
-
when :has_one, :belongs_to
|
83
|
-
return if record.id.to_s != join.parent.record_id(row).to_s or
|
84
|
-
join.aliased_primary_key.
|
85
|
-
association = join.instantiate(row)
|
86
|
-
record.send("set_#{join.reflection.name}_target", association)
|
87
|
-
else
|
88
|
-
raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
|
89
|
-
end
|
90
|
-
return association
|
91
|
-
end
|
92
|
-
|
93
|
-
class JoinBase
|
94
|
-
def aliased_primary_key
|
95
|
-
active_record.composite? ?
|
96
|
-
primary_key.inject([]) {|aliased_keys, key| aliased_keys << "#{ aliased_prefix }_r#{aliased_keys.length}"} :
|
97
|
-
"#{ aliased_prefix }_r0"
|
98
|
-
end
|
99
|
-
|
100
|
-
def record_id(row)
|
101
|
-
active_record.composite? ?
|
102
|
-
aliased_primary_key.map {|key| row[key]}.to_composite_ids :
|
103
|
-
row[aliased_primary_key]
|
104
|
-
end
|
105
|
-
|
106
|
-
def column_names_with_alias
|
107
|
-
unless @column_names_with_alias
|
108
|
-
@column_names_with_alias = []
|
109
|
-
keys = active_record.composite? ? primary_key.map(&:to_s) : [primary_key]
|
110
|
-
(keys + (column_names - keys)).each_with_index do |column_name, i|
|
111
|
-
@column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
|
112
|
-
end
|
113
|
-
end
|
114
|
-
return @column_names_with_alias
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
class JoinAssociation < JoinBase
|
119
|
-
alias single_association_join association_join
|
120
|
-
def association_join
|
121
|
-
reflection.active_record.composite? ?
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
when reflection.macro == :
|
180
|
-
raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
keys.is_a?(Array)
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
@
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
class
|
313
|
-
def construct_sql
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
end
|
405
|
-
end
|
406
|
-
alias_method_chain :
|
407
|
-
|
408
|
-
|
409
|
-
|
1
|
+
module CompositePrimaryKeys
|
2
|
+
module ActiveRecord
|
3
|
+
module Associations
|
4
|
+
def self.append_features(base)
|
5
|
+
super
|
6
|
+
base.send(:extend, ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Composite key versions of Association functions
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def construct_counter_sql_with_included_associations(options, join_dependency)
|
13
|
+
scope = scope(:find)
|
14
|
+
sql = "SELECT COUNT(DISTINCT #{quoted_table_columns(primary_key)})"
|
15
|
+
|
16
|
+
# A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
|
17
|
+
if !self.connection.supports_count_distinct?
|
18
|
+
sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{quoted_table_columns(primary_key)}"
|
19
|
+
end
|
20
|
+
|
21
|
+
sql << " FROM #{quoted_table_name} "
|
22
|
+
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
23
|
+
|
24
|
+
add_joins!(sql, options, scope)
|
25
|
+
add_conditions!(sql, options[:conditions], scope)
|
26
|
+
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
27
|
+
|
28
|
+
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
29
|
+
|
30
|
+
if !self.connection.supports_count_distinct?
|
31
|
+
sql << ")"
|
32
|
+
end
|
33
|
+
|
34
|
+
return sanitize_sql(sql)
|
35
|
+
end
|
36
|
+
|
37
|
+
def construct_finder_sql_with_included_associations(options, join_dependency)
|
38
|
+
scope = scope(:find)
|
39
|
+
sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
|
40
|
+
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
41
|
+
|
42
|
+
add_joins!(sql, options, scope)
|
43
|
+
add_conditions!(sql, options[:conditions], scope)
|
44
|
+
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]
|
45
|
+
|
46
|
+
sql << "ORDER BY #{options[:order]} " if options[:order]
|
47
|
+
|
48
|
+
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
49
|
+
|
50
|
+
return sanitize_sql(sql)
|
51
|
+
end
|
52
|
+
|
53
|
+
def table_columns(columns)
|
54
|
+
columns.collect {|column| "#{self.quoted_table_name}.#{connection.quote_column_name(column)}"}
|
55
|
+
end
|
56
|
+
|
57
|
+
def quoted_table_columns(columns)
|
58
|
+
table_columns(columns).join(ID_SEP)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module ActiveRecord::Associations::ClassMethods
|
68
|
+
class JoinDependency
|
69
|
+
def construct_association(record, join, row)
|
70
|
+
case join.reflection.macro
|
71
|
+
when :has_many, :has_and_belongs_to_many
|
72
|
+
collection = record.send(join.reflection.name)
|
73
|
+
collection.loaded
|
74
|
+
|
75
|
+
join_aliased_primary_keys = join.active_record.composite? ?
|
76
|
+
join.aliased_primary_key : [join.aliased_primary_key]
|
77
|
+
return nil if
|
78
|
+
record.id.to_s != join.parent.record_id(row).to_s or not
|
79
|
+
join_aliased_primary_keys.select {|key| row[key].nil?}.blank?
|
80
|
+
association = join.instantiate(row)
|
81
|
+
collection.target.push(association) unless collection.target.include?(association)
|
82
|
+
when :has_one, :belongs_to
|
83
|
+
return if record.id.to_s != join.parent.record_id(row).to_s or
|
84
|
+
[*join.aliased_primary_key].any? { |key| row[key].nil? }
|
85
|
+
association = join.instantiate(row)
|
86
|
+
record.send("set_#{join.reflection.name}_target", association)
|
87
|
+
else
|
88
|
+
raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
|
89
|
+
end
|
90
|
+
return association
|
91
|
+
end
|
92
|
+
|
93
|
+
class JoinBase
|
94
|
+
def aliased_primary_key
|
95
|
+
active_record.composite? ?
|
96
|
+
primary_key.inject([]) {|aliased_keys, key| aliased_keys << "#{ aliased_prefix }_r#{aliased_keys.length}"} :
|
97
|
+
"#{ aliased_prefix }_r0"
|
98
|
+
end
|
99
|
+
|
100
|
+
def record_id(row)
|
101
|
+
active_record.composite? ?
|
102
|
+
aliased_primary_key.map {|key| row[key]}.to_composite_ids :
|
103
|
+
row[aliased_primary_key]
|
104
|
+
end
|
105
|
+
|
106
|
+
def column_names_with_alias
|
107
|
+
unless @column_names_with_alias
|
108
|
+
@column_names_with_alias = []
|
109
|
+
keys = active_record.composite? ? primary_key.map(&:to_s) : [primary_key]
|
110
|
+
(keys + (column_names - keys)).each_with_index do |column_name, i|
|
111
|
+
@column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
return @column_names_with_alias
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class JoinAssociation < JoinBase
|
119
|
+
alias single_association_join association_join
|
120
|
+
def association_join
|
121
|
+
reflection.active_record.composite? ? composite_association_join : single_association_join
|
122
|
+
end
|
123
|
+
|
124
|
+
def composite_association_join
|
125
|
+
join = case reflection.macro
|
126
|
+
when :has_and_belongs_to_many
|
127
|
+
" LEFT OUTER JOIN %s ON %s " % [
|
128
|
+
table_alias_for(options[:join_table], aliased_join_table_name),
|
129
|
+
composite_join_clause(
|
130
|
+
full_keys(aliased_join_table_name, options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key),
|
131
|
+
full_keys(reflection.active_record.table_name, reflection.active_record.primary_key)
|
132
|
+
)
|
133
|
+
] +
|
134
|
+
" LEFT OUTER JOIN %s ON %s " % [
|
135
|
+
table_name_and_alias,
|
136
|
+
composite_join_clause(
|
137
|
+
full_keys(aliased_table_name, klass.primary_key),
|
138
|
+
full_keys(aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key)
|
139
|
+
)
|
140
|
+
]
|
141
|
+
when :has_many, :has_one
|
142
|
+
case
|
143
|
+
when reflection.macro == :has_many && reflection.options[:through]
|
144
|
+
through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
|
145
|
+
if through_reflection.options[:as] # has_many :through against a polymorphic join
|
146
|
+
raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
|
147
|
+
else
|
148
|
+
if source_reflection.macro == :has_many && source_reflection.options[:as]
|
149
|
+
raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
|
150
|
+
else
|
151
|
+
case source_reflection.macro
|
152
|
+
when :belongs_to
|
153
|
+
first_key = primary_key
|
154
|
+
second_key = options[:foreign_key] || klass.to_s.classify.foreign_key
|
155
|
+
when :has_many
|
156
|
+
first_key = through_reflection.klass.to_s.classify.foreign_key
|
157
|
+
second_key = options[:foreign_key] || primary_key
|
158
|
+
end
|
159
|
+
|
160
|
+
" LEFT OUTER JOIN %s ON %s " % [
|
161
|
+
table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
|
162
|
+
composite_join_clause(
|
163
|
+
full_keys(aliased_join_table_name, through_reflection.primary_key_name),
|
164
|
+
full_keys(parent.aliased_table_name, parent.primary_key)
|
165
|
+
)
|
166
|
+
] +
|
167
|
+
" LEFT OUTER JOIN %s ON %s " % [
|
168
|
+
table_name_and_alias,
|
169
|
+
composite_join_clause(
|
170
|
+
full_keys(aliased_table_name, first_key),
|
171
|
+
full_keys(aliased_join_table_name, second_key)
|
172
|
+
)
|
173
|
+
]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
when reflection.macro == :has_many && reflection.options[:as]
|
178
|
+
raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
|
179
|
+
when reflection.macro == :has_one && reflection.options[:as]
|
180
|
+
raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
|
181
|
+
else
|
182
|
+
foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
|
183
|
+
" LEFT OUTER JOIN %s ON %s " % [
|
184
|
+
table_name_and_alias,
|
185
|
+
composite_join_clause(
|
186
|
+
full_keys(aliased_table_name, foreign_key),
|
187
|
+
full_keys(parent.aliased_table_name, parent.primary_key)),
|
188
|
+
]
|
189
|
+
end
|
190
|
+
when :belongs_to
|
191
|
+
" LEFT OUTER JOIN %s ON %s " % [
|
192
|
+
table_name_and_alias,
|
193
|
+
composite_join_clause(
|
194
|
+
full_keys(aliased_table_name, reflection.klass.primary_key),
|
195
|
+
full_keys(parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key)),
|
196
|
+
]
|
197
|
+
else
|
198
|
+
""
|
199
|
+
end || ''
|
200
|
+
join << %(AND %s.%s = %s ) % [
|
201
|
+
aliased_table_name,
|
202
|
+
reflection.active_record.connection.quote_column_name(reflection.active_record.inheritance_column),
|
203
|
+
klass.connection.quote(klass.name)] unless klass.descends_from_active_record?
|
204
|
+
join << "AND #{interpolate_sql(sanitize_sql(reflection.options[:conditions]))} " if reflection.options[:conditions]
|
205
|
+
join
|
206
|
+
end
|
207
|
+
|
208
|
+
def full_keys(table_name, keys)
|
209
|
+
connection = reflection.active_record.connection
|
210
|
+
quoted_table_name = connection.quote_table_name(table_name)
|
211
|
+
if keys.is_a?(Array)
|
212
|
+
keys.collect {|key| "#{quoted_table_name}.#{connection.quote_column_name(key)}"}.join(CompositePrimaryKeys::ID_SEP)
|
213
|
+
else
|
214
|
+
"#{quoted_table_name}.#{connection.quote_column_name(keys)}"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def composite_join_clause(full_keys1, full_keys2)
|
219
|
+
full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String)
|
220
|
+
full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String)
|
221
|
+
where_clause = [full_keys1, full_keys2].transpose.map do |key1, key2|
|
222
|
+
"#{key1}=#{key2}"
|
223
|
+
end.join(" AND ")
|
224
|
+
"(#{where_clause})"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
module ActiveRecord::Associations
|
231
|
+
class AssociationProxy #:nodoc:
|
232
|
+
|
233
|
+
def composite_where_clause(full_keys, ids)
|
234
|
+
full_keys = full_keys.split(CompositePrimaryKeys::ID_SEP) if full_keys.is_a?(String)
|
235
|
+
|
236
|
+
if ids.is_a?(String)
|
237
|
+
ids = [[ids]]
|
238
|
+
elsif not ids.first.is_a?(Array) # if single comp key passed, turn into an array of 1
|
239
|
+
ids = [ids.to_composite_ids]
|
240
|
+
end
|
241
|
+
|
242
|
+
where_clause = ids.map do |id_set|
|
243
|
+
transposed = id_set.size == 1 ? [[full_keys, id_set.first]] : [full_keys, id_set].transpose
|
244
|
+
transposed.map do |full_key, id|
|
245
|
+
"#{full_key.to_s}=#{@reflection.klass.sanitize(id)}"
|
246
|
+
end.join(" AND ")
|
247
|
+
end.join(") OR (")
|
248
|
+
|
249
|
+
"(#{where_clause})"
|
250
|
+
end
|
251
|
+
|
252
|
+
def composite_join_clause(full_keys1, full_keys2)
|
253
|
+
full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String)
|
254
|
+
full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String)
|
255
|
+
|
256
|
+
where_clause = [full_keys1, full_keys2].transpose.map do |key1, key2|
|
257
|
+
"#{key1}=#{key2}"
|
258
|
+
end.join(" AND ")
|
259
|
+
|
260
|
+
"(#{where_clause})"
|
261
|
+
end
|
262
|
+
|
263
|
+
def full_composite_join_clause(table1, full_keys1, table2, full_keys2)
|
264
|
+
connection = @reflection.active_record.connection
|
265
|
+
full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String)
|
266
|
+
full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String)
|
267
|
+
|
268
|
+
quoted1 = connection.quote_table_name(table1)
|
269
|
+
quoted2 = connection.quote_table_name(table2)
|
270
|
+
|
271
|
+
where_clause = [full_keys1, full_keys2].transpose.map do |key_pair|
|
272
|
+
"#{quoted1}.#{connection.quote_column_name(key_pair.first)}=#{quoted2}.#{connection.quote_column_name(key_pair.last)}"
|
273
|
+
end.join(" AND ")
|
274
|
+
|
275
|
+
"(#{where_clause})"
|
276
|
+
end
|
277
|
+
|
278
|
+
def full_keys(table_name, keys)
|
279
|
+
connection = @reflection.active_record.connection
|
280
|
+
quoted_table_name = connection.quote_table_name(table_name)
|
281
|
+
keys = keys.split(CompositePrimaryKeys::ID_SEP) if keys.is_a?(String)
|
282
|
+
if keys.is_a?(Array)
|
283
|
+
keys.collect {|key| "#{quoted_table_name}.#{connection.quote_column_name(key)}"}.join(CompositePrimaryKeys::ID_SEP)
|
284
|
+
else
|
285
|
+
"#{quoted_table_name}.#{connection.quote_column_name(keys)}"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def full_columns_equals(table_name, keys, quoted_ids)
|
290
|
+
connection = @reflection.active_record.connection
|
291
|
+
quoted_table_name = connection.quote_table_name(table_name)
|
292
|
+
if keys.is_a?(Symbol) or (keys.is_a?(String) and keys == keys.to_s.split(CompositePrimaryKeys::ID_SEP))
|
293
|
+
return "#{quoted_table_name}.#{connection.quote_column_name(keys)} = #{quoted_ids}"
|
294
|
+
end
|
295
|
+
keys = keys.split(CompositePrimaryKeys::ID_SEP) if keys.is_a?(String)
|
296
|
+
quoted_ids = quoted_ids.split(CompositePrimaryKeys::ID_SEP) if quoted_ids.is_a?(String)
|
297
|
+
keys_ids = [keys, quoted_ids].transpose
|
298
|
+
keys_ids.collect {|key, id| "(#{quoted_table_name}.#{connection.quote_column_name(key)} = #{id})"}.join(' AND ')
|
299
|
+
end
|
300
|
+
|
301
|
+
def set_belongs_to_association_for(record)
|
302
|
+
if @reflection.options[:as]
|
303
|
+
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
|
304
|
+
record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
|
305
|
+
else
|
306
|
+
key_values = @reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP).zip([@owner.id].flatten)
|
307
|
+
key_values.each{|key, value| record[key] = value} unless @owner.new_record?
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
313
|
+
def construct_sql
|
314
|
+
@reflection.options[:finder_sql] &&= interpolate_sql(@reflection.options[:finder_sql])
|
315
|
+
|
316
|
+
if @reflection.options[:finder_sql]
|
317
|
+
@finder_sql = @reflection.options[:finder_sql]
|
318
|
+
else
|
319
|
+
@finder_sql = full_columns_equals(@reflection.options[:join_table], @reflection.primary_key_name, @owner.quoted_id)
|
320
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
321
|
+
end
|
322
|
+
|
323
|
+
@join_sql = "INNER JOIN #{@reflection.active_record.connection.quote_table_name(@reflection.options[:join_table])} ON " +
|
324
|
+
full_composite_join_clause(@reflection.klass.table_name, @reflection.klass.primary_key, @reflection.options[:join_table], @reflection.association_foreign_key)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
class HasManyAssociation < AssociationCollection #:nodoc:
|
329
|
+
def construct_sql
|
330
|
+
case
|
331
|
+
when @reflection.options[:finder_sql]
|
332
|
+
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
333
|
+
|
334
|
+
when @reflection.options[:as]
|
335
|
+
@finder_sql =
|
336
|
+
"#{@reflection.klass.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
|
337
|
+
"#{@reflection.klass.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
338
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
339
|
+
|
340
|
+
else
|
341
|
+
@finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, @owner.quoted_id)
|
342
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
343
|
+
end
|
344
|
+
|
345
|
+
if @reflection.options[:counter_sql]
|
346
|
+
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
347
|
+
elsif @reflection.options[:finder_sql]
|
348
|
+
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
|
349
|
+
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
|
350
|
+
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
351
|
+
else
|
352
|
+
@counter_sql = @finder_sql
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def delete_records(records)
|
357
|
+
if @reflection.options[:dependent]
|
358
|
+
records.each { |r| r.destroy }
|
359
|
+
else
|
360
|
+
connection = @reflection.active_record.connection
|
361
|
+
field_names = @reflection.primary_key_name.split(',')
|
362
|
+
field_names.collect! {|n| connection.quote_column_name(n) + " = NULL"}
|
363
|
+
records.each do |r|
|
364
|
+
where_clause = nil
|
365
|
+
|
366
|
+
if r.quoted_id.to_s.include?(CompositePrimaryKeys::ID_SEP)
|
367
|
+
where_clause_terms = [@reflection.klass.primary_key, r.quoted_id].transpose.map do |pair|
|
368
|
+
"(#{connection.quote_column_name(pair[0])} = #{pair[1]})"
|
369
|
+
end
|
370
|
+
where_clause = where_clause_terms.join(" AND ")
|
371
|
+
else
|
372
|
+
where_clause = connection.quote_column_name(@reflection.klass.primary_key) + ' = ' + r.quoted_id
|
373
|
+
end
|
374
|
+
|
375
|
+
@reflection.klass.update_all( field_names.join(',') , where_clause)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
class HasOneAssociation < BelongsToAssociation #:nodoc:
|
382
|
+
def construct_sql
|
383
|
+
case
|
384
|
+
when @reflection.options[:as]
|
385
|
+
@finder_sql =
|
386
|
+
"#{@reflection.klass.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
|
387
|
+
"#{@reflection.klass.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
388
|
+
else
|
389
|
+
@finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, @owner.quoted_id)
|
390
|
+
end
|
391
|
+
|
392
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
|
397
|
+
def construct_conditions_with_composite_keys
|
398
|
+
if @reflection.through_reflection.options[:as]
|
399
|
+
construct_conditions_without_composite_keys
|
400
|
+
else
|
401
|
+
conditions = full_columns_equals(@reflection.through_reflection.table_name, @reflection.through_reflection.primary_key_name, @owner.quoted_id)
|
402
|
+
conditions << " AND (#{sql_conditions})" if sql_conditions
|
403
|
+
conditions
|
404
|
+
end
|
405
|
+
end
|
406
|
+
alias_method_chain :construct_conditions, :composite_keys
|
407
|
+
|
408
|
+
def construct_joins_with_composite_keys(custom_joins = nil)
|
409
|
+
if @reflection.through_reflection.options[:as] || @reflection.source_reflection.options[:as]
|
410
|
+
construct_joins_without_composite_keys(custom_joins)
|
411
|
+
else
|
412
|
+
if @reflection.source_reflection.macro == :belongs_to
|
413
|
+
reflection_primary_key = @reflection.klass.primary_key
|
414
|
+
source_primary_key = @reflection.source_reflection.primary_key_name
|
415
|
+
else
|
416
|
+
reflection_primary_key = @reflection.source_reflection.primary_key_name
|
417
|
+
source_primary_key = @reflection.klass.primary_key
|
418
|
+
end
|
419
|
+
|
420
|
+
"INNER JOIN %s ON %s #{@reflection.options[:joins]} #{custom_joins}" % [
|
421
|
+
@reflection.through_reflection.quoted_table_name,
|
422
|
+
composite_join_clause(full_keys(@reflection.table_name, reflection_primary_key), full_keys(@reflection.through_reflection.table_name, source_primary_key))
|
423
|
+
]
|
424
|
+
end
|
425
|
+
end
|
426
|
+
alias_method_chain :construct_joins, :composite_keys
|
427
|
+
end
|
428
|
+
end
|