composite_primary_keys 12.0.10 → 13.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.rdoc +4 -1
- data/README.rdoc +1 -0
- data/lib/composite_primary_keys.rb +3 -1
- data/lib/composite_primary_keys/associations/association_scope.rb +4 -6
- data/lib/composite_primary_keys/associations/join_dependency.rb +34 -19
- data/lib/composite_primary_keys/attribute_methods.rb +14 -2
- data/lib/composite_primary_keys/attribute_methods/primary_key.rb +0 -2
- data/lib/composite_primary_keys/attribute_methods/read.rb +1 -1
- data/lib/composite_primary_keys/attribute_methods/write.rb +1 -1
- data/lib/composite_primary_keys/nested_attributes.rb +1 -1
- data/lib/composite_primary_keys/reflection.rb +64 -2
- data/lib/composite_primary_keys/relation.rb +13 -9
- data/lib/composite_primary_keys/relation/batches.rb +15 -7
- data/lib/composite_primary_keys/relation/calculations.rb +46 -23
- data/lib/composite_primary_keys/relation/finder_methods.rb +7 -7
- data/lib/composite_primary_keys/relation/predicate_builder/association_query_value.rb +23 -4
- data/lib/composite_primary_keys/relation/query_methods.rb +2 -2
- data/lib/composite_primary_keys/relation/where_clause.rb +6 -11
- data/lib/composite_primary_keys/table_metadata.rb +11 -0
- data/lib/composite_primary_keys/version.rb +2 -2
- data/test/fixtures/departments.yml +4 -0
- data/test/fixtures/employees.yml +6 -1
- data/test/test_associations.rb +6 -0
- data/test/test_attributes.rb +19 -4
- data/test/test_calculations.rb +9 -2
- data/test/test_delete.rb +2 -2
- data/test/test_find.rb +8 -2
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa8acb9d77d6a2de529283cbaa0cbea67daec08813942c8bfa83f39b0bdf299f
|
4
|
+
data.tar.gz: deb46f06053d8a07b19903a46b3d30e3ae82699d265f339c5463e810900416b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d31a495089eafc1cced865e0cd55e6e7a09698b124f30c7d18a754aa396c611f85abcfcf32af55e5c23534432d0f74c7eee5f1d674574c0a0b0c937d61fb16fd
|
7
|
+
data.tar.gz: ab7776729d53de9d12c08bb0035c256ef1d4cc00dab2b7be029a4c610c6cd36b6e1397160ebde56d7acaecc29817b1456e83b482a6a7f33c0a5ac15c6ba715e2
|
data/History.rdoc
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
== 13.0.0
|
2
|
+
* Update to ActiveRecord 6.1 (Javier Julio, Charlie Savage, Sammy Larbi)
|
3
|
+
|
1
4
|
== 12.0.10 (2021-05-09)
|
2
5
|
* Support using the #id method on records with primary keys that also have an :id attribute.
|
3
6
|
|
@@ -514,7 +517,7 @@ by replacing quoted identifiers with all-caps equivalents on Oracle (Rhett Sutph
|
|
514
517
|
* Update Oracle tests (Rhett Sutphin)
|
515
518
|
|
516
519
|
== 4.1.1 2011-08-31
|
517
|
-
* Support for AR 3.1.1
|
520
|
+
* Support for AR 3.1.1
|
518
521
|
* Make polymorphic belongs_to work in rails 3.1.1 (Tom Hughes)
|
519
522
|
* Eliminate relative paths from the test suite (Rhett Sutphin)
|
520
523
|
* Minor improvements to the CPK test runner w/o relative path changes (Rhett Sutphin)
|
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 13.x is designed to work with ActiveRecord 6.1.x
|
23
24
|
Version 12.x is designed to work with ActiveRecord 6.0.x
|
24
25
|
Version 11.x is designed to work with ActiveRecord 5.2.x
|
25
26
|
Version 10.x is designed to work with ActiveRecord 5.1.x
|
@@ -23,7 +23,7 @@
|
|
23
23
|
|
24
24
|
unless defined?(ActiveRecord)
|
25
25
|
require 'rubygems'
|
26
|
-
gem 'activerecord', '~>6.
|
26
|
+
gem 'activerecord', '~>6.1.0'
|
27
27
|
require 'active_record'
|
28
28
|
end
|
29
29
|
|
@@ -64,6 +64,7 @@ require 'active_record/connection_adapters/abstract_adapter'
|
|
64
64
|
require 'active_record/connection_adapters/postgresql/database_statements'
|
65
65
|
|
66
66
|
require 'active_record/relation/where_clause'
|
67
|
+
require 'active_record/table_metadata'
|
67
68
|
|
68
69
|
# CPK overrides
|
69
70
|
require_relative 'composite_primary_keys/active_model/attribute_assignment'
|
@@ -115,3 +116,4 @@ require_relative 'composite_primary_keys/composite_relation'
|
|
115
116
|
|
116
117
|
require_relative 'composite_primary_keys/arel/to_sql'
|
117
118
|
require_relative 'composite_primary_keys/arel/sqlserver'
|
119
|
+
require_relative 'composite_primary_keys/table_metadata'
|
@@ -23,9 +23,8 @@ module ActiveRecord
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def last_chain_scope(scope, reflection, owner)
|
26
|
-
|
27
|
-
|
28
|
-
foreign_key = join_keys.foreign_key
|
26
|
+
key = reflection.join_primary_key
|
27
|
+
foreign_key = reflection.join_foreign_key
|
29
28
|
|
30
29
|
table = reflection.aliased_table
|
31
30
|
|
@@ -46,9 +45,8 @@ module ActiveRecord
|
|
46
45
|
end
|
47
46
|
|
48
47
|
def next_chain_scope(scope, reflection, next_reflection)
|
49
|
-
|
50
|
-
|
51
|
-
foreign_key = join_keys.foreign_key
|
48
|
+
key = reflection.join_primary_key
|
49
|
+
foreign_key = reflection.join_foreign_key
|
52
50
|
|
53
51
|
table = reflection.aliased_table
|
54
52
|
foreign_table = next_reflection.aliased_table
|
@@ -15,46 +15,62 @@ module ActiveRecord
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
def instantiate(result_set, &block)
|
18
|
+
def instantiate(result_set, strict_loading_value, &block)
|
19
19
|
primary_key = aliases.column_alias(join_root, join_root.primary_key)
|
20
20
|
|
21
|
-
seen = Hash.new { |i,
|
22
|
-
i[
|
21
|
+
seen = Hash.new { |i, parent|
|
22
|
+
i[parent] = Hash.new { |j, child_class|
|
23
23
|
j[child_class] = {}
|
24
24
|
}
|
25
|
-
}
|
25
|
+
}.compare_by_identity
|
26
26
|
|
27
27
|
model_cache = Hash.new { |h, klass| h[klass] = {} }
|
28
28
|
parents = model_cache[join_root]
|
29
|
-
|
29
|
+
|
30
|
+
column_aliases = aliases.column_aliases(join_root)
|
31
|
+
column_names = []
|
32
|
+
|
33
|
+
result_set.columns.each do |name|
|
34
|
+
column_names << name unless /\At\d+_r\d+\z/.match?(name)
|
35
|
+
end
|
36
|
+
|
37
|
+
if column_names.empty?
|
38
|
+
column_types = {}
|
39
|
+
else
|
40
|
+
column_types = result_set.column_types
|
41
|
+
unless column_types.empty?
|
42
|
+
attribute_types = join_root.attribute_types
|
43
|
+
column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
|
44
|
+
end
|
45
|
+
column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
|
46
|
+
end
|
30
47
|
|
31
48
|
message_bus = ActiveSupport::Notifications.instrumenter
|
32
49
|
|
33
50
|
payload = {
|
34
|
-
|
35
|
-
|
51
|
+
record_count: result_set.length,
|
52
|
+
class_name: join_root.base_klass.name
|
36
53
|
}
|
37
54
|
|
38
55
|
message_bus.instrument("instantiation.active_record", payload) do
|
39
56
|
result_set.each { |row_hash|
|
40
57
|
# CPK
|
41
58
|
# parent_key = primary_key ? row_hash[primary_key] : row_hash
|
42
|
-
# CPK
|
43
59
|
parent_key = if primary_key.kind_of?(Array)
|
44
60
|
primary_key.map {|key| row_hash[key]}
|
45
61
|
else
|
46
62
|
primary_key ? row_hash[primary_key] : row_hash
|
47
63
|
end
|
48
64
|
|
49
|
-
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
|
50
|
-
construct(parent, join_root, row_hash, seen, model_cache)
|
65
|
+
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
|
66
|
+
construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
|
51
67
|
}
|
52
68
|
end
|
53
69
|
|
54
70
|
parents.values
|
55
71
|
end
|
56
72
|
|
57
|
-
def construct(ar_parent, parent, row, seen, model_cache)
|
73
|
+
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
|
58
74
|
return if ar_parent.nil?
|
59
75
|
|
60
76
|
parent.children.each do |node|
|
@@ -63,12 +79,11 @@ module ActiveRecord
|
|
63
79
|
other.loaded!
|
64
80
|
elsif ar_parent.association_cached?(node.reflection.name)
|
65
81
|
model = ar_parent.association(node.reflection.name).target
|
66
|
-
construct(model, node, row, seen, model_cache)
|
82
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
67
83
|
next
|
68
84
|
end
|
69
85
|
|
70
86
|
key = aliases.column_alias(node, node.primary_key)
|
71
|
-
|
72
87
|
# CPK
|
73
88
|
if key.is_a?(Array)
|
74
89
|
id = Array(key).map do |column_alias|
|
@@ -80,21 +95,21 @@ module ActiveRecord
|
|
80
95
|
id = row[key]
|
81
96
|
end
|
82
97
|
|
83
|
-
if id.nil?
|
98
|
+
if id.nil?
|
84
99
|
nil_association = ar_parent.association(node.reflection.name)
|
85
100
|
nil_association.loaded!
|
86
101
|
next
|
87
102
|
end
|
88
103
|
|
89
|
-
model = seen[ar_parent
|
104
|
+
model = seen[ar_parent][node][id]
|
90
105
|
|
91
106
|
if model
|
92
|
-
construct(model, node, row, seen, model_cache)
|
107
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
93
108
|
else
|
94
|
-
model = construct_model(ar_parent, node, row, model_cache, id)
|
109
|
+
model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
|
95
110
|
|
96
|
-
seen[ar_parent
|
97
|
-
construct(model, node, row, seen, model_cache)
|
111
|
+
seen[ar_parent][node][id] = model
|
112
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
98
113
|
end
|
99
114
|
end
|
100
115
|
end
|
@@ -2,8 +2,20 @@ module ActiveRecord
|
|
2
2
|
module AttributeMethods
|
3
3
|
def has_attribute?(attr_name)
|
4
4
|
# CPK
|
5
|
-
#
|
6
|
-
|
5
|
+
# attr_name = attr_name.to_s
|
6
|
+
# attr_name = self.class.attribute_aliases[attr_name] || attr_name
|
7
|
+
# @attributes.key?(attr_name)
|
8
|
+
Array(attr_name).all? do |attr|
|
9
|
+
attr = attr.to_s
|
10
|
+
attr = self.class.attribute_aliases[attr] || attr
|
11
|
+
@attributes.key?(attr)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def _has_attribute?(attr_name)
|
16
|
+
# CPK
|
17
|
+
# @attributes.key?(attr_name)
|
18
|
+
Array(attr_name).all? { |attr| @attributes.key?(attr) }
|
7
19
|
end
|
8
20
|
end
|
9
21
|
end
|
@@ -16,7 +16,6 @@ module ActiveRecord
|
|
16
16
|
|
17
17
|
# Returns the primary key previous value.
|
18
18
|
def id_was
|
19
|
-
sync_with_transaction_state
|
20
19
|
# CPK
|
21
20
|
# attribute_was(self.class.primary_key)
|
22
21
|
if self.composite?
|
@@ -29,7 +28,6 @@ module ActiveRecord
|
|
29
28
|
end
|
30
29
|
|
31
30
|
def id_in_database
|
32
|
-
sync_with_transaction_state
|
33
31
|
# CPK
|
34
32
|
# attribute_in_database(self.class.primary_key)
|
35
33
|
if self.composite?
|
@@ -70,7 +70,7 @@ module ActiveRecord
|
|
70
70
|
if target_record
|
71
71
|
existing_record = target_record
|
72
72
|
else
|
73
|
-
association.add_to_target(existing_record, :
|
73
|
+
association.add_to_target(existing_record, skip_callbacks: true)
|
74
74
|
end
|
75
75
|
|
76
76
|
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
@@ -6,8 +6,8 @@ module ActiveRecord
|
|
6
6
|
scope_chain_items = join_scopes(table, predicate_builder)
|
7
7
|
klass_scope = klass_join_scope(table, predicate_builder)
|
8
8
|
|
9
|
-
key =
|
10
|
-
foreign_key =
|
9
|
+
key = join_primary_key
|
10
|
+
foreign_key = join_foreign_key
|
11
11
|
|
12
12
|
# CPK
|
13
13
|
#klass_scope.where!(table[key].eq(foreign_table[foreign_key]))
|
@@ -25,5 +25,67 @@ module ActiveRecord
|
|
25
25
|
scope_chain_items.inject(klass_scope, &:merge!)
|
26
26
|
end
|
27
27
|
end
|
28
|
+
|
29
|
+
class AssociationReflection < MacroReflection
|
30
|
+
def foreign_key
|
31
|
+
# CPK
|
32
|
+
# @foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key)
|
33
|
+
@foreign_key ||= extract_keys(options[:foreign_key]) || derive_foreign_key
|
34
|
+
end
|
35
|
+
|
36
|
+
def association_foreign_key
|
37
|
+
# CPK
|
38
|
+
# @association_foreign_key ||= -(options[:association_foreign_key]&.to_s || class_name.foreign_key)
|
39
|
+
@association_foreign_key ||= extract_keys(options[:association_foreign_key]) || class_name.foreign_key
|
40
|
+
end
|
41
|
+
|
42
|
+
def active_record_primary_key
|
43
|
+
# CPK (Rails freezes the string returned in the expression that calculates PK here. But Rails uses the `-` method which is not available on Array for CPK, so we calculate it in one line and freeze it on the next)
|
44
|
+
# @active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record))
|
45
|
+
@active_record_primary_key ||= begin
|
46
|
+
pk = options[:primary_key] || primary_key(active_record)
|
47
|
+
pk.freeze
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def extract_keys(keys)
|
54
|
+
case keys
|
55
|
+
when Array
|
56
|
+
keys.map { |k| k.to_s }
|
57
|
+
when NilClass
|
58
|
+
nil
|
59
|
+
else
|
60
|
+
keys.to_s
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class BelongsToReflection < AssociationReflection
|
66
|
+
def association_primary_key(klass = nil)
|
67
|
+
if primary_key = options[:primary_key]
|
68
|
+
# CPK
|
69
|
+
# @association_primary_key ||= -primary_key.to_s
|
70
|
+
@association_primary_key ||= primary_key.freeze
|
71
|
+
else
|
72
|
+
primary_key(klass || self.klass)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class ThroughReflection < AbstractReflection #:nodoc:
|
78
|
+
def association_primary_key(klass = nil)
|
79
|
+
# Get the "actual" source reflection if the immediate source reflection has a
|
80
|
+
# source reflection itself
|
81
|
+
if primary_key = actual_source_reflection.options[:primary_key]
|
82
|
+
# CPK
|
83
|
+
# @association_primary_key ||= -primary_key.to_s
|
84
|
+
@association_primary_key ||= primary_key.freeze
|
85
|
+
else
|
86
|
+
primary_key(klass || self.klass)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
28
90
|
end
|
29
91
|
end
|
@@ -25,13 +25,15 @@ module ActiveRecord
|
|
25
25
|
end
|
26
26
|
|
27
27
|
stmt = Arel::UpdateManager.new
|
28
|
+
stmt.table(arel.join_sources.empty? ? table : arel.source)
|
29
|
+
stmt.key = table[primary_key]
|
30
|
+
|
28
31
|
# CPK
|
29
|
-
if @klass.composite?
|
32
|
+
if @klass.composite? && stmt.to_sql =~ /['"]#{primary_key.to_s}['"]/
|
33
|
+
stmt = Arel::UpdateManager.new
|
30
34
|
stmt.table(arel_table)
|
31
35
|
cpk_subquery(stmt)
|
32
36
|
else
|
33
|
-
stmt.table(arel.join_sources.empty? ? table : arel.source)
|
34
|
-
stmt.key = arel_attribute(primary_key)
|
35
37
|
stmt.wheres = arel.constraints
|
36
38
|
end
|
37
39
|
stmt.take(arel.limit)
|
@@ -42,7 +44,7 @@ module ActiveRecord
|
|
42
44
|
if klass.locking_enabled? &&
|
43
45
|
!updates.key?(klass.locking_column) &&
|
44
46
|
!updates.key?(klass.locking_column.to_sym)
|
45
|
-
attr =
|
47
|
+
attr = table[klass.locking_column]
|
46
48
|
updates[attr.name] = _increment_attribute(attr)
|
47
49
|
end
|
48
50
|
stmt.set _substitute_values(updates)
|
@@ -68,13 +70,15 @@ module ActiveRecord
|
|
68
70
|
end
|
69
71
|
|
70
72
|
stmt = Arel::DeleteManager.new
|
73
|
+
stmt.from(arel.join_sources.empty? ? table : arel.source)
|
74
|
+
stmt.key = table[primary_key]
|
71
75
|
|
72
|
-
|
76
|
+
# CPK
|
77
|
+
if @klass.composite? && stmt.to_sql =~ /['"]#{primary_key.to_s}['"]/
|
78
|
+
stmt = Arel::DeleteManager.new
|
73
79
|
stmt.from(arel_table)
|
74
80
|
cpk_subquery(stmt)
|
75
81
|
else
|
76
|
-
stmt.from(arel.join_sources.empty? ? table : arel.source)
|
77
|
-
stmt.key = arel_attribute(primary_key)
|
78
82
|
stmt.wheres = arel.constraints
|
79
83
|
end
|
80
84
|
|
@@ -138,7 +142,7 @@ module ActiveRecord
|
|
138
142
|
# reference_codes.reference_code = cpk_child.reference_code)
|
139
143
|
def cpk_exists_subquery(stmt)
|
140
144
|
arel_attributes = primary_keys.map do |key|
|
141
|
-
|
145
|
+
table[key]
|
142
146
|
end.to_composite_keys
|
143
147
|
|
144
148
|
# Clone the query
|
@@ -173,7 +177,7 @@ module ActiveRecord
|
|
173
177
|
# FROM `reference_codes`) __active_record_temp)
|
174
178
|
def cpk_mysql_subquery(stmt)
|
175
179
|
arel_attributes = primary_keys.map do |key|
|
176
|
-
|
180
|
+
table[key]
|
177
181
|
end.to_composite_keys
|
178
182
|
|
179
183
|
subselect = arel.clone
|
@@ -1,12 +1,16 @@
|
|
1
1
|
module CompositePrimaryKeys
|
2
2
|
module ActiveRecord
|
3
3
|
module Batches
|
4
|
-
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
|
4
|
+
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: :asc)
|
5
5
|
relation = self
|
6
6
|
unless block_given?
|
7
7
|
return ::ActiveRecord::Batches::BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
|
8
8
|
end
|
9
9
|
|
10
|
+
unless [:asc, :desc].include?(order)
|
11
|
+
raise ArgumentError, ":order must be :asc or :desc, got #{order.inspect}"
|
12
|
+
end
|
13
|
+
|
10
14
|
if arel.orders.present?
|
11
15
|
act_on_ignored_order(error_on_ignore)
|
12
16
|
end
|
@@ -17,8 +21,8 @@ module CompositePrimaryKeys
|
|
17
21
|
batch_limit = remaining if remaining < batch_limit
|
18
22
|
end
|
19
23
|
|
20
|
-
relation = relation.reorder(batch_order).limit(batch_limit)
|
21
|
-
relation = apply_limits(relation, start, finish)
|
24
|
+
relation = relation.reorder(batch_order(order)).limit(batch_limit)
|
25
|
+
relation = apply_limits(relation, start, finish, order)
|
22
26
|
relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
|
23
27
|
batch_relation = relation
|
24
28
|
|
@@ -61,7 +65,9 @@ module CompositePrimaryKeys
|
|
61
65
|
end
|
62
66
|
|
63
67
|
# CPK
|
64
|
-
#
|
68
|
+
#batch_relation = relation.where(
|
69
|
+
# predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt]
|
70
|
+
#)
|
65
71
|
batch_relation = if composite?
|
66
72
|
# CPK
|
67
73
|
# Lexicographically select records
|
@@ -81,7 +87,9 @@ module CompositePrimaryKeys
|
|
81
87
|
end.reduce(:or)
|
82
88
|
relation.where(query)
|
83
89
|
else
|
84
|
-
relation.where(
|
90
|
+
batch_relation = relation.where(
|
91
|
+
predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt]
|
92
|
+
)
|
85
93
|
end
|
86
94
|
end
|
87
95
|
end
|
@@ -95,9 +103,9 @@ module CompositePrimaryKeys
|
|
95
103
|
ary.length.times.reduce([]) { |results, i| results << ary[0..i] }
|
96
104
|
end
|
97
105
|
|
98
|
-
def batch_order
|
106
|
+
def batch_order(order)
|
99
107
|
self.primary_key.map do |key|
|
100
|
-
|
108
|
+
table[key].public_send(order)
|
101
109
|
end
|
102
110
|
end
|
103
111
|
end
|
@@ -4,11 +4,12 @@ module CompositePrimaryKeys
|
|
4
4
|
def aggregate_column(column_name)
|
5
5
|
# CPK
|
6
6
|
if column_name.kind_of?(Array)
|
7
|
+
# Note: Test don't seem to run this code?
|
7
8
|
column_name.map do |column|
|
8
|
-
@klass.
|
9
|
+
@klass.arel_table[column]
|
9
10
|
end
|
10
11
|
elsif @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name)
|
11
|
-
@klass.
|
12
|
+
@klass.arel_table[column_name]
|
12
13
|
else
|
13
14
|
Arel.sql(column_name == :all ? "*" : column_name.to_s)
|
14
15
|
end
|
@@ -31,38 +32,33 @@ module CompositePrimaryKeys
|
|
31
32
|
relation = unscope(:order).distinct!(false)
|
32
33
|
|
33
34
|
column = aggregate_column(column_name)
|
34
|
-
|
35
35
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
36
|
-
if operation == "sum" && distinct
|
37
|
-
select_value.distinct = true
|
38
|
-
end
|
36
|
+
select_value.distinct = true if operation == "sum" && distinct
|
39
37
|
|
40
|
-
column_alias = select_value.alias
|
41
|
-
column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
|
42
38
|
relation.select_values = [select_value]
|
43
39
|
|
44
40
|
query_builder = relation.arel
|
45
41
|
end
|
46
42
|
|
47
|
-
result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder
|
48
|
-
row = result.first
|
49
|
-
value = row && row.values.first
|
50
|
-
type = result.column_types.fetch(column_alias) do
|
51
|
-
type_for(column_name)
|
52
|
-
end
|
43
|
+
result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
|
53
44
|
|
54
|
-
type_cast_calculated_value(
|
45
|
+
type_cast_calculated_value(result.cast_values.first, operation) do |value|
|
46
|
+
type = column.try(:type_caster) ||
|
47
|
+
# CPK
|
48
|
+
# lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
49
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || ::ActiveRecord::Type.default_value
|
50
|
+
type.deserialize(value)
|
51
|
+
end
|
55
52
|
end
|
56
53
|
|
57
54
|
def build_count_subquery(relation, column_name, distinct)
|
58
55
|
if column_name == :all
|
56
|
+
column_alias = Arel.star
|
57
|
+
# CPK
|
58
|
+
# relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
59
59
|
relation.select_values = [ Arel.sql(::ActiveRecord::FinderMethods::ONE_AS_ONE) ] unless distinct
|
60
|
-
if relation.select_values.first.is_a?(Array)
|
61
|
-
relation.select_values = relation.select_values.first.map do |column|
|
62
|
-
Arel::Attribute.new(@klass.unscoped.table, column)
|
63
|
-
end
|
64
|
-
end
|
65
60
|
elsif column_name.is_a?(Array)
|
61
|
+
column_alias = Arel.star
|
66
62
|
relation.select_values = column_name.map do |column|
|
67
63
|
Arel::Attribute.new(@klass.unscoped.table, column)
|
68
64
|
end
|
@@ -71,10 +67,37 @@ module CompositePrimaryKeys
|
|
71
67
|
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
72
68
|
end
|
73
69
|
|
74
|
-
|
75
|
-
select_value = operation_over_aggregate_column(column_alias
|
70
|
+
subquery_alias = Arel.sql("subquery_for_count")
|
71
|
+
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
72
|
+
|
73
|
+
relation.build_subquery(subquery_alias, select_value)
|
74
|
+
end
|
75
|
+
|
76
|
+
def calculate(operation, column_name)
|
77
|
+
if has_include?(column_name)
|
78
|
+
relation = apply_join_dependency
|
76
79
|
|
77
|
-
|
80
|
+
if operation.to_s.downcase == "count"
|
81
|
+
unless distinct_value || distinct_select?(column_name || select_for_count)
|
82
|
+
relation.distinct!
|
83
|
+
# CPK
|
84
|
+
# relation.select_values = [ klass.primary_key || table[Arel.star] ]
|
85
|
+
if klass.primary_key.present? && klass.primary_key.is_a?(Array)
|
86
|
+
relation.select_values = klass.primary_key.map do |k|
|
87
|
+
"#{connection.quote_table_name(klass.table_name)}.#{connection.quote_column_name(k)}"
|
88
|
+
end
|
89
|
+
else
|
90
|
+
relation.select_values = [ klass.primary_key || table[Arel.star] ]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
94
|
+
relation.order_values = [] if group_values.empty?
|
95
|
+
end
|
96
|
+
|
97
|
+
relation.calculate(operation, column_name)
|
98
|
+
else
|
99
|
+
perform_calculation(operation, column_name)
|
100
|
+
end
|
78
101
|
end
|
79
102
|
end
|
80
103
|
end
|
@@ -26,12 +26,12 @@ module CompositePrimaryKeys
|
|
26
26
|
def limited_ids_for(relation)
|
27
27
|
# CPK
|
28
28
|
# values = @klass.connection.columns_for_distinct(
|
29
|
-
# connection.column_name_from_arel_node(
|
29
|
+
# connection.column_name_from_arel_node(table[primary_key]),
|
30
30
|
# relation.order_values
|
31
31
|
# )
|
32
32
|
|
33
33
|
columns = @klass.primary_keys.map do |key|
|
34
|
-
connection.visitor.compile(
|
34
|
+
connection.visitor.compile(table[key])
|
35
35
|
end
|
36
36
|
values = @klass.connection.columns_for_distinct(columns, relation.order_values)
|
37
37
|
|
@@ -110,12 +110,12 @@ module CompositePrimaryKeys
|
|
110
110
|
#
|
111
111
|
# result = limit(limit || 1)
|
112
112
|
# # CPK
|
113
|
-
# # result.order!(
|
113
|
+
# # result.order!(table[primary_key]) if order_values.empty? && primary_key
|
114
114
|
# if order_values.empty? && primary_key
|
115
115
|
# if composite?
|
116
|
-
# result.order!(primary_keys.map { |pk|
|
116
|
+
# result.order!(primary_keys.map { |pk| table[pk].asc })
|
117
117
|
# elsif
|
118
|
-
# result.order!(
|
118
|
+
# result.order!(table[primary_key])
|
119
119
|
# end
|
120
120
|
# end
|
121
121
|
#
|
@@ -224,8 +224,8 @@ module CompositePrimaryKeys
|
|
224
224
|
def ordered_relation
|
225
225
|
if order_values.empty? && (implicit_order_column || primary_key)
|
226
226
|
# CPK
|
227
|
-
# order(
|
228
|
-
order(Array(implicit_order_column || primary_key).map {|key|
|
227
|
+
# order(table[implicit_order_column || primary_key].asc)
|
228
|
+
order(Array(implicit_order_column || primary_key).map {|key| table[key].asc})
|
229
229
|
else
|
230
230
|
self
|
231
231
|
end
|
@@ -3,16 +3,35 @@ module ActiveRecord
|
|
3
3
|
class AssociationQueryValue
|
4
4
|
def queries
|
5
5
|
# CPK
|
6
|
-
if associated_table.
|
6
|
+
if associated_table.join_foreign_key.is_a?(Array)
|
7
7
|
if ids.is_a?(ActiveRecord::Relation)
|
8
8
|
ids.map do |id|
|
9
|
-
associated_table.
|
9
|
+
associated_table.join_foreign_key.zip(id.id).to_h
|
10
10
|
end
|
11
11
|
else
|
12
|
-
[associated_table.
|
12
|
+
[associated_table.join_foreign_key.zip(ids).to_h]
|
13
13
|
end
|
14
14
|
else
|
15
|
-
[associated_table.
|
15
|
+
[associated_table.join_foreign_key => ids]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def ids
|
20
|
+
case value
|
21
|
+
when Relation
|
22
|
+
value.select_values.empty? ? value.select(primary_key) : value
|
23
|
+
when Array
|
24
|
+
value.map { |v| convert_to_id(v) }
|
25
|
+
else
|
26
|
+
# CPK
|
27
|
+
# convert_to_id(value)
|
28
|
+
if value.nil?
|
29
|
+
nil
|
30
|
+
elsif value.respond_to?(:composite?) && value.composite?
|
31
|
+
value.class.primary_keys.zip(value.id)
|
32
|
+
else
|
33
|
+
convert_to_id(value)
|
34
|
+
end
|
16
35
|
end
|
17
36
|
end
|
18
37
|
end
|
@@ -4,12 +4,12 @@ module CompositePrimaryKeys
|
|
4
4
|
def reverse_sql_order(order_query)
|
5
5
|
if order_query.empty?
|
6
6
|
# CPK
|
7
|
-
# return [
|
7
|
+
# return [table[primary_key].desc] if primary_key
|
8
8
|
|
9
9
|
if primary_key
|
10
10
|
# break apart CPKs
|
11
11
|
return primary_key.map do |key|
|
12
|
-
|
12
|
+
table[key].desc
|
13
13
|
end
|
14
14
|
else
|
15
15
|
raise IrreversibleOrderError,
|
@@ -1,23 +1,18 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class Relation
|
3
3
|
class WhereClause
|
4
|
-
def to_h(table_name = nil)
|
5
|
-
equalities = equalities(predicates)
|
4
|
+
def to_h(table_name = nil, equality_only: false)
|
5
|
+
equalities = equalities(predicates, equality_only)
|
6
6
|
|
7
7
|
# CPK Adds this line, because ours are coming in with AND->{EQUALITY, EQUALITY}
|
8
8
|
equalities = predicates.grep(Arel::Nodes::And).map(&:children).flatten.grep(Arel::Nodes::Equality) if equalities.empty?
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
node.left.relation.name == table_name
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
equalities.map { |node|
|
10
|
+
equalities.each_with_object({}) do |node, hash|
|
11
|
+
next if table_name&.!= node.left.relation.name
|
17
12
|
name = node.left.name.to_s
|
18
13
|
value = extract_node_value(node.right)
|
19
|
-
[name
|
20
|
-
|
14
|
+
hash[name] = value
|
15
|
+
end
|
21
16
|
end
|
22
17
|
end
|
23
18
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class TableMetadata # :nodoc:
|
5
|
+
def associated_with?(table_name)
|
6
|
+
# CPK
|
7
|
+
# klass&._reflect_on_association(table_name) || klass&._reflect_on_association(table_name.singularize)
|
8
|
+
klass&._reflect_on_association(table_name) || klass&._reflect_on_association(table_name.to_s.singularize)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/test/fixtures/employees.yml
CHANGED
data/test/test_associations.rb
CHANGED
@@ -355,4 +355,10 @@ class TestAssociations < ActiveSupport::TestCase
|
|
355
355
|
article.reading_ids = Reading.pluck(:id)
|
356
356
|
assert_equal article.reading_ids, Reading.pluck(:id)
|
357
357
|
end
|
358
|
+
|
359
|
+
def test_find_by_association
|
360
|
+
assert_equal Membership.where(user: '1').count, 1
|
361
|
+
assert_equal Membership.where(user_id: '1').count, 1
|
362
|
+
assert_equal Membership.where(user: User.find(1)).count, 1
|
363
|
+
end
|
358
364
|
end
|
data/test/test_attributes.rb
CHANGED
@@ -2,28 +2,32 @@ require File.expand_path('../abstract_unit', __FILE__)
|
|
2
2
|
|
3
3
|
class TestAttributes < ActiveSupport::TestCase
|
4
4
|
fixtures :reference_types, :reference_codes, :products, :tariffs, :product_tariffs
|
5
|
-
|
5
|
+
|
6
6
|
CLASSES = {
|
7
7
|
:single => {
|
8
8
|
:class => ReferenceType,
|
9
9
|
:primary_keys => :reference_type_id,
|
10
10
|
},
|
11
|
-
:dual => {
|
11
|
+
:dual => {
|
12
12
|
:class => ReferenceCode,
|
13
13
|
:primary_keys => [:reference_type_id, :reference_code],
|
14
14
|
},
|
15
15
|
}
|
16
|
-
|
16
|
+
|
17
17
|
def setup
|
18
18
|
self.class.classes = CLASSES
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def test_brackets
|
22
|
+
tested_at_least_on_attribute = false
|
22
23
|
testing_with do
|
23
24
|
@first.attributes.each_pair do |attr_name, value|
|
25
|
+
next if value.nil?
|
24
26
|
assert_equal value, @first[attr_name]
|
27
|
+
tested_at_least_on_attribute = true
|
25
28
|
end
|
26
29
|
end
|
30
|
+
assert tested_at_least_on_attribute
|
27
31
|
end
|
28
32
|
|
29
33
|
def test_brackets_primary_key
|
@@ -49,6 +53,17 @@ class TestAttributes < ActiveSupport::TestCase
|
|
49
53
|
compare_indexes(tarrif, tarrif.class.primary_key, product_tariff, [:tariff_id, :tariff_start_date])
|
50
54
|
end
|
51
55
|
|
56
|
+
def test_has_attribute
|
57
|
+
tariff = tariffs(:flat)
|
58
|
+
assert(tariff.has_attribute?([:tariff_id, :start_date]))
|
59
|
+
assert(tariff.has_attribute?(['tariff_id', 'start_date']))
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_has__attribute
|
63
|
+
tariff = tariffs(:flat)
|
64
|
+
assert(tariff._has_attribute?(['tariff_id', 'start_date']))
|
65
|
+
end
|
66
|
+
|
52
67
|
private
|
53
68
|
|
54
69
|
def compare_indexes(obj1, indexes1, obj2, indexes2)
|
data/test/test_calculations.rb
CHANGED
@@ -3,7 +3,7 @@ require File.expand_path('../abstract_unit', __FILE__)
|
|
3
3
|
class TestCalculations < ActiveSupport::TestCase
|
4
4
|
fixtures :articles, :products, :tariffs, :product_tariffs, :suburbs, :streets, :restaurants,
|
5
5
|
:dorms, :rooms, :room_attributes, :room_attribute_assignments, :students, :room_assignments, :users, :readings,
|
6
|
-
:departments, :employees, :memberships, :membership_statuses
|
6
|
+
:departments, :employees, :memberships, :membership_statuses, :reference_codes, :reference_types
|
7
7
|
|
8
8
|
def test_count
|
9
9
|
assert_equal(3, Product.includes(:product_tariffs).count)
|
@@ -19,7 +19,14 @@ class TestCalculations < ActiveSupport::TestCase
|
|
19
19
|
product = products(:first_product)
|
20
20
|
assert_equal(1, product.product_tariffs.select('tariff_start_date').distinct.count)
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
|
+
def test_count_on_joined_relations_that_have_column_names_in_common
|
24
|
+
count_without_includes = ReferenceCode.count
|
25
|
+
count_with_includes = ReferenceCode.includes(:reference_type).references(:reference_type).count
|
26
|
+
assert_equal(count_without_includes, count_with_includes)
|
27
|
+
assert_equal(5, count_with_includes)
|
28
|
+
end
|
29
|
+
|
23
30
|
def test_count_not_distinct
|
24
31
|
product = products(:first_product)
|
25
32
|
assert_equal(2, product.product_tariffs.select('tariff_start_date').count)
|
data/test/test_delete.rb
CHANGED
@@ -33,13 +33,13 @@ class TestDelete < ActiveSupport::TestCase
|
|
33
33
|
def test_delete_all_with_join
|
34
34
|
employee = employees(:mindy)
|
35
35
|
|
36
|
-
assert_equal(
|
36
|
+
assert_equal(5, Department.count)
|
37
37
|
|
38
38
|
deleted = Department.joins(:employees).
|
39
39
|
where('employees.name = ?', employee.name).
|
40
40
|
delete_all
|
41
41
|
|
42
|
-
assert_equal(
|
42
|
+
assert_equal(4, Department.count)
|
43
43
|
assert_equal(1, deleted)
|
44
44
|
end
|
45
45
|
|
data/test/test_find.rb
CHANGED
@@ -129,13 +129,19 @@ class TestFind < ActiveSupport::TestCase
|
|
129
129
|
def test_find_by_all_associations
|
130
130
|
departments = Department.all
|
131
131
|
employees = Employee.where(:department => departments)
|
132
|
-
assert_equal(
|
132
|
+
assert_equal(6, employees.to_a.count)
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_find_by_some_associations
|
136
|
+
departments = Department.where(location_id: 1)
|
137
|
+
employees = Employee.where(:department => departments)
|
138
|
+
assert_equal(4, employees.to_a.count)
|
133
139
|
end
|
134
140
|
|
135
141
|
def test_expand_all
|
136
142
|
departments = Department.all
|
137
143
|
employees = Employee.where(:department => departments)
|
138
|
-
assert_equal(
|
144
|
+
assert_equal(6, employees.count)
|
139
145
|
end
|
140
146
|
|
141
147
|
def test_find_one_with_params_id
|
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: 13.0.0
|
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: 6.
|
19
|
+
version: 6.1.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: 6.
|
26
|
+
version: 6.1.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,6 +87,7 @@ files:
|
|
87
87
|
- lib/composite_primary_keys/relation/query_methods.rb
|
88
88
|
- lib/composite_primary_keys/relation/where_clause.rb
|
89
89
|
- lib/composite_primary_keys/sanitization.rb
|
90
|
+
- lib/composite_primary_keys/table_metadata.rb
|
90
91
|
- lib/composite_primary_keys/transactions.rb
|
91
92
|
- lib/composite_primary_keys/validations/uniqueness.rb
|
92
93
|
- lib/composite_primary_keys/version.rb
|