composite_primary_keys 0.9.93 → 1.0.0

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