composite_primary_keys 11.2.0 → 11.3.1

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: 76e658e30475707dc7495c8f5f5647c18ad0a67edc3cc5787a312a1785418524
4
- data.tar.gz: f824d0c22fb21ec5d7d8311047433a6661cb5c134549ee46460e6c693b0cbe4c
3
+ metadata.gz: 9568b1a0261ca52beff062804d3885a795fe4193c9289b140c49c3c36ea199fb
4
+ data.tar.gz: b458b36aa5e082fc060838953e56eaea26557c4a9570d161c03420ded1a8097e
5
5
  SHA512:
6
- metadata.gz: 0721b3f2dcb649fd585baf7facf0747d73edf8929e1f7c2ef9b4522ce3824b23d94cec7caafd810d7ea77e03043ee3cc31abfd6dca16eb012556be147c634fe0
7
- data.tar.gz: 40282b497748d0a9eda0cc461c38c4dc91714560022fa37e7466e085a65ea2bd53b85f50cd5845560a2ab43e0c24544b76faf493457f9f9f166ce3761b420285
6
+ metadata.gz: 70cbc4245e31da5701b16dfe7fa109cfe1f395311446537567804b4a378ea6cd2c33d771b00b72e6cd0833a6365b6adcba03a3df4e6564668d298d28f89abbad
7
+ data.tar.gz: 52df628c9b690b8855f8dbfb52394fca4f07d674aba598b00439cf036138ef73b7975f82c434c185b9c4d4ee4537f70070c27007c2bb5ff81422c513b1a5b96f
@@ -1,3 +1,11 @@
1
+ == 11.3.1 (2020-04-01)
2
+ * Fix overriding AbstractReflection for activerecord 5.2.4 (Sergey Semyonov)
3
+ * Fix handling CPK with fields containing comma (Sergey Semyonov)
4
+ * Fixed incorrect SQL condition for joining by CPK (Sergey Semyonov)
5
+ * Update travis.yml file (Sergey Semyonov)
6
+ * Add tests for composite keys with default values (Daniel Wiklund)
7
+ * Fix create record where one or more of the primary keys has a default value (Daniel Wiklund)
8
+
1
9
  == 11.2.0 (2019-03-16)
2
10
  * When creating new records, honor composite key autoincrementing fields if possible (Antti Pitkänen)
3
11
  * Update Association#run to more closely match ActiveRecord (Fabian Mersch)
@@ -26,7 +26,7 @@ $:.unshift(File.dirname(__FILE__)) unless
26
26
 
27
27
  unless defined?(ActiveRecord)
28
28
  require 'rubygems'
29
- gem 'activerecord', '~> 5.2.1'
29
+ gem 'activerecord', '~> 5.2.4'
30
30
  require 'active_record'
31
31
  end
32
32
 
@@ -123,7 +123,7 @@ module ActiveRecord
123
123
  end
124
124
 
125
125
  def to_param
126
- persisted? ? to_key.join(CompositePrimaryKeys::ID_SEP) : nil
126
+ persisted? ? to_key.to_composite_keys.to_s : nil
127
127
  end
128
128
  end
129
129
  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 several 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
@@ -43,9 +59,36 @@ module CompositePrimaryKeys
43
59
 
44
60
  def to_s
45
61
  # Doing this makes it easier to parse Base#[](attr_name)
