composite_primary_keys 0.9.93 → 1.0.0

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 CHANGED
@@ -1,3 +1,7 @@
1
+ == 1.0.0 2008-06-05
2
+
3
+ * Support for Rails 2.1
4
+
1
5
  == 0.9.93 2008-06-01
2
6
 
3
7
  * set fixed dependency on activerecord 2.0.2
data/Manifest.txt CHANGED
@@ -11,6 +11,7 @@ lib/adapter_helper/oracle.rb
11
11
  lib/adapter_helper/postgresql.rb
12
12
  lib/adapter_helper/sqlite3.rb
13
13
  lib/composite_primary_keys.rb
14
+ lib/composite_primary_keys/association_preload.rb
14
15
  lib/composite_primary_keys/associations.rb
15
16
  lib/composite_primary_keys/attribute_methods.rb
16
17
  lib/composite_primary_keys/base.rb
data/Rakefile CHANGED
@@ -52,7 +52,7 @@ hoe = Hoe.new(GEM_NAME, VERS) do |p|
52
52
 
53
53
  # == Optional
54
54
  p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
55
- p.extra_deps = [['activerecord', '= 2.0.2']] #An array of rubygem dependencies.
55
+ p.extra_deps = [['activerecord', '>= 2.1.0']] #An array of rubygem dependencies.
56
56
  #p.spec_extras - A hash of extra values to set in the gemspec.
57
57
  end
58
58
 
@@ -36,6 +36,7 @@ end
36
36
  require 'composite_primary_keys/fixtures'
37
37
  require 'composite_primary_keys/composite_arrays'
38
38
  require 'composite_primary_keys/associations'
39
+ require 'composite_primary_keys/association_preload'
39
40
  require 'composite_primary_keys/reflection'
40
41
  require 'composite_primary_keys/base'
41
42
  require 'composite_primary_keys/calculations'
