torque-postgresql 2.2.3 → 2.2.4

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: 3e9379e388022831a9441cb6b3b646ff8bdea7340cba61f0dd197bcdbf589f72
4
- data.tar.gz: cfb88b94086027202e92a2120ad8ba1a5e939ef089aea5dcdfd85e05a7d122ea
3
+ metadata.gz: 95fa82a4869d180b12518b184191e6877b9e6cf3108fb5e68e55d6a836809389
4
+ data.tar.gz: 45c6e24a30b3782ec26ff5caafe01430c24f137c460eb19b6f2a49e0b6609142
5
5
  SHA512:
6
- metadata.gz: 809642eb8f6ee574e7b3b9557590baa58624fec54d1c69b6f341999dfc4217aea5010abc1b438119b3213f958918ce00ddd1af1df3a4714321b1f6153f8ee57d
7
- data.tar.gz: e81cf089a977d964326548144b14dd4ab7ce4be0612f2e923e34add1aa951aa2e5467daaa544da42ac17773fb2cc047335a297f3807a1e72f8dc1942f1df4195
6
+ metadata.gz: cd7e925ab13b8ae3eb6f0d92fbb994f12c6c121998792263b0752eb9647c0d2e4f72dec8d3973c6e0365fe0c5194a2af5fba9189189743c5bca7b06a4e6dfa41
7
+ data.tar.gz: 470bb4e8b0816318ca5ee1b99e1896a8a4f3297af46e653de4ddac5347cf40a781b530a4e2531cf41eae38f7205332e52ced09e5b857a618f965851a6d9d7162
@@ -12,16 +12,16 @@ module Torque
12
12
  args.each { |name| column(name, :interval, **options) }
13
13
  end
14
14
 
15
- # Creates a column with an enum type, needing to specify the subtype,
15
+ # Creates a column with an enum type, needing to specify the enum_type,
16
16
  # which is basically the name of the type defined prior creating the
17
17
  # column
18
18
  def enum(*args, **options)
19
- subtype = options.delete(:subtype)
20
- args.each { |name| column(name, (subtype || name), **options) }
19
+ enum_type = [options.delete(:subtype), options.delete(:enum_type)].compact.first
20
+ args.each { |name| column(name, (enum_type || name), **options) }
21
21
  end
22
22
 
23
23
  # Creates a column with an enum array type, needing to specify the
24
- # subtype, which is basically the name of the type defined prior
24
+ # enum_type, which is basically the name of the type defined prior
25
25
  # creating the column
26
26
  def enum_set(*args, **options)
27
27
  super(*args, **options.merge(array: true))
@@ -47,7 +47,7 @@ module Torque
47
47
 
48
48
  if ActiveRecord::ConnectionAdapters::PostgreSQL.const_defined?('ColumnDefinition')
49
49
  module ColumnDefinition
50
- attr_accessor :subtype
50
+ attr_accessor :subtype, :enum_type
51
51
  end
52
52
 
53
53
  ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnDefinition.include ColumnDefinition
@@ -22,12 +22,12 @@ module Torque
22
22
  column.type == :enum_set ? :enum : super
23
23
  end
24
24
 
25
- # Adds +:subtype+ option to the default set
25
+ # Adds +:enum_type+ option to the default set
26
26
  def prepare_column_options(column)
27
27
  spec = super
28
28
 
29
- if subtype = schema_subtype(column)
30
- spec[:subtype] = subtype
29
+ if enum_type = schema_enum_type(column)
30
+ spec[:enum_type] = enum_type
31
31
  end
32
32
 
33
33
  spec
@@ -35,7 +35,7 @@ module Torque
35
35
 
36
36
  private
37
37
 
38
- def schema_subtype(column)
38
+ def schema_enum_type(column)
39
39
  column.sql_type.to_sym.inspect if column.type == :enum || column.type == :enum_set
40
40
  end
41
41
 