46
- join(ID_SEP)
62
+ map { |key| Utils.escape_string_key(key.to_s) }.join(ID_SEP)
63
+ end
64
+ end
65
+
66
+ module Utils
67
+ class << self
68
+ def escape_string_key(key)
69
+ key.gsub(Regexp.union(ESCAPE_CHAR, ID_SEP)) do |unsafe|
70
+ "#{ESCAPE_CHAR}#{unsafe.ord.to_s(16).upcase}"
71
+ end
72
+ end
73
+
74
+ def unescape_string_key(key)
75
+ key.gsub(/#{Regexp.escape(ESCAPE_CHAR)}[0-9a-fA-F]{2}/) do |escaped|
76
+ char = escaped.slice(1, 2).hex.chr
77
+ (char == ESCAPE_CHAR || char == ID_SEP) ? char : escaped
78
+ end
79
+ end
80
+
81
+ def cpk_as_array?(value, pk_size)
82
+ # We don't permit Array to be an element of CPK.
83
+ value.is_a?(Array) && value.size == pk_size && value.none? { |item| item.is_a?(Array) }
84
+ end
85
+
86
+ def cpk_as_string?(value, pk_size)
87
+ value.is_a?(String) && value.count(ID_SEP) == pk_size - 1
88
+ end
47
89
  end
48
90
  end
91
+ private_constant :Utils
49
92
  end
50
93
 
51
94
  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
@@ -64,8 +64,9 @@ module ActiveRecord
64
64
  new_id = self.class._insert_record(attributes_values)
65
65
 
66
66
  # CPK
67
- if self.composite? && self.id.compact.empty?
68
- self.id = new_id
67
+ if self.composite?
68
+ # Merge together the specified id with the new id (specified id gets precedence)
69
+ self.id = self.id.zip(Array(new_id)).map {|id1, id2| (id1 || id2)}
69
70
  else
70
71
  self.id ||= new_id if self.class.primary_key
71
72
  end
@@ -1,19 +1,28 @@
1
1
  module ActiveRecord
2
2
  module Reflection
3
3
  class AbstractReflection
4
- def build_join_constraint(table, foreign_table)
4
+ # Overriding for activerecord v5.2.4
5
+ def join_scope(table, foreign_table, foreign_klass)
6
+ predicate_builder = predicate_builder(table)
7
+ scope_chain_items = join_scopes(table, predicate_builder)
8
+ klass_scope = klass_join_scope(table, predicate_builder)
9
+
5
10
  key = join_keys.key
6
11
  foreign_key = join_keys.foreign_key
7
12
 
8
13
  # CPK
9
- #constraint = table[key].eq(foreign_table[foreign_key])
10
- constraint = cpk_join_predicate(table, key, foreign_table, foreign_key)
14
+ # klass_scope.where!(table[key].eq(foreign_table[foreign_key]))
15
+ klass_scope.where!(cpk_join_predicate(table, key, foreign_table, foreign_key))
16
+
17
+ if type
18
+ klass_scope.where!(type => foreign_klass.polymorphic_name)
19
+ end
11
20
 
12
21
  if klass.finder_needs_type_condition?
13
- table.create_and([constraint, klass.send(:type_condition, table)])
14
- else
15
- constraint
22
+ klass_scope.where!(klass.send(:type_condition, table))
16
23
  end
24
+
25
+ scope_chain_items.inject(klass_scope, &:merge!)
17
26
  end
18
27
  end
19
28
  end
@@ -79,7 +79,7 @@ module CompositePrimaryKeys
79
79
 
80
80
  # CPK
81
81
  # expects_array = ids.first.kind_of?(Array)
82
- ids = CompositePrimaryKeys.normalize(ids)
82
+ ids = CompositePrimaryKeys.normalize(ids, @klass.primary_keys.length)
83
83
  expects_array = ids.flatten != ids.flatten(1)
84
84
  return ids.first if expects_array && ids.first.empty?
85
85
 
@@ -156,10 +156,10 @@ module CompositePrimaryKeys
156
156
  # CPK
157
157
  if composite?
158
158
  ids = if ids.length == 1
159
- ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
160
- else
161
- ids.to_composite_keys
162
- end
159
+ CompositePrimaryKeys::CompositeKeys.parse(ids.first)
160
+ else
161
+ ids.to_composite_keys
162
+ end
163
163
  end
164
164
 
165
165
  return find_some_ordered(ids) unless order_values.present?
@@ -1,8 +1,8 @@
1
1
  module CompositePrimaryKeys
2
2
  module VERSION
3
3
  MAJOR = 11
4
- MINOR = 2
5
- TINY = 0
4
+ MINOR = 3
5
+ TINY = 1
6
6
  STRING = [MAJOR, MINOR, TINY].join('.')
7
7
  end
8
8
  end
@@ -0,0 +1,3 @@
1
+ class CpkWithDefaultValue < ActiveRecord::Base
2
+ self.primary_keys = :record_id, :record_version
3
+ end
@@ -0,0 +1,7 @@
1
+ first:
2
+ record_id: 1
3
+ record_version: First
4
+
5
+ second:
6
+ record_id: 1
7
+ record_version: Second
@@ -215,4 +215,10 @@ create table pk_called_ids (
215
215
  abbreviation varchar(50) default null,
216
216
  description varchar(50) default null,
217
217
  primary key (id, reference_code)
218
- );
218
+ );
219
+
220
+ create table cpk_with_default_values (
221
+ record_id integer not null,
222
+ record_version varchar(50) default '' not null,
223
+ primary key (record_id, record_version)
224
+ );
@@ -45,4 +45,6 @@ drop table capitols;
45
45
  drop table products_restaurants;
