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