@@ -89,7 +89,8 @@ module Torque
89
89
  types = @connection.user_defined_types('e')
90
90
  return unless types.any?
91
91
 
92
- stream.puts " # These are user-defined types used on this database"
92
+ stream.puts " # Custom types defined in this database."
93
+ stream.puts " # Note that some types may not work with other database engines. Be careful if changing database."
93
94
  types.sort_by(&:first).each { |(name, type)| send(type.to_sym, name, stream) }
94
95
  stream.puts
95
96
  rescue => e
@@ -6,9 +6,6 @@ module Torque
6
6
  module AbstractReflection
7
7
  AREL_ATTR = ::Arel::Attributes::Attribute
8
8
 
9
- ARR_NO_CAST = 'bigint'
10
- ARR_CAST = 'bigint[]'
11
-
12
9
  # Check if the foreign key actually exists
13
10
  def connected_through_array?
14
11
  false
@@ -40,34 +37,29 @@ module Torque
40
37
  result
41
38
  end
42
39
 
43
- # Build the id constraint checking if both types are perfect matching
40
+ # Build the id constraint checking if both types are perfect matching.
41
+ # The klass attribute (left side) will always be a column attribute
44
42
  def build_id_constraint(klass_attr, source_attr)
45
43
  return klass_attr.eq(source_attr) unless connected_through_array?
46
44
 
47
45
  # Klass and key are associated with the reflection Class
48
46
  klass_type = klass.columns_hash[join_keys.key.to_s]
49
- # active_record and foreign_key are associated with the source Class
50
- source_type = active_record.columns_hash[join_keys.foreign_key.to_s]
51
47
 
52
- # If both are attributes but the left side is not an array, and the
53
- # right side is, use the ANY operation
54
- any_operation = arel_array_to_any(klass_attr, source_attr, klass_type, source_type)
55
- return klass_attr.eq(any_operation) if any_operation
48
+ # Apply an ANY operation which checks if the single value on the left
49
+ # side exists in the array on the right side
50
+ if source_attr.is_a?(AREL_ATTR)
51
+ any_value = [klass_attr, source_attr]
52
+ any_value.reverse! if klass_type.try(:array?)
53
+ return any_value.shift.eq(::Arel::Nodes::NamedFunction.new('ANY', any_value))
54
+ end
56
55
 
57
56
  # If the left side is not an array, just use the IN condition
58
57
  return klass_attr.in(source_attr) unless klass_type.try(:array)
59
58
 
60
- # Decide if should apply a cast to ensure same type comparision
61
- should_cast = klass_type.type.eql?(:integer) && source_type.type.eql?(:integer)
62
- should_cast &= !klass_type.sql_type.eql?(source_type.sql_type)
63
- should_cast |= !(klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR))
64
-
65
- # Apply necessary transformations to values
66
- klass_attr = cast_constraint_to_array(klass_type, klass_attr, should_cast)
67
- source_attr = cast_constraint_to_array(source_type, source_attr, should_cast)
68
-
69
- # Return the overlap condition
70
- klass_attr.overlaps(source_attr)
59
+ # Build the overlap condition (array && array) ensuring that the right
60
+ # side has the same type as the left side
61
+ source_attr = ::Arel::Nodes.build_quoted(Array.wrap(source_attr))
62
+ klass_attr.overlaps(source_attr.cast(klass_type.sql_type_metadata.sql_type))
71
63
  end
72
64
 
73
65
  if PostgreSQL::AR610
@@ -85,24 +77,6 @@ module Torque
85
77
 
86
78
  build_id_constraint(klass_attr, source_attr)
87
79
  end