@@ -0,0 +1,220 @@
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module AssociationPreload
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
+ def preload_has_and_belongs_to_many_association(records, reflection, preload_options={})
12
+ table_name = reflection.klass.quoted_table_name
13
+ id_to_record_map, ids = construct_id_map(records)
14
+ records.each {|record| record.send(reflection.name).loaded}
15
+ options = reflection.options
16
+
17
+ if(composite?)
18
+ primary_key = reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP)
19
+ where = (primary_key * ids.size).in_groups_of(primary_key.size).map do |keys|
20
+ "(" + keys.map{|key| "t0.#{key} = ?"}.join(" AND ") + ")"
21
+ end.join(" OR ")
22
+
23
+ conditions = [where, ids].flatten
24
+ joins = "INNER JOIN #{connection.quote_table_name options[:join_table]} as 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)
27
+ else
28
+ conditions = ["t0.#{reflection.primary_key_name} IN (?)", ids]
29
+ joins = "INNER JOIN #{connection.quote_table_name options[:join_table]} as t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}"
30
+ parent_record_id = reflection.primary_key_name
31
+ end
32
+
33
+ conditions.first << append_conditions(options, preload_options)
34
+
35
+ associated_records = reflection.klass.find(:all,
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
+
42
+ set_association_collection_records(id_to_record_map, reflection.name, associated_records, '_parent_record_id')
43
+ end
44
+
45
+ def preload_has_many_association(records, reflection, preload_options={})
46
+ id_to_record_map, ids = construct_id_map(records)
47
+ records.each {|record| record.send(reflection.name).loaded}
48
+ options = reflection.options
49
+
50
+ if options[:through]
51
+ through_records = preload_through_records(records, reflection, options[:through])
52
+ through_reflection = reflections[options[:through]]
53
+ through_primary_key = through_reflection.primary_key_name
54
+
55
+ unless through_records.empty?
56
+ source = reflection.source_reflection.name
57
+ #add conditions from reflection!
58
+ through_records.first.class.preload_associations(through_records, source, reflection.options)
59
+ through_records.each do |through_record|
60
+ add_preloaded_records_to_collection(id_to_record_map[through_record[through_primary_key].to_s], reflection.name, through_record.send(source))
61
+ end
62
+ end
63
+ else
64
+ associated_records = find_associated_records(ids, reflection, preload_options)
65
+ set_association_collection_records(id_to_record_map, reflection.name, associated_records, reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP))
66
+ end
67
+ end
68
+
69
+ def preload_through_records(records, reflection, through_association)
70
+ through_reflection = reflections[through_association]
71
+ through_primary_key = through_reflection.primary_key_name
72
+
73
+ if reflection.options[:source_type]
74
+ interface = reflection.source_reflection.options[:foreign_type]
75
+ preload_options = {:conditions => ["#{connection.quote_column_name interface} = ?", reflection.options[:source_type]]}
76
+
77
+ records.compact!
78
+ records.first.class.preload_associations(records, through_association, preload_options)
79
+
80
+ # Dont cache the association - we would only be caching a subset
81
+ through_records = []
82
+ records.each do |record|
83
+ proxy = record.send(through_association)
84
+
85
+ if proxy.respond_to?(:target)
86
+ through_records << proxy.target
87
+ proxy.reset
88
+ else # this is a has_one :through reflection
89
+ through_records << proxy if proxy
90
+ end
91
+ end
92
+ through_records.flatten!
93
+ else
94
+ records.first.class.preload_associations(records, through_association)
95
+ through_records = records.map {|record| record.send(through_association)}.flatten
96
+ end
97
+
98
+ through_records.compact!
99
+ through_records
100
+ end
101
+
102
+ def preload_belongs_to_association(records, reflection, preload_options={})
103
+ options = reflection.options
104
+ primary_key_name = reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP)
105
+
106
+ if options[:polymorphic]
107
+ raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
108
+ else
109
+ id_map = {}
110
+ records.each do |record|
111
+ key = primary_key_name.map{|k| record.send(k)}.join(CompositePrimaryKeys::ID_SEP)
112
+ if key
113
+ mapped_records = (id_map[key.to_s] ||= [])
114
+ mapped_records << record
115
+ end
116
+ end
117
+ klasses_and_ids = [[reflection.klass.name, id_map]]
118
+ end
119
+
120
+ klasses_and_ids.each do |klass_and_id|
121
+ klass_name, id_map = *klass_and_id
122
+ klass = klass_name.constantize
123
+ table_name = klass.quoted_table_name
124
+
125
+ if(composite?)
126
+ primary_key = klass.primary_key.to_s.split(CompositePrimaryKeys::ID_SEP)
127
+ ids = id_map.keys.uniq.map {|id| id.to_s.split(CompositePrimaryKeys::ID_SEP)}
128
+
129
+ where = (primary_key * ids.size).in_groups_of(primary_key.size).map do |keys|
130
+ "(" + keys.map{|key| "#{table_name}.#{key} = ?"}.join(" AND ") + ")"
131
+ end.join(" OR ")
132
+
133
+ conditions = [where, ids].flatten
134
+ else
135
+ conditions = ["#{table_name}.#{primary_key} IN (?)", id_map.keys.uniq]
136
+ end
137
+
138
+ conditions.first << append_conditions(options, preload_options)
139
+
140
+ associated_records = klass.find(:all, :conditions => conditions,
141
+ :include => options[:include],
142
+ :select => options[:select],
143
+ :joins => options[:joins],
144
+ :order => options[:order])
145
+
146
+ set_association_single_records(id_map, reflection.name, associated_records, primary_key)
147
+ end
148
+ end
149
+
150
+ def set_association_collection_records(id_to_record_map, reflection_name, associated_records, key)
151
+ associated_records.each do |associated_record|
152
+ associated_record_key = associated_record[key]
153
+ associated_record_key = associated_record_key.is_a?(Array) ? associated_record_key.join(CompositePrimaryKeys::ID_SEP) : associated_record_key.to_s
154
+ mapped_records = id_to_record_map[associated_record_key]
155
+ add_preloaded_records_to_collection(mapped_records, reflection_name, associated_record)
156
+ end
157
+ end
158
+
159
+ def set_association_single_records(id_to_record_map, reflection_name, associated_records, key)
160
+ seen_keys = {}
161
+ associated_records.each do |associated_record|
162
+ associated_record_key = associated_record[key]
163
+ associated_record_key = associated_record_key.is_a?(Array) ? associated_record_key.join(CompositePrimaryKeys::ID_SEP) : associated_record_key.to_s
164
+
165
+ #this is a has_one or belongs_to: there should only be one record.
166
+ #Unfortunately we can't (in portable way) ask the database for 'all records where foo_id in (x,y,z), but please
167
+ # only one row per distinct foo_id' so this where we enforce that
168
+ next if seen_keys[associated_record_key]
169
+ seen_keys[associated_record_key] = true
170
+ mapped_records = id_to_record_map[associated_record_key]
171
+ mapped_records.each do |mapped_record|
172
+ mapped_record.send("set_#{reflection_name}_target", associated_record)
173
+ end
174
+ end
175
+ end
176
+
177
+ def find_associated_records(ids, reflection, preload_options)
178
+ options = reflection.options
179
+ table_name = reflection.klass.quoted_table_name
180
+
181
+ if interface = reflection.options[:as]
182
+ raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
183
+ else
184
+ foreign_key = reflection.primary_key_name
185
+ conditions = ["#{reflection.klass.quoted_table_name}.#{foreign_key} IN (?)", ids]
186
+
187
+ if(composite?)
188
+ foreign_keys = foreign_key.to_s.split(CompositePrimaryKeys::ID_SEP)
189
+
190
+ where = (foreign_keys * ids.size).in_groups_of(foreign_keys.size).map do |keys|
191
+ "(" + keys.map{|key| "#{reflection.klass.quoted_table_name}.#{key} = ?"}.join(" AND ") + ")"
192
+ end.join(" OR ")
193
+
194
+ conditions = [where, ids].flatten
195
+ end
196
+ end
197
+
198
+ conditions.first << append_conditions(options, preload_options)
199
+
200
+ reflection.klass.find(:all,
201
+ :select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
202
+ :include => preload_options[:include] || options[:include],
203
+ :conditions => conditions,
204
+ :joins => options[:joins],
205
+ :group => preload_options[:group] || options[:group],
206
+ :order => preload_options[:order] || options[:order])
207
+ end
208
+
209
+ def full_composite_join_clause(table1, full_keys1, table2, full_keys2)
210
+ full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String)
211
+ full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String)
212
+ where_clause = [full_keys1, full_keys2].transpose.map do |key_pair|
213
+ "#{table1}.#{key_pair.first}=#{table2}.#{key_pair.last}"
214
+ end.join(" AND ")
215
+ "(#{where_clause})"
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
@@ -362,7 +362,7 @@ module ActiveRecord::Associations
362
362
  end
