composite_primary_keys 13.0.3 → 14.0.1

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.
@@ -1,53 +1,68 @@
1
- module ActiveRecord
2
- module Associations
3
- class Preloader
4
- class Association
5
- def records_for(ids)
6
- records = if association_key_name.is_a?(Array)
7
- predicate = cpk_in_predicate(klass.arel_table, association_key_name, ids)
8
- scope.where(predicate)
9
- else
10
- scope.where(association_key_name => ids)
11
- end
12
- records.load do |record|
13
- # Processing only the first owner
14
- # because the record is modified but not an owner
15
- owner = owners_by_key[convert_key(record[association_key_name])].first
16
- association = owner.association(reflection.name)
17
- association.set_inverse_instance(record)
18
- end
19
- end
20
-
21
- def owners_by_key
22
- @owners_by_key ||= owners.each_with_object({}) do |owner, result|
23
- # CPK
24
- # key = convert_key(owner[owner_key_name])
25
- key = if owner_key_name.is_a?(Array)
26
- Array(owner_key_name).map do |key_name|
27
- convert_key(owner[key_name])
28
- end
29
- else
30
- convert_key(owner[owner_key_name])
31
- end
32
- (result[key] ||= []) << owner if key
33
- end
34
- end
35
-
36
- def records_by_owner
37
- @records_by_owner ||= preloaded_records.each_with_object({}) do |record, result|
38
- key = if association_key_name.is_a?(Array)
39
- Array(record[association_key_name]).map do |key|
40
- convert_key(key)
41
- end
42
- else
43
- convert_key(record[association_key_name])
44
- end
45
- owners_by_key[key].each do |owner|
46
- (result[owner] ||= []) << record
47
- end
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
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
19
+ def records_for(ids)
20
+ records = if association_key_name.is_a?(Array)
21
+ predicate = cpk_in_predicate(klass.arel_table, association_key_name, ids)
22
+ scope.where(predicate)
23
+ else
24
+ scope.where(association_key_name => ids)
25
+ end
26
+ records.load do |record|
27
+ # Processing only the first owner
28
+ # because the record is modified but not an owner
29
+ owner = owners_by_key[convert_key(record[association_key_name])].first
30
+ association = owner.association(reflection.name)
31
+ association.set_inverse_instance(record)
32
+ end
33
+ end
34
+
35
+ def owners_by_key
36
+ @owners_by_key ||= owners.each_with_object({}) do |owner, result|
37
+ # CPK
38
+ # key = convert_key(owner[owner_key_name])
39
+ key = if owner_key_name.is_a?(Array)
40
+ Array(owner_key_name).map do |key_name|
41
+ convert_key(owner[key_name])
42
+ end
43
+ else
44
+ convert_key(owner[owner_key_name])
45
+ end
46
+ (result[key] ||= []) << owner if key
47
+ end
48
+ end
49
+
50
+ # TODO: is records_by_owner needed anymore? Rails' implementation has changed significantly
51
+ def records_by_owner
52
+ @records_by_owner ||= preloaded_records.each_with_object({}) do |record, result|
53
+ key = if association_key_name.is_a?(Array)
54
+ Array(record[association_key_name]).map do |key|
55
+ convert_key(key)
56
+ end
57
+ else
58
+ convert_key(record[association_key_name])
59
+ end
60
+ owners_by_key[key].each do |owner|
61
+ (result[owner] ||= []) << record
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,24 +1,25 @@
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
+ 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
+ is_composite = self.source_reflection.polymorphic? ? source_reflection.active_record.composite? : source_reflection.klass.composite?
9
+ if is_composite
10
+ ensure_mutable
11
+
12
+ ids = records.map do |record|
13
+ source_reflection.association_primary_key(reflection.klass).map do |key|
14
+ record.send(key)
15
+ end
16
+ end
17
+
18
+ cpk_in_predicate(through_association.scope.klass.arel_table, source_reflection.foreign_key, ids)
19
+ else
20
+ original_construct_join_attributes(*records)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,137 +1,141 @@
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
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
+ def to_param
137
+ persisted? ? to_key.to_composite_keys.to_s : nil
138
+ end
139
+ end
140
+ end
141
+ end
@@ -1,70 +1,71 @@
1
- module CompositePrimaryKeys
2
- module Predicates
3
- # Similar to module_function, but does not make instance methods private.
4
- # https://idiosyncratic-ruby.com/8-self-improvement.html
5
- extend self
6
-
7
- def cpk_and_predicate(predicates)
8
- if predicates.length == 1
9
- predicates.first
10
- else
11
- Arel::Nodes::And.new(predicates)
12
- end
13
- end
14
-
15
- def cpk_or_predicate(predicates, group = true)
16
- if predicates.length <= 1
17
- predicates.first
18
- else
19
- split_point = predicates.length / 2
20
- predicates_first_half = predicates[0...split_point]
21
- predicates_second_half = predicates[split_point..-1]
22
-
23
- or_predicate = ::Arel::Nodes::Or.new(cpk_or_predicate(predicates_first_half, false),
24
- cpk_or_predicate(predicates_second_half, false))
25
-
26
- if group
27
- ::Arel::Nodes::Grouping.new(or_predicate)
28
- else
29
- or_predicate
30
- end
31
- end
32
- end
33
-
34
- def cpk_id_predicate(table, keys, values)
35
- # We zip on values then keys in case values are not provided for each key field
36
- eq_predicates = values.zip(keys).map do |value, key|
37
- table[key].eq(value)
38
- end
39
- cpk_and_predicate(eq_predicates)
40
- end
41
-
42
- def cpk_join_predicate(table1, key1, table2, key2)
43
- key1_fields = Array(key1).map {|key| table1[key]}
44
- key2_fields = Array(key2).map {|key| table2[key]}
45
-
46
- eq_predicates = key1_fields.zip(key2_fields).map do |key_field1, key_field2|
47
- key_field2 = Arel::Nodes::Quoted.new(key_field2) unless Arel::Attributes::Attribute === key_field2
48
- key_field1.eq(key_field2)
49
- end
50
- cpk_and_predicate(eq_predicates)
51
- end
52
-
53
- def cpk_in_predicate(table, primary_keys, ids)
54
- and_predicates = ids.map do |id|
55
- cpk_id_predicate(table, primary_keys, id)
56
- end
57
- cpk_or_predicate(and_predicates)
58
- end
59
- end
60
- end
61
-
62
- ActiveRecord::Associations::AssociationScope.send(:include, CompositePrimaryKeys::Predicates)
63
- ActiveRecord::Associations::JoinDependency::JoinAssociation.send(:include, CompositePrimaryKeys::Predicates)
64
- ActiveRecord::Associations::Preloader::Association.send(:include, CompositePrimaryKeys::Predicates)
65
- ActiveRecord::Associations::HasManyAssociation.send(:include, CompositePrimaryKeys::Predicates)
66
- ActiveRecord::Associations::HasManyThroughAssociation.send(:include, CompositePrimaryKeys::Predicates)
67
- ActiveRecord::Base.send(:extend, CompositePrimaryKeys::Predicates)
68
- ActiveRecord::Reflection::AbstractReflection.send(:include, CompositePrimaryKeys::Predicates)
69
- ActiveRecord::Relation.send(:include, CompositePrimaryKeys::Predicates)
70
- ActiveRecord::PredicateBuilder.send(:extend, CompositePrimaryKeys::Predicates)
1
+ module CompositePrimaryKeys
2
+ module Predicates
3
+ # Similar to module_function, but does not make instance methods private.
4
+ # https://idiosyncratic-ruby.com/8-self-improvement.html
5
+ extend self
6
+
7
+ def cpk_and_predicate(predicates)
8
+ if predicates.length == 1
9
+ predicates.first
10
+ else
11
+ Arel::Nodes::And.new(predicates)
12
+ end
13
+ end
14
+
15
+ def cpk_or_predicate(predicates, group = true)
16
+ if predicates.length <= 1
17
+ predicates.first
18
+ else
19
+ split_point = predicates.length / 2
20
+ predicates_first_half = predicates[0...split_point]
21
+ predicates_second_half = predicates[split_point..-1]
22
+
23
+ or_predicate = ::Arel::Nodes::Or.new(cpk_or_predicate(predicates_first_half, false),
24
+ cpk_or_predicate(predicates_second_half, false))
25
+
26
+ if group
27
+ ::Arel::Nodes::Grouping.new(or_predicate)
28
+ else
29
+ or_predicate
30
+ end
31
+ end
32
+ end
33
+
34
+ def cpk_id_predicate(table, keys, values)
35
+ # We zip on values then keys in case values are not provided for each key field
36
+ eq_predicates = values.zip(keys).map do |value, key|
37
+ table[key].eq(value)
38
+ end
39
+ cpk_and_predicate(eq_predicates)
40
+ end
41
+
42
+ def cpk_join_predicate(table1, key1, table2, key2)
43
+ key1_fields = Array(key1).map {|key| table1[key]}
44
+ key2_fields = Array(key2).map {|key| table2[key]}
45
+
46
+ eq_predicates = key1_fields.zip(key2_fields).map do |key_field1, key_field2|
47
+ key_field2 = Arel::Nodes::Quoted.new(key_field2) unless Arel::Attributes::Attribute === key_field2
48
+ key_field1.eq(key_field2)
49
+ end
50
+ cpk_and_predicate(eq_predicates)
51
+ end
52
+
53
+ def cpk_in_predicate(table, primary_keys, ids)
54
+ and_predicates = ids.map do |id|
55
+ cpk_id_predicate(table, primary_keys, id)
56
+ end
57
+ cpk_or_predicate(and_predicates)
58
+ end
59
+ end
60
+ end
61
+
62
+ ActiveRecord::Associations::AssociationScope.send(:include, CompositePrimaryKeys::Predicates)
63
+ ActiveRecord::Associations::JoinDependency::JoinAssociation.send(:include, CompositePrimaryKeys::Predicates)
64
+ ActiveRecord::Associations::Preloader::Association.send(:include, CompositePrimaryKeys::Predicates)
65
+ ActiveRecord::Associations::Preloader::Association::LoaderQuery.send(:include, CompositePrimaryKeys::Predicates)
66
+ ActiveRecord::Associations::HasManyAssociation.send(:include, CompositePrimaryKeys::Predicates)
67
+ ActiveRecord::Associations::HasManyThroughAssociation.send(:include, CompositePrimaryKeys::Predicates)
68
+ ActiveRecord::Base.send(:extend, CompositePrimaryKeys::Predicates)
69
+ ActiveRecord::Reflection::AbstractReflection.send(:include, CompositePrimaryKeys::Predicates)
70
+ ActiveRecord::Relation.send(:include, CompositePrimaryKeys::Predicates)
71
+ ActiveRecord::PredicateBuilder.send(:extend, CompositePrimaryKeys::Predicates)