composite_primary_keys 13.0.2 → 14.0.3
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.
- checksums.yaml +4 -4
- data/History.rdoc +19 -0
- data/README.rdoc +1 -0
- data/lib/composite_primary_keys/associations/join_association.rb +137 -137
- data/lib/composite_primary_keys/associations/preloader/association.rb +15 -0
- data/lib/composite_primary_keys/associations/through_association.rb +24 -25
- data/lib/composite_primary_keys/base.rb +137 -141
- data/lib/composite_primary_keys/composite_predicates.rb +1 -0
- data/lib/composite_primary_keys/persistence.rb +21 -7
- data/lib/composite_primary_keys/relation/calculations.rb +7 -1
- data/lib/composite_primary_keys/version.rb +2 -2
- data/lib/composite_primary_keys.rb +1 -1
- data/test/abstract_unit.rb +5 -1
- data/test/test_associations.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9cb00a73781e630f40a83391449a048e05a28e6c93ab41b71af55ad7d0e5aa1
|
4
|
+
data.tar.gz: ef9cdf02f59b135080e2eefa621d35764a7a4002904aaa066031675b2bf05873
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 422d8d5bc43d9b477e2d8dee49235580ff532c961ce1947ac8a09e47b13b2e6eaa484e0ee8045f4fc9bdebf1c0ffb50ca197e243a4d8e93c43909d16640a8433
|
7
|
+
data.tar.gz: 7cc5b56bbb3749d9edf5aef12d4e231e07496eb5f76a605cb7de436b99958a7b6a9aaab9160bdf51b43404b15175a7c93ee02ce6d00d5140e92a68bd7b42f0cc
|
data/History.rdoc
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
== 14.0.3 (2022-01-09)
|
2
|
+
* Remove override on ActiveRecord::Base#to_param. That method has moved to Integration
|
3
|
+
so no longer works. #541. (Charlie Savage)
|
4
|
+
* Check if an assocation is polymorhpic. Fixes #558.
|
5
|
+
|
6
|
+
== 14.0.2 (2022-01-09)
|
7
|
+
* Fix scoped associations take #2 (Charlie Savage)
|
8
|
+
|
9
|
+
== 14.0.1 (2022-01-9)
|
10
|
+
* Fix mistake in Gemfile (Charlie Savage)
|
11
|
+
|
12
|
+
== 14.0.0 (2022-01-9)
|
13
|
+
* Update to ActiveRecord 7.0 (Sammy Larbi)
|
14
|
+
|
15
|
+
== 13.0.3 (2022-01-09)
|
16
|
+
* Remove override on ActiveRecord::Base#to_param. That method has moved to Integration
|
17
|
+
so no longer works. #541. (Charlie Savage)
|
18
|
+
* Check if an assocation is polymorhpic. Fixes #558.
|
19
|
+
|
1
20
|
== 13.0.2 (2022-01-09)
|
2
21
|
* Fix scoped associations take #2 (Charlie Savage)
|
3
22
|
|
data/README.rdoc
CHANGED
@@ -20,6 +20,7 @@ Every major version of ActiveRecord has included numerous internal changes. As
|
|
20
20
|
CPK has to be rewritten for each version of ActiveRecord. To help keep
|
21
21
|
things straight, here is the mapping:
|
22
22
|
|
23
|
+
Version 14.x is designed to work with ActiveRecord 7.0.x
|
23
24
|
Version 13.x is designed to work with ActiveRecord 6.1.x
|
24
25
|
Version 12.x is designed to work with ActiveRecord 6.0.x
|
25
26
|
Version 11.x is designed to work with ActiveRecord 5.2.x
|
@@ -1,137 +1,137 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Associations
|
3
|
-
class JoinDependency
|
4
|
-
|
5
|
-
class JoinAssociation < JoinPart # :nodoc:
|
6
|
-
private
|
7
|
-
def append_constraints(join, constraints)
|
8
|
-
if join.is_a?(Arel::Nodes::StringJoin)
|
9
|
-
join_string = Arel::Nodes::And.new(constraints.unshift join.left)
|
10
|
-
join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
|
11
|
-
else
|
12
|
-
right = join.right
|
13
|
-
# CPK
|
14
|
-
if right.expr.is_a?(Arel::Nodes::And) && right.expr.children.empty?
|
15
|
-
right.expr = Arel::Nodes::And.new(constraints)
|
16
|
-
else
|
17
|
-
right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class Aliases # :nodoc:
|
24
|
-
def column_alias(node, column)
|
25
|
-
# CPK
|
26
|
-
#@alias_cache[node][column]
|
27
|
-
if column.kind_of?(Array)
|
28
|
-
column.map do |a_column|
|
29
|
-
@alias_cache[node][a_column]
|
30
|
-
end
|
31
|
-
else
|
32
|
-
@alias_cache[node][column]
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def instantiate(result_set, strict_loading_value, &block)
|
38
|
-
primary_key = aliases.column_alias(join_root, join_root.primary_key)
|
39
|
-
|
40
|
-
seen = Hash.new { |i, parent|
|
41
|
-
i[parent] = Hash.new { |j, child_class|
|
42
|
-
j[child_class] = {}
|
43
|
-
}
|
44
|
-
}.compare_by_identity
|
45
|
-
|
46
|
-
model_cache = Hash.new { |h, klass| h[klass] = {} }
|
47
|
-
parents = model_cache[join_root]
|
48
|
-
|
49
|
-
column_aliases = aliases.column_aliases(join_root)
|
50
|
-
column_names = []
|
51
|
-
|
52
|
-
result_set.columns.each do |name|
|
53
|
-
column_names << name unless /\At\d+_r\d+\z/.match?(name)
|
54
|
-
end
|
55
|
-
|
56
|
-
if column_names.empty?
|
57
|
-
column_types = {}
|
58
|
-
else
|
59
|
-
column_types = result_set.column_types
|
60
|
-
unless column_types.empty?
|
61
|
-
attribute_types = join_root.attribute_types
|
62
|
-
column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
|
63
|
-
end
|
64
|
-
column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
|
65
|
-
end
|
66
|
-
|
67
|
-
message_bus = ActiveSupport::Notifications.instrumenter
|
68
|
-
|
69
|
-
payload = {
|
70
|
-
record_count: result_set.length,
|
71
|
-
class_name: join_root.base_klass.name
|
72
|
-
}
|
73
|
-
|
74
|
-
message_bus.instrument("instantiation.active_record", payload) do
|
75
|
-
result_set.each { |row_hash|
|
76
|
-
# CPK
|
77
|
-
# parent_key = primary_key ? row_hash[primary_key] : row_hash
|
78
|
-
parent_key = if primary_key.kind_of?(Array)
|
79
|
-
primary_key.map {|key| row_hash[key]}
|
80
|
-
else
|
81
|
-
primary_key ? row_hash[primary_key] : row_hash
|
82
|
-
end
|
83
|
-
|
84
|
-
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
|
85
|
-
construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
|
86
|
-
}
|
87
|
-
end
|
88
|
-
|
89
|
-
parents.values
|
90
|
-
end
|
91
|
-
|
92
|
-
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
|
93
|
-
return if ar_parent.nil?
|
94
|
-
|
95
|
-
parent.children.each do |node|
|
96
|
-
if node.reflection.collection?
|
97
|
-
other = ar_parent.association(node.reflection.name)
|
98
|
-
other.loaded!
|
99
|
-
elsif ar_parent.association_cached?(node.reflection.name)
|
100
|
-
model = ar_parent.association(node.reflection.name).target
|
101
|
-
construct(model, node, row, seen, model_cache, strict_loading_value)
|
102
|
-
next
|
103
|
-
end
|
104
|
-
|
105
|
-
key = aliases.column_alias(node, node.primary_key)
|
106
|
-
# CPK
|
107
|
-
if key.is_a?(Array)
|
108
|
-
id = Array(key).map do |column_alias|
|
109
|
-
row[column_alias]
|
110
|
-
end
|
111
|
-
# At least the first value in the key has to be set. Should we require all values to be set?
|
112
|
-
id = nil if id.first.nil?
|
113
|
-
else # original
|
114
|
-
id = row[key]
|
115
|
-
end
|
116
|
-
|
117
|
-
if id.nil?
|
118
|
-
nil_association = ar_parent.association(node.reflection.name)
|
119
|
-
nil_association.loaded!
|
120
|
-
next
|
121
|
-
end
|
122
|
-
|
123
|
-
model = seen[ar_parent][node][id]
|
124
|
-
|
125
|
-
if model
|
126
|
-
construct(model, node, row, seen, model_cache, strict_loading_value)
|
127
|
-
else
|
128
|
-
model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
|
129
|
-
|
130
|
-
seen[ar_parent][node][id] = model
|
131
|
-
construct(model, node, row, seen, model_cache, strict_loading_value)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency
|
4
|
+
|
5
|
+
class JoinAssociation < JoinPart # :nodoc:
|
6
|
+
private
|
7
|
+
def append_constraints(join, constraints)
|
8
|
+
if join.is_a?(Arel::Nodes::StringJoin)
|
9
|
+
join_string = Arel::Nodes::And.new(constraints.unshift join.left)
|
10
|
+
join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
|
11
|
+
else
|
12
|
+
right = join.right
|
13
|
+
# CPK
|
14
|
+
if right.expr.is_a?(Arel::Nodes::And) && right.expr.children.empty?
|
15
|
+
right.expr = Arel::Nodes::And.new(constraints)
|
16
|
+
else
|
17
|
+
right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Aliases # :nodoc:
|
24
|
+
def column_alias(node, column)
|
25
|
+
# CPK
|
26
|
+
#@alias_cache[node][column]
|
27
|
+
if column.kind_of?(Array)
|
28
|
+
column.map do |a_column|
|
29
|
+
@alias_cache[node][a_column]
|
30
|
+
end
|
31
|
+
else
|
32
|
+
@alias_cache[node][column]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def instantiate(result_set, strict_loading_value, &block)
|
38
|
+
primary_key = aliases.column_alias(join_root, join_root.primary_key)
|
39
|
+
|
40
|
+
seen = Hash.new { |i, parent|
|
41
|
+
i[parent] = Hash.new { |j, child_class|
|
42
|
+
j[child_class] = {}
|
43
|
+
}
|
44
|
+
}.compare_by_identity
|
45
|
+
|
46
|
+
model_cache = Hash.new { |h, klass| h[klass] = {} }
|
47
|
+
parents = model_cache[join_root]
|
48
|
+
|
49
|
+
column_aliases = aliases.column_aliases(join_root)
|
50
|
+
column_names = []
|
51
|
+
|
52
|
+
result_set.columns.each do |name|
|
53
|
+
column_names << name unless /\At\d+_r\d+\z/.match?(name)
|
54
|
+
end
|
55
|
+
|
56
|
+
if column_names.empty?
|
57
|
+
column_types = {}
|
58
|
+
else
|
59
|
+
column_types = result_set.column_types
|
60
|
+
unless column_types.empty?
|
61
|
+
attribute_types = join_root.attribute_types
|
62
|
+
column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
|
63
|
+
end
|
64
|
+
column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
|
65
|
+
end
|
66
|
+
|
67
|
+
message_bus = ActiveSupport::Notifications.instrumenter
|
68
|
+
|
69
|
+
payload = {
|
70
|
+
record_count: result_set.length,
|
71
|
+
class_name: join_root.base_klass.name
|
72
|
+
}
|
73
|
+
|
74
|
+
message_bus.instrument("instantiation.active_record", payload) do
|
75
|
+
result_set.each { |row_hash|
|
76
|
+
# CPK
|
77
|
+
# parent_key = primary_key ? row_hash[primary_key] : row_hash
|
78
|
+
parent_key = if primary_key.kind_of?(Array)
|
79
|
+
primary_key.map {|key| row_hash[key]}
|
80
|
+
else
|
81
|
+
primary_key ? row_hash[primary_key] : row_hash
|
82
|
+
end
|
83
|
+
|
84
|
+
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
|
85
|
+
construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
parents.values
|
90
|
+
end
|
91
|
+
|
92
|
+
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
|
93
|
+
return if ar_parent.nil?
|
94
|
+
|
95
|
+
parent.children.each do |node|
|
96
|
+
if node.reflection.collection?
|
97
|
+
other = ar_parent.association(node.reflection.name)
|
98
|
+
other.loaded!
|
99
|
+
elsif ar_parent.association_cached?(node.reflection.name)
|
100
|
+
model = ar_parent.association(node.reflection.name).target
|
101
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
102
|
+
next
|
103
|
+
end
|
104
|
+
|
105
|
+
key = aliases.column_alias(node, node.primary_key)
|
106
|
+
# CPK
|
107
|
+
if key.is_a?(Array)
|
108
|
+
id = Array(key).map do |column_alias|
|
109
|
+
row[column_alias]
|
110
|
+
end
|
111
|
+
# At least the first value in the key has to be set. Should we require all values to be set?
|
112
|
+
id = nil if id.first.nil?
|
113
|
+
else # original
|
114
|
+
id = row[key]
|
115
|
+
end
|
116
|
+
|
117
|
+
if id.nil?
|
118
|
+
nil_association = ar_parent.association(node.reflection.name)
|
119
|
+
nil_association.loaded!
|
120
|
+
next
|
121
|
+
end
|
122
|
+
|
123
|
+
model = seen[ar_parent][node][id]
|
124
|
+
|
125
|
+
if model
|
126
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
127
|
+
else
|
128
|
+
model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
|
129
|
+
|
130
|
+
seen[ar_parent][node][id] = model
|
131
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -2,6 +2,20 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
class Preloader
|
4
4
|
class Association
|
5
|
+
|
6
|
+
class LoaderQuery
|
7
|
+
def load_records_for_keys(keys, &block)
|
8
|
+
# CPK
|
9
|
+
if association_key_name.is_a?(Array)
|
10
|
+
predicate = cpk_in_predicate(scope.klass.arel_table, association_key_name, keys)
|
11
|
+
scope.where(predicate).load(&block)
|
12
|
+
else
|
13
|
+
scope.where(association_key_name => keys).load(&block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# TODO: is records_for needed anymore? Rails' implementation has changed significantly
|
5
19
|
def records_for(ids)
|
6
20
|
records = if association_key_name.is_a?(Array)
|
7
21
|
predicate = cpk_in_predicate(klass.arel_table, association_key_name, ids)
|
@@ -33,6 +47,7 @@ module ActiveRecord
|
|
33
47
|
end
|
34
48
|
end
|
35
49
|
|
50
|
+
# TODO: is records_by_owner needed anymore? Rails' implementation has changed significantly
|
36
51
|
def records_by_owner
|
37
52
|
@records_by_owner ||= preloaded_records.each_with_object({}) do |record, result|
|
38
53
|
key = if association_key_name.is_a?(Array)
|
@@ -1,25 +1,24 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Associations
|
3
|
-
module ThroughAssociation
|
4
|
-
alias :original_construct_join_attributes :construct_join_attributes
|
5
|
-
|
6
|
-
def construct_join_attributes(*records)
|
7
|
-
# CPK
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
module ThroughAssociation
|
4
|
+
alias :original_construct_join_attributes :construct_join_attributes
|
5
|
+
|
6
|
+
def construct_join_attributes(*records)
|
7
|
+
# CPK
|
8
|
+
if !self.source_reflection.polymorphic? && source_reflection.klass.composite?
|
9
|
+
ensure_mutable
|
10
|
+
|
11
|
+
ids = records.map do |record|
|
12
|
+
source_reflection.association_primary_key(reflection.klass).map do |key|
|
13
|
+
record.send(key)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
cpk_in_predicate(through_association.scope.klass.arel_table, source_reflection.foreign_key, ids)
|
18
|
+
else
|
19
|
+
original_construct_join_attributes(*records)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,141 +1,137 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
class CompositeKeyError < StandardError #:nodoc:
|
3
|
-
end
|
4
|
-
|
5
|
-
class Base
|
6
|
-
INVALID_FOR_COMPOSITE_KEYS = 'Not appropriate for composite primary keys'
|
7
|
-
NOT_IMPLEMENTED_YET = 'Not implemented for composite primary keys yet'
|
8
|
-
|
9
|
-
class << self
|
10
|
-
alias_method :primary_key_without_composite_key_support=, :primary_key=
|
11
|
-
def primary_key=(keys)
|
12
|
-
unless keys.kind_of?(Array)
|
13
|
-
self.primary_key_without_composite_key_support = keys
|
14
|
-
return
|
15
|
-
end
|
16
|
-
|
17
|
-
@primary_keys = keys.map { |k| k.to_s }.to_composite_keys
|
18
|
-
|
19
|
-
class_eval <<-EOV
|
20
|
-
extend CompositeClassMethods
|
21
|
-
include CompositeInstanceMethods
|
22
|
-
EOV
|
23
|
-
end
|
24
|
-
alias_method :primary_keys=, :primary_key=
|
25
|
-
|
26
|
-
def set_primary_keys(*keys)
|
27
|
-
ActiveSupport::Deprecation.warn(
|
28
|
-
"Calling set_primary_keys is deprecated. Please use `self.primary_keys = keys` instead."
|
29
|
-
)
|
30
|
-
|
31
|
-
keys = keys.first if keys.first.is_a?(Array)
|
32
|
-
if keys.length == 1
|
33
|
-
self.primary_key = keys.first
|
34
|
-
else
|
35
|
-
self.primary_keys = keys
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def composite?
|
40
|
-
false
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def composite?
|
45
|
-
self.class.composite?
|
46
|
-
end
|
47
|
-
|
48
|
-
module CompositeClassMethods
|
49
|
-
def primary_keys
|
50
|
-
@primary_keys = reset_primary_keys unless defined? @primary_keys
|
51
|
-
@primary_keys
|
52
|
-
end
|
53
|
-
|
54
|
-
# Don't like this method name, but its modeled after how AR does it
|
55
|
-
def reset_primary_keys #:nodoc:
|
56
|
-
if self == base_class
|
57
|
-
# CPK
|
58
|
-
self.primary_keys = get_primary_key(base_class.name)
|
59
|
-
else
|
60
|
-
self.primary_keys = base_class.primary_keys
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def primary_key
|
65
|
-
primary_keys
|
66
|
-
end
|
67
|
-
|
68
|
-
def primary_key=(keys)
|
69
|
-
self.primary_keys = keys
|
70
|
-
end
|
71
|
-
|
72
|
-
def composite?
|
73
|
-
true
|
74
|
-
end
|
75
|
-
|
76
|
-
#ids_to_s([[1,2],[7,3]]) -> "(1,2),(7,3)"
|
77
|
-
#ids_to_s([[1,2],[7,3]], ',', ';') -> "1,2;7,3"
|
78
|
-
def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')')
|
79
|
-
many_ids.map {|ids| "#{left_bracket}#{CompositePrimaryKeys::CompositeKeys.new(ids)}#{right_bracket}"}.join(list_sep)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
module CompositeInstanceMethods
|
84
|
-
# A model instance's primary keys is always available as model.ids
|
85
|
-
# whether you name it the default 'id' or set it to something else.
|
86
|
-
def id
|
87
|
-
attr_names = self.class.primary_keys
|
88
|
-
::CompositePrimaryKeys::CompositeKeys.new(attr_names.map { |attr_name| read_attribute(attr_name) })
|
89
|
-
end
|
90
|
-
alias_method :ids, :id
|
91
|
-
|
92
|
-
# This is overridden purely for json serialization support. If the model is composite
|
93
|
-
# and one of the keys is id, then we don't want to call the id method, instead we want
|
94
|
-
# to get the id attribute value
|
95
|
-
def read_attribute_for_serialization(attribute)
|
96
|
-
if self.composite? && attribute == 'id'
|
97
|
-
read_attribute(attribute)
|
98
|
-
else
|
99
|
-
send(attribute)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def ids_hash
|
104
|
-
self.class.primary_key.zip(ids).inject(Hash.new) do |hash, (key, value)|
|
105
|
-
hash[key] = value
|
106
|
-
hash
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def id_before_type_cast
|
111
|
-
self.class.primary_keys.map do |key|
|
112
|
-
self.read_attribute_before_type_cast(key)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
# Sets the primary ID.
|
117
|
-
def id=(ids)
|
118
|
-
ids = CompositePrimaryKeys::CompositeKeys.parse(ids)
|
119
|
-
unless ids.length == self.class.primary_keys.length
|
120
|
-
raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"
|
121
|
-
end
|
122
|
-
[self.class.primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}
|
123
|
-
id
|
124
|
-
end
|
125
|
-
|
126
|
-
def can_change_primary_key_values?
|
127
|
-
false
|
128
|
-
end
|
129
|
-
|
130
|
-
# Returns this record's primary keys values in an Array
|
131
|
-
# if any value is available
|
132
|
-
def to_key
|
133
|
-
ids.to_a if !ids.compact.empty? # XXX Maybe use primary_keys with send instead of ids
|
134
|
-
end
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
class CompositeKeyError < StandardError #:nodoc:
|
3
|
+
end
|
4
|
+
|
5
|
+
class Base
|
6
|
+
INVALID_FOR_COMPOSITE_KEYS = 'Not appropriate for composite primary keys'
|
7
|
+
NOT_IMPLEMENTED_YET = 'Not implemented for composite primary keys yet'
|
8
|
+
|
9
|
+
class << self
|
10
|
+
alias_method :primary_key_without_composite_key_support=, :primary_key=
|
11
|
+
def primary_key=(keys)
|
12
|
+
unless keys.kind_of?(Array)
|
13
|
+
self.primary_key_without_composite_key_support = keys
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
@primary_keys = keys.map { |k| k.to_s }.to_composite_keys
|
18
|
+
|
19
|
+
class_eval <<-EOV
|
20
|
+
extend CompositeClassMethods
|
21
|
+
include CompositeInstanceMethods
|
22
|
+
EOV
|
23
|
+
end
|
24
|
+
alias_method :primary_keys=, :primary_key=
|
25
|
+
|
26
|
+
def set_primary_keys(*keys)
|
27
|
+
ActiveSupport::Deprecation.warn(
|
28
|
+
"Calling set_primary_keys is deprecated. Please use `self.primary_keys = keys` instead."
|
29
|
+
)
|
30
|
+
|
31
|
+
keys = keys.first if keys.first.is_a?(Array)
|
32
|
+
if keys.length == 1
|
33
|
+
self.primary_key = keys.first
|
34
|
+
else
|
35
|
+
self.primary_keys = keys
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def composite?
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def composite?
|
45
|
+
self.class.composite?
|
46
|
+
end
|
47
|
+
|
48
|
+
module CompositeClassMethods
|
49
|
+
def primary_keys
|
50
|
+
@primary_keys = reset_primary_keys unless defined? @primary_keys
|
51
|
+
@primary_keys
|
52
|
+
end
|
53
|
+
|
54
|
+
# Don't like this method name, but its modeled after how AR does it
|
55
|
+
def reset_primary_keys #:nodoc:
|
56
|
+
if self == base_class
|
57
|
+
# CPK
|
58
|
+
self.primary_keys = get_primary_key(base_class.name)
|
59
|
+
else
|
60
|
+
self.primary_keys = base_class.primary_keys
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def primary_key
|
65
|
+
primary_keys
|
66
|
+
end
|
67
|
+
|
68
|
+
def primary_key=(keys)
|
69
|
+
self.primary_keys = keys
|
70
|
+
end
|
71
|
+
|
72
|
+
def composite?
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
#ids_to_s([[1,2],[7,3]]) -> "(1,2),(7,3)"
|
77
|
+
#ids_to_s([[1,2],[7,3]], ',', ';') -> "1,2;7,3"
|
78
|
+
def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')')
|
79
|
+
many_ids.map {|ids| "#{left_bracket}#{CompositePrimaryKeys::CompositeKeys.new(ids)}#{right_bracket}"}.join(list_sep)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
module CompositeInstanceMethods
|
84
|
+
# A model instance's primary keys is always available as model.ids
|
85
|
+
# whether you name it the default 'id' or set it to something else.
|
86
|
+
def id
|
87
|
+
attr_names = self.class.primary_keys
|
88
|
+
::CompositePrimaryKeys::CompositeKeys.new(attr_names.map { |attr_name| read_attribute(attr_name) })
|
89
|
+
end
|
90
|
+
alias_method :ids, :id
|
91
|
+
|
92
|
+
# This is overridden purely for json serialization support. If the model is composite
|
93
|
+
# and one of the keys is id, then we don't want to call the id method, instead we want
|
94
|
+
# to get the id attribute value
|
95
|
+
def read_attribute_for_serialization(attribute)
|
96
|
+
if self.composite? && attribute == 'id'
|
97
|
+
read_attribute(attribute)
|
98
|
+
else
|
99
|
+
send(attribute)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def ids_hash
|
104
|
+
self.class.primary_key.zip(ids).inject(Hash.new) do |hash, (key, value)|
|
105
|
+
hash[key] = value
|
106
|
+
hash
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def id_before_type_cast
|
111
|
+
self.class.primary_keys.map do |key|
|
112
|
+
self.read_attribute_before_type_cast(key)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Sets the primary ID.
|
117
|
+
def id=(ids)
|
118
|
+
ids = CompositePrimaryKeys::CompositeKeys.parse(ids)
|
119
|
+
unless ids.length == self.class.primary_keys.length
|
120
|
+
raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"
|
121
|
+
end
|
122
|
+
[self.class.primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}
|
123
|
+
id
|
124
|
+
end
|
125
|
+
|
126
|
+
def can_change_primary_key_values?
|
127
|
+
false
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns this record's primary keys values in an Array
|
131
|
+
# if any value is available
|
132
|
+
def to_key
|
133
|
+
ids.to_a if !ids.compact.empty? # XXX Maybe use primary_keys with send instead of ids
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -62,6 +62,7 @@ end
|
|
62
62
|
ActiveRecord::Associations::AssociationScope.send(:include, CompositePrimaryKeys::Predicates)
|
63
63
|
ActiveRecord::Associations::JoinDependency::JoinAssociation.send(:include, CompositePrimaryKeys::Predicates)
|
64
64
|
ActiveRecord::Associations::Preloader::Association.send(:include, CompositePrimaryKeys::Predicates)
|
65
|
+
ActiveRecord::Associations::Preloader::Association::LoaderQuery.send(:include, CompositePrimaryKeys::Predicates)
|
65
66
|
ActiveRecord::Associations::HasManyAssociation.send(:include, CompositePrimaryKeys::Predicates)
|
66
67
|
ActiveRecord::Associations::HasManyThroughAssociation.send(:include, CompositePrimaryKeys::Predicates)
|
67
68
|
ActiveRecord::Base.send(:extend, CompositePrimaryKeys::Predicates)
|
@@ -30,11 +30,18 @@ module ActiveRecord
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
constraints =
|
33
|
+
constraints = constraints.map { |name, value| predicate_builder[name, value] }
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
default_constraint = build_default_constraint
|
36
|
+
constraints << default_constraint if default_constraint
|
37
|
+
|
38
|
+
if current_scope = self.global_current_scope
|
39
|
+
constraints << current_scope.where_clause.ast
|
40
|
+
end
|
41
|
+
|
42
|
+
um = Arel::UpdateManager.new(arel_table)
|
43
|
+
um.set(values.transform_keys { |name| arel_table[name] })
|
44
|
+
um.wheres = constraints
|
38
45
|
|
39
46
|
connection.update(um, "#{self} Update")
|
40
47
|
end
|
@@ -48,10 +55,16 @@ module ActiveRecord
|
|
48
55
|
end
|
49
56
|
end
|
50
57
|
|
51
|
-
constraints =
|
58
|
+
constraints = constraints.map { |name, value| predicate_builder[name, value] }
|
59
|
+
|
60
|
+
default_constraint = build_default_constraint
|
61
|
+
constraints << default_constraint if default_constraint
|
62
|
+
|
63
|
+
if current_scope = self.global_current_scope
|
64
|
+
constraints << current_scope.where_clause.ast
|
65
|
+
end
|
52
66
|
|
53
|
-
dm = Arel::DeleteManager.new
|
54
|
-
dm.from(arel_table)
|
67
|
+
dm = Arel::DeleteManager.new(arel_table)
|
55
68
|
dm.wheres = constraints
|
56
69
|
|
57
70
|
connection.delete(dm, "#{self} Destroy")
|
@@ -73,6 +86,7 @@ module ActiveRecord
|
|
73
86
|
end
|
74
87
|
|
75
88
|
@new_record = false
|
89
|
+
@previously_new_record = true
|
76
90
|
|
77
91
|
yield(self) if block_given?
|
78
92
|
|
@@ -42,7 +42,13 @@ module CompositePrimaryKeys
|
|
42
42
|
|
43
43
|
result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
|
44
44
|
|
45
|
-
|
45
|
+
if operation != "count"
|
46
|
+
type = column.try(:type_caster) ||
|
47
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || ::ActiveRecord::Type.default_value
|
48
|
+
type = type.subtype if ::ActiveRecord::Enum::EnumType === type
|
49
|
+
end
|
50
|
+
|
51
|
+
type_cast_calculated_value(result.cast_values.first, operation, type) do |value|
|
46
52
|
type = column.try(:type_caster) ||
|
47
53
|
# CPK
|
48
54
|
# lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
data/test/abstract_unit.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
spec_name = ENV['ADAPTER'] || '
|
1
|
+
spec_name = ENV['ADAPTER'] || 'postgresql'
|
2
2
|
require 'bundler'
|
3
3
|
require 'minitest/autorun'
|
4
4
|
|
@@ -21,6 +21,10 @@ ActiveRecord::Base.configurations = {test: spec}
|
|
21
21
|
|
22
22
|
# Tell ActiveRecord where to find models
|
23
23
|
ActiveSupport::Dependencies.autoload_paths << File.join(PROJECT_ROOT, 'test', 'fixtures')
|
24
|
+
Dir[File.join(PROJECT_ROOT, 'test', 'fixtures', '*.rb')].each do |file_path|
|
25
|
+
require_file = file_path.sub(PROJECT_ROOT, '..').sub('.rb', '')
|
26
|
+
require_relative require_file
|
27
|
+
end
|
24
28
|
|
25
29
|
I18n.config.enforce_available_locales = true
|
26
30
|
|
data/test/test_associations.rb
CHANGED
@@ -343,7 +343,7 @@ class TestAssociations < ActiveSupport::TestCase
|
|
343
343
|
assert_equal([3,2], memberships[1].id)
|
344
344
|
end
|
345
345
|
|
346
|
-
def
|
346
|
+
def test_scoped_has_many_with_primary_key_with_associations
|
347
347
|
memberships = Membership.joins(:active_statuses)
|
348
348
|
assert_equal(2, memberships.length)
|
349
349
|
|
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:
|
4
|
+
version: 14.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charlie Savage
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 7.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 7.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -209,7 +209,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
209
209
|
requirements:
|
210
210
|
- - ">="
|
211
211
|
- !ruby/object:Gem::Version
|
212
|
-
version: 2.
|
212
|
+
version: 2.7.0
|
213
213
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
214
214
|
requirements:
|
215
215
|
- - ">="
|