363
363
  end
364
364
 
365
- class HasManyThroughAssociation < AssociationProxy #:nodoc:
365
+ class HasManyThroughAssociation < HasManyAssociation #:nodoc:
366
366
  def construct_conditions
367
367
  conditions = if @reflection.through_reflection.options[:as]
368
368
  "#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.options[:as]}_id = #{@owner.quoted_id} " +
@@ -26,6 +26,7 @@ module CompositePrimaryKeys
26
26
  include CompositeInstanceMethods
27
27
 
28
28
  include CompositePrimaryKeys::ActiveRecord::Associations
29
+ include CompositePrimaryKeys::ActiveRecord::AssociationPreload
29
30
  include CompositePrimaryKeys::ActiveRecord::Calculations
30
31
  include CompositePrimaryKeys::ActiveRecord::AttributeMethods
31
32
  EOV
@@ -6,6 +6,10 @@ module ActiveRecord
6
6
  def supports_count_distinct? #:nodoc:
7
7
  false
8
8
  end
9
+
10
+ def concat(*columns)
11
+ "(#{columns.join('||')})"
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -6,6 +6,10 @@ module ActiveRecord
6
6
  def supports_count_distinct? #:nodoc:
7
7
  false
8
8
  end
9
+
10
+ def concat(*columns)
11
+ "(#{columns.join('||')})"
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -1,9 +1,8 @@
1
1
  module CompositePrimaryKeys