46
46
  drop table employees_groups;
47
47
  drop table pk_called_ids;
48
- drop sequence pk_called_ids_seq;
48
+ drop sequence pk_called_ids_seq;
49
+ drop table cpk_with_default_values;
50
+ drop sequence cpk_with_default_values_seq;
@@ -234,3 +234,11 @@ create table pk_called_ids (
234
234
  description varchar(50) default null,
235
235
  constraint pk_called_ids_pk primary key (id, reference_code)
236
236
  );
237
+
238
+ create sequence cpk_with_default_values_seq start with 1000;
239
+
240
+ create table cpk_with_default_values (
241
+ record_id int not null,
242
+ record_version varchar(50) default '' not null,
243
+ constraint cpk_with_default_values_pk primary key (record_id, record_version)
244
+ );
@@ -218,3 +218,9 @@ create table pk_called_ids (
218
218
  description varchar(50) default null,
219
219
  primary key (id, reference_code)
220
220
  );
221
+
222
+ create table cpk_with_default_values (
223
+ record_id serial not null,
224
+ record_version varchar(50) default '' not null,
225
+ primary key (record_id, record_version)
226
+ );
@@ -204,3 +204,9 @@ create table pk_called_ids (
204
204
  description varchar(50) default null,
205
205
  primary key (id, reference_code)
206
206
  );
207
+
208
+ create table cpk_with_default_values (
209
+ record_id integer not null,
210
+ record_version varchar(50) default '' not null,
211
+ primary key (record_id, record_version)
212
+ );
@@ -210,4 +210,11 @@ CREATE TABLE pk_called_ids (
210
210
  description [varchar](50) default null
211
211
  CONSTRAINT [pk_called_ids_pk] PRIMARY KEY
212
212
  ( [id], [reference_code] )
213
- );
213
+ );
214
+
215
+ CREATE TABLE cpk_with_default_values (
216
+ record_id [int] IDENTITY(1000,1) NOT NULL,
217
+ record_version [varchar](50) default '' NOT NULL
218
+ CONSTRAINT [cpk_with_default_values_pk] PRIMARY KEY
219
+ ( [record_id], [record_version] )
220
+ );
@@ -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
@@ -1,7 +1,7 @@
1
1
  require File.expand_path('../abstract_unit', __FILE__)
2
2
 
3
3
  class TestCreate < ActiveSupport::TestCase
4
- fixtures :articles, :students, :dorms, :rooms, :room_assignments, :reference_types, :reference_codes, :streets, :suburbs
4
+ fixtures :articles, :students, :dorms, :rooms, :room_assignments, :reference_types, :reference_codes, :streets, :suburbs, :cpk_with_default_values
5
5
 
