composite_primary_keys 14.0.4 → 14.0.5
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 +15 -0
- data/README.rdoc +2 -2
- data/lib/composite_primary_keys/associations/collection_association.rb +38 -31
- 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/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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4feff3a4f8ce272695c7a081666b99f3e9b79b206dce5acf9728c0c2c0cb2d9a
|
4
|
+
data.tar.gz: f5d7533a624427d056ad9fd7576242a2b6318ed584939bea834de44bd8d618af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2e52091afe3577f46df1e0093ad6d4d6167a250e27ec7776af7706b8ec01fce0539f15a0bcaa1d12273ac2ed8055cddcc6fef5536b5e7ae8761d4158c2f9a6a
|
7
|
+
data.tar.gz: 8159181dd45c05ba6aad2a57ff482bdf8471904d6990517e04a992bc177e42b5cb201dfb48ff7e645ecfb2e23270ed129a9a6666b87a6412d07a3163200e4d72
|
data/History.rdoc
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
== 14.0.5 (2023-02-04)
|
2
|
+
* Improve query generation for cpk_in_predicate. This reduces the length of
|
3
|
+
queries when loading many keys and enables Postgres to use index scans
|
4
|
+
more frequently. (Andrew Kiellor)
|
5
|
+
* Add Ruby 3.2 to CI and update checkout action versions (Peter Goldstein)
|
6
|
+
* Fix grammatical correction in README.rdoc (Sam Corl)
|
7
|
+
* Add an assertion for previously_new_record? (Akinori Musha)
|
8
|
+
* Fix validate_each (Benjamin Fleischer)
|
9
|
+
* Reduce ambiguity by avoiding #normalize (Mitsuhiro Shibuya)
|
10
|
+
* Accept strings in has_many ids assignment (Mitsuhiro Shibuya)
|
11
|
+
* Make CompositeKeys respond to #to_param consistently with ActiveRecord::Base (Mitsuhiro Shibuya)
|
12
|
+
|
1
13
|
== 14.0.4 (2022-02-13)
|
2
14
|
* Fix for changed method in Rails 7.0.2 (Yota)
|
3
15
|
|
@@ -15,6 +27,9 @@
|
|
15
27
|
== 14.0.0 (2022-01-9)
|
16
28
|
* Update to ActiveRecord 7.0 (Sammy Larbi)
|
17
29
|
|
30
|
+
== 13.0.4 (2022-12-05)
|
31
|
+
* Fix previously_new_record? not being set to true after create (Akinori MUSHA)
|
32
|
+
|
18
33
|
== 13.0.3 (2022-01-09)
|
19
34
|
* Remove override on ActiveRecord::Base#to_param. That method has moved to Integration
|
20
35
|
so no longer works. #541. (Charlie Savage)
|
data/README.rdoc
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
== Summary
|
4
4
|
|
5
|
-
|
5
|
+
ActiveRecord infamously doesn't support composite primary keys.
|
6
6
|
This gem, composite_primary_keys, or CPK for short, extends ActiveRecord
|
7
7
|
to support composite keys.
|
8
8
|
|
@@ -72,7 +72,7 @@ But first, lets check out our primary keys.
|
|
72
72
|
Membership.primary_key # => [:user_id, :group_id] # composite keys
|
73
73
|
Membership.primary_key.to_s # => "user_id,group_id"
|
74
74
|
|
75
|
-
Now we want to be able to find instances using the same syntax we always use for ActiveRecords
|
75
|
+
Now we want to be able to find instances using the same syntax we always use for ActiveRecords.
|
76
76
|
|
77
77
|
MembershipStatus.find(1) # single id returns single instance
|
78
78
|
=> <MembershipStatus:0x392a8c8 @attributes={"id"=>"1", "status"=>"Active"}>
|
@@ -1,32 +1,39 @@
|
|
1
|
-
module CompositePrimaryKeys
|
2
|
-
module CollectionAssociation
|
3
|
-
def ids_writer(ids)
|
4
|
-
primary_key = reflection.association_primary_key
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
records = klass.where(
|
17
|
-
r.
|
18
|
-
end.values_at(*ids).compact
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
1
|
+
module CompositePrimaryKeys
|
2
|
+
module CollectionAssociation
|
3
|
+
def ids_writer(ids)
|
4
|
+
primary_key = reflection.association_primary_key
|
5
|
+
ids = Array(ids).reject(&:blank?)
|
6
|
+
|
7
|
+
# CPK-
|
8
|
+
if primary_key.is_a?(Array)
|
9
|
+
ids = ids.map { |id| CompositePrimaryKeys::CompositeKeys.parse(id) }
|
10
|
+
primary_key.each_with_index do |key, i|
|
11
|
+
pk_type = klass.type_for_attribute(key)
|
12
|
+
ids.each { |id| id[i] = pk_type.cast(id[i]) }
|
13
|
+
end
|
14
|
+
|
15
|
+
predicate = CompositePrimaryKeys::Predicates.cpk_in_predicate(klass.arel_table, reflection.association_primary_key, ids)
|
16
|
+
records = klass.where(predicate).index_by do |r|
|
17
|
+
reflection.association_primary_key.map{ |k| r.send(k) }
|
18
|
+
end.values_at(*ids).compact
|
19
|
+
else
|
20
|
+
pk_type = klass.type_for_attribute(primary_key)
|
21
|
+
ids.map! { |i| pk_type.cast(i) }
|
22
|
+
|
23
|
+
records = klass.where(primary_key => ids).index_by do |r|
|
24
|
+
r.public_send(primary_key)
|
25
|
+
end.values_at(*ids).compact
|
26
|
+
end
|
27
|
+
|
28
|
+
if records.size != ids.size
|
29
|
+
found_ids = records.map { |record| record.public_send(primary_key) }
|
30
|
+
not_found_ids = ids - found_ids
|
31
|
+
klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids)
|
32
|
+
else
|
33
|
+
replace(records)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
32
39
|
ActiveRecord::Associations::CollectionAssociation.prepend CompositePrimaryKeys::CollectionAssociation
|
@@ -1,86 +1,88 @@
|
|
1
|
-
module CompositePrimaryKeys
|
2
|
-
ID_SEP = ','
|
3
|
-
ID_SET_SEP = ';'
|
4
|
-
ESCAPE_CHAR = '^'
|
5
|
-
|
6
|
-
module ArrayExtension
|
7
|
-
def to_composite_keys
|
8
|
-
CompositeKeys.new(self)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
# Convert mixed representation of CPKs (by strings or arrays) to normalized
|
13
|
-
# representation (just by arrays).
|
14
|
-
#
|
15
|
-
# `ids` is Array that may contain:
|
16
|
-
# 1. A CPK represented by an array or a string.
|
17
|
-
# 2. An array of CPKs represented by arrays or strings.
|
18
|
-
#
|
19
|
-
# There is an issue. Let `ids` contain an array with serveral strings. We can't distinguish case 1
|
20
|
-
# from case 2 there in general. E.g. the item can be an array containing appropriate number of strings,
|
21
|
-
# and each string can contain appropriate number of commas. We consider case 2 to win there.
|
22
|
-
def self.normalize(ids, cpk_size)
|
23
|
-
ids.map do |id|
|
24
|
-
if Utils.cpk_as_array?(id, cpk_size) && id.any? { |item| !Utils.cpk_as_string?(item, cpk_size) }
|
25
|
-
# CPK as an array - case 1
|
26
|
-
id
|
27
|
-
elsif id.is_a?(Array)
|
28
|
-
# An array of CPKs - case 2
|
29
|
-
normalize(id, cpk_size)
|
30
|
-
elsif id.is_a?(String)
|
31
|
-
# CPK as a string - case 1
|
32
|
-
CompositeKeys.parse(id)
|
33
|
-
else
|
34
|
-
id
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class CompositeKeys < Array
|
40
|
-
|
41
|
-
def self.parse(value)
|
42
|
-
case value
|
43
|
-
when Array
|
44
|
-
value.to_composite_keys
|
45
|
-
when String
|
46
|
-
value.split(ID_SEP).map { |key| Utils.unescape_string_key(key) }.to_composite_keys
|
47
|
-
else
|
48
|
-
raise(ArgumentError, "Unsupported type: #{value}")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def to_s
|
53
|
-
# Doing this makes it easier to parse Base#[](attr_name)
|
54
|
-
map { |key| Utils.escape_string_key(key.to_s) }.join(ID_SEP)
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
85
|
-
|
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
|
+
|
57
|
+
alias_method :to_param, :to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
module Utils
|
61
|
+
class << self
|
62
|
+
def escape_string_key(key)
|
63
|
+
key.gsub(Regexp.union(ESCAPE_CHAR, ID_SEP)) do |unsafe|
|
64
|
+
"#{ESCAPE_CHAR}#{unsafe.ord.to_s(16).upcase}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def unescape_string_key(key)
|
69
|
+
key.gsub(/#{Regexp.escape(ESCAPE_CHAR)}[0-9a-fA-F]{2}/) do |escaped|
|
70
|
+
char = escaped.slice(1, 2).hex.chr
|
71
|
+
(char == ESCAPE_CHAR || char == ID_SEP) ? char : escaped
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def cpk_as_array?(value, pk_size)
|
76
|
+
# We don't permit Array to be an element of CPK.
|
77
|
+
value.is_a?(Array) && value.size == pk_size && value.none? { |item| item.is_a?(Array) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def cpk_as_string?(value, pk_size)
|
81
|
+
value.is_a?(String) && value.count(ID_SEP) == pk_size - 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
private_constant :Utils
|
86
|
+
end
|
87
|
+
|
88
|
+
Array.send(:include, CompositePrimaryKeys::ArrayExtension)
|
@@ -51,9 +51,59 @@ module CompositePrimaryKeys
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def cpk_in_predicate(table, primary_keys, ids)
|
54
|
+
if primary_keys.length == 2
|
55
|
+
cpk_in_predicate_with_grouped_keys(table, primary_keys, ids)
|
56
|
+
else
|
57
|
+
cpk_in_predicate_with_non_grouped_keys(table, primary_keys, ids)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def cpk_in_predicate_with_non_grouped_keys(table, primary_keys, ids)
|
54
62
|
and_predicates = ids.map do |id|
|
55
63
|
cpk_id_predicate(table, primary_keys, id)
|
56
64
|
end
|
65
|
+
|
66
|
+
cpk_or_predicate(and_predicates)
|
67
|
+
end
|
68
|
+
|
69
|
+
def cpk_in_predicate_with_grouped_keys(table, primary_keys, ids)
|
70
|
+
keys_by_first_column_name = Hash.new { |hash, key| hash[key] = [] }
|
71
|
+
keys_by_second_column_name = Hash.new { |hash, key| hash[key] = [] }
|
72
|
+
|
73
|
+
ids.map.each do |first_key_part, second_key_part|
|
74
|
+
keys_by_first_column_name[first_key_part] << second_key_part
|
75
|
+
keys_by_second_column_name[second_key_part] << first_key_part
|
76
|
+
end
|
77
|
+
|
78
|
+
low_cardinality_column_name, high_cardinality_column_name, groups = \
|
79
|
+
if keys_by_first_column_name.size <= keys_by_second_column_name.size
|
80
|
+
[primary_keys.first, primary_keys.second, keys_by_first_column_name]
|
81
|
+
else
|
82
|
+
[primary_keys.second, primary_keys.first, keys_by_second_column_name]
|
83
|
+
end
|
84
|
+
|
85
|
+
and_predicates = groups.map do |low_cardinality_value, high_cardinality_values|
|
86
|
+
non_nil_high_cardinality_values = high_cardinality_values.compact
|
87
|
+
in_clause = table[high_cardinality_column_name].in(non_nil_high_cardinality_values)
|
88
|
+
inclusion_clauses = if non_nil_high_cardinality_values.size != high_cardinality_values.size
|
89
|
+
Arel::Nodes::Grouping.new(
|
90
|
+
Arel::Nodes::Or.new(
|
91
|
+
in_clause,
|
92
|
+
table[high_cardinality_column_name].eq(nil)
|
93
|
+
)
|
94
|
+
)
|
95
|
+
else
|
96
|
+
in_clause
|
97
|
+
end
|
98
|
+
|
99
|
+
Arel::Nodes::And.new(
|
100
|
+
[
|
101
|
+
table[low_cardinality_column_name].eq(low_cardinality_value),
|
102
|
+
inclusion_clauses
|
103
|
+
]
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
57
107
|
cpk_or_predicate(and_predicates)
|
58
108
|
end
|
59
109
|
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
|
data/test/test_create.rb
CHANGED
@@ -1,218 +1,219 @@
|
|
1
|
-
require File.expand_path('../abstract_unit', __FILE__)
|
2
|
-
|
3
|
-
class TestCreate < ActiveSupport::TestCase
|
4
|
-
fixtures :articles, :students, :dorms, :rooms, :room_assignments, :reference_types, :reference_codes, :streets, :suburbs
|
5
|
-
|
6
|
-
CLASSES = {
|
7
|
-
:single => {
|
8
|
-
:class => ReferenceType,
|
9
|
-
:primary_keys => :reference_type_id,
|
10
|
-
:create => {:reference_type_id => 10, :type_label => 'NEW_TYPE', :abbreviation => 'New Type'}
|
11
|
-
},
|
12
|
-
:dual => {
|
13
|
-
:class => ReferenceCode,
|
14
|
-
:primary_keys => [:reference_type_id, :reference_code],
|
15
|
-
:create => {:reference_type_id => 1, :reference_code => 20, :code_label => 'NEW_CODE', :abbreviation => 'New Code'}
|
16
|
-
}
|
17
|
-
}
|
18
|
-
|
19
|
-
def setup
|
20
|
-
self.class.classes = CLASSES
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_setup
|
24
|
-
testing_with do
|
25
|
-
assert_not_nil @klass_info[:create]
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def test_create
|
30
|
-
testing_with do
|
31
|
-
assert new_obj = @klass.create(@klass_info[:create])
|
32
|
-
assert !new_obj.new_record?
|
33
|
-
assert new_obj.id
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
@
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
assert_equal(date, tariff.
|
57
|
-
assert_equal(
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
assert_equal(2, department.
|
80
|
-
assert_equal(
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
assert_equal(2, department.
|
85
|
-
assert_equal(
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
suburb
|
91
|
-
|
92
|
-
|
93
|
-
assert_equal(suburb.
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
rt
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
:
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
:
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
assert_equal(
|
117
|
-
assert_equal(
|
118
|
-
assert_equal(
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
assert_equal(
|
124
|
-
assert_equal(
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
:
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
:
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
assert_equal(
|
139
|
-
assert_equal(
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
assert_equal(
|
147
|
-
assert_equal(
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
room.
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
assignment1.
|
161
|
-
|
162
|
-
|
163
|
-
room.
|
164
|
-
|
165
|
-
|
166
|
-
assert_equal(
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
room.
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
assignment1.
|
181
|
-
|
182
|
-
|
183
|
-
assignment2.
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
assert_equal(
|
191
|
-
assert_equal(
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
refute_nil(suburb
|
215
|
-
refute_nil(suburb.
|
216
|
-
|
217
|
-
|
218
|
-
end
|
1
|
+
require File.expand_path('../abstract_unit', __FILE__)
|
2
|
+
|
3
|
+
class TestCreate < ActiveSupport::TestCase
|
4
|
+
fixtures :articles, :students, :dorms, :rooms, :room_assignments, :reference_types, :reference_codes, :streets, :suburbs
|
5
|
+
|
6
|
+
CLASSES = {
|
7
|
+
:single => {
|
8
|
+
:class => ReferenceType,
|
9
|
+
:primary_keys => :reference_type_id,
|
10
|
+
:create => {:reference_type_id => 10, :type_label => 'NEW_TYPE', :abbreviation => 'New Type'}
|
11
|
+
},
|
12
|
+
:dual => {
|
13
|
+
:class => ReferenceCode,
|
14
|
+
:primary_keys => [:reference_type_id, :reference_code],
|
15
|
+
:create => {:reference_type_id => 1, :reference_code => 20, :code_label => 'NEW_CODE', :abbreviation => 'New Code'}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
def setup
|
20
|
+
self.class.classes = CLASSES
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_setup
|
24
|
+
testing_with do
|
25
|
+
assert_not_nil @klass_info[:create]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_create
|
30
|
+
testing_with do
|
31
|
+
assert new_obj = @klass.create(@klass_info[:create])
|
32
|
+
assert !new_obj.new_record?
|
33
|
+
assert new_obj.id
|
34
|
+
assert new_obj.previously_new_record?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_create_no_id
|
39
|
+
testing_with do
|
40
|
+
begin
|
41
|
+
@obj = @klass.create(@klass_info[:create].except(@klass.primary_key))
|
42
|
+
@successful = !composite?
|
43
|
+
rescue ActiveRecord::CompositeKeyError
|
44
|
+
@successful = false
|
45
|
+
rescue
|
46
|
+
flunk "Incorrect exception raised: #{$!}, #{$!.class}"
|
47
|
+
end
|
48
|
+
assert_equal composite?, !@successful, "Create should have failed for composites; #{@obj.inspect}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_create_with_array
|
53
|
+
date = Date.new(2027, 01, 27)
|
54
|
+
tariff = Tariff.create!(id: [10, date], amount: 27)
|
55
|
+
refute_nil(tariff)
|
56
|
+
assert_equal([10, date], tariff.id)
|
57
|
+
assert_equal(date, tariff.start_date)
|
58
|
+
assert_equal(27, tariff.amount)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_create_with_partial_serial
|
62
|
+
attributes = {:location_id => 100}
|
63
|
+
|
64
|
+
# SQLite does not support an autoincrementing field in a composite key
|
65
|
+
if Department.connection.class.name == "ActiveRecord::ConnectionAdapters::SQLite3Adapter"
|
66
|
+
attributes[:id] = 200
|
67
|
+
end
|
68
|
+
|
69
|
+
department = Department.new(attributes)
|
70
|
+
assert_nil(department.attributes[:id])
|
71
|
+
|
72
|
+
department.save!
|
73
|
+
refute_nil(department.attributes["id"])
|
74
|
+
assert_equal(100, department.location_id)
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_create_with_id
|
78
|
+
department = Department.create!(id: [2, 3])
|
79
|
+
assert_equal([2, 3], department.id)
|
80
|
+
assert_equal(2, department.attributes["id"])
|
81
|
+
assert_equal(3, department.attributes["location_id"])
|
82
|
+
|
83
|
+
department.reload
|
84
|
+
assert_equal([2, 3], department.id)
|
85
|
+
assert_equal(2, department.attributes["id"])
|
86
|
+
assert_equal(3, department.attributes["location_id"])
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_create_on_association
|
90
|
+
suburb = Suburb.first
|
91
|
+
suburb.streets.create(:name => "my street")
|
92
|
+
street = Street.find_by_name('my street')
|
93
|
+
assert_equal(suburb.city_id, street.city_id)
|
94
|
+
assert_equal(suburb.suburb_id, street.suburb_id)
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_create_on_association_when_belongs_to_is_single_key
|
98
|
+
rt = ReferenceType.first
|
99
|
+
rt.reference_codes.create(:reference_code => 4321, :code_label => 'foo', :abbreviation => 'bar')
|
100
|
+
rc = ReferenceCode.find_by_reference_code(4321)
|
101
|
+
assert_equal(rc.reference_type_id, rt.reference_type_id)
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_new_habtm
|
105
|
+
restaurant = Restaurant.new(:franchise_id => 101,
|
106
|
+
:store_id => 201,
|
107
|
+
:name => "My Store")
|
108
|
+
|
109
|
+
restaurant.suburbs << Suburb.new(:city_id => 24,
|
110
|
+
:suburb_id => 25,
|
111
|
+
:name => "My Suburb")
|
112
|
+
|
113
|
+
restaurant.save!
|
114
|
+
|
115
|
+
# Test restaurant
|
116
|
+
assert_equal(101, restaurant.franchise_id)
|
117
|
+
assert_equal(201, restaurant.store_id)
|
118
|
+
assert_equal("My Store", restaurant.name)
|
119
|
+
assert_equal(1, restaurant.suburbs.length)
|
120
|
+
|
121
|
+
# Test suburbs
|
122
|
+
suburb = restaurant.suburbs[0]
|
123
|
+
assert_equal(24, suburb.city_id)
|
124
|
+
assert_equal(25, suburb.suburb_id)
|
125
|
+
assert_equal("My Suburb", suburb.name)
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_create_habtm
|
129
|
+
restaurant = Restaurant.create(:franchise_id => 100,
|
130
|
+
:store_id => 200,
|
131
|
+
:name => "My Store")
|
132
|
+
|
133
|
+
restaurant.suburbs.create(:city_id => 24,
|
134
|
+
:suburb_id => 25,
|
135
|
+
:name => "My Suburb")
|
136
|
+
|
137
|
+
# Test restaurant
|
138
|
+
assert_equal(100, restaurant.franchise_id)
|
139
|
+
assert_equal(200, restaurant.store_id)
|
140
|
+
assert_equal("My Store", restaurant.name)
|
141
|
+
|
142
|
+
assert_equal(1, restaurant.suburbs.reload.length)
|
143
|
+
|
144
|
+
# Test suburbs
|
145
|
+
suburb = restaurant.suburbs[0]
|
146
|
+
assert_equal(24, suburb.city_id)
|
147
|
+
assert_equal(25, suburb.suburb_id)
|
148
|
+
assert_equal("My Suburb", suburb.name)
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_has_many_ids_1
|
152
|
+
dorm = dorms(:toyon)
|
153
|
+
room = Room.new(:dorm_id => dorm.id, :room_id => 5)
|
154
|
+
room.save!
|
155
|
+
|
156
|
+
student1 = students(:kelly)
|
157
|
+
|
158
|
+
RoomAssignment.delete_all
|
159
|
+
|
160
|
+
assignment1 = RoomAssignment.new(:student_id => student1.id, :dorm_id => room.dorm_id, :room_id => room.room_id)
|
161
|
+
assignment1.save!
|
162
|
+
|
163
|
+
room.room_assignment_ids = [[assignment1.student_id, assignment1.dorm_id, assignment1.room_id]]
|
164
|
+
room.save!
|
165
|
+
|
166
|
+
assert_equal(1, room.room_assignments.length)
|
167
|
+
assert_equal(assignment1, room.room_assignments.first)
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_has_many_ids_2
|
171
|
+
dorm = dorms(:toyon)
|
172
|
+
room = Room.new(:dorm_id => dorm.id, :room_id => 5)
|
173
|
+
room.save!
|
174
|
+
|
175
|
+
student1 = students(:kelly)
|
176
|
+
student2 = students(:jordan)
|
177
|
+
|
178
|
+
RoomAssignment.delete_all
|
179
|
+
|
180
|
+
assignment1 = RoomAssignment.new(:student_id => student1.id, :dorm_id => room.dorm_id, :room_id => room.room_id)
|
181
|
+
assignment1.save!
|
182
|
+
|
183
|
+
assignment2 = RoomAssignment.new(:student_id => student2.id, :dorm_id => room.dorm_id, :room_id => room.room_id)
|
184
|
+
assignment2.save!
|
185
|
+
|
186
|
+
room.room_assignment_ids = [[assignment1.student_id, assignment1.dorm_id, assignment1.room_id],
|
187
|
+
[assignment2.student_id, assignment2.dorm_id, assignment2.room_id]]
|
188
|
+
room.save!
|
189
|
+
|
190
|
+
assert_equal(2, room.room_assignments.length)
|
191
|
+
assert_equal(assignment1, room.room_assignments[0])
|
192
|
+
assert_equal(assignment2, room.room_assignments[1])
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_find_or_create_by
|
196
|
+
suburb = Suburb.find_by(:city_id => 3, :suburb_id => 1)
|
197
|
+
assert_nil(suburb)
|
198
|
+
|
199
|
+
suburb = Suburb.find_or_create_by!(:name => 'New Suburb', :city_id => 3, :suburb_id => 1)
|
200
|
+
refute_nil(suburb)
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_cache
|
204
|
+
Suburb.cache do
|
205
|
+
# Suburb does not exist
|
206
|
+
suburb = Suburb.find_by(:city_id => 10, :suburb_id => 10)
|
207
|
+
assert_nil(suburb)
|
208
|
+
|
209
|
+
# Create it
|
210
|
+
suburb = Suburb.create!(:name => 'New Suburb', :city_id => 10, :suburb_id => 10)
|
211
|
+
|
212
|
+
# Should be able to find it
|
213
|
+
suburb = Suburb.find_by(:city_id => 10)
|
214
|
+
refute_nil(suburb)
|
215
|
+
refute_nil(suburb.city_id)
|
216
|
+
refute_nil(suburb.suburb_id)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
data/test/test_predicates.rb
CHANGED
@@ -1,60 +1,130 @@
|
|
1
|
-
require File.expand_path('../abstract_unit', __FILE__)
|
2
|
-
|
3
|
-
class TestPredicates < ActiveSupport::TestCase
|
4
|
-
fixtures :departments
|
5
|
-
|
6
|
-
include CompositePrimaryKeys::Predicates
|
7
|
-
|
8
|
-
def test_or
|
9
|
-
dep = Department.arel_table
|
10
|
-
|
11
|
-
predicates = Array.new
|
12
|
-
|
13
|
-
3.times do |i|
|
14
|
-
predicates << dep[:id].eq(i)
|
15
|
-
end
|
16
|
-
|
17
|
-
connection = ActiveRecord::Base.connection
|
18
|
-
quoted = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('id')}"
|
19
|
-
expected = "(#{quoted} = 0 OR #{quoted} = 1 OR #{quoted} = 2)"
|
20
|
-
|
21
|
-
pred = cpk_or_predicate(predicates)
|
22
|
-
assert_equal(with_quoted_identifiers(expected), pred.to_sql)
|
23
|
-
end
|
24
|
-
|
25
|
-
def test_or_with_many_values
|
26
|
-
dep = Arel::Table.new(:departments)
|
27
|
-
|
28
|
-
predicates = Array.new
|
29
|
-
|
30
|
-
number_of_predicates = 3000 # This should really be big
|
31
|
-
number_of_predicates.times do |i|
|
32
|
-
predicates << dep[:id].eq(i)
|
33
|
-
end
|
34
|
-
|
35
|
-
connection = ActiveRecord::Base.connection
|
36
|
-
quoted = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('id')}"
|
37
|
-
expected_ungrouped = ((0...number_of_predicates).map { |i| "#{quoted} = #{i}" }).join(' OR ')
|
38
|
-
expected = "(#{expected_ungrouped})"
|
39
|
-
|
40
|
-
pred = cpk_or_predicate(predicates)
|
41
|
-
assert_equal(with_quoted_identifiers(expected), pred.to_sql)
|
42
|
-
end
|
43
|
-
|
44
|
-
def test_and
|
45
|
-
dep = Department.arel_table
|
46
|
-
|
47
|
-
predicates = Array.new
|
48
|
-
|
49
|
-
3.times do |i|
|
50
|
-
predicates << dep[:id].eq(i)
|
51
|
-
end
|
52
|
-
|
53
|
-
connection = ActiveRecord::Base.connection
|
54
|
-
quoted = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('id')}"
|
55
|
-
expected = "#{quoted} = 0 AND #{quoted} = 1 AND #{quoted} = 2"
|
56
|
-
|
57
|
-
pred = cpk_and_predicate(predicates)
|
58
|
-
assert_equal(with_quoted_identifiers(expected), pred.to_sql)
|
59
|
-
end
|
60
|
-
|
1
|
+
require File.expand_path('../abstract_unit', __FILE__)
|
2
|
+
|
3
|
+
class TestPredicates < ActiveSupport::TestCase
|
4
|
+
fixtures :departments
|
5
|
+
|
6
|
+
include CompositePrimaryKeys::Predicates
|
7
|
+
|
8
|
+
def test_or
|
9
|
+
dep = Department.arel_table
|
10
|
+
|
11
|
+
predicates = Array.new
|
12
|
+
|
13
|
+
3.times do |i|
|
14
|
+
predicates << dep[:id].eq(i)
|
15
|
+
end
|
16
|
+
|
17
|
+
connection = ActiveRecord::Base.connection
|
18
|
+
quoted = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('id')}"
|
19
|
+
expected = "(#{quoted} = 0 OR #{quoted} = 1 OR #{quoted} = 2)"
|
20
|
+
|
21
|
+
pred = cpk_or_predicate(predicates)
|
22
|
+
assert_equal(with_quoted_identifiers(expected), pred.to_sql)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_or_with_many_values
|
26
|
+
dep = Arel::Table.new(:departments)
|
27
|
+
|
28
|
+
predicates = Array.new
|
29
|
+
|
30
|
+
number_of_predicates = 3000 # This should really be big
|
31
|
+
number_of_predicates.times do |i|
|
32
|
+
predicates << dep[:id].eq(i)
|
33
|
+
end
|
34
|
+
|
35
|
+
connection = ActiveRecord::Base.connection
|
36
|
+
quoted = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('id')}"
|
37
|
+
expected_ungrouped = ((0...number_of_predicates).map { |i| "#{quoted} = #{i}" }).join(' OR ')
|
38
|
+
expected = "(#{expected_ungrouped})"
|
39
|
+
|
40
|
+
pred = cpk_or_predicate(predicates)
|
41
|
+
assert_equal(with_quoted_identifiers(expected), pred.to_sql)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_and
|
45
|
+
dep = Department.arel_table
|
46
|
+
|
47
|
+
predicates = Array.new
|
48
|
+
|
49
|
+
3.times do |i|
|
50
|
+
predicates << dep[:id].eq(i)
|
51
|
+
end
|
52
|
+
|
53
|
+
connection = ActiveRecord::Base.connection
|
54
|
+
quoted = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('id')}"
|
55
|
+
expected = "#{quoted} = 0 AND #{quoted} = 1 AND #{quoted} = 2"
|
56
|
+
|
57
|
+
pred = cpk_and_predicate(predicates)
|
58
|
+
assert_equal(with_quoted_identifiers(expected), pred.to_sql)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_in
|
62
|
+
dep = Department.arel_table
|
63
|
+
|
64
|
+
primary_keys = [[1, 1], [1, 2]]
|
65
|
+
|
66
|
+
connection = ActiveRecord::Base.connection
|
67
|
+
quoted_id_column = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('id')}"
|
68
|
+
quoted_location_id_column = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('location_id')}"
|
69
|
+
expected = "#{quoted_id_column} = 1 AND #{quoted_location_id_column} IN (1, 2)"
|
70
|
+
|
71
|
+
pred = cpk_in_predicate(dep, [:id, :location_id], primary_keys)
|
72
|
+
assert_equal(with_quoted_identifiers(expected), pred.to_sql)
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_in_with_low_cardinality_second_key_part
|
76
|
+
dep = Department.arel_table
|
77
|
+
|
78
|
+
primary_keys = [[1, 1], [2, 1]]
|
79
|
+
|
80
|
+
connection = ActiveRecord::Base.connection
|
81
|
+
quoted_id_column = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('id')}"
|
82
|
+
quoted_location_id_column = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('location_id')}"
|
83
|
+
expected = "#{quoted_location_id_column} = 1 AND #{quoted_id_column} IN (1, 2)"
|
84
|
+
|
85
|
+
pred = cpk_in_predicate(dep, [:id, :location_id], primary_keys)
|
86
|
+
assert_equal(with_quoted_identifiers(expected), pred.to_sql)
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_in_with_nil_primary_key_part
|
90
|
+
dep = Department.arel_table
|
91
|
+
|
92
|
+
primary_keys = [[nil, 1], [nil, 2]]
|
93
|
+
|
94
|
+
connection = ActiveRecord::Base.connection
|
95
|
+
quoted_id_column = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('id')}"
|
96
|
+
quoted_location_id_column = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('location_id')}"
|
97
|
+
expected = "#{quoted_id_column} IS NULL AND #{quoted_location_id_column} IN (1, 2)"
|
98
|
+
|
99
|
+
pred = cpk_in_predicate(dep, [:id, :location_id], primary_keys)
|
100
|
+
assert_equal(with_quoted_identifiers(expected), pred.to_sql)
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_in_with_nil_secondary_key_part
|
104
|
+
dep = Department.arel_table
|
105
|
+
|
106
|
+
primary_keys = [[1, 1], [1, nil]]
|
107
|
+
|
108
|
+
connection = ActiveRecord::Base.connection
|
109
|
+
quoted_id_column = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('id')}"
|
110
|
+
quoted_location_id_column = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('location_id')}"
|
111
|
+
expected = "#{quoted_id_column} = 1 AND (#{quoted_location_id_column} IN (1) OR #{quoted_location_id_column} IS NULL)"
|
112
|
+
|
113
|
+
pred = cpk_in_predicate(dep, [:id, :location_id], primary_keys)
|
114
|
+
assert_equal(with_quoted_identifiers(expected), pred.to_sql)
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_in_with_multiple_primary_key_parts
|
118
|
+
dep = Department.arel_table
|
119
|
+
|
120
|
+
primary_keys = [[1, 1], [1, 2], [2, 3], [2, 4]]
|
121
|
+
|
122
|
+
connection = ActiveRecord::Base.connection
|
123
|
+
quoted_id_column = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('id')}"
|
124
|
+
quoted_location_id_column = "#{connection.quote_table_name('departments')}.#{connection.quote_column_name('location_id')}"
|
125
|
+
expected = "(#{quoted_id_column} = 1 AND #{quoted_location_id_column} IN (1, 2) OR #{quoted_id_column} = 2 AND #{quoted_location_id_column} IN (3, 4))"
|
126
|
+
|
127
|
+
pred = cpk_in_predicate(dep, [:id, :location_id], primary_keys)
|
128
|
+
assert_equal(with_quoted_identifiers(expected), pred.to_sql)
|
129
|
+
end
|
130
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: composite_primary_keys
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 14.0.
|
4
|
+
version: 14.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charlie Savage
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-02-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -216,7 +216,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
216
216
|
- !ruby/object:Gem::Version
|
217
217
|
version: '0'
|
218
218
|
requirements: []
|
219
|
-
rubygems_version: 3.
|
219
|
+
rubygems_version: 3.4.6
|
220
220
|
signing_key:
|
221
221
|
specification_version: 4
|
222
222
|
summary: Composite key support for ActiveRecord
|