2
2
  module VERSION #:nodoc:
3
- MAJOR = 0
4
- MINOR = 9
5
- TINY = 93
6
-
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 0
7
6
  STRING = [MAJOR, MINOR, TINY].join('.')
8
7
  end
9
8
  end
@@ -43,4 +43,9 @@ class CompositeArraysTest < Test::Unit::TestCase
43
43
  assert_equal CompositePrimaryKeys::CompositeIds, keys.class
44
44
  assert_equal '1,2,3', keys.to_s
45
45
  end
46
+
47
+ def test_flatten
48
+ keys = [CompositePrimaryKeys::CompositeIds.new([1,2,3]), CompositePrimaryKeys::CompositeIds.new([4,5,6])]
49
+ assert_equal 6, keys.flatten.size
50
+ end
46
51
  end
data/tmp/test.db CHANGED
Binary file
data/website/index.html CHANGED
@@ -33,7 +33,7 @@
33
33
  <h1>Composite Primary Keys</h1>
34
34
  <div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/compositekeys"; return false'>
35
35
  Get Version
36
- <a href="http://rubyforge.org/projects/compositekeys" class="numbers">0.9.92</a>
36
+ <a href="http://rubyforge.org/projects/compositekeys" class="numbers">1.0.0</a>
37
37
  </div>
38
38
  <h1>&#x2192; Ruby on Rails</h1>
39
39
 
@@ -296,7 +296,7 @@ other stories and things.</p>
296
296
 
297
297
  <p>Comments are welcome. Send an email to <a href="mailto:drnicwilliams@gmail.com">Dr Nic Williams</a>.</p>
298
298
  <p class="coda">
299
- <a href="mailto:drnicwilliams@gmail.com">Dr Nic</a>, 5th March 2008<br>
299
+ <a href="mailto:drnicwilliams@gmail.com">Dr Nic</a>, 5th June 2008<br>
300
300
  Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
301
301
  </p>
302
302
  </div>
@@ -1,3 +1,3 @@
1
1
  // Announcement JS file
2
- var version = "0.9.92";
2
+ var version = "1.0.0";
3
3
  MagicAnnouncement.show('compositekeys', version);
data/website/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // Version JS file
2
- var version = "0.9.92";
2
+ var version = "1.0.0";
3
3
 
4
4
  document.write(" - " + version);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: composite_primary_keys
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.93
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dr Nic Williams
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-06-04 00:00:00 -05:00
12
+ date: 2008-06-05 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -17,9 +17,9 @@ dependencies:
17
17
  version_requirement:
18
18
  version_requirements: !ruby/object:Gem::Requirement
19
19
  requirements:
20
- - - "="
20
+ - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 2.0.2
22
+ version: 2.1.0
23
23
  version:
24
24
  description: Composite key support for ActiveRecords
25
25
  email: drnicwilliams@gmail.com
@@ -50,6 +50,7 @@ files:
50
50
  - lib/adapter_helper/postgresql.rb
51
51
  - lib/adapter_helper/sqlite3.rb
52
52
  - lib/composite_primary_keys.rb
53
+ - lib/composite_primary_keys/association_preload.rb
53
54
  - lib/composite_primary_keys/associations.rb
54
55
  - lib/composite_primary_keys/attribute_methods.rb
55
56
  - lib/composite_primary_keys/base.rb
@@ -180,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
181
  requirements: []
181
182
 
182
183
  rubyforge_project: compositekeys
183
- rubygems_version: 1.1.1
184
+ rubygems_version: 1.0.1
184
185
  signing_key:
185
186
  specification_version: 2
186
187
  summary: Composite key support for ActiveRecords