6
6
  CLASSES = {
7
7
  :single => {
@@ -172,4 +172,26 @@ class TestCreate < ActiveSupport::TestCase
172
172
 
173
173
  assert_equal('Validation failed: Id has already been taken', error.to_s)
174
174
  end
175
+
176
+ def test_find_or_create_by
177
+ suburb = Suburb.find_by(:city_id => 3, :suburb_id => 1)
178
+ assert_nil(suburb)
179
+
180
+ suburb = Suburb.find_or_create_by!(:name => 'New Suburb', :city_id => 3, :suburb_id => 1)
181
+ refute_nil(suburb)
182
+ end
183
+
184
+ def test_create_when_pk_has_default_value
185
+ first = CpkWithDefaultValue.create!
186
+ refute_nil(first.record_id)
187
+ assert_equal('', first.record_version)
188
+
189
+ second = CpkWithDefaultValue.create!(record_id: first.record_id, record_version: 'Same id, different version')
190
+ assert_equal(first.record_id, second.record_id)
191
+ assert_equal('Same id, different version', second.record_version)
192
+
193
+ third = CpkWithDefaultValue.create!(record_version: 'Created by version only')
194
+ refute_nil(third.record_id)
195
+ assert_equal('Created by version only', third.record_version)
196
+ end
175
197
  end
@@ -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|
@@ -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: 11.2.0
4
+ version: 11.3.1
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-03-16 00:00:00.000000000 Z
11
+ date: 2020-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 5.2.1
19
+ version: 5.2.4
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 5.2.1
26
+ version: 5.2.4
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -147,13 +147,14 @@ files:
147
147
  - test/connections/databases.ci.yml
148
148
  - test/connections/databases.example.yml
149
149
  - test/connections/databases.yml
150
- - test/db_test.rb
151
150
  - test/fixtures/article.rb
152
151
  - test/fixtures/articles.yml
153
152
  - test/fixtures/capitol.rb
154
153
  - test/fixtures/capitols.yml
155
154
  - test/fixtures/comment.rb
156
155
  - test/fixtures/comments.yml
156
+ - test/fixtures/cpk_with_default_value.rb
157
+ - test/fixtures/cpk_with_default_values.yml
157
158
  - test/fixtures/db_definitions/db2-create-tables.sql
158
159
  - test/fixtures/db_definitions/db2-drop-tables.sql
159
160
  - test/fixtures/db_definitions/mysql.sql
@@ -272,13 +273,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
272
273
  - !ruby/object:Gem::Version
273
274
  version: '0'
274
275
  requirements: []
275
- rubygems_version: 3.0.2
276
+ rubygems_version: 3.1.2
276
277
  signing_key:
277
278
  specification_version: 4
278
279
  summary: Composite key support for ActiveRecord
279
280
  test_files:
280
281
  - test/abstract_unit.rb
281
- - test/db_test.rb
282
282
  - test/README_tests.rdoc
283
283
  - test/setup.rb
284
284
  - test/test_aliases.rb
@@ -1,53 +0,0 @@
1
- # assoc_test.rb
2
-
3
- path = File.expand_path(File.join(File.basename(__FILE__), "..", "lib", "composite_primary_keys"))
4
- puts path
5
-
6
- require File.join(path)
7
- require 'active_record'
8
-
9
- $configuration = {
10
- :adapter => 'postgresql',
11
- :database => 'cpk_test',
12
- :username => 'postgres'
13
- }
14
-
15
- ActiveRecord::Base.establish_connection($configuration) unless ActiveRecord::Base.connected?
16
-
17
- module GlobePG
18
- class PGBase < ActiveRecord::Base
19
- self.abstract_class = true
20
- # establish_connection($configuration) unless connected?
21
- end
22
- end
23
-
24
- module GlobePG
25
- class TeacherToSchool < PGBase
26
- set_table_name 'teacher_to_school'
27
- self.primary_keys = ['teacherid', 'schoolid']
28
-
29
- belongs_to :globe_teacher, :foreign_key => 'teacherid'
30
- belongs_to :globe_school, :foreign_key => 'schoolid'
31
- end
32
- end
33
-
34
- module GlobePG
35
- class GlobeSchool < PGBase
36
- set_table_name 'globe_school'
37
- self.primary_key = 'schoolid'
38
- has_many :teacher_to_schools, :foreign_key => :schoolid
39
- has_many :globe_teachers, :through => :teacher_to_schools
40
- end
41
- end
42
-
43
- module GlobePG
44
- class GlobeTeacher < PGBase
45
- set_table_name 'globe_teacher'
46
- self.primary_key = 'teacherid'
47
- has_many :teacher_to_schools, :foreign_key => :teacherid
48
- has_many :globe_schools, :through => :teacher_to_schools
49
- end
50
- end
51
-
52
- teacher = GlobePG::GlobeTeacher.find_by_teacherid('ZZGLOBEY')
53
- p teacher.globe_schools