composite_primary_keys 7.0.16 → 8.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.
- checksums.yaml +4 -4
- data/History.rdoc +600 -623
- data/lib/composite_primary_keys.rb +113 -115
- data/lib/composite_primary_keys/associations/association.rb +23 -23
- data/lib/composite_primary_keys/associations/association_scope.rb +73 -77
- data/lib/composite_primary_keys/associations/collection_association.rb +15 -0
- data/lib/composite_primary_keys/associations/has_many_association.rb +69 -56
- data/lib/composite_primary_keys/associations/has_many_through_association.rb +30 -28
- data/lib/composite_primary_keys/associations/join_dependency.rb +87 -89
- data/lib/composite_primary_keys/associations/join_dependency/join_association.rb +22 -22
- data/lib/composite_primary_keys/associations/preloader/association.rb +90 -78
- data/lib/composite_primary_keys/associations/preloader/belongs_to.rb +19 -19
- data/lib/composite_primary_keys/associations/singular_association.rb +15 -0
- data/lib/composite_primary_keys/attribute_methods.rb +9 -0
- data/lib/composite_primary_keys/attribute_methods/dirty.rb +29 -26
- data/lib/composite_primary_keys/attribute_methods/read.rb +19 -34
- data/lib/composite_primary_keys/attribute_methods/write.rb +30 -36
- data/lib/composite_primary_keys/attribute_set/builder.rb +20 -0
- data/lib/composite_primary_keys/base.rb +135 -129
- data/lib/composite_primary_keys/composite_arrays.rb +30 -30
- data/lib/composite_primary_keys/composite_predicates.rb +50 -50
- data/lib/composite_primary_keys/composite_relation.rb +48 -48
- data/lib/composite_primary_keys/connection_adapters/abstract/connection_specification_changes.rb +2 -4
- data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +46 -60
- data/lib/composite_primary_keys/core.rb +69 -47
- data/lib/composite_primary_keys/fixtures.rb +22 -22
- data/lib/composite_primary_keys/persistence.rb +56 -60
- data/lib/composite_primary_keys/relation.rb +68 -56
- data/lib/composite_primary_keys/relation/calculations.rb +79 -75
- data/lib/composite_primary_keys/relation/finder_methods.rb +175 -196
- data/lib/composite_primary_keys/relation/query_methods.rb +40 -40
- data/lib/composite_primary_keys/sanitization.rb +52 -52
- data/lib/composite_primary_keys/validations/uniqueness.rb +36 -37
- data/lib/composite_primary_keys/version.rb +8 -8
- data/tasks/databases/oracle.rake +25 -25
- data/tasks/databases/sqlserver.rake +27 -40
- data/test/abstract_unit.rb +113 -113
- data/test/connections/databases.ci.yml +15 -15
- data/test/connections/databases.example.yml +18 -18
- data/test/connections/native_oracle/connection.rb +11 -11
- data/test/connections/native_oracle_enhanced/connection.rb +16 -16
- data/test/connections/native_sqlserver/connection.rb +11 -14
- data/test/fixtures/comment.rb +7 -7
- data/test/fixtures/db_definitions/db2-create-tables.sql +125 -126
- data/test/fixtures/db_definitions/db2-drop-tables.sql +18 -18
- data/test/fixtures/db_definitions/mysql.sql +207 -208
- data/test/fixtures/db_definitions/oracle.drop.sql +45 -45
- data/test/fixtures/db_definitions/oracle.sql +222 -223
- data/test/fixtures/db_definitions/postgresql.sql +209 -210
- data/test/fixtures/db_definitions/sqlite.sql +196 -197
- data/test/fixtures/db_definitions/sqlserver.drop.sql +91 -94
- data/test/fixtures/db_definitions/sqlserver.sql +225 -232
- data/test/fixtures/dorm.rb +2 -2
- data/test/fixtures/employee.rb +5 -5
- data/test/fixtures/membership.rb +6 -6
- data/test/fixtures/membership_statuses.yml +16 -16
- data/test/fixtures/memberships.yml +10 -10
- data/test/fixtures/product_tariffs.yml +14 -14
- data/test/fixtures/reference_code.rb +7 -7
- data/test/fixtures/restaurants_suburb.rb +2 -2
- data/test/fixtures/suburb.rb +5 -5
- data/test/fixtures/topic.rb +5 -5
- data/test/fixtures/topic_source.rb +6 -6
- data/test/fixtures/topic_sources.yml +3 -3
- data/test/fixtures/topics.yml +8 -8
- data/test/fixtures/users.yml +10 -10
- data/test/test_associations.rb +295 -275
- data/test/test_attribute_methods.rb +63 -63
- data/test/test_attributes.rb +60 -60
- data/test/test_calculations.rb +37 -42
- data/test/test_callbacks.rb +99 -99
- data/test/test_create.rb +112 -112
- data/test/test_delete.rb +148 -152
- data/test/test_delete_all.rb +28 -26
- data/test/test_dumpable.rb +15 -15
- data/test/test_enum.rb +21 -20
- data/test/test_equal.rb +26 -26
- data/test/test_find.rb +118 -118
- data/test/test_habtm.rb +113 -113
- data/test/test_nested_attributes.rb +124 -124
- data/test/test_polymorphic.rb +26 -26
- data/test/test_predicates.rb +40 -40
- data/test/test_santiago.rb +23 -23
- data/test/test_suite.rb +33 -34
- data/test/test_touch.rb +23 -23
- data/test/test_tutorial_example.rb +21 -21
- data/test/test_update.rb +71 -71
- metadata +9 -13
- data/lib/composite_primary_keys/arel/visitors/to_sql.rb +0 -20
- data/lib/composite_primary_keys/associations/has_and_belongs_to_many_association.rb +0 -59
- data/lib/composite_primary_keys/associations/join_dependency/join_part.rb +0 -39
- data/lib/composite_primary_keys/associations/preloader/has_and_belongs_to_many.rb +0 -46
- data/lib/composite_primary_keys/connection_adapters/sqlserver_adapter.rb +0 -17
- data/lib/composite_primary_keys/locking/optimistic.rb +0 -55
- data/test/test_optimistic.rb +0 -18
@@ -16,12 +16,7 @@ module ActiveRecord
|
|
16
16
|
def delete_records(records, method)
|
17
17
|
ensure_not_nested
|
18
18
|
|
19
|
-
# This is unoptimised; it will load all the target records
|
20
|
-
# even when we just want to delete everything.
|
21
|
-
records = load_target if records == :all
|
22
|
-
|
23
19
|
scope = through_association.scope
|
24
|
-
|
25
20
|
# CPK
|
26
21
|
# scope.where! construct_join_attributes(*records)
|
27
22
|
if source_reflection.klass.composite?
|
@@ -31,26 +26,26 @@ module ActiveRecord
|
|
31
26
|
end
|
32
27
|
|
33
28
|
case method
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
29
|
+
when :destroy
|
30
|
+
if scope.klass.primary_key
|
31
|
+
count = scope.destroy_all.length
|
32
|
+
else
|
33
|
+
scope.to_a.each do |record|
|
34
|
+
record.run_callbacks :destroy
|
35
|
+
end
|
41
36
|
|
42
|
-
|
37
|
+
arel = scope.arel
|
43
38
|
|
44
|
-
|
45
|
-
|
46
|
-
|
39
|
+
stmt = Arel::DeleteManager.new arel.engine
|
40
|
+
stmt.from scope.klass.arel_table
|
41
|
+
stmt.wheres = arel.constraints
|
47
42
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
43
|
+
count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values)
|
44
|
+
end
|
45
|
+
when :nullify
|
46
|
+
count = scope.update_all(source_reflection.foreign_key => nil)
|
47
|
+
else
|
48
|
+
count = scope.delete_all
|
54
49
|
end
|
55
50
|
|
56
51
|
delete_through_records(records)
|
@@ -60,7 +55,7 @@ module ActiveRecord
|
|
60
55
|
klass.decrement_counter counter, records.map(&:id)
|
61
56
|
end
|
62
57
|
|
63
|
-
if through_reflection.
|
58
|
+
if through_reflection.collection? && update_through_counter?(method)
|
64
59
|
update_counter(-count, through_reflection)
|
65
60
|
end
|
66
61
|
|
@@ -69,17 +64,24 @@ module ActiveRecord
|
|
69
64
|
|
70
65
|
def through_records_for(record)
|
71
66
|
# CPK
|
72
|
-
#attributes = construct_join_attributes(record)
|
73
|
-
#candidates = Array.wrap(through_association.target)
|
74
|
-
#candidates.find_all
|
75
|
-
|
67
|
+
# attributes = construct_join_attributes(record)
|
68
|
+
# candidates = Array.wrap(through_association.target)
|
69
|
+
# candidates.find_all do |c|
|
70
|
+
# attributes.all? do |key, value|
|
71
|
+
# c.public_send(key) == value
|
72
|
+
# end
|
73
|
+
# end
|
76
74
|
if record.composite?
|
77
75
|
candidates = Array.wrap(through_association.target)
|
78
76
|
candidates.find_all { |c| c.attributes.slice(*source_reflection.association_primary_key) == record.ids_hash }
|
79
77
|
else
|
80
78
|
attributes = construct_join_attributes(record)
|
81
79
|
candidates = Array.wrap(through_association.target)
|
82
|
-
candidates.find_all
|
80
|
+
candidates.find_all do |c|
|
81
|
+
attributes.all? do |key, value|
|
82
|
+
c.public_send(key) == value
|
83
|
+
end
|
84
|
+
end
|
83
85
|
end
|
84
86
|
end
|
85
87
|
|
@@ -1,89 +1,87 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Associations
|
3
|
-
class JoinDependency
|
4
|
-
class Aliases # :nodoc:
|
5
|
-
def column_alias(node, column)
|
6
|
-
# CPK
|
7
|
-
#@alias_cache[node][column]
|
8
|
-
if column.kind_of?(Array)
|
9
|
-
column.map do |a_column|
|
10
|
-
@alias_cache[node][a_column]
|
11
|
-
end
|
12
|
-
else
|
13
|
-
@alias_cache[node][column]
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def instantiate(result_set, aliases)
|
19
|
-
primary_key = aliases.column_alias(join_root, join_root.primary_key)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
model
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
model
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
89
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency
|
4
|
+
class Aliases # :nodoc:
|
5
|
+
def column_alias(node, column)
|
6
|
+
# CPK
|
7
|
+
#@alias_cache[node][column]
|
8
|
+
if column.kind_of?(Array)
|
9
|
+
column.map do |a_column|
|
10
|
+
@alias_cache[node][a_column]
|
11
|
+
end
|
12
|
+
else
|
13
|
+
@alias_cache[node][column]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def instantiate(result_set, aliases)
|
19
|
+
primary_key = aliases.column_alias(join_root, join_root.primary_key)
|
20
|
+
|
21
|
+
seen = Hash.new { |h,parent_klass|
|
22
|
+
h[parent_klass] = Hash.new { |i,parent_id|
|
23
|
+
i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} }
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
model_cache = Hash.new { |h,klass| h[klass] = {} }
|
28
|
+
parents = model_cache[join_root]
|
29
|
+
column_aliases = aliases.column_aliases join_root
|
30
|
+
|
31
|
+
result_set.each { |row_hash|
|
32
|
+
# CPK
|
33
|
+
primary_id = if primary_key.kind_of?(Array)
|
34
|
+
primary_key.map {|key| row_hash[key]}
|
35
|
+
else
|
36
|
+
row_hash[primary_key]
|
37
|
+
end
|
38
|
+
parent = parents[primary_id] ||= join_root.instantiate(row_hash, column_aliases)
|
39
|
+
construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
|
40
|
+
}
|
41
|
+
|
42
|
+
parents.values
|
43
|
+
end
|
44
|
+
|
45
|
+
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
|
46
|
+
primary_id = ar_parent.id
|
47
|
+
|
48
|
+
parent.children.each do |node|
|
49
|
+
if node.reflection.collection?
|
50
|
+
other = ar_parent.association(node.reflection.name)
|
51
|
+
other.loaded!
|
52
|
+
else
|
53
|
+
if ar_parent.association_cache.key?(node.reflection.name)
|
54
|
+
model = ar_parent.association(node.reflection.name).target
|
55
|
+
construct(model, node, row, rs, seen, model_cache, aliases)
|
56
|
+
next
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
key = aliases.column_alias(node, node.primary_key)
|
61
|
+
|
62
|
+
# CPK
|
63
|
+
if key.is_a?(Array)
|
64
|
+
id = Array(key).map do |column_alias|
|
65
|
+
value = row[column_alias]
|
66
|
+
end
|
67
|
+
# At least the first value in the key has to be set. Should we require all values to be set?
|
68
|
+
next if id.first.nil?
|
69
|
+
else
|
70
|
+
id = row[key]
|
71
|
+
next if id.nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
model = seen[parent.base_klass][primary_id][node.base_klass][id]
|
75
|
+
|
76
|
+
if model
|
77
|
+
construct(model, node, row, rs, seen, model_cache, aliases)
|
78
|
+
else
|
79
|
+
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
|
80
|
+
seen[parent.base_klass][primary_id][node.base_klass][id] = model
|
81
|
+
construct(model, node, row, rs, seen, model_cache, aliases)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -1,22 +1,22 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Associations
|
3
|
-
class JoinDependency
|
4
|
-
class JoinAssociation
|
5
|
-
def build_constraint(klass, table, key, foreign_table, foreign_key)
|
6
|
-
# CPK
|
7
|
-
# constraint = table[key].eq(foreign_table[foreign_key])
|
8
|
-
constraint = cpk_join_predicate(table, key, foreign_table, foreign_key)
|
9
|
-
|
10
|
-
if klass.finder_needs_type_condition?
|
11
|
-
constraint = table.create_and([
|
12
|
-
constraint,
|
13
|
-
klass.send(:type_condition, table)
|
14
|
-
])
|
15
|
-
end
|
16
|
-
|
17
|
-
constraint
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency
|
4
|
+
class JoinAssociation
|
5
|
+
def build_constraint(klass, table, key, foreign_table, foreign_key)
|
6
|
+
# CPK
|
7
|
+
# constraint = table[key].eq(foreign_table[foreign_key])
|
8
|
+
constraint = cpk_join_predicate(table, key, foreign_table, foreign_key)
|
9
|
+
|
10
|
+
if klass.finder_needs_type_condition?
|
11
|
+
constraint = table.create_and([
|
12
|
+
constraint,
|
13
|
+
klass.send(:type_condition, table)
|
14
|
+
])
|
15
|
+
end
|
16
|
+
|
17
|
+
constraint
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,78 +1,90 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Associations
|
3
|
-
class Preloader
|
4
|
-
class Association
|
5
|
-
def query_scope(ids)
|
6
|
-
# CPK
|
7
|
-
# scope.where(association_key.in(ids))
|
8
|
-
|
9
|
-
if reflection.foreign_key.is_a?(Array)
|
10
|
-
predicate = cpk_in_predicate(table, reflection.foreign_key, ids)
|
11
|
-
scope.where(predicate)
|
12
|
-
else
|
13
|
-
scope.where(association_key.in(ids))
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def associated_records_by_owner(preloader)
|
18
|
-
|
19
|
-
|
20
|
-
#owner_keys = owners_map.keys.compact
|
21
|
-
owner_keys =
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
@preloaded_records.
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
[record,
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class Association
|
5
|
+
def query_scope(ids)
|
6
|
+
# CPK
|
7
|
+
# scope.where(association_key.in(ids))
|
8
|
+
|
9
|
+
if reflection.foreign_key.is_a?(Array)
|
10
|
+
predicate = cpk_in_predicate(table, reflection.foreign_key, ids)
|
11
|
+
scope.where(predicate)
|
12
|
+
else
|
13
|
+
scope.where(association_key.in(ids))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def associated_records_by_owner(preloader)
|
18
|
+
owners_map = owners_by_key
|
19
|
+
# CPK
|
20
|
+
# owner_keys = owners_map.keys.compact
|
21
|
+
owner_keys = if reflection.foreign_key.is_a?(Array)
|
22
|
+
owners.map do |owner|
|
23
|
+
Array(owner_key_name).map do |owner_key|
|
24
|
+
owner[owner_key]
|
25
|
+
end
|
26
|
+
end.compact.uniq
|
27
|
+
else
|
28
|
+
owners_map.keys.compact
|
29
|
+
end
|
30
|
+
|
31
|
+
# Each record may have multiple owners, and vice-versa
|
32
|
+
records_by_owner = owners.each_with_object({}) do |owner,h|
|
33
|
+
h[owner] = []
|
34
|
+
end
|
35
|
+
|
36
|
+
if owner_keys.any?
|
37
|
+
# Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
|
38
|
+
# Make several smaller queries if necessary or make one query if the adapter supports it
|
39
|
+
sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
|
40
|
+
|
41
|
+
records = load_slices sliced
|
42
|
+
records.each do |record, owner_key|
|
43
|
+
owners_map[owner_key].each do |owner|
|
44
|
+
records_by_owner[owner] << record
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
records_by_owner
|
50
|
+
end
|
51
|
+
|
52
|
+
def load_slices(slices)
|
53
|
+
@preloaded_records = slices.flat_map { |slice|
|
54
|
+
records_for(slice)
|
55
|
+
}
|
56
|
+
|
57
|
+
@preloaded_records.map { |record|
|
58
|
+
# CPK
|
59
|
+
#[record, record[association_key_name]]
|
60
|
+
owner_key = Array(association_key_name).map do |key_name|
|
61
|
+
record[key_name]
|
62
|
+
end.join(CompositePrimaryKeys::ID_SEP)
|
63
|
+
[record, owner_key]
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def owners_by_key
|
68
|
+
@owners_by_key ||= if key_conversion_required?
|
69
|
+
owners.group_by do |owner|
|
70
|
+
# CPK
|
71
|
+
# owner[owner_key_name].to_s
|
72
|
+
key = Array(owner_key_name).map do |key_name|
|
73
|
+
owner[key_name]
|
74
|
+
end.join(CompositePrimaryKeys::ID_SEP)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
owners.group_by do |owner|
|
78
|
+
# CPK
|
79
|
+
# owner[owner_key_name]
|
80
|
+
key = Array(owner_key_name).map do |key_name|
|
81
|
+
owner[key_name]
|
82
|
+
end.join(CompositePrimaryKeys::ID_SEP)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|