88
-
89
- # Prepare a value for an array constraint overlap condition
90
- def cast_constraint_to_array(type, value, should_cast)
91
- base_ready = type.try(:array) && value.is_a?(AREL_ATTR)
92
- return value if base_ready && (type.sql_type.eql?(ARR_NO_CAST) || !should_cast)
93
-
94
- value = ::Arel::Nodes.build_quoted(Array.wrap(value)) unless base_ready
95
- value = value.cast(ARR_CAST) if should_cast
96
- value
97
- end
98
-
99
- # Check if it's possible to turn both attributes into an ANY condition
100
- def arel_array_to_any(klass_attr, source_attr, klass_type, source_type)
101
- return unless !klass_type.try(:array) && source_type.try(:array) &&
102
- klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR)
103
-
104
- ::Arel::Nodes::NamedFunction.new('ANY', [source_attr])
105
- end
106
80
  end
107
81
 
108
82
  ::ActiveRecord::Reflection::AbstractReflection.prepend(AbstractReflection)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Torque
4
4
  module PostgreSQL
5
- VERSION = '2.2.3'
5
+ VERSION = '2.2.4'
6
6
  end
7
7
  end
data/spec/schema.rb CHANGED
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- version = 77
13
+ version = 2
14
14
 
15
15
  return if ActiveRecord::Migrator.current_version == version
16
16
  ActiveRecord::Schema.define(version: version) do
@@ -20,7 +20,8 @@ ActiveRecord::Schema.define(version: version) do
20
20
  enable_extension "pgcrypto"
21
21
  enable_extension "plpgsql"
22
22
 
23
- # These are user-defined types used on this database
23
+ # Custom types defined in this database.
24
+ # Note that some types may not work with other database engines. Be careful if changing database.
24
25
  create_enum "content_status", ["created", "draft", "published", "archived"], force: :cascade
25
26
  create_enum "specialties", ["books", "movies", "plays"], force: :cascade
26
27
  create_enum "roles", ["visitor", "assistant", "manager", "admin"], force: :cascade
@@ -53,8 +54,8 @@ ActiveRecord::Schema.define(version: version) do
53
54
  t.bigint "tag_ids", array: true
54
55
  t.string "title"
55
56
  t.string "url"
56
- t.enum "type", subtype: :types
57
- t.enum "conflicts", subtype: :conflicts, array: true
57
+ t.enum "type", enum_type: :types
58
+ t.enum "conflicts", enum_type: :conflicts, array: true
58
59
  t.datetime "created_at", null: false
59
60
  t.datetime "updated_at", null: false
60
61
  end
@@ -62,13 +63,13 @@ ActiveRecord::Schema.define(version: version) do
62
63
  create_table "authors", force: :cascade do |t|
63
64
  t.string "name"
64
65
  t.string "type"
65
- t.enum "specialty", subtype: :specialties
66
+ t.enum "specialty", enum_type: :specialties
66
67
  end
67
68
 
68
69
  create_table "texts", force: :cascade do |t|
69
70
  t.integer "user_id"
70
71
  t.string "content"
71
- t.enum "conflict", subtype: :conflicts
72
+ t.enum "conflict", enum_type: :conflicts
72
73
  end
73
74
 
74
75
  create_table "comments", force: :cascade do |t|
@@ -84,7 +85,7 @@ ActiveRecord::Schema.define(version: version) do
84
85
  create_table "courses", force: :cascade do |t|
85
86
  t.string "title", null: false
86
87
  t.interval "duration"
87
- t.enum "types", subtype: :types, array: true
88
+ t.enum "types", enum_type: :types, array: true
88
89
  t.datetime "created_at", null: false
89
90
  t.datetime "updated_at", null: false
90
91
  end
@@ -98,7 +99,7 @@ ActiveRecord::Schema.define(version: version) do
98
99
  t.integer "activity_id"
99
100
  t.string "title"
100
101
  t.text "content"
101
- t.enum "status", subtype: :content_status
102
+ t.enum "status", enum_type: :content_status
102
103
  t.index ["author_id"], name: "index_posts_on_author_id", using: :btree
103
104
  end
104
105
 
@@ -111,7 +112,7 @@ ActiveRecord::Schema.define(version: version) do
111
112
 
