torque-postgresql 2.2.3 → 2.2.4

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: 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