composite_primary_keys 14.0.4 → 14.0.6
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 +30 -0
- data/README.rdoc +2 -2
- data/lib/composite_primary_keys/associations/collection_association.rb +38 -31
- data/lib/composite_primary_keys/associations/preloader/association.rb +0 -16
- data/lib/composite_primary_keys/composite_arrays.rb +88 -86
- data/lib/composite_primary_keys/composite_predicates.rb +50 -0
- data/lib/composite_primary_keys/relation.rb +197 -197
- data/lib/composite_primary_keys/validations/uniqueness.rb +40 -32
- data/lib/composite_primary_keys/version.rb +1 -1
- data/test/fixtures/room_assignment.rb +18 -14
- data/test/test_associations.rb +23 -1
- data/test/test_composite_arrays.rb +44 -38
- data/test/test_create.rb +219 -218
- data/test/test_predicates.rb +130 -60
- metadata +3 -3
@@ -1,197 +1,197 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
class Relation
|
3
|
-
alias :initialize_without_cpk :initialize
|
4
|
-
def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
|
5
|
-
initialize_without_cpk(klass, table: table, predicate_builder: predicate_builder, values: values)
|
6
|
-
add_cpk_support if klass && klass.composite?
|
7
|
-
end
|
8
|
-
|
9
|
-
alias :initialize_copy_without_cpk :initialize_copy
|
10
|
-
def initialize_copy(other)
|
11
|
-
initialize_copy_without_cpk(other)
|
12
|
-
add_cpk_support if klass.composite?
|
13
|
-
end
|
14
|
-
|
15
|
-
def add_cpk_support
|
16
|
-
extend CompositePrimaryKeys::CompositeRelation
|
17
|
-
end
|
18
|
-
|
19
|
-
def update_all(updates)
|
20
|
-
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
|
21
|
-
|
22
|
-
if eager_loading?
|
23
|
-
relation = apply_join_dependency
|
24
|
-
return relation.update_all(updates)
|
25
|
-
end
|
26
|
-
|
27
|
-
stmt = Arel::UpdateManager.new
|
28
|
-
stmt.table(arel.join_sources.empty? ? table : arel.source)
|
29
|
-
stmt.key = table[primary_key]
|
30
|
-
|
31
|
-
# CPK
|
32
|
-
if @klass.composite?
|
33
|
-
stmt = Arel::UpdateManager.new
|
34
|
-
stmt.table(arel_table)
|
35
|
-
cpk_subquery(stmt)
|
36
|
-
else
|
37
|
-
stmt.wheres = arel.constraints
|
38
|
-
end
|
39
|
-
stmt.take(arel.limit)
|
40
|
-
stmt.offset(arel.offset)
|
41
|
-
stmt.order(*arel.orders)
|
42
|
-
|
43
|
-
if updates.is_a?(Hash)
|
44
|
-
if klass.locking_enabled? &&
|
45
|
-
!updates.key?(klass.locking_column) &&
|
46
|
-
!updates.key?(klass.locking_column.to_sym)
|
47
|
-
attr = table[klass.locking_column]
|
48
|
-
updates[attr.name] = _increment_attribute(attr)
|
49
|
-
end
|
50
|
-
stmt.set _substitute_values(updates)
|
51
|
-
else
|
52
|
-
stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
|
53
|
-
end
|
54
|
-
|
55
|
-
@klass.connection.update stmt, "#{@klass} Update All"
|
56
|
-
end
|
57
|
-
|
58
|
-
def delete_all
|
59
|
-
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
|
60
|
-
value = @values[method]
|
61
|
-
method == :distinct ? value : value&.any?
|
62
|
-
end
|
63
|
-
if invalid_methods.any?
|
64
|
-
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
|
65
|
-
end
|
66
|
-
|
67
|
-
if eager_loading?
|
68
|
-
relation = apply_join_dependency
|
69
|
-
return relation.delete_all
|
70
|
-
end
|
71
|
-
|
72
|
-
stmt = Arel::DeleteManager.new
|
73
|
-
stmt.from(arel.join_sources.empty? ? table : arel.source)
|
74
|
-
stmt.key = table[primary_key]
|
75
|
-
|
76
|
-
# CPK
|
77
|
-
if @klass.composite?
|
78
|
-
stmt = Arel::DeleteManager.new
|
79
|
-
stmt.from(arel_table)
|
80
|
-
cpk_subquery(stmt)
|
81
|
-
else
|
82
|
-
stmt.wheres = arel.constraints
|
83
|
-
end
|
84
|
-
|
85
|
-
stmt.take(arel.limit)
|
86
|
-
stmt.offset(arel.offset)
|
87
|
-
stmt.order(*arel.orders)
|
88
|
-
|
89
|
-
affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
|
90
|
-
|
91
|
-
reset
|
92
|
-
affected
|
93
|
-
end
|
94
|
-
|
95
|
-
# CPK
|
96
|
-
def cpk_subquery(stmt)
|
97
|
-
# For update and delete statements we need a way to specify which records should
|
98
|
-
# get updated. By default, Rails creates a nested IN subquery that uses the primary
|
99
|
-
# key. Postgresql, Sqlite, MariaDb and Oracle support IN subqueries with multiple
|
100
|
-
# columns but MySQL and SqlServer do not. Instead SQL server supports EXISTS queries
|
101
|
-
# and MySQL supports obfuscated IN queries. Thus we need to check the type of
|
102
|
-
# database adapter to decide how to proceed.
|
103
|
-
if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
|
104
|
-
cpk_mysql_subquery(stmt)
|
105
|
-
elsif defined?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
|
106
|
-
cpk_exists_subquery(stmt)
|
107
|
-
else
|
108
|
-
cpk_in_subquery(stmt)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
# Used by postgresql, sqlite, mariadb and oracle. Example query:
|
113
|
-
#
|
114
|
-
# UPDATE reference_codes
|
115
|
-
# SET ...
|
116
|
-
# WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
|
117
|
-
# (SELECT reference_codes.reference_type_id, reference_codes.reference_code
|
118
|
-
# FROM reference_codes)
|
119
|
-
def cpk_in_subquery(stmt)
|
120
|
-
# Setup the subquery
|
121
|
-
subquery = arel.clone
|
122
|
-
subquery.projections = primary_keys.map do |key|
|
123
|
-
arel_table[key]
|
124
|
-
end
|
125
|
-
|
126
|
-
where_fields = primary_keys.map do |key|
|
127
|
-
arel_table[key]
|
128
|
-
end
|
129
|
-
where = Arel::Nodes::Grouping.new(where_fields).in(subquery)
|
130
|
-
stmt.wheres = [where]
|
131
|
-
end
|
132
|
-
|
133
|
-
# CPK. This is an alternative to IN subqueries. It is used by sqlserver.
|
134
|
-
# Example query:
|
135
|
-
#
|
136
|
-
# UPDATE reference_codes
|
137
|
-
# SET ...
|
138
|
-
# WHERE EXISTS
|
139
|
-
# (SELECT 1
|
140
|
-
# FROM reference_codes cpk_child
|
141
|
-
# WHERE reference_codes.reference_type_id = cpk_child.reference_type_id AND
|
142
|
-
# reference_codes.reference_code = cpk_child.reference_code)
|
143
|
-
def cpk_exists_subquery(stmt)
|
144
|
-
arel_attributes = primary_keys.map do |key|
|
145
|
-
table[key]
|
146
|
-
end.to_composite_keys
|
147
|
-
|
148
|
-
# Clone the query
|
149
|
-
subselect = arel.clone
|
150
|
-
|
151
|
-
# Alias the table - we assume just one table
|
152
|
-
aliased_table = subselect.froms.first
|
153
|
-
aliased_table.table_alias = "cpk_child"
|
154
|
-
|
155
|
-
# Project - really we could just set this to "1"
|
156
|
-
subselect.projections = arel_attributes
|
157
|
-
|
158
|
-
# Setup correlation to the outer query via where clauses
|
159
|
-
primary_keys.map do |key|
|
160
|
-
outer_attribute = arel_table[key]
|
161
|
-
inner_attribute = aliased_table[key]
|
162
|
-
where = outer_attribute.eq(inner_attribute)
|
163
|
-
subselect.where(where)
|
164
|
-
end
|
165
|
-
stmt.wheres = [Arel::Nodes::Exists.new(subselect)]
|
166
|
-
end
|
167
|
-
|
168
|
-
# CPK. This is the old way CPK created subqueries and is used by MySql.
|
169
|
-
# MySQL does not support referencing the same table that is being UPDATEd or
|
170
|
-
# DELETEd in a subquery so we obfuscate it. The ugly query looks like this:
|
171
|
-
#
|
172
|
-
# UPDATE `reference_codes`
|
173
|
-
# SET ...
|
174
|
-
# WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
|
175
|
-
# (SELECT reference_type_id,reference_code
|
176
|
-
# FROM (SELECT DISTINCT `reference_codes`.`reference_type_id`, `reference_codes`.`reference_code`
|
177
|
-
# FROM `reference_codes`) __active_record_temp)
|
178
|
-
def cpk_mysql_subquery(stmt)
|
179
|
-
arel_attributes = primary_keys.map do |key|
|
180
|
-
table[key]
|
181
|
-
end.to_composite_keys
|
182
|
-
|
183
|
-
subselect = arel.clone
|
184
|
-
subselect.projections = arel_attributes
|
185
|
-
|
186
|
-
# Materialize subquery by adding distinct
|
187
|
-
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
188
|
-
subselect.distinct unless arel.limit || arel.offset || arel.orders.any?
|
189
|
-
|
190
|
-
key_name = arel_attributes.map(&:name).join(',')
|
191
|
-
|
192
|
-
manager = Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
|
193
|
-
|
194
|
-
stmt.wheres = [Arel::Nodes::In.new(arel_attributes, manager.ast)]
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
class Relation
|
3
|
+
alias :initialize_without_cpk :initialize
|
4
|
+
def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
|
5
|
+
initialize_without_cpk(klass, table: table, predicate_builder: predicate_builder, values: values)
|
6
|
+
add_cpk_support if klass && klass.composite?
|
7
|
+
end
|
8
|
+
|
9
|
+
alias :initialize_copy_without_cpk :initialize_copy
|
10
|
+
def initialize_copy(other)
|
11
|
+
initialize_copy_without_cpk(other)
|
12
|
+
add_cpk_support if klass.composite?
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_cpk_support
|
16
|
+
extend CompositePrimaryKeys::CompositeRelation
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_all(updates)
|
20
|
+
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
|
21
|
+
|
22
|
+
if eager_loading?
|
23
|
+
relation = apply_join_dependency
|
24
|
+
return relation.update_all(updates)
|
25
|
+
end
|
26
|
+
|
27
|
+
stmt = Arel::UpdateManager.new
|
28
|
+
stmt.table(arel.join_sources.empty? ? table : arel.source)
|
29
|
+
stmt.key = table[primary_key]
|
30
|
+
|
31
|
+
# CPK
|
32
|
+
if @klass.composite?
|
33
|
+
stmt = Arel::UpdateManager.new
|
34
|
+
stmt.table(arel_table)
|
35
|
+
cpk_subquery(stmt)
|
36
|
+
else
|
37
|
+
stmt.wheres = arel.constraints
|
38
|
+
end
|
39
|
+
stmt.take(arel.limit)
|
40
|
+
stmt.offset(arel.offset)
|
41
|
+
stmt.order(*arel.orders)
|
42
|
+
|
43
|
+
if updates.is_a?(Hash)
|
44
|
+
if klass.locking_enabled? &&
|
45
|
+
!updates.key?(klass.locking_column) &&
|
46
|
+
!updates.key?(klass.locking_column.to_sym)
|
47
|
+
attr = table[klass.locking_column]
|
48
|
+
updates[attr.name] = _increment_attribute(attr)
|
49
|
+
end
|
50
|
+
stmt.set _substitute_values(updates)
|
51
|
+
else
|
52
|
+
stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
|
53
|
+
end
|
54
|
+
|
55
|
+
@klass.connection.update stmt, "#{@klass} Update All"
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete_all
|
59
|
+
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
|
60
|
+
value = @values[method]
|
61
|
+
method == :distinct ? value : value&.any?
|
62
|
+
end
|
63
|
+
if invalid_methods.any?
|
64
|
+
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
|
65
|
+
end
|
66
|
+
|
67
|
+
if eager_loading?
|
68
|
+
relation = apply_join_dependency
|
69
|
+
return relation.delete_all
|
70
|
+
end
|
71
|
+
|
72
|
+
stmt = Arel::DeleteManager.new
|
73
|
+
stmt.from(arel.join_sources.empty? ? table : arel.source)
|
74
|
+
stmt.key = table[primary_key]
|
75
|
+
|
76
|
+
# CPK
|
77
|
+
if @klass.composite?
|
78
|
+
stmt = Arel::DeleteManager.new
|
79
|
+
stmt.from(arel_table)
|
80
|
+
cpk_subquery(stmt)
|
81
|
+
else
|
82
|
+
stmt.wheres = arel.constraints
|
83
|
+
end
|
84
|
+
|
85
|
+
stmt.take(arel.limit)
|
86
|
+
stmt.offset(arel.offset)
|
87
|
+
stmt.order(*arel.orders)
|
88
|
+
|
89
|
+
affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
|
90
|
+
|
91
|
+
reset
|
92
|
+
affected
|
93
|
+
end
|
94
|
+
|
95
|
+
# CPK
|
96
|
+
def cpk_subquery(stmt)
|
97
|
+
# For update and delete statements we need a way to specify which records should
|
98
|
+
# get updated. By default, Rails creates a nested IN subquery that uses the primary
|
99
|
+
# key. Postgresql, Sqlite, MariaDb and Oracle support IN subqueries with multiple
|
100
|
+
# columns but MySQL and SqlServer do not. Instead SQL server supports EXISTS queries
|
101
|
+
# and MySQL supports obfuscated IN queries. Thus we need to check the type of
|
102
|
+
# database adapter to decide how to proceed.
|
103
|
+
if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
|
104
|
+
cpk_mysql_subquery(stmt)
|
105
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
|
106
|
+
cpk_exists_subquery(stmt)
|
107
|
+
else
|
108
|
+
cpk_in_subquery(stmt)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Used by postgresql, sqlite, mariadb and oracle. Example query:
|
113
|
+
#
|
114
|
+
# UPDATE reference_codes
|
115
|
+
# SET ...
|
116
|
+
# WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
|
117
|
+
# (SELECT reference_codes.reference_type_id, reference_codes.reference_code
|
118
|
+
# FROM reference_codes)
|
119
|
+
def cpk_in_subquery(stmt)
|
120
|
+
# Setup the subquery
|
121
|
+
subquery = arel.clone
|
122
|
+
subquery.projections = primary_keys.map do |key|
|
123
|
+
arel_table[key]
|
124
|
+
end
|
125
|
+
|
126
|
+
where_fields = primary_keys.map do |key|
|
127
|
+
arel_table[key]
|
128
|
+
end
|
129
|
+
where = Arel::Nodes::Grouping.new(where_fields).in(subquery)
|
130
|
+
stmt.wheres = [where]
|
131
|
+
end
|
132
|
+
|
133
|
+
# CPK. This is an alternative to IN subqueries. It is used by sqlserver.
|
134
|
+
# Example query:
|
135
|
+
#
|
136
|
+
# UPDATE reference_codes
|
137
|
+
# SET ...
|
138
|
+
# WHERE EXISTS
|
139
|
+
# (SELECT 1
|
140
|
+
# FROM reference_codes cpk_child
|
141
|
+
# WHERE reference_codes.reference_type_id = cpk_child.reference_type_id AND
|
142
|
+
# reference_codes.reference_code = cpk_child.reference_code)
|
143
|
+
def cpk_exists_subquery(stmt)
|
144
|
+
arel_attributes = primary_keys.map do |key|
|
145
|
+
table[key]
|
146
|
+
end.to_composite_keys
|
147
|
+
|
148
|
+
# Clone the query
|
149
|
+
subselect = arel.clone
|
150
|
+
|
151
|
+
# Alias the table - we assume just one table
|
152
|
+
aliased_table = subselect.froms.first
|
153
|
+
aliased_table.table_alias = "cpk_child"
|
154
|
+
|
155
|
+
# Project - really we could just set this to "1"
|
156
|
+
subselect.projections = arel_attributes
|
157
|
+
|
158
|
+
# Setup correlation to the outer query via where clauses
|
159
|
+
primary_keys.map do |key|
|
160
|
+
outer_attribute = arel_table[key]
|
161
|
+
inner_attribute = aliased_table[key]
|
162
|
+
where = outer_attribute.eq(inner_attribute)
|
163
|
+
subselect.where(where)
|
164
|
+
end
|
165
|
+
stmt.wheres = [Arel::Nodes::Exists.new(subselect)]
|
166
|
+
end
|
167
|
+
|
168
|
+
# CPK. This is the old way CPK created subqueries and is used by MySql.
|
169
|
+
# MySQL does not support referencing the same table that is being UPDATEd or
|
170
|
+
# DELETEd in a subquery so we obfuscate it. The ugly query looks like this:
|
171
|
+
#
|
172
|
+
# UPDATE `reference_codes`
|
173
|
+
# SET ...
|
174
|
+
# WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
|
175
|
+
# (SELECT reference_type_id,reference_code
|
176
|
+
# FROM (SELECT DISTINCT `reference_codes`.`reference_type_id`, `reference_codes`.`reference_code`
|
177
|
+
# FROM `reference_codes`) __active_record_temp)
|
178
|
+
def cpk_mysql_subquery(stmt)
|
179
|
+
arel_attributes = primary_keys.map do |key|
|
180
|
+
table[key]
|
181
|
+
end.to_composite_keys
|
182
|
+
|
183
|
+
subselect = arel.clone
|
184
|
+
subselect.projections = arel_attributes
|
185
|
+
|
186
|
+
# Materialize subquery by adding distinct
|
187
|
+
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
188
|
+
subselect.distinct unless arel.limit || arel.offset || arel.orders.any?
|
189
|
+
|
190
|
+
key_name = arel_attributes.map(&:name).join(',')
|
191
|
+
|
192
|
+
manager = Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
|
193
|
+
|
194
|
+
stmt.wheres = [Arel::Nodes::In.new(arel_attributes, manager.ast)]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -1,32 +1,40 @@
|
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
+
if options[:conditions]
|
22
|
+
conditions = options[:conditions]
|
23
|
+
|
24
|
+
relation = if conditions.arity.zero?
|
25
|
+
relation.instance_exec(&conditions)
|
26
|
+
else
|
27
|
+
relation.instance_exec(record, &conditions)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
if relation.exists?
|
32
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
33
|
+
error_options[:value] = value
|
34
|
+
|
35
|
+
record.errors.add(attribute, :taken, **error_options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,14 +1,18 @@
|
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
puts record
|
13
|
-
end
|
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 :student_id, uniqueness: {
|
6
|
+
conditions: ->(record) {
|
7
|
+
where(student_id: record.student_id) # enough just to exercise this code path
|
8
|
+
}
|
9
|
+
}
|
10
|
+
|
11
|
+
before_destroy do |record|
|
12
|
+
puts record
|
13
|
+
end
|
14
|
+
|
15
|
+
after_destroy do |record|
|
16
|
+
puts record
|
17
|
+
end
|
18
|
+
end
|
data/test/test_associations.rb
CHANGED
@@ -367,7 +367,29 @@ class TestAssociations < ActiveSupport::TestCase
|
|
367
367
|
assert_equal(false, associations.send('foreign_key_present?'))
|
368
368
|
end
|
369
369
|
|
370
|
-
def
|
370
|
+
def test_assignment_by_ids_as_arrays
|
371
|
+
room = Room.create dorm: dorms(:branner), room_id: 4
|
372
|
+
room.room_assignment_ids = [[3, 1, 2], [4, 1, 2]]
|
373
|
+
room.save!
|
374
|
+
assert_equal room.room_assignments.map(&:student_id), [3, 4]
|
375
|
+
end
|
376
|
+
|
377
|
+
def test_assignment_by_ids_as_arrays_that_contains_a_comma
|
378
|
+
room = Room.create dorm: dorms(:branner), room_id: 4
|
379
|
+
e = assert_raises ActiveRecord::RecordNotFound do
|
380
|
+
room.room_assignment_ids = [['5,,', '5,,', '5,,']]
|
381
|
+
end
|
382
|
+
assert_match /'student_id,dorm_id,room_id'=\[\[5, 5, 5\]\]/, e.message
|
383
|
+
end
|
384
|
+
|
385
|
+
def test_assignment_by_ids_as_strings
|
386
|
+
room = Room.create dorm: dorms(:branner), room_id: 4
|
387
|
+
room.room_assignment_ids = ['3,1,2', '4,1,2']
|
388
|
+
room.save!
|
389
|
+
assert_equal room.room_assignments.map(&:student_id), [3, 4]
|
390
|
+
end
|
391
|
+
|
392
|
+
def test_assignment_by_ids_for_non_CPK_case
|
371
393
|
article = Article.new
|
372
394
|
article.reading_ids = Reading.pluck(:id)
|
373
395
|
assert_equal article.reading_ids, Reading.pluck(:id)
|
@@ -1,38 +1,44 @@
|
|
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
|
-
|
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
|
+
|
39
|
+
def test_to_param
|
40
|
+
assert_equal '1,2', CompositePrimaryKeys::CompositeKeys.new([1, 2]).to_param
|
41
|
+
assert_equal 'The USA,^5EWashington^2C D.C.',
|
42
|
+
CompositePrimaryKeys::CompositeKeys.new(['The USA', '^Washington, D.C.']).to_param
|
43
|
+
end
|
44
|
+
end
|