112
113
  create_table "users", force: :cascade do |t|
113
114
  t.string "name", null: false
114
- t.enum "role", subtype: :roles, default: :visitor
115
+ t.enum "role", enum_type: :roles, default: :visitor
115
116
  t.datetime "created_at", null: false
116
117
  t.datetime "updated_at", null: false
117
118
  end
@@ -120,7 +121,7 @@ ActiveRecord::Schema.define(version: version) do
120
121
  t.integer "author_id"
121
122
  t.string "title"
122
123
  t.boolean "active"
123
- t.enum "kind", subtype: :types
124
+ t.enum "kind", enum_type: :types
124
125
  t.datetime "created_at", null: false
125
126
  t.datetime "updated_at", null: false
126
127
  end
data/spec/spec_helper.rb CHANGED
@@ -39,6 +39,7 @@ RSpec.configure do |config|
39
39
 
40
40
  # Handles acton before rspec initialize
41
41
  config.before(:suite) do
42
+ ActiveSupport::Deprecation.silenced = true
42
43
  DatabaseCleaner.clean_with(:truncation)
43
44
  end
44
45
 
@@ -46,10 +47,6 @@ RSpec.configure do |config|
46
47
  DatabaseCleaner.strategy = :transaction
47
48
  end
48
49
 
49
- config.before(:each, js: true) do
50
- DatabaseCleaner.strategy = :truncation
51
- end
52
-
53
50
  config.before(:each) do
54
51
  DatabaseCleaner.start
55
52
  end
@@ -68,26 +68,37 @@ RSpec.describe 'Arel' do
68
68
 
69
69
  it 'works properly when column is an array' do
70
70
  expect { connection.add_column(:authors, :tag_ids, :bigint, array: true, default: []) }.not_to raise_error
71
- expect(Author.columns_hash['tag_ids'].default).to eq([])
71
+ expect(Author.new.tag_ids).to eq([])
72
72
  end
73
73
 
74
- it 'works with an array with enum values' do
74
+ it 'works with an array with enum values for a new enum' do
75
+ value = ['a', 'b']
76
+
77
+ expect do
78
+ connection.create_enum(:samples, %i[a b c d])
79
+ connection.add_column(:authors, :samples, :samples, array: true, default: value)
80
+ end.not_to raise_error
81
+
82
+ expect(Author.new.samples).to eq(value)
83
+ end
84
+
85
+ it 'works with an array with enum values for an existing enum' do
75
86
  value = ['visitor', 'assistant']
76
87
  expect { connection.add_column(:authors, :roles, :roles, array: true, default: value) }.not_to raise_error
77
- expect(Author.columns_hash['roles'].default).to eq(value)
88
+ expect(Author.new.roles).to eq(value)
78
89
  end
79
90
 
80
91
  it 'works with multi dimentional array' do
81
92
  value = [['1', '2'], ['3', '4']]
82
93
  expect { connection.add_column(:authors, :tag_ids, :string, array: true, default: value) }.not_to raise_error
83
- expect(Author.columns_hash['tag_ids'].default).to eq(value)
94
+ expect(Author.new.tag_ids).to eq(value)
84
95
  end
85
96
 
86
97
  it 'works with change column default value' do
87
98
  value = ['2', '3']
88
99
  connection.add_column(:authors, :tag_ids, :string, array: true)
89
100
  expect { connection.change_column_default(:authors, :tag_ids, { from: nil, to: value }) }.not_to raise_error
90
- expect(Author.columns_hash['tag_ids'].default).to eq(value)
101
+ expect(Author.new.tag_ids).to eq(value)
91
102
  end
92
103
  end
93
104
 
@@ -392,4 +392,53 @@ RSpec.describe 'BelongsToMany' do
392
392
  end
393
393
  end
394
394
  end
395
+
396
+ context 'using uuid' do
397
+ let(:connection) { ActiveRecord::Base.connection }
398
+ let(:game) { Class.new(ActiveRecord::Base) }
399
+ let(:player) { Class.new(ActiveRecord::Base) }
400
+ let(:other) { player.create }
401
+
402
+ # TODO: Set as a shred example
403
+ before do
404
+ connection.create_table(:players, id: :uuid) { |t| t.string :name }
405
+ connection.create_table(:games, id: :uuid) { |t| t.uuid :player_ids, array: true }
406
+
407
+ options = { anonymous_class: player, foreign_key: :player_ids }
408
+ options[:inverse_of] = false if Torque::PostgreSQL::AR610
409
+
410
+ game.table_name = 'games'
411
+ player.table_name = 'players'
412
+ game.belongs_to_many :players, **options
413
+ end
414
+
415
+ subject { game.create }
416
+
417
+ it 'loads associated records' do
418
+ subject.update(player_ids: [other.id])
419
+ expect(subject.players.to_sql).to be_eql(<<-SQL.squish)
420
+ SELECT "players".* FROM "players" WHERE "players"."id" IN ('#{other.id}')
421
+ SQL
422
+
423
+ expect(subject.players.load).to be_a(ActiveRecord::Associations::CollectionProxy)
424
+ expect(subject.players.to_a).to be_eql([other])
425
+ end
426
+
427
+ it 'can preload records' do
428
+ records = 5.times.map { player.create }
429
+ subject.players.concat(records)
430
+
431
+ entries = game.all.includes(:players).load
432
+
433
+ expect(entries.size).to be_eql(1)
434
+ expect(entries.first.players).to be_loaded
435
+ expect(entries.first.players.size).to be_eql(5)
436
+ end
437
+
438
+ it 'can joins records' do
439
+ query = game.all.joins(:players)
440
+ expect(query.to_sql).to match(/INNER JOIN "players"/)
441
+ expect { query.load }.not_to raise_error
442
+ end
443
+ end
395
444
  end
@@ -29,7 +29,7 @@ RSpec.describe 'Enum' do
29
29
  subject { table_definition.new(connection, 'articles') }
30
30
 
31
31
  it 'can be defined as an array' do
32
- subject.enum(:content_status, array: true)
32
+ subject.enum(:content_status, array: true, enum_type: :content_status)
33
33
  expect(subject['content_status'].name).to be_eql('content_status')
34
34
  expect(subject['content_status'].type).to be_eql(:content_status)
35
35
 
@@ -44,14 +44,14 @@ RSpec.describe 'Enum' do
44
44
  context 'on schema' do
45
45
  it 'can be used on tables' do
46
46
  dump_io = StringIO.new
47
- checker = /t\.enum +"conflicts", +array: true, +subtype: :conflicts/
47
+ checker = /t\.enum +"conflicts", +array: true, +enum_type: :conflicts/
48
48
  ActiveRecord::SchemaDumper.dump(connection, dump_io)
49
49
  expect(dump_io.string).to match checker
50
50
  end
51
51
 
52
52
  xit 'can have a default value as an array of symbols' do
53
53
  dump_io = StringIO.new
54
- checker = /t\.enum +"types", +default: \[:A, :B\], +array: true, +subtype: :types/
54
+ checker = /t\.enum +"types", +default: \[:A, :B\], +array: true, +enum_type: :types/
55
55
  ActiveRecord::SchemaDumper.dump(connection, dump_io)
56
56
  expect(dump_io.string).to match checker
57
57
  end
@@ -99,13 +99,19 @@ RSpec.describe 'Enum' do
99
99
  end
100
100
 
101
101
  it 'can be used in a multiple form' do
102
- subject.enum('foo', 'bar', 'baz', subtype: :content_status)
102
+ subject.enum('foo', 'bar', 'baz', enum_type: :content_status)
103
103
  expect(subject['foo'].type).to be_eql(:content_status)
104
104
  expect(subject['bar'].type).to be_eql(:content_status)
