composite_primary_keys 13.0.1 → 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.
- checksums.yaml +4 -4
- data/History.rdoc +8 -2
- data/README.rdoc +1 -0
- data/Rakefile +37 -37
- data/lib/composite_primary_keys/arel/sqlserver.rb +37 -37
- data/lib/composite_primary_keys/arel/to_sql.rb +18 -18
- data/lib/composite_primary_keys/associations/association.rb +23 -23
- data/lib/composite_primary_keys/associations/collection_association.rb +31 -31
- data/lib/composite_primary_keys/associations/foreign_association.rb +15 -15
- data/lib/composite_primary_keys/associations/has_many_association.rb +35 -35
- data/lib/composite_primary_keys/associations/preloader/association.rb +68 -53
- data/lib/composite_primary_keys/associations/through_association.rb +25 -25
- data/lib/composite_primary_keys/autosave_association.rb +60 -60
- data/lib/composite_primary_keys/composite_arrays.rb +86 -86
- data/lib/composite_primary_keys/composite_predicates.rb +71 -70
- data/lib/composite_primary_keys/composite_relation.rb +29 -29
- data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +10 -10
- data/lib/composite_primary_keys/connection_adapters/postgresql/database_statements.rb +26 -26
- data/lib/composite_primary_keys/counter_cache.rb +15 -15
- data/lib/composite_primary_keys/fixtures.rb +21 -21
- data/lib/composite_primary_keys/persistence.rb +21 -7
- data/lib/composite_primary_keys/relation/calculations.rb +7 -1
- data/lib/composite_primary_keys/sanitization.rb +42 -42
- data/lib/composite_primary_keys/transactions.rb +34 -34
- data/lib/composite_primary_keys/validations/uniqueness.rb +31 -31
- data/lib/composite_primary_keys/version.rb +1 -1
- data/lib/composite_primary_keys.rb +1 -1
- data/scripts/console.rb +48 -48
- data/scripts/txt2html +76 -76
- data/scripts/txt2js +65 -65
- data/tasks/databases/mysql.rake +40 -40
- data/tasks/databases/oracle.rake +41 -41
- data/tasks/databases/postgresql.rake +38 -38
- data/tasks/databases/sqlite.rake +25 -25
- data/tasks/databases/sqlserver.rake +43 -43
- data/tasks/website.rake +18 -18
- data/test/README_tests.rdoc +56 -56
- data/test/abstract_unit.rb +5 -1
- data/test/connections/connection_spec.rb +27 -27
- data/test/connections/databases.example.yml +40 -40
- data/test/connections/databases.yml +40 -39
- data/test/fixtures/article.rb +10 -10
- data/test/fixtures/articles.yml +7 -7
- data/test/fixtures/capitol.rb +3 -3
- data/test/fixtures/capitols.yml +16 -16
- data/test/fixtures/comment.rb +5 -5
- data/test/fixtures/comments.yml +17 -17
- data/test/fixtures/dorm.rb +2 -2
- data/test/fixtures/dorms.yml +4 -4
- data/test/fixtures/employee.rb +5 -5
- data/test/fixtures/group.rb +2 -2
- data/test/fixtures/groups.yml +6 -6
- data/test/fixtures/membership_status.rb +2 -2
- data/test/fixtures/membership_statuses.yml +16 -16
- data/test/fixtures/memberships.yml +10 -10
- data/test/fixtures/product.rb +9 -9
- data/test/fixtures/product_tariff.rb +5 -5
- data/test/fixtures/product_tariffs.yml +14 -14
- data/test/fixtures/products.yml +11 -11
- data/test/fixtures/reading.rb +4 -4
- data/test/fixtures/readings.yml +10 -10
- data/test/fixtures/reference_code.rb +7 -7
- data/test/fixtures/reference_codes.yml +28 -28
- data/test/fixtures/reference_type.rb +12 -12
- data/test/fixtures/reference_types.yml +9 -9
- data/test/fixtures/restaurant.rb +9 -9
- data/test/fixtures/restaurants.yml +14 -14
- data/test/fixtures/restaurants_suburb.rb +2 -2
- data/test/fixtures/room.rb +11 -11
- data/test/fixtures/room_assignment.rb +13 -13
- data/test/fixtures/room_assignments.yml +24 -24
- data/test/fixtures/room_attribute.rb +2 -2
- data/test/fixtures/room_attribute_assignment.rb +4 -4
- data/test/fixtures/room_attribute_assignments.yml +4 -4
- data/test/fixtures/room_attributes.yml +2 -2
- data/test/fixtures/rooms.yml +12 -12
- data/test/fixtures/street.rb +2 -2
- data/test/fixtures/student.rb +3 -3
- data/test/fixtures/students.yml +15 -15
- data/test/fixtures/suburb.rb +5 -5
- data/test/fixtures/tariff.rb +5 -5
- data/test/fixtures/tariffs.yml +14 -14
- data/test/fixtures/topic_sources.yml +3 -3
- data/test/fixtures/topics.yml +8 -8
- data/test/fixtures/users.yml +10 -10
- data/test/plugins/pagination.rb +405 -405
- data/test/plugins/pagination_helper.rb +135 -135
- data/test/test_attribute_methods.rb +63 -63
- data/test/test_callbacks.rb +99 -99
- data/test/test_composite_arrays.rb +38 -38
- data/test/test_counter_cache.rb +30 -30
- data/test/test_dumpable.rb +15 -15
- data/test/test_dup.rb +37 -37
- data/test/test_equal.rb +26 -26
- data/test/test_habtm.rb +141 -141
- data/test/test_miscellaneous.rb +32 -32
- data/test/test_optimistic.rb +18 -18
- data/test/test_pagination.rb +35 -35
- data/test/test_polymorphic.rb +43 -43
- data/test/test_predicates.rb +59 -59
- data/test/test_preload.rb +102 -102
- data/test/test_santiago.rb +23 -23
- data/test/test_touch.rb +23 -23
- data/test/test_tutorial_example.rb +25 -25
- data/test/test_validations.rb +13 -13
- metadata +6 -6
@@ -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,86 +1,86 @@
|
|
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
|
-
end
|
57
|
-
|
58
|
-
module Utils
|
59
|
-
class << self
|
60
|
-
def escape_string_key(key)
|
61
|
-
key.gsub(Regexp.union(ESCAPE_CHAR, ID_SEP)) do |unsafe|
|
62
|
-
"#{ESCAPE_CHAR}#{unsafe.ord.to_s(16).upcase}"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def unescape_string_key(key)
|
67
|
-
key.gsub(/#{Regexp.escape(ESCAPE_CHAR)}[0-9a-fA-F]{2}/) do |escaped|
|
68
|
-
char = escaped.slice(1, 2).hex.chr
|
69
|
-
(char == ESCAPE_CHAR || char == ID_SEP) ? char : escaped
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def cpk_as_array?(value, pk_size)
|
74
|
-
# We don't permit Array to be an element of CPK.
|
75
|
-
value.is_a?(Array) && value.size == pk_size && value.none? { |item| item.is_a?(Array) }
|
76
|
-
end
|
77
|
-
|
78
|
-
def cpk_as_string?(value, pk_size)
|
79
|
-
value.is_a?(String) && value.count(ID_SEP) == pk_size - 1
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
private_constant :Utils
|
84
|
-
end
|
85
|
-
|
86
|
-
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
|
+
end
|
57
|
+
|
58
|
+
module Utils
|
59
|
+
class << self
|
60
|
+
def escape_string_key(key)
|
61
|
+
key.gsub(Regexp.union(ESCAPE_CHAR, ID_SEP)) do |unsafe|
|
62
|
+
"#{ESCAPE_CHAR}#{unsafe.ord.to_s(16).upcase}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def unescape_string_key(key)
|
67
|
+
key.gsub(/#{Regexp.escape(ESCAPE_CHAR)}[0-9a-fA-F]{2}/) do |escaped|
|
68
|
+
char = escaped.slice(1, 2).hex.chr
|
69
|
+
(char == ESCAPE_CHAR || char == ID_SEP) ? char : escaped
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def cpk_as_array?(value, pk_size)
|
74
|
+
# We don't permit Array to be an element of CPK.
|
75
|
+
value.is_a?(Array) && value.size == pk_size && value.none? { |item| item.is_a?(Array) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def cpk_as_string?(value, pk_size)
|
79
|
+
value.is_a?(String) && value.count(ID_SEP) == pk_size - 1
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
private_constant :Utils
|
84
|
+
end
|
85
|
+
|
86
|
+
Array.send(:include, CompositePrimaryKeys::ArrayExtension)
|
@@ -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::
|
66
|
-
ActiveRecord::Associations::
|
67
|
-
ActiveRecord::
|
68
|
-
ActiveRecord::
|
69
|
-
ActiveRecord::
|
70
|
-
ActiveRecord::
|
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)
|
@@ -1,29 +1,29 @@
|
|
1
|
-
module CompositePrimaryKeys
|
2
|
-
module CompositeRelation
|
3
|
-
include CompositePrimaryKeys::ActiveRecord::Batches
|
4
|
-
include CompositePrimaryKeys::ActiveRecord::Calculations
|
5
|
-
include CompositePrimaryKeys::ActiveRecord::FinderMethods
|
6
|
-
include CompositePrimaryKeys::ActiveRecord::QueryMethods
|
7
|
-
|
8
|
-
def destroy(id_or_array)
|
9
|
-
# Without CPK:
|
10
|
-
#if id.is_a?(Array)
|
11
|
-
# id.map { |one_id| destroy(one_id) }
|
12
|
-
#else
|
13
|
-
# find(id).destroy
|
14
|
-
#end
|
15
|
-
|
16
|
-
id_or_array = if id_or_array.kind_of?(CompositePrimaryKeys::CompositeKeys)
|
17
|
-
[id_or_array]
|
18
|
-
else
|
19
|
-
Array(id_or_array)
|
20
|
-
end
|
21
|
-
|
22
|
-
id_or_array.each do |id|
|
23
|
-
where(cpk_id_predicate(table, self.primary_key, id)).each do |record|
|
24
|
-
record.destroy
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
1
|
+
module CompositePrimaryKeys
|
2
|
+
module CompositeRelation
|
3
|
+
include CompositePrimaryKeys::ActiveRecord::Batches
|
4
|
+
include CompositePrimaryKeys::ActiveRecord::Calculations
|
5
|
+
include CompositePrimaryKeys::ActiveRecord::FinderMethods
|
6
|
+
include CompositePrimaryKeys::ActiveRecord::QueryMethods
|
7
|
+
|
8
|
+
def destroy(id_or_array)
|
9
|
+
# Without CPK:
|
10
|
+
#if id.is_a?(Array)
|
11
|
+
# id.map { |one_id| destroy(one_id) }
|
12
|
+
#else
|
13
|
+
# find(id).destroy
|
14
|
+
#end
|
15
|
+
|
16
|
+
id_or_array = if id_or_array.kind_of?(CompositePrimaryKeys::CompositeKeys)
|
17
|
+
[id_or_array]
|
18
|
+
else
|
19
|
+
Array(id_or_array)
|
20
|
+
end
|
21
|
+
|
22
|
+
id_or_array.each do |id|
|
23
|
+
where(cpk_id_predicate(table, self.primary_key, id)).each do |record|
|
24
|
+
record.destroy
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module ConnectionAdapters
|
3
|
-
class AbstractAdapter
|
4
|
-
def quote_column_names(name)
|
5
|
-
Array(name).map do |col|
|
6
|
-
quote_column_name(col.to_s)
|
7
|
-
end.to_composite_keys.to_s
|
8
|
-
end
|
9
|
-
end
|
10
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class AbstractAdapter
|
4
|
+
def quote_column_names(name)
|
5
|
+
Array(name).map do |col|
|
6
|
+
quote_column_name(col.to_s)
|
7
|
+
end.to_composite_keys.to_s
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
11
|
end
|
@@ -1,26 +1,26 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module ConnectionAdapters
|
3
|
-
module PostgreSQL
|
4
|
-
module DatabaseStatements
|
5
|
-
def sql_for_insert(sql, pk, binds) # :nodoc:
|
6
|
-
if pk.nil?
|
7
|
-
# Extract the table from the insert sql. Yuck.
|
8
|
-
table_ref = extract_table_ref_from_insert_sql(sql)
|
9
|
-
pk = primary_key(table_ref) if table_ref
|
10
|
-
end
|
11
|
-
|
12
|
-
# CPK
|
13
|
-
# if pk = suppress_composite_primary_key(pk)
|
14
|
-
# sql = "#{sql} RETURNING #{quote_column_name(pk)}"
|
15
|
-
#end
|
16
|
-
# NOTE pk can be false.
|
17
|
-
if pk
|
18
|
-
sql = "#{sql} RETURNING #{quote_column_names(pk)}"
|
19
|
-
end
|
20
|
-
|
21
|
-
super
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module PostgreSQL
|
4
|
+
module DatabaseStatements
|
5
|
+
def sql_for_insert(sql, pk, binds) # :nodoc:
|
6
|
+
if pk.nil?
|
7
|
+
# Extract the table from the insert sql. Yuck.
|
8
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
9
|
+
pk = primary_key(table_ref) if table_ref
|
10
|
+
end
|
11
|
+
|
12
|
+
# CPK
|
13
|
+
# if pk = suppress_composite_primary_key(pk)
|
14
|
+
# sql = "#{sql} RETURNING #{quote_column_name(pk)}"
|
15
|
+
#end
|
16
|
+
# NOTE pk can be false.
|
17
|
+
if pk
|
18
|
+
sql = "#{sql} RETURNING #{quote_column_names(pk)}"
|
19
|
+
end
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,15 +1,15 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module CounterCache
|
3
|
-
module ClassMethods
|
4
|
-
def update_counters(id, counters)
|
5
|
-
# CPK
|
6
|
-
if self.composite?
|
7
|
-
predicate = cpk_id_predicate(self.arel_table, primary_key, id)
|
8
|
-
unscoped.where!(predicate).update_counters(counters)
|
9
|
-
else
|
10
|
-
unscoped.where!(primary_key => id).update_counters(counters)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module CounterCache
|
3
|
+
module ClassMethods
|
4
|
+
def update_counters(id, counters)
|
5
|
+
# CPK
|
6
|
+
if self.composite?
|
7
|
+
predicate = cpk_id_predicate(self.arel_table, primary_key, id)
|
8
|
+
unscoped.where!(predicate).update_counters(counters)
|
9
|
+
else
|
10
|
+
unscoped.where!(primary_key => id).update_counters(counters)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,21 +1,21 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
class Fixture
|
3
|
-
def find
|
4
|
-
raise FixtureClassNotFound, "No class attached to find." unless model_class
|
5
|
-
model_class.unscoped do
|
6
|
-
# CPK
|
7
|
-
#model_class.find(fixture[model_class.primary_key])
|
8
|
-
ids = self.ids(model_class.primary_key)
|
9
|
-
model_class.find(ids)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def ids(key)
|
14
|
-
if key.is_a? Array
|
15
|
-
key.map {|a_key| fixture[a_key.to_s] }
|
16
|
-
else
|
17
|
-
fixture[key]
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
class Fixture
|
3
|
+
def find
|
4
|
+
raise FixtureClassNotFound, "No class attached to find." unless model_class
|
5
|
+
model_class.unscoped do
|
6
|
+
# CPK
|
7
|
+
#model_class.find(fixture[model_class.primary_key])
|
8
|
+
ids = self.ids(model_class.primary_key)
|
9
|
+
model_class.find(ids)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def ids(key)
|
14
|
+
if key.is_a? Array
|
15
|
+
key.map {|a_key| fixture[a_key.to_s] }
|
16
|
+
else
|
17
|
+
fixture[key]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|