composite_primary_keys 13.0.1 → 13.0.2
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 +891 -888
- data/README.rdoc +181 -181
- 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/{join_dependency.rb → join_association.rb} +1 -1
- 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_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 +82 -82
- data/lib/composite_primary_keys/relation/calculations.rb +104 -104
- 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 +8 -8
- data/lib/composite_primary_keys.rb +118 -118
- 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 +114 -114
- 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/department.rb +16 -16
- 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.rb +8 -8
- 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_associations.rb +372 -372
- 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 +4 -4
@@ -1,15 +1,15 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActiveRecord::Associations
|
4
|
-
module ForeignAssociation # :nodoc:
|
5
|
-
def foreign_key_present?
|
6
|
-
if reflection.klass.primary_key
|
7
|
-
# CPK
|
8
|
-
# owner.attribute_present?(reflection.active_record_primary_key)
|
9
|
-
Array(reflection.active_record_primary_key).all? {|key| owner.attribute_present?(key)}
|
10
|
-
else
|
11
|
-
false
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord::Associations
|
4
|
+
module ForeignAssociation # :nodoc:
|
5
|
+
def foreign_key_present?
|
6
|
+
if reflection.klass.primary_key
|
7
|
+
# CPK
|
8
|
+
# owner.attribute_present?(reflection.active_record_primary_key)
|
9
|
+
Array(reflection.active_record_primary_key).all? {|key| owner.attribute_present?(key)}
|
10
|
+
else
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,35 +1,35 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Associations
|
3
|
-
class HasManyAssociation
|
4
|
-
def delete_records(records, method)
|
5
|
-
if method == :destroy
|
6
|
-
records.each(&:destroy!)
|
7
|
-
update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
|
8
|
-
# CPK
|
9
|
-
elsif self.reflection.klass.composite?
|
10
|
-
predicate = cpk_in_predicate(self.scope.table, self.reflection.klass.primary_keys, records.map(&:id))
|
11
|
-
scope = self.scope.where(predicate)
|
12
|
-
update_counter(-delete_count(method, scope))
|
13
|
-
else
|
14
|
-
scope = self.scope.where(reflection.klass.primary_key => records)
|
15
|
-
update_counter(-delete_count(method, scope))
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def delete_count(method, scope)
|
20
|
-
if method == :delete_all
|
21
|
-
scope.delete_all
|
22
|
-
else
|
23
|
-
# CPK
|
24
|
-
# scope.update_all(nullified_owner_attributes)
|
25
|
-
conds = Array(reflection.foreign_key).inject(Hash.new) do |mem, key|
|
26
|
-
mem[key] = nil
|
27
|
-
mem
|
28
|
-
end
|
29
|
-
conds[reflection.type] = nil if reflection.type.present?
|
30
|
-
scope.update_all(conds)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class HasManyAssociation
|
4
|
+
def delete_records(records, method)
|
5
|
+
if method == :destroy
|
6
|
+
records.each(&:destroy!)
|
7
|
+
update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
|
8
|
+
# CPK
|
9
|
+
elsif self.reflection.klass.composite?
|
10
|
+
predicate = cpk_in_predicate(self.scope.table, self.reflection.klass.primary_keys, records.map(&:id))
|
11
|
+
scope = self.scope.where(predicate)
|
12
|
+
update_counter(-delete_count(method, scope))
|
13
|
+
else
|
14
|
+
scope = self.scope.where(reflection.klass.primary_key => records)
|
15
|
+
update_counter(-delete_count(method, scope))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete_count(method, scope)
|
20
|
+
if method == :delete_all
|
21
|
+
scope.delete_all
|
22
|
+
else
|
23
|
+
# CPK
|
24
|
+
# scope.update_all(nullified_owner_attributes)
|
25
|
+
conds = Array(reflection.foreign_key).inject(Hash.new) do |mem, key|
|
26
|
+
mem[key] = nil
|
27
|
+
mem
|
28
|
+
end
|
29
|
+
conds[reflection.type] = nil if reflection.type.present?
|
30
|
+
scope.update_all(conds)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -11,7 +11,7 @@ module ActiveRecord
|
|
11
11
|
else
|
12
12
|
right = join.right
|
13
13
|
# CPK
|
14
|
-
if right.expr.children.empty?
|
14
|
+
if right.expr.is_a?(Arel::Nodes::And) && right.expr.children.empty?
|
15
15
|
right.expr = Arel::Nodes::And.new(constraints)
|
16
16
|
else
|
17
17
|
right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
|
@@ -1,25 +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
|
-
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
|
+
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,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,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
|