105
105
  expect(subject['baz'].type).to be_eql(:content_status)
106
106
  end
107
107
 
108
108
  it 'can have custom type' do
109
+ subject.enum('foo', enum_type: :content_status)
110
+ expect(subject['foo'].name).to be_eql('foo')
111
+ expect(subject['foo'].type).to be_eql(:content_status)
112
+ end
113
+
114
+ it 'can use the deprecated subtype option' do
109
115
  subject.enum('foo', subtype: :content_status)
110
116
  expect(subject['foo'].name).to be_eql('foo')
111
117
  expect(subject['foo'].type).to be_eql(:content_status)
@@ -143,13 +149,13 @@ RSpec.describe 'Enum' do
143
149
  it 'can be used on tables too' do
144
150
  dump_io = StringIO.new
145
151
  ActiveRecord::SchemaDumper.dump(connection, dump_io)
146
- expect(dump_io.string).to match /t\.enum +"status", +subtype: :content_status/
152
+ expect(dump_io.string).to match /t\.enum +"status", +enum_type: :content_status/
147
153
  end
148
154
 
149
155
  it 'can have a default value as symbol' do
150
156
  dump_io = StringIO.new
151
157
  ActiveRecord::SchemaDumper.dump(connection, dump_io)
152
- expect(dump_io.string).to match /t\.enum +"role", +default: :visitor, +subtype: :roles/
158
+ expect(dump_io.string).to match /t\.enum +"role", +default: :visitor, +enum_type: :roles/
153
159
  end
154
160
  end
155
161
 
@@ -411,4 +411,50 @@ RSpec.describe 'HasMany' do
411
411
  expect { query.load }.not_to raise_error
412
412
  end
413
413
  end
414
+
415
+ context 'using uuid' do
416
+ let(:connection) { ActiveRecord::Base.connection }
417
+ let(:game) { Class.new(ActiveRecord::Base) }
418
+ let(:player) { Class.new(ActiveRecord::Base) }
419
+
420
+ # TODO: Set as a shred example
421
+ before do
422
+ connection.create_table(:players, id: :uuid) { |t| t.string :name }
423
+ connection.create_table(:games, id: :uuid) { |t| t.uuid :player_ids, array: true }
424
+
425
+ options = { anonymous_class: game, foreign_key: :player_ids }
426
+ options[:inverse_of] = false if Torque::PostgreSQL::AR610
427
+
428
+ game.table_name = 'games'
429
+ player.table_name = 'players'
430
+ player.has_many :games, array: true, **options
431
+ end
432
+
433
+ subject { player.create }
434
+
435
+ it 'loads associated records' do
436
+ expect(subject.games.to_sql).to match(Regexp.new(<<-SQL.squish))
437
+ SELECT "games"\\.\\* FROM "games"
438
+ WHERE \\(?"games"\\."player_ids" && ARRAY\\['#{subject.id}'\\]::uuid\\[\\]\\)?
439
+ SQL
440
+
441
+ expect(subject.games.load).to be_a(ActiveRecord::Associations::CollectionProxy)
442
+ expect(subject.games.to_a).to be_eql([])
443
+ end
444
+
445
+ it 'can preload records' do
446
+ 5.times { game.create(player_ids: [subject.id]) }
447
+ entries = player.all.includes(:games).load
448
+
449
+ expect(entries.size).to be_eql(1)
450
+ expect(entries.first.games).to be_loaded
451
+ expect(entries.first.games.size).to be_eql(5)
452
+ end
453
+
454
+ it 'can joins records' do
455
+ query = player.all.joins(:games)
456
+ expect(query.to_sql).to match(/INNER JOIN "games"/)
457
+ expect { query.load }.not_to raise_error
458
+ end
459
+ end
414
460
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: torque-postgresql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.3
4
+ version: 2.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Silva
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-31 00:00:00.000000000 Z
11
+ date: 2022-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails