composite_primary_keys 14.0.7 → 14.0.8

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,60 +1,60 @@
1
- module ActiveRecord
2
- module AutosaveAssociation
3
- def save_has_one_association(reflection)
4
- association = association_instance_get(reflection.name)
5
- record = association && association.load_target
6
-
7
- if record && !record.destroyed?
8
- autosave = reflection.options[:autosave]
9
-
10
- if autosave && record.marked_for_destruction?
11
- record.destroy
12
- elsif autosave != false
13
- # CPK
14
- #key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
15
- key = reflection.options[:primary_key] ? self[reflection.options[:primary_key]] : id
16
-
17
- if (autosave && record.changed_for_autosave?) || new_record? || _record_changed?(reflection, record, key)
18
- unless reflection.through_reflection
19
- record[reflection.foreign_key] = key
20
- if inverse_reflection = reflection.inverse_of
21
- record.association(inverse_reflection.name).loaded!
22
- end
23
- end
24
-
25
- saved = record.save(validate: !autosave)
26
- raise ActiveRecord::Rollback if !saved && autosave
27
- saved
28
- end
29
- end
30
- end
31
- end
32
-
33
- def save_belongs_to_association(reflection)
34
- association = association_instance_get(reflection.name)
35
- return unless association && association.loaded? && !association.stale_target?
36
-
37
- record = association.load_target
38
- if record && !record.destroyed?
39
- autosave = reflection.options[:autosave]
40
-
41
- if autosave && record.marked_for_destruction?
42
- self[reflection.foreign_key] = nil
43
- record.destroy
44
- elsif autosave != false
45
- saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
46
-
47
- if association.updated?
48
- # CPK
49
- # association_id = record.send(reflection.options[:primary_key] || :id)
50
- association_id = reflection.options[:primary_key] ? record[reflection.options[:primary_key]] : record.id
51
- self[reflection.foreign_key] = association_id
52
- association.loaded!
53
- end
54
-
55
- saved if autosave
56
- end
57
- end
58
- end
59
- end
60
- end
1
+ module ActiveRecord
2
+ module AutosaveAssociation
3
+ def save_has_one_association(reflection)
4
+ association = association_instance_get(reflection.name)
5
+ record = association && association.load_target
6
+
7
+ if record && !record.destroyed?
8
+ autosave = reflection.options[:autosave]
9
+
10
+ if autosave && record.marked_for_destruction?
11
+ record.destroy
12
+ elsif autosave != false
13
+ # CPK
14
+ #key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
15
+ key = reflection.options[:primary_key] ? self[reflection.options[:primary_key]] : id
16
+
17
+ if (autosave && record.changed_for_autosave?) || new_record? || _record_changed?(reflection, record, key)
18
+ unless reflection.through_reflection
19
+ record[reflection.foreign_key] = key
20
+ if inverse_reflection = reflection.inverse_of
21
+ record.association(inverse_reflection.name).loaded!
22
+ end
23
+ end
24
+
25
+ saved = record.save(validate: !autosave)
26
+ raise ActiveRecord::Rollback if !saved && autosave
27
+ saved
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def save_belongs_to_association(reflection)
34
+ association = association_instance_get(reflection.name)
35
+ return unless association && association.loaded? && !association.stale_target?
36
+
37
+ record = association.load_target
38
+ if record && !record.destroyed?
39
+ autosave = reflection.options[:autosave]
40
+
41
+ if autosave && record.marked_for_destruction?
42
+ self[reflection.foreign_key] = nil
43
+ record.destroy
44
+ elsif autosave != false
45
+ saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
46
+
47
+ if association.updated?
48
+ # CPK
49
+ # association_id = record.send(reflection.options[:primary_key] || :id)
50
+ association_id = reflection.options[:primary_key] ? record[reflection.options[:primary_key]] : record.id
51
+ self[reflection.foreign_key] = association_id
52
+ association.loaded!
53
+ end
54
+
55
+ saved if autosave
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,88 +1,88 @@
1
- module CompositePrimaryKeys
2
- ID_SEP = ','
3
- ID_SET_SEP = ';'
4
- ESCAPE_CHAR = '^'
5
-
6
- module ArrayExtension
7
- def to_composite_keys
8
- CompositeKeys.new(self)
9
- end
10
- end
11
-
12
- # Convert mixed representation of CPKs (by strings or arrays) to normalized
13
- # representation (just by arrays).
14
- #
15
- # `ids` is Array that may contain:
16
- # 1. A CPK represented by an array or a string.
17
- # 2. An array of CPKs represented by arrays or strings.
18
- #
19
- # There is an issue. Let `ids` contain an array with serveral strings. We can't distinguish case 1
20
- # from case 2 there in general. E.g. the item can be an array containing appropriate number of strings,
21
- # and each string can contain appropriate number of commas. We consider case 2 to win there.
22
- def self.normalize(ids, cpk_size)
23
- ids.map do |id|
24
- if Utils.cpk_as_array?(id, cpk_size) && id.any? { |item| !Utils.cpk_as_string?(item, cpk_size) }
25
- # CPK as an array - case 1
26
- id
27
- elsif id.is_a?(Array)
28
- # An array of CPKs - case 2
29
- normalize(id, cpk_size)
30
- elsif id.is_a?(String)
31
- # CPK as a string - case 1
32
- CompositeKeys.parse(id)
33
- else
34
- id
35
- end
36
- end
37
- end
38
-
39
- class CompositeKeys < Array
40
-
41
- def self.parse(value)
42
- case value
43
- when Array
44
- value.to_composite_keys
45
- when String
46
- value.split(ID_SEP).map { |key| Utils.unescape_string_key(key) }.to_composite_keys
47
- else
48
- raise(ArgumentError, "Unsupported type: #{value}")
49
- end
50
- end
51
-
52
- def to_s
53
- # Doing this makes it easier to parse Base#[](attr_name)
54
- map { |key| Utils.escape_string_key(key.to_s) }.join(ID_SEP)
55
- end
56
-
57
- alias_method :to_param, :to_s
58
- end
59
-
60
- module Utils
61
- class << self
62
- def escape_string_key(key)
63
- key.gsub(Regexp.union(ESCAPE_CHAR, ID_SEP)) do |unsafe|
64
- "#{ESCAPE_CHAR}#{unsafe.ord.to_s(16).upcase}"
65
- end
66
- end
67
-
68
- def unescape_string_key(key)
69
- key.gsub(/#{Regexp.escape(ESCAPE_CHAR)}[0-9a-fA-F]{2}/) do |escaped|
70
- char = escaped.slice(1, 2).hex.chr
71
- (char == ESCAPE_CHAR || char == ID_SEP) ? char : escaped
72
- end
73
- end
74
-
75
- def cpk_as_array?(value, pk_size)
76
- # We don't permit Array to be an element of CPK.
77
- value.is_a?(Array) && value.size == pk_size && value.none? { |item| item.is_a?(Array) }
78
- end
79
-
80
- def cpk_as_string?(value, pk_size)
81
- value.is_a?(String) && value.count(ID_SEP) == pk_size - 1
82
- end
83
- end
84
- end
85
- private_constant :Utils
86
- end
87
-
88
- Array.send(:include, CompositePrimaryKeys::ArrayExtension)
1
+ module CompositePrimaryKeys
2
+ ID_SEP = ','
3
+ ID_SET_SEP = ';'
4
+ ESCAPE_CHAR = '^'
5
+
6
+ module ArrayExtension
7
+ def to_composite_keys
8
+ CompositeKeys.new(self)
9
+ end
10
+ end
11
+
12
+ # Convert mixed representation of CPKs (by strings or arrays) to normalized
13
+ # representation (just by arrays).
14
+ #
15
+ # `ids` is Array that may contain:
16
+ # 1. A CPK represented by an array or a string.
17
+ # 2. An array of CPKs represented by arrays or strings.
18
+ #
19
+ # There is an issue. Let `ids` contain an array with serveral strings. We can't distinguish case 1
20
+ # from case 2 there in general. E.g. the item can be an array containing appropriate number of strings,
21
+ # and each string can contain appropriate number of commas. We consider case 2 to win there.
22
+ def self.normalize(ids, cpk_size)
23
+ ids.map do |id|
24
+ if Utils.cpk_as_array?(id, cpk_size) && id.any? { |item| !Utils.cpk_as_string?(item, cpk_size) }
25
+ # CPK as an array - case 1
26
+ id
27
+ elsif id.is_a?(Array)
28
+ # An array of CPKs - case 2
29
+ normalize(id, cpk_size)
30
+ elsif id.is_a?(String)
31
+ # CPK as a string - case 1
32
+ CompositeKeys.parse(id)
33
+ else
34
+ id
35
+ end
36
+ end
37
+ end
38
+
39
+ class CompositeKeys < Array
40
+
41
+ def self.parse(value)
42
+ case value
43
+ when Array
44
+ value.to_composite_keys
45
+ when String
46
+ value.split(ID_SEP).map { |key| Utils.unescape_string_key(key) }.to_composite_keys
47
+ else
48
+ raise(ArgumentError, "Unsupported type: #{value}")
49
+ end
50
+ end
51
+
52
+ def to_s
53
+ # Doing this makes it easier to parse Base#[](attr_name)
54
+ map { |key| Utils.escape_string_key(key.to_s) }.join(ID_SEP)
55
+ end
56
+
57
+ alias_method :to_param, :to_s
58
+ end
59
+
60
+ module Utils
61
+ class << self
62
+ def escape_string_key(key)
63
+ key.gsub(Regexp.union(ESCAPE_CHAR, ID_SEP)) do |unsafe|
64
+ "#{ESCAPE_CHAR}#{unsafe.ord.to_s(16).upcase}"
65
+ end
66
+ end
67
+
68
+ def unescape_string_key(key)
69
+ key.gsub(/#{Regexp.escape(ESCAPE_CHAR)}[0-9a-fA-F]{2}/) do |escaped|
70
+ char = escaped.slice(1, 2).hex.chr
71
+ (char == ESCAPE_CHAR || char == ID_SEP) ? char : escaped
72
+ end
73
+ end
74
+
75
+ def cpk_as_array?(value, pk_size)
76
+ # We don't permit Array to be an element of CPK.
77
+ value.is_a?(Array) && value.size == pk_size && value.none? { |item| item.is_a?(Array) }
78
+ end
79
+
80
+ def cpk_as_string?(value, pk_size)
81
+ value.is_a?(String) && value.count(ID_SEP) == pk_size - 1
82
+ end
83
+ end
84
+ end
85
+ private_constant :Utils
86
+ end
87
+
88
+ Array.send(:include, CompositePrimaryKeys::ArrayExtension)
@@ -1,121 +1,121 @@
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
- if primary_keys.length == 2
55
- cpk_in_predicate_with_grouped_keys(table, primary_keys, ids)
56
- else
57
- cpk_in_predicate_with_non_grouped_keys(table, primary_keys, ids)
58
- end
59
- end
60
-
61
- def cpk_in_predicate_with_non_grouped_keys(table, primary_keys, ids)
62
- and_predicates = ids.map do |id|
63
- cpk_id_predicate(table, primary_keys, id)
64
- end
65
-
66
- cpk_or_predicate(and_predicates)
67
- end
68
-
69
- def cpk_in_predicate_with_grouped_keys(table, primary_keys, ids)
70
- keys_by_first_column_name = Hash.new { |hash, key| hash[key] = [] }
71
- keys_by_second_column_name = Hash.new { |hash, key| hash[key] = [] }
72
-
73
- ids.map.each do |first_key_part, second_key_part|
74
- keys_by_first_column_name[first_key_part] << second_key_part
75
- keys_by_second_column_name[second_key_part] << first_key_part
76
- end
77
-
78
- low_cardinality_column_name, high_cardinality_column_name, groups = \
79
- if keys_by_first_column_name.size <= keys_by_second_column_name.size
80
- [primary_keys.first, primary_keys.second, keys_by_first_column_name]
81
- else
82
- [primary_keys.second, primary_keys.first, keys_by_second_column_name]
83
- end
84
-
85
- and_predicates = groups.map do |low_cardinality_value, high_cardinality_values|
86
- non_nil_high_cardinality_values = high_cardinality_values.compact
87
- in_clause = table[high_cardinality_column_name].in(non_nil_high_cardinality_values)
88
- inclusion_clauses = if non_nil_high_cardinality_values.size != high_cardinality_values.size
89
- Arel::Nodes::Grouping.new(
90
- Arel::Nodes::Or.new(
91
- in_clause,
92
- table[high_cardinality_column_name].eq(nil)
93
- )
94
- )
95
- else
96
- in_clause
97
- end
98
-
99
- Arel::Nodes::And.new(
100
- [
101
- table[low_cardinality_column_name].eq(low_cardinality_value),
102
- inclusion_clauses
103
- ]
104
- )
105
- end
106
-
107
- cpk_or_predicate(and_predicates)
108
- end
109
- end
110
- end
111
-
112
- ActiveRecord::Associations::AssociationScope.send(:include, CompositePrimaryKeys::Predicates)
113
- ActiveRecord::Associations::JoinDependency::JoinAssociation.send(:include, CompositePrimaryKeys::Predicates)
114
- ActiveRecord::Associations::Preloader::Association.send(:include, CompositePrimaryKeys::Predicates)
115
- ActiveRecord::Associations::Preloader::Association::LoaderQuery.send(:include, CompositePrimaryKeys::Predicates)
116
- ActiveRecord::Associations::HasManyAssociation.send(:include, CompositePrimaryKeys::Predicates)
117
- ActiveRecord::Associations::HasManyThroughAssociation.send(:include, CompositePrimaryKeys::Predicates)
118
- ActiveRecord::Base.send(:extend, CompositePrimaryKeys::Predicates)
119
- ActiveRecord::Reflection::AbstractReflection.send(:include, CompositePrimaryKeys::Predicates)
120
- ActiveRecord::Relation.send(:include, CompositePrimaryKeys::Predicates)
121
- 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
+ if primary_keys.length == 2
55
+ cpk_in_predicate_with_grouped_keys(table, primary_keys, ids)
56
+ else
57
+ cpk_in_predicate_with_non_grouped_keys(table, primary_keys, ids)
58
+ end
59
+ end
60
+
61
+ def cpk_in_predicate_with_non_grouped_keys(table, primary_keys, ids)
62
+ and_predicates = ids.map do |id|
63
+ cpk_id_predicate(table, primary_keys, id)
64
+ end
65
+
66
+ cpk_or_predicate(and_predicates)
67
+ end
68
+
69
+ def cpk_in_predicate_with_grouped_keys(table, primary_keys, ids)
70
+ keys_by_first_column_name = Hash.new { |hash, key| hash[key] = [] }
71
+ keys_by_second_column_name = Hash.new { |hash, key| hash[key] = [] }
72
+
73
+ ids.map.each do |first_key_part, second_key_part|
74
+ keys_by_first_column_name[first_key_part] << second_key_part
75
+ keys_by_second_column_name[second_key_part] << first_key_part
76
+ end
77
+
78
+ low_cardinality_column_name, high_cardinality_column_name, groups = \
79
+ if keys_by_first_column_name.size <= keys_by_second_column_name.size
80
+ [primary_keys.first, primary_keys.second, keys_by_first_column_name]
81
+ else
82
+ [primary_keys.second, primary_keys.first, keys_by_second_column_name]
83
+ end
84
+
85
+ and_predicates = groups.map do |low_cardinality_value, high_cardinality_values|
86
+ non_nil_high_cardinality_values = high_cardinality_values.compact
87
+ in_clause = table[high_cardinality_column_name].in(non_nil_high_cardinality_values)
88
+ inclusion_clauses = if non_nil_high_cardinality_values.size != high_cardinality_values.size
89
+ Arel::Nodes::Grouping.new(
90
+ Arel::Nodes::Or.new(
91
+ in_clause,
92
+ table[high_cardinality_column_name].eq(nil)
93
+ )
94
+ )
95
+ else
96
+ in_clause
97
+ end
98
+
99
+ Arel::Nodes::And.new(
100
+ [
101
+ table[low_cardinality_column_name].eq(low_cardinality_value),
102
+ inclusion_clauses
103
+ ]
104
+ )
105
+ end
106
+
107
+ cpk_or_predicate(and_predicates)
108
+ end
109
+ end
110
+ end
111
+
112
+ ActiveRecord::Associations::AssociationScope.send(:include, CompositePrimaryKeys::Predicates)
113
+ ActiveRecord::Associations::JoinDependency::JoinAssociation.send(:include, CompositePrimaryKeys::Predicates)
114
+ ActiveRecord::Associations::Preloader::Association.send(:include, CompositePrimaryKeys::Predicates)
115
+ ActiveRecord::Associations::Preloader::Association::LoaderQuery.send(:include, CompositePrimaryKeys::Predicates)
116
+ ActiveRecord::Associations::HasManyAssociation.send(:include, CompositePrimaryKeys::Predicates)
117
+ ActiveRecord::Associations::HasManyThroughAssociation.send(:include, CompositePrimaryKeys::Predicates)
118
+ ActiveRecord::Base.send(:extend, CompositePrimaryKeys::Predicates)
119
+ ActiveRecord::Reflection::AbstractReflection.send(:include, CompositePrimaryKeys::Predicates)
120
+ ActiveRecord::Relation.send(:include, CompositePrimaryKeys::Predicates)
121
+ ActiveRecord::PredicateBuilder.send(:extend, CompositePrimaryKeys::Predicates)