composite_primary_keys 1.0.8 → 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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(composite?)
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.quoted_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.#{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 => options[:include],
38
- :joins => joins,
39
- :select => "#{options[:select] || table_name+'.*'}, #{parent_record_id} as parent_record_id_",
40
- :order => options[: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
- # 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...]}}
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
- if(composite?)
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, :conditions => conditions,
151
- :include => options[:include],
152
- :select => options[:select],
153
- :joins => options[:joins],
154
- :order => options[:order])
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 = ["#{reflection.klass.quoted_table_name}.#{foreign_key} IN (?)", ids]
198
+ conditions = ["#{table_name}.#{connection.quote_column_name(foreign_key)} IN (?)", ids]
196
199
 
197
- if(composite?)
200
+ if composite?
198
201
  foreign_keys = foreign_key.to_s.split(CompositePrimaryKeys::ID_SEP)
199
202
 
200
- where = (foreign_keys * ids.size).in_groups_of(foreign_keys.size).map do |keys|
201
- "(" + keys.map{|key| "#{reflection.klass.quoted_table_name}.#{key} = ?"}.join(" AND ") + ")"
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
- end
207
-
210
+
208
211
  conditions.first << append_conditions(options, preload_options)
209
212
 
210
213
  reflection.klass.find(:all,
211
- :select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
212
- :include => preload_options[:include] || options[:include],
213
- :conditions => conditions,
214
- :joins => options[:joins],
215
- :group => preload_options[:group] || options[:group],
216
- :order => preload_options[:order] || options[:order])
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
- "#{table1}.#{key_pair.first}=#{table2}.#{key_pair.last}"
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 #{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] || 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.table_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.to_a.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? ?
122
- composite_association_join :
123
- single_association_join
124
- end
125
-
126
- def composite_association_join
127
- join = case reflection.macro
128
- when :has_and_belongs_to_many
129
- " LEFT OUTER JOIN %s ON %s " % [
130
- table_alias_for(options[:join_table], aliased_join_table_name),
131
- composite_join_clause(
132
- full_keys(aliased_join_table_name, options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key),
133
- full_keys(reflection.active_record.table_name, reflection.active_record.primary_key)
134
- )
135
- ] +
136
- " LEFT OUTER JOIN %s ON %s " % [
137
- table_name_and_alias,
138
- composite_join_clause(
139
- full_keys(aliased_table_name, klass.primary_key),
140
- full_keys(aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key)
141
- )
142
- ]
143
- when :has_many, :has_one
144
- case
145
- when reflection.macro == :has_many && reflection.options[:through]
146
- through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
147
- if through_reflection.options[:as] # has_many :through against a polymorphic join
148
- raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
149
- else
150
- if source_reflection.macro == :has_many && source_reflection.options[:as]
151
- raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
152
- else
153
- case source_reflection.macro
154
- when :belongs_to
155
- first_key = primary_key
156
- second_key = options[:foreign_key] || klass.to_s.classify.foreign_key
157
- when :has_many
158
- first_key = through_reflection.klass.to_s.classify.foreign_key
159
- second_key = options[:foreign_key] || primary_key
160
- end
161
-
162
- " LEFT OUTER JOIN %s ON %s " % [
163
- table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
164
- composite_join_clause(
165
- full_keys(aliased_join_table_name, through_reflection.primary_key_name),
166
- full_keys(parent.aliased_table_name, parent.primary_key)
167
- )
168
- ] +
169
- " LEFT OUTER JOIN %s ON %s " % [
170
- table_name_and_alias,
171
- composite_join_clause(
172
- full_keys(aliased_table_name, first_key),
173
- full_keys(aliased_join_table_name, second_key)
174
- )
175
- ]
176
- end
177
- end
178
-
179
- when reflection.macro == :has_many && reflection.options[:as]
180
- raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
181
- when reflection.macro == :has_one && reflection.options[:as]
182
- raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
183
- else
184
- foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
185
- " LEFT OUTER JOIN %s ON %s " % [
186
- table_name_and_alias,
187
- composite_join_clause(
188
- full_keys(aliased_table_name, foreign_key),
189
- full_keys(parent.aliased_table_name, parent.primary_key)),
190
- ]
191
- end
192
- when :belongs_to
193
- " LEFT OUTER JOIN %s ON %s " % [
194
- table_name_and_alias,
195
- composite_join_clause(
196
- full_keys(aliased_table_name, reflection.klass.primary_key),
197
- full_keys(parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key)),
198
- ]
199
- else
200
- ""
201
- end || ''
202
- join << %(AND %s.%s = %s ) % [
203
- aliased_table_name,
204
- reflection.active_record.connection.quote_column_name(reflection.active_record.inheritance_column),
205
- klass.connection.quote(klass.name)] unless klass.descends_from_active_record?
206
- join << "AND #{interpolate_sql(sanitize_sql(reflection.options[:conditions]))} " if reflection.options[:conditions]
207
- join
208
- end
209
-
210
- def full_keys(table_name, keys)
211
- keys.is_a?(Array) ? keys.collect {|key| "#{table_name}.#{key}"}.join(CompositePrimaryKeys::ID_SEP) : "#{table_name}.#{keys}"
212
- end
213
-
214
- def composite_join_clause(full_keys1, full_keys2)
215
- full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String)
216
- full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String)
217
- where_clause = [full_keys1, full_keys2].transpose.map do |key_pair|
218
- "#{key_pair.first}=#{key_pair.last}"
219
- end.join(" AND ")
220
- "(#{where_clause})"
221
- end
222
- end
223
- end
224
- end
225
-
226
- module ActiveRecord::Associations
227
- class AssociationProxy #:nodoc:
228
-
229
- def composite_where_clause(full_keys, ids)
230
- full_keys = full_keys.split(CompositePrimaryKeys::ID_SEP) if full_keys.is_a?(String)
231
-
232
- if ids.is_a?(String)
233
- ids = [[ids]]
234
- elsif not ids.first.is_a?(Array) # if single comp key passed, turn into an array of 1
235
- ids = [ids.to_composite_ids]
236
- end
237
-
238
- where_clause = ids.map do |id_set|
239
- transposed = id_set.size == 1 ? [[full_keys, id_set.first]] : [full_keys, id_set].transpose
240
- transposed.map do |full_key, id|
241
- "#{full_key.to_s}=#{@reflection.klass.sanitize(id)}"
242
- end.join(" AND ")
243
- end.join(") OR (")
244
-
245
- "(#{where_clause})"
246
- end
247
-
248
- def composite_join_clause(full_keys1, full_keys2)
249
- full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String)
250
- full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String)
251
-
252
- where_clause = [full_keys1, full_keys2].transpose.map do |key_pair|
253
- "#{key_pair.first}=#{key_pair.last}"
254
- end.join(" AND ")
255
-
256
- "(#{where_clause})"
257
- end
258
-
259
- def full_composite_join_clause(table1, full_keys1, table2, full_keys2)
260
- full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String)
261
- full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String)
262
-
263
- where_clause = [full_keys1, full_keys2].transpose.map do |key_pair|
264
- "#{table1}.#{key_pair.first}=#{table2}.#{key_pair.last}"
265
- end.join(" AND ")
266
-
267
- "(#{where_clause})"
268
- end
269
-
270
- def full_keys(table_name, keys)
271
- keys = keys.split(CompositePrimaryKeys::ID_SEP) if keys.is_a?(String)
272
- keys.is_a?(Array) ? keys.collect {|key| "#{table_name}.#{key}"}.join(CompositePrimaryKeys::ID_SEP) : "#{table_name}.#{keys}"
273
- end
274
-
275
- def full_columns_equals(table_name, keys, quoted_ids)
276
- if keys.is_a?(Symbol) or (keys.is_a?(String) and keys == keys.to_s.split(CompositePrimaryKeys::ID_SEP))
277
- return "#{table_name}.#{keys} = #{quoted_ids}"
278
- end
279
- keys = keys.split(CompositePrimaryKeys::ID_SEP) if keys.is_a?(String)
280
- quoted_ids = quoted_ids.split(CompositePrimaryKeys::ID_SEP) if quoted_ids.is_a?(String)
281
- keys_ids = [keys, quoted_ids].transpose
282
- keys_ids.collect {|key, id| "(#{table_name}.#{key} = #{id})"}.join(' AND ')
283
- end
284
-
285
- def set_belongs_to_association_for(record)
286
- if @reflection.options[:as]
287
- record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
288
- record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
289
- else
290
- key_values = @reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP).zip([@owner.id].flatten)
291
- key_values.each{|kv| record[kv.first] = kv.last} unless @owner.new_record?
292
- end
293
- end
294
- end
295
-
296
- class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
297
- def construct_sql
298
- interpolate_sql_options!(@reflection.options, :finder_sql)
299
-
300
- if @reflection.options[:finder_sql]
301
- @finder_sql = @reflection.options[:finder_sql]
302
- else
303
- @finder_sql = full_columns_equals(@reflection.options[:join_table], @reflection.primary_key_name, @owner.quoted_id)
304
- @finder_sql << " AND (#{conditions})" if conditions
305
- end
306
-
307
- @join_sql = "INNER JOIN #{@reflection.options[:join_table]} ON " +
308
- full_composite_join_clause(@reflection.klass.table_name, @reflection.klass.primary_key, @reflection.options[:join_table], @reflection.association_foreign_key)
309
- end
310
- end
311
-
312
- class HasManyAssociation < AssociationCollection #:nodoc:
313
- def construct_sql
314
- case
315
- when @reflection.options[:finder_sql]
316
- @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
317
-
318
- when @reflection.options[:as]
319
- @finder_sql =
320
- "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
321
- "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
322
- @finder_sql << " AND (#{conditions})" if conditions
323
-
324
- else
325
- @finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, @owner.quoted_id)
326
- @finder_sql << " AND (#{conditions})" if conditions
327
- end
328
-
329
- if @reflection.options[:counter_sql]
330
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
331
- elsif @reflection.options[:finder_sql]
332
- # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
333
- @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
334
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
335
- else
336
- @counter_sql = @finder_sql
337
- end
338
- end
339
-
340
- def delete_records(records)
341
- if @reflection.options[:dependent]
342
- records.each { |r| r.destroy }
343
- else
344
- field_names = @reflection.primary_key_name.split(',')
345
- field_names.collect! {|n| n + " = NULL"}
346
- records.each do |r|
347
- where_class = nil
348
-
349
- if r.quoted_id.include?(',')
350
- where_class = [@reflection.klass.primary_key, r.quoted_id].transpose.map {|pair| "(#{pair[0]} = #{pair[1]})"}.join(" AND ")
351
- else
352
- where_class = @reflection.klass.primary_key + ' = ' + r.quoted_id
353
- end
354
-
355
- @reflection.klass.update_all( field_names.join(',') , where_class)
356
- end
357
- end
358
- end
359
- end
360
-
361
- class HasOneAssociation < BelongsToAssociation #:nodoc:
362
- def construct_sql
363
- case
364
- when @reflection.options[:as]
365
- @finder_sql =
366
- "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
367
- "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
368
- else
369
- @finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, @owner.quoted_id)
370
- end
371
-
372
- @finder_sql << " AND (#{conditions})" if conditions
373
- end
374
- end
375
-
376
- class HasManyThroughAssociation < HasManyAssociation #:nodoc:
377
- def construct_conditions_with_composite_keys
378
- if @reflection.through_reflection.options[:as]
379
- construct_conditions_without_composite_keys;
380
- else
381
- conditions = full_columns_equals(@reflection.through_reflection.table_name, @reflection.through_reflection.primary_key_name, @owner.quoted_id)
382
- conditions << " AND (#{sql_conditions})" if sql_conditions
383
- conditions
384
- end
385
- end
386
- alias_method_chain :construct_conditions, :composite_keys
387
-
388
- def construct_joins_with_composite_keys(custom_joins = nil)
389
- if @reflection.through_reflection.options[:as] || @reflection.source_reflection.options[:as]
390
- construct_joins_without_composite_keys(custom_joins)
391
- else
392
- if @reflection.source_reflection.macro == :belongs_to
393
- reflection_primary_key = @reflection.klass.primary_key
394
- source_primary_key = @reflection.source_reflection.primary_key_name
395
- else
396
- reflection_primary_key = @reflection.source_reflection.primary_key_name
397
- source_primary_key = @reflection.klass.primary_key
398
- end
399
-
400
- "INNER JOIN %s ON %s #{@reflection.options[:joins]} #{custom_joins}" % [
401
- @reflection.through_reflection.table_name,
402
- composite_join_clause(full_keys(@reflection.table_name, reflection_primary_key), full_keys(@reflection.through_reflection.table_name, source_primary_key))
403
- ]
404
- end
405
- end
406
- alias_method_chain :construct_joins, :composite_keys
407
- end
408
-
409
- end
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