composite_primary_keys 11.2.0 → 11.3.1

Sign up to get free protection for your applications and to get access to all the features.
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