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 +4 -0
- data/Manifest.txt +1 -0
- data/Rakefile +1 -1
- data/lib/composite_primary_keys.rb +1 -0
- data/lib/composite_primary_keys/association_preload.rb +220 -0
- data/lib/composite_primary_keys/associations.rb +1 -1
- data/lib/composite_primary_keys/base.rb +1 -0
- data/lib/composite_primary_keys/connection_adapters/oracle_adapter.rb +4 -0
- data/lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb +4 -0
- data/lib/composite_primary_keys/version.rb +3 -4
- data/test/test_composite_arrays.rb +5 -0
- data/tmp/test.db +0 -0
- data/website/index.html +2 -2
- data/website/version-raw.js +1 -1
- data/website/version.js +1 -1
- metadata +6 -5
data/History.txt
CHANGED
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', '
|
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 <
|
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
|
@@ -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.
|
36
|
+
<a href="http://rubyforge.org/projects/compositekeys" class="numbers">1.0.0</a>
|
37
37
|
</div>
|
38
38
|
<h1>→ 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
|
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>
|
data/website/version-raw.js
CHANGED
data/website/version.js
CHANGED
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.
|
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-
|
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
|
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.
|
184
|
+
rubygems_version: 1.0.1
|
184
185
|
signing_key:
|
185
186
|
specification_version: 2
|
186
187
|
summary: Composite key support for ActiveRecords
|