composite_primary_keys 13.0.2 → 13.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.rdoc +8 -0
- data/lib/composite_primary_keys/associations/collection_association.rb +31 -31
- data/lib/composite_primary_keys/associations/join_association.rb +137 -137
- data/lib/composite_primary_keys/associations/through_association.rb +24 -25
- data/lib/composite_primary_keys/autosave_association.rb +60 -60
- data/lib/composite_primary_keys/base.rb +137 -141
- data/lib/composite_primary_keys/composite_arrays.rb +86 -86
- data/lib/composite_primary_keys/persistence.rb +1 -0
- data/lib/composite_primary_keys/validations/uniqueness.rb +31 -31
- data/lib/composite_primary_keys/version.rb +1 -1
- data/test/fixtures/room_assignment.rb +13 -13
- data/test/test_composite_arrays.rb +38 -38
- data/test/test_create.rb +219 -218
- metadata +3 -3
@@ -1,141 +1,137 @@
|
|
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
|
-
|
137
|
-
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
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
|
+
end
|
136
|
+
end
|
137
|
+
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,32 +1,32 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Validations
|
3
|
-
class UniquenessValidator
|
4
|
-
def validate_each(record, attribute, value)
|
5
|
-
finder_class = find_finder_class_for(record)
|
6
|
-
value = map_enum_attribute(finder_class, attribute, value)
|
7
|
-
|
8
|
-
relation = build_relation(finder_class, attribute, value)
|
9
|
-
if record.persisted?
|
10
|
-
# CPK
|
11
|
-
if finder_class.primary_key.is_a?(Array)
|
12
|
-
predicate = finder_class.cpk_id_predicate(finder_class.arel_table, finder_class.primary_key, record.id_in_database || record.id)
|
13
|
-
relation = relation.where.not(predicate)
|
14
|
-
elsif finder_class.primary_key
|
15
|
-
relation = relation.where.not(finder_class.primary_key => record.id_in_database)
|
16
|
-
else
|
17
|
-
raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
|
18
|
-
end
|
19
|
-
end
|
20
|
-
relation = scope_relation(record, relation)
|
21
|
-
relation = relation.merge(options[:conditions]) if options[:conditions]
|
22
|
-
|
23
|
-
if relation.exists?
|
24
|
-
error_options = options.except(:case_sensitive, :scope, :conditions)
|
25
|
-
error_options[:value] = value
|
26
|
-
|
27
|
-
record.errors.add(attribute, :taken, **error_options)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module Validations
|
3
|
+
class UniquenessValidator
|
4
|
+
def validate_each(record, attribute, value)
|
5
|
+
finder_class = find_finder_class_for(record)
|
6
|
+
value = map_enum_attribute(finder_class, attribute, value)
|
7
|
+
|
8
|
+
relation = build_relation(finder_class, attribute, value)
|
9
|
+
if record.persisted?
|
10
|
+
# CPK
|
11
|
+
if finder_class.primary_key.is_a?(Array)
|
12
|
+
predicate = finder_class.cpk_id_predicate(finder_class.arel_table, finder_class.primary_key, record.id_in_database || record.id)
|
13
|
+
relation = relation.where.not(predicate)
|
14
|
+
elsif finder_class.primary_key
|
15
|
+
relation = relation.where.not(finder_class.primary_key => record.id_in_database)
|
16
|
+
else
|
17
|
+
raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
relation = scope_relation(record, relation)
|
21
|
+
relation = relation.merge(options[:conditions]) if options[:conditions]
|
22
|
+
|
23
|
+
if relation.exists?
|
24
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
25
|
+
error_options[:value] = value
|
26
|
+
|
27
|
+
record.errors.add(attribute, :taken, **error_options)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
32
|
end
|
@@ -1,14 +1,14 @@
|
|
1
|
-
class RoomAssignment < ActiveRecord::Base
|
2
|
-
self.primary_keys = :student_id, :dorm_id, :room_id
|
3
|
-
belongs_to :student
|
4
|
-
belongs_to :room, :foreign_key => [:dorm_id, :room_id], :primary_key => [:dorm_id, :room_id]
|
5
|
-
validates_uniqueness_of :student_id
|
6
|
-
|
7
|
-
before_destroy do |record|
|
8
|
-
puts record
|
9
|
-
end
|
10
|
-
|
11
|
-
after_destroy do |record|
|
12
|
-
puts record
|
13
|
-
end
|
1
|
+
class RoomAssignment < ActiveRecord::Base
|
2
|
+
self.primary_keys = :student_id, :dorm_id, :room_id
|
3
|
+
belongs_to :student
|
4
|
+
belongs_to :room, :foreign_key => [:dorm_id, :room_id], :primary_key => [:dorm_id, :room_id]
|
5
|
+
validates_uniqueness_of :student_id
|
6
|
+
|
7
|
+
before_destroy do |record|
|
8
|
+
puts record
|
9
|
+
end
|
10
|
+
|
11
|
+
after_destroy do |record|
|
12
|
+
puts record
|
13
|
+
end
|
14
14
|
end
|
@@ -1,38 +1,38 @@
|
|
1
|
-
require File.expand_path('../abstract_unit', __FILE__)
|
2
|
-
|
3
|
-
class CompositeArraysTest < ActiveSupport::TestCase
|
4
|
-
|
5
|
-
def test_new_primary_keys
|
6
|
-
keys = CompositePrimaryKeys::CompositeKeys.new
|
7
|
-
assert_not_nil keys
|
8
|
-
assert_equal '', keys.to_s
|
9
|
-
assert_equal '', "#{keys}"
|
10
|
-
end
|
11
|
-
|
12
|
-
def test_initialize_primary_keys
|
13
|
-
keys = CompositePrimaryKeys::CompositeKeys.new([1,2,3])
|
14
|
-
assert_not_nil keys
|
15
|
-
assert_equal '1,2,3', keys.to_s
|
16
|
-
assert_equal '1,2,3', "#{keys}"
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_to_composite_keys
|
20
|
-
keys = [1,2,3].to_composite_keys
|
21
|
-
assert_equal CompositePrimaryKeys::CompositeKeys, keys.class
|
22
|
-
assert_equal '1,2,3', keys.to_s
|
23
|
-
end
|
24
|
-
|
25
|
-
def test_parse
|
26
|
-
assert_equal ['1', '2'], CompositePrimaryKeys::CompositeKeys.parse('1,2')
|
27
|
-
assert_equal ['The USA', '^Washington, D.C.'],
|
28
|
-
CompositePrimaryKeys::CompositeKeys.parse('The USA,^5EWashington^2C D.C.')
|
29
|
-
assert_equal ['The USA', '^Washington, D.C.'],
|
30
|
-
CompositePrimaryKeys::CompositeKeys.parse(['The USA', '^Washington, D.C.'])
|
31
|
-
end
|
32
|
-
|
33
|
-
def test_to_s
|
34
|
-
assert_equal '1,2', CompositePrimaryKeys::CompositeKeys.new([1, 2]).to_s
|
35
|
-
assert_equal 'The USA,^5EWashington^2C D.C.',
|
36
|
-
CompositePrimaryKeys::CompositeKeys.new(['The USA', '^Washington, D.C.']).to_s
|
37
|
-
end
|
38
|
-
end
|
1
|
+
require File.expand_path('../abstract_unit', __FILE__)
|
2
|
+
|
3
|
+
class CompositeArraysTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def test_new_primary_keys
|
6
|
+
keys = CompositePrimaryKeys::CompositeKeys.new
|
7
|
+
assert_not_nil keys
|
8
|
+
assert_equal '', keys.to_s
|
9
|
+
assert_equal '', "#{keys}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_initialize_primary_keys
|
13
|
+
keys = CompositePrimaryKeys::CompositeKeys.new([1,2,3])
|
14
|
+
assert_not_nil keys
|
15
|
+
assert_equal '1,2,3', keys.to_s
|
16
|
+
assert_equal '1,2,3', "#{keys}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_to_composite_keys
|
20
|
+
keys = [1,2,3].to_composite_keys
|
21
|
+
assert_equal CompositePrimaryKeys::CompositeKeys, keys.class
|
22
|
+
assert_equal '1,2,3', keys.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_parse
|
26
|
+
assert_equal ['1', '2'], CompositePrimaryKeys::CompositeKeys.parse('1,2')
|
27
|
+
assert_equal ['The USA', '^Washington, D.C.'],
|
28
|
+
CompositePrimaryKeys::CompositeKeys.parse('The USA,^5EWashington^2C D.C.')
|
29
|
+
assert_equal ['The USA', '^Washington, D.C.'],
|
30
|
+
CompositePrimaryKeys::CompositeKeys.parse(['The USA', '^Washington, D.C.'])
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_to_s
|
34
|
+
assert_equal '1,2', CompositePrimaryKeys::CompositeKeys.new([1, 2]).to_s
|
35
|
+
assert_equal 'The USA,^5EWashington^2C D.C.',
|
36
|
+
CompositePrimaryKeys::CompositeKeys.new(['The USA', '^Washington, D.C.']).to_s
|
37
|
+
end
|
38
|
+
end
|