composite_primary_keys 8.1.7 → 8.1.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf0a1ef2ec3bc66204ea285b2a47f4ef85abb4b0968bad900d7a4cc4d8f4d154
4
- data.tar.gz: 94d5e203222b2ab17b9aba0f2a23a0e0df1bcc608083540cbb0765d887be0c61
3
+ metadata.gz: 98777241ea29aef880ba2b53631fb47686751a898df5b7200cd64a06fb84b838
4
+ data.tar.gz: 0f5d62c48beab81bcc7a31dd9a025af2867eeb8821482e2aabc0933ae10b95e5
5
5
  SHA512:
6
- metadata.gz: 4cf0795ee5311d91749c120764a8ab6ba3c748a8802c2176dfb927ea72de4f625d165d87c698e0128a49413e8b97b95a5ec832f91280d1e5d3acf4c328f70dbb
7
- data.tar.gz: 1f2789ece64261de5cd6fdc1b892495f4f2f8efc86a462e1f61924d9fa3417bbd479e6c3f7787285ea68e26869d72bfa1fff51d81f43465868a06af4aa87af89
6
+ metadata.gz: 498c215b18b6056347095a00e81edf399cb0a684c8130413fbb0db87181451bf0801d9c5446b2d34d707137df0e984267d79a9f432accf0287003719a1e229b3
7
+ data.tar.gz: 1ed759331f302c751f4f93899d3d95945c8c768cd279f817b6431add9c7d97e5caa6b47f8500b8871fa6993a145b288fc6e56f7067ab2f5c479956a10e303d71
data/History.rdoc CHANGED
@@ -1,3 +1,6 @@
1
+ == 8.1.8 (2020-01-01)
2
+ Fix handling CPK with fields containing comma, #505 (Sergey Semyonov)
3
+
1
4
  == 8.1.7 (2019-05-04)
2
5
 
3
6
  Fix key not being an array in subquery on has_many :through (Eric Proulx)
@@ -64,7 +64,7 @@ module ActiveRecord
64
64
  @preloaded_records.map { |record|
65
65
  key = Array(association_key_name).map do |key_name|
66
66
  record[key_name]
67
- end.join(CompositePrimaryKeys::ID_SEP)
67
+ end.to_composite_keys.to_s
68
68
 
69
69
  [record, key]
70
70
  }
@@ -77,7 +77,7 @@ module ActiveRecord
77
77
  # owner[owner_key_name].to_s
78
78
  Array(owner_key_name).map do |key_name|
79
79
  owner[key_name]
80
- end.join(CompositePrimaryKeys::ID_SEP)
80
+ end.to_composite_keys.to_s
81
81
  end
82
82
  else
83
83
  owners.group_by do |owner|
@@ -85,10 +85,9 @@ module ActiveRecord
85
85
  # owner[owner_key_name]
86
86
  Array(owner_key_name).map do |key_name|
87
87
  owner[key_name]
88
- end.join(CompositePrimaryKeys::ID_SEP)
88
+ end.to_composite_keys.to_s
89
89
  end
90
90
  end
91
-
92
91
  end
93
92
  end
94
93
  end
@@ -186,7 +186,7 @@ module ActiveRecord
186
186
  end
187
187
 
188
188
  def to_param
189
- persisted? ? to_key.join(CompositePrimaryKeys::ID_SEP) : nil
189
+ persisted? ? to_key.to_composite_keys.to_s : nil
190
190
  end
191
191
  end
192
192
  end
@@ -1,6 +1,7 @@
1
1
  module CompositePrimaryKeys
2
2
  ID_SEP = ','
3
3
  ID_SET_SEP = ';'
4
+ ESCAPE_CHAR = '^'
4
5
 
5
6
  module ArrayExtension
6
7
  def to_composite_keys
@@ -8,12 +9,27 @@ module CompositePrimaryKeys
8
9
  end
9
10
  end
10
11
 
11
- def self.normalize(ids)
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)
12
23
  ids.map do |id|
13
- if id.is_a?(Array)
14
- normalize(id)
15
- elsif id.is_a?(String) && id.index(ID_SEP)
16
- id.split(ID_SEP)
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)
17
33
  else
18
34
  id
19
35
  end
@@ -27,7 +43,7 @@ module CompositePrimaryKeys
27
43
  when Array
28
44
  value.to_composite_keys
29
45
  when String
30
- self.new(value.split(ID_SEP))
46
+ value.split(ID_SEP).map { |key| Utils.unescape_string_key(key) }.to_composite_keys
31
47
  else
32
48
  raise(ArgumentError, "Unsupported type: #{value}")
33
49
  end
@@ -35,9 +51,36 @@ module CompositePrimaryKeys
35
51
 
36
52
  def to_s
37
53
  # Doing this makes it easier to parse Base#[](attr_name)
38
- join(ID_SEP)
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
39
81
  end
40
82
  end
83
+ private_constant :Utils
41
84
  end
42
85
 
43
86
  Array.send(:include, CompositePrimaryKeys::ArrayExtension)
@@ -4,7 +4,7 @@ module ActiveRecord
4
4
  def quote_column_names(name)
5
5
  Array(name).map do |col|
6
6
  quote_column_name(col.to_s)
7
- end.join(CompositePrimaryKeys::ID_SEP)
7
+ end.to_composite_keys.to_s
8
8
  end
9
9
  end
10
10
  end
@@ -84,7 +84,7 @@ module CompositePrimaryKeys
84
84
 
85
85
  # CPK
86
86
  # expects_array = ids.first.kind_of?(Array)
87
- ids = CompositePrimaryKeys.normalize(ids)
87
+ ids = CompositePrimaryKeys.normalize(ids, @klass.primary_keys.length)
88
88
  expects_array = ids.flatten != ids.flatten(1)
89
89
 
90
90
  return ids.first if expects_array && ids.first.empty?
@@ -138,7 +138,7 @@ module CompositePrimaryKeys
138
138
 
139
139
  result = ids.map do |cpk_ids|
140
140
  cpk_ids = if cpk_ids.length == 1
141
- cpk_ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
141
+ CompositePrimaryKeys::CompositeKeys.parse(cpk_ids.first)
142
142
  else
143
143
  cpk_ids.to_composite_keys
144
144
  end
@@ -2,7 +2,7 @@ module CompositePrimaryKeys
2
2
  module VERSION
3
3
  MAJOR = 8
4
4
  MINOR = 1
5
- TINY = 7
5
+ TINY = 8
6
6
  STRING = [MAJOR, MINOR, TINY].join('.')
7
7
  end
8
8
  end
@@ -202,8 +202,8 @@ create table seats (
202
202
  );
203
203
 
204
204
  create table capitols (
205
- country varchar(100) default null,
206
- city varchar(100) default null,
205
+ country varchar(100) not null,
206
+ city varchar(100) not null,
207
207
  primary key (country, city)
208
208
  );
209
209
 
@@ -21,4 +21,18 @@ class CompositeArraysTest < ActiveSupport::TestCase
21
21
  assert_equal CompositePrimaryKeys::CompositeKeys, keys.class
22
22
  assert_equal '1,2,3', keys.to_s
23
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
24
38
  end
data/test/test_find.rb CHANGED
@@ -41,6 +41,14 @@ class TestFind < ActiveSupport::TestCase
41
41
  assert_equal(['The Netherlands', 'Amsterdam'], capitol.id)
42
42
  end
43
43
 
44
+ def test_find_with_strings_with_comma_as_composite_keys
45
+ capitol = Capitol.create!(country: 'The USA', city: 'Washington, D.C.')
46
+ assert_equal ['The USA', 'Washington, D.C.'], capitol.id
47
+
48
+ assert_equal capitol, Capitol.find(['The USA', 'Washington, D.C.'])
49
+ assert_equal capitol, Capitol.find(capitol.to_param)
50
+ end
51
+
44
52
  def test_find_each
45
53
  room_assignments = []
46
54
  RoomAssignment.find_each(:batch_size => 2) do |assignment|
data/test/test_ids.rb CHANGED
@@ -40,6 +40,9 @@ class TestIds < ActiveSupport::TestCase
40
40
  testing_with do
41
41
  assert_equal '1,1', @first.to_param if composite?
42
42
  end
43
+
44
+ capitol = Capitol.create!(country: 'The USA', city: 'Washington, D.C.')
45
+ assert_equal 'The USA,Washington^2C D.C.', capitol.to_param
43
46
  end
44
47
 
45
48
  def test_ids_to_s
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: 8.1.7
4
+ version: 8.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charlie Savage
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-04 00:00:00.000000000 Z
11
+ date: 2020-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -28,30 +28,30 @@ dependencies:
28
28
  name: sqlite3
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 1.3.6
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 1.3.6
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pg
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '0.15'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '0.15'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: mysql2
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -285,7 +285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
285
285
  - !ruby/object:Gem::Version
286
286
  version: '0'
287
287
  requirements: []
288
- rubygems_version: 3.0.3
288
+ rubygems_version: 3.0.6
289
289
  signing_key:
290
290
  specification_version: 4
291
291
  summary: Composite key support for ActiveRecord