torque-postgresql 2.0.1 → 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql/adapter.rb +7 -0
  3. data/lib/torque/postgresql/adapter/database_statements.rb +2 -0
  4. data/lib/torque/postgresql/adapter/oid.rb +3 -1
  5. data/lib/torque/postgresql/adapter/oid/box.rb +2 -0
  6. data/lib/torque/postgresql/adapter/oid/circle.rb +2 -0
  7. data/lib/torque/postgresql/adapter/oid/enum.rb +2 -0
  8. data/lib/torque/postgresql/adapter/oid/enum_set.rb +2 -0
  9. data/lib/torque/postgresql/adapter/oid/interval.rb +2 -0
  10. data/lib/torque/postgresql/adapter/oid/line.rb +2 -0
  11. data/lib/torque/postgresql/adapter/oid/range.rb +2 -0
  12. data/lib/torque/postgresql/adapter/oid/segment.rb +2 -0
  13. data/lib/torque/postgresql/adapter/quoting.rb +2 -0
  14. data/lib/torque/postgresql/adapter/schema_creation.rb +8 -1
  15. data/lib/torque/postgresql/adapter/schema_definitions.rb +2 -0
  16. data/lib/torque/postgresql/adapter/schema_dumper.rb +11 -2
  17. data/lib/torque/postgresql/adapter/schema_statements.rb +2 -0
  18. data/lib/torque/postgresql/arel/infix_operation.rb +5 -1
  19. data/lib/torque/postgresql/arel/join_source.rb +2 -0
  20. data/lib/torque/postgresql/arel/nodes.rb +2 -0
  21. data/lib/torque/postgresql/arel/operations.rb +2 -0
  22. data/lib/torque/postgresql/arel/select_manager.rb +2 -0
  23. data/lib/torque/postgresql/arel/visitors.rb +6 -3
  24. data/lib/torque/postgresql/associations/association.rb +14 -3
  25. data/lib/torque/postgresql/associations/association_scope.rb +2 -0
  26. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +164 -47
  27. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +8 -5
  28. data/lib/torque/postgresql/associations/builder/has_many.rb +2 -0
  29. data/lib/torque/postgresql/associations/preloader/association.rb +30 -1
  30. data/lib/torque/postgresql/attributes/builder.rb +3 -1
  31. data/lib/torque/postgresql/attributes/builder/enum.rb +5 -3
  32. data/lib/torque/postgresql/attributes/builder/period.rb +6 -4
  33. data/lib/torque/postgresql/attributes/enum.rb +5 -10
  34. data/lib/torque/postgresql/attributes/enum_set.rb +2 -0
  35. data/lib/torque/postgresql/attributes/lazy.rb +3 -1
  36. data/lib/torque/postgresql/attributes/period.rb +2 -0
  37. data/lib/torque/postgresql/autosave_association.rb +19 -16
  38. data/lib/torque/postgresql/auxiliary_statement.rb +2 -0
  39. data/lib/torque/postgresql/auxiliary_statement/settings.rb +2 -0
  40. data/lib/torque/postgresql/base.rb +5 -2
  41. data/lib/torque/postgresql/coder.rb +5 -3
  42. data/lib/torque/postgresql/collector.rb +2 -0
  43. data/lib/torque/postgresql/config.rb +5 -0
  44. data/lib/torque/postgresql/geometry_builder.rb +2 -0
  45. data/lib/torque/postgresql/i18n.rb +2 -0
  46. data/lib/torque/postgresql/inheritance.rb +2 -0
  47. data/lib/torque/postgresql/migration/command_recorder.rb +2 -0
  48. data/lib/torque/postgresql/railtie.rb +2 -0
  49. data/lib/torque/postgresql/reflection.rb +2 -0
  50. data/lib/torque/postgresql/reflection/abstract_reflection.rb +13 -28
  51. data/lib/torque/postgresql/reflection/association_reflection.rb +24 -0
  52. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +18 -4
  53. data/lib/torque/postgresql/reflection/has_many_reflection.rb +2 -0
  54. data/lib/torque/postgresql/reflection/runtime_reflection.rb +2 -0
  55. data/lib/torque/postgresql/reflection/through_reflection.rb +2 -0
  56. data/lib/torque/postgresql/relation.rb +15 -11
  57. data/lib/torque/postgresql/relation/auxiliary_statement.rb +6 -1
  58. data/lib/torque/postgresql/relation/distinct_on.rb +2 -0
  59. data/lib/torque/postgresql/relation/inheritance.rb +2 -0
  60. data/lib/torque/postgresql/relation/merger.rb +2 -0
  61. data/lib/torque/postgresql/schema_cache.rb +2 -0
  62. data/lib/torque/postgresql/version.rb +3 -1
  63. data/spec/schema.rb +3 -2
  64. data/spec/tests/arel_spec.rb +3 -1
  65. data/spec/tests/belongs_to_many_spec.rb +104 -12
  66. data/spec/tests/enum_set_spec.rb +1 -1
  67. data/spec/tests/has_many_spec.rb +25 -1
  68. data/spec/tests/table_inheritance_spec.rb +1 -1
  69. metadata +7 -7
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Reflection
@@ -22,6 +24,28 @@ module Torque
22
24
  result
23
25
  end
24
26
 
27
+ # returns either +nil+ or the inverse association name that it finds.
28
+ def automatic_inverse_of
29
+ return super unless connected_through_array?
30
+
31
+ if can_find_inverse_of_automatically?(self)
32
+ inverse_name = options[:as] || active_record.name.demodulize
33
+ inverse_name = ActiveSupport::Inflector.underscore(inverse_name)
34
+ inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
35
+ inverse_name = inverse_name.to_sym
36
+
37
+ begin
38
+ reflection = klass._reflect_on_association(inverse_name)
39
+ rescue NameError
40
+ # Give up: we couldn't compute the klass type so we won't be able
41
+ # to find any associations either.
42
+ reflection = false
43
+ end
44
+
45
+ return inverse_name if valid_inverse_reflection?(reflection)
46
+ end
47
+ end
48
+
25
49
  end
26
50
 
27
51
  ::ActiveRecord::Reflection::AssociationReflection.prepend(AssociationReflection)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Reflection
@@ -10,6 +12,10 @@ module Torque
10
12
  true
11
13
  end
12
14
 
15
+ def belongs_to?
16
+ true
17
+ end
18
+
13
19
  def collection?
14
20
  true
15
21
  end
@@ -19,7 +25,7 @@ module Torque
19
25
  end
20
26
 
21
27
  def foreign_key
22
- @foreign_key ||= options[:primary_key] || derive_foreign_key.freeze
28
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
23
29
  end
24
30
 
25
31
  def association_foreign_key
@@ -27,16 +33,24 @@ module Torque
27
33
  end
28
34
 
29
35
  def active_record_primary_key
30
- @active_record_primary_key ||= options[:foreign_key] || derive_primary_key
36
+ @active_record_primary_key ||= options[:primary_key] || derive_primary_key
37
+ end
38
+
39
+ def join_primary_key(*)
40
+ active_record_primary_key
41
+ end
42
+
43
+ def join_foreign_key
44
+ foreign_key
31
45
  end
32
46
 
33
47
  private
34
48
 
35
- def derive_foreign_key
49
+ def derive_primary_key
36
50
  klass.primary_key
37
51
  end
38
52
 
39
- def derive_primary_key
53
+ def derive_foreign_key
40
54
  "#{name.to_s.singularize}_ids"
41
55
  end
42
56
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Reflection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Reflection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Reflection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'relation/distinct_on'
2
4
  require_relative 'relation/auxiliary_statement'
3
5
  require_relative 'relation/inheritance'
@@ -48,12 +50,12 @@ module Torque
48
50
  when String
49
51
  ::Arel.sql(klass.send(:sanitize_sql, item.to_s))
50
52
  when Symbol
51
- base ? base.arel_attribute(item) : klass.arel_attribute(item)
53
+ base ? base.arel_table[item] : klass.arel_table[item]
52
54
  when Array
53
55
  resolve_column(item, base)
54
56
  when Hash
55
57
  raise ArgumentError, 'Unsupported Hash for attributes on third level' if base
56
- item.map { |key, other_list| resolve_column(Array.wrap(other_list), key) }
58
+ item.map { |key, other_list| resolve_column(other_list, key) }
57
59
  else
58
60
  raise ArgumentError, "Unsupported argument type: #{value} (#{value.class})"
59
61
  end
@@ -65,8 +67,8 @@ module Torque
65
67
  return unless relation
66
68
 
67
69
  table = predicate_builder.send(:table)
68
- if table.associated_with?(relation)
69
- table.associated_table(relation).send(:klass)
70
+ if table.associated_with?(relation.to_s)
71
+ table.associated_table(relation.to_s).send(:klass)
70
72
  else
71
73
  raise ArgumentError, "Relation for #{relation} not found on #{klass}"
72
74
  end
@@ -138,14 +140,16 @@ module Torque
138
140
  ActiveRecord::QueryMethods::VALID_UNSCOPING_VALUES += %i[cast_records itself_only
139
141
  distinct_on auxiliary_statements]
140
142
 
141
- Relation::SINGLE_VALUE_METHODS.each do |value|
142
- ActiveRecord::QueryMethods::DEFAULT_VALUES[value] = nil \
143
- if ActiveRecord::QueryMethods::DEFAULT_VALUES[value].nil?
144
- end
143
+ unless AR610
144
+ Relation::SINGLE_VALUE_METHODS.each do |value|
145
+ ActiveRecord::QueryMethods::DEFAULT_VALUES[value] = nil \
146
+ if ActiveRecord::QueryMethods::DEFAULT_VALUES[value].nil?
147
+ end
145
148
 
146
- Relation::MULTI_VALUE_METHODS.each do |value|
147
- ActiveRecord::QueryMethods::DEFAULT_VALUES[value] ||= \
148
- ActiveRecord::QueryMethods::FROZEN_EMPTY_ARRAY
149
+ Relation::MULTI_VALUE_METHODS.each do |value|
150
+ ActiveRecord::QueryMethods::DEFAULT_VALUES[value] ||= \
151
+ ActiveRecord::QueryMethods::FROZEN_EMPTY_ARRAY
152
+ end
149
153
  end
150
154
 
151
155
  $VERBOSE = warn_level
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Relation
@@ -34,7 +36,10 @@ module Torque
34
36
  # attributes as well
35
37
  def bound_attributes
36
38
  visitor = ::Arel::Visitors::PostgreSQL.new(ActiveRecord::Base.connection)
37
- visitor.accept(self.arel.ast, ::Arel::Collectors::Bind.new).value
39
+ visitor.accept(self.arel.ast, ::Arel::Collectors::Composite.new(
40
+ ::Arel::Collectors::SQLString.new,
41
+ ::Arel::Collectors::Bind.new,
42
+ )).value.last
38
43
  end
39
44
 
40
45
  private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Relation
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Relation
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Relation
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  LookupError = Class.new(ArgumentError)
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
- VERSION = '2.0.1'
5
+ VERSION = '2.0.6'
4
6
  end
5
7
  end
data/spec/schema.rb CHANGED
@@ -11,7 +11,7 @@
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
13
  begin
14
- version = 61
14
+ version = 63
15
15
 
16
16
  raise SystemExit if ActiveRecord::Migrator.current_version == version
17
17
  ActiveRecord::Schema.define(version: version) do
@@ -74,6 +74,7 @@ begin
74
74
  create_table "comments", force: :cascade do |t|
75
75
  t.integer "user_id", null: false
76
76
  t.integer "comment_id"
77
+ t.integer "video_id"
77
78
  t.text "content", null: false
78
79
  t.string "kind"
79
80
  t.index ["user_id"], name: "index_comments_on_user_id", using: :btree
@@ -83,7 +84,7 @@ begin
83
84
  create_table "courses", force: :cascade do |t|
84
85
  t.string "title", null: false
85
86
  t.interval "duration"
86
- t.enum "types", subtype: :types, array: true, default: [:A, :B]
87
+ t.enum "types", subtype: :types, array: true
87
88
  t.datetime "created_at", null: false
88
89
  t.datetime "updated_at", null: false
89
90
  end
@@ -25,7 +25,9 @@ RSpec.describe 'Arel' do
25
25
  klass_name = operator.to_s.camelize
26
26
 
27
27
  context "##{operator}" do
28
- let(:instance) { attribute.public_send(operator, value) }
28
+ let(:instance) do
29
+ attribute.public_send(operator, value.is_a?(Array) ? ::Arel.array(value) : value)
30
+ end
29
31
 
30
32
  context 'for attribute' do
31
33
  let(:klass) { ::Arel::Nodes.const_get(klass_name) }
@@ -26,15 +26,24 @@ RSpec.describe 'BelongsToMany' do
26
26
  let(:other) { Tag }
27
27
  let(:initial) { FactoryBot.create(:tag) }
28
28
 
29
- before { Video.belongs_to_many :tags }
29
+ before { Video.belongs_to_many(:tags) }
30
30
  subject { Video.create(title: 'A') }
31
- after { Video._reflections = {} }
31
+
32
+ after do
33
+ Video.reset_callbacks(:save)
34
+ Video._reflections = {}
35
+ end
32
36
 
33
37
  it 'has the method' do
34
38
  expect(subject).to respond_to(:tags)
35
39
  expect(subject._reflections).to include('tags')
36
40
  end
37
41
 
42
+ it 'has correct foreign key' do
43
+ item = subject._reflections['tags']
44
+ expect(item.foreign_key).to be_eql('tag_ids')
45
+ end
46
+
38
47
  it 'loads associated records' do
39
48
  subject.update(tag_ids: [initial.id])
40
49
  expect(subject.tags.to_sql).to be_eql(<<-SQL.squish)
@@ -88,13 +97,64 @@ RSpec.describe 'BelongsToMany' do
88
97
  expect(records.map(&:id).sort).to be_eql(ids.sort)
89
98
  end
90
99
 
100
+ it 'can create the owner record with direct set items' do
101
+ # Having another association would break this test due to how
102
+ # +@new_record_before_save+ is set on autosave association
103
+ Video.has_many(:comments)
104
+
105
+ record = Video.create(title: 'A', tags: [initial])
106
+ record.reload
107
+
108
+ expect(record.tags.size).to be_eql(1)
109
+ expect(record.tags.first.id).to be_eql(initial.id)
110
+ end
111
+
112
+ it 'can keep record changes accordingly' do
113
+ expect(subject.tags.count).to be_eql(0)
114
+
115
+ local_previous_changes = nil
116
+ local_saved_changes = nil
117
+
118
+ Video.after_commit do
119
+ local_previous_changes = self.previous_changes.dup
120
+ local_saved_changes = self.saved_changes.dup
121
+ end
122
+
123
+ subject.update(title: 'B')
124
+
125
+ expect(local_previous_changes).to include('title')
126
+ expect(local_saved_changes).to include('title')
127
+
128
+ subject.tags = FactoryBot.create_list(:tag, 5)
129
+ subject.update(title: 'C', url: 'X')
130
+ subject.reload
131
+
132
+ expect(local_previous_changes).to include('title', 'url')
133
+ expect(local_saved_changes).to include('title', 'url')
134
+ expect(local_previous_changes).not_to include('tag_ids')
135
+ expect(local_saved_changes).not_to include('tag_ids')
136
+ expect(subject.tag_ids.size).to be_eql(5)
137
+ expect(subject.tags.count).to be_eql(5)
138
+ end
139
+
140
+ it 'can assign the record ids during before callback' do
141
+ Video.before_save { self.tags = FactoryBot.create_list(:tag, 5) }
142
+
143
+ record = Video.create(title: 'A')
144
+
145
+ expect(Tag.count).to be_eql(5)
146
+ expect(record.tag_ids.size).to be_eql(5)
147
+ expect(record.tags.count).to be_eql(5)
148
+ end
149
+
91
150
  it 'can build an associated record' do
92
151
  record = subject.tags.build(name: 'Test')
93
152
  expect(record).to be_a(other)
94
153
  expect(record).not_to be_persisted
95
154
  expect(record.name).to be_eql('Test')
155
+ expect(subject.tags.target).to be_eql([record])
96
156
 
97
- expect(subject.save).to be_truthy
157
+ expect(subject.save && subject.reload).to be_truthy
98
158
  expect(subject.tag_ids).to be_eql([record.id])
99
159
  expect(subject.tags.size).to be_eql(1)
100
160
  end
@@ -115,7 +175,8 @@ RSpec.describe 'BelongsToMany' do
115
175
  expect(subject.tags.size).to be_eql(1)
116
176
 
117
177
  subject.tags.concat(other.new(name: 'Test'))
118
- subject.tags.reload
178
+ subject.reload
179
+
119
180
  expect(subject.tags.size).to be_eql(2)
120
181
  expect(subject.tag_ids.size).to be_eql(2)
121
182
  expect(subject.tags.last.name).to be_eql('Test')
@@ -125,10 +186,27 @@ RSpec.describe 'BelongsToMany' do
125
186
  subject.tags << FactoryBot.create(:tag)
126
187
  expect(subject.tags.size).to be_eql(1)
127
188
 
128
- subject.tags.replace([other.new(name: 'Test 1'), other.new(name: 'Test 2')])
129
- expect(subject.tags.size).to be_eql(2)
189
+ subject.tags = [other.new(name: 'Test 1')]
190
+ subject.reload
191
+
192
+ expect(subject.tags.size).to be_eql(1)
130
193
  expect(subject.tags[0].name).to be_eql('Test 1')
131
- expect(subject.tags[1].name).to be_eql('Test 2')
194
+
195
+ subject.tags.replace([other.new(name: 'Test 2'), other.new(name: 'Test 3')])
196
+ subject.reload
197
+
198
+ expect(subject.tags.size).to be_eql(2)
199
+ expect(subject.tags[0].name).to be_eql('Test 2')
200
+ expect(subject.tags[1].name).to be_eql('Test 3')
201
+ end
202
+
203
+ it 'can delete specific records' do
204
+ subject.tags << initial
205
+ expect(subject.tags.size).to be_eql(1)
206
+
207
+ subject.tags.delete(initial)
208
+ expect(subject.tags.size).to be_eql(0)
209
+ expect(subject.reload.tags.size).to be_eql(0)
132
210
  end
133
211
 
134
212
  it 'can delete all records' do
@@ -176,11 +254,15 @@ RSpec.describe 'BelongsToMany' do
176
254
  it 'can check if a record is included on the list' do
177
255
  outside = FactoryBot.create(:tag)
178
256
  inside = FactoryBot.create(:tag)
257
+
258
+ expect(subject.tags).not_to be_include(inside)
259
+ expect(subject.tags).not_to be_include(outside)
260
+
179
261
  subject.tags << inside
180
262
 
181
263
  expect(subject.tags).to respond_to(:include?)
182
- expect(subject.tags.include?(inside)).to be_truthy
183
- expect(subject.tags.include?(outside)).to be_falsey
264
+ expect(subject.tags).to be_include(inside)
265
+ expect(subject.tags).not_to be_include(outside)
184
266
  end
185
267
 
186
268
  it 'can append records' do
@@ -188,6 +270,9 @@ RSpec.describe 'BelongsToMany' do
188
270
  expect(subject.tags.size).to be_eql(1)
189
271
 
190
272
  subject.tags << other.new(name: 'Test 2')
273
+ subject.update(title: 'B')
274
+ subject.reload
275
+
191
276
  expect(subject.tags.size).to be_eql(2)
192
277
  expect(subject.tags.last.name).to be_eql('Test 2')
193
278
  end
@@ -202,10 +287,18 @@ RSpec.describe 'BelongsToMany' do
202
287
 
203
288
  it 'can reload records' do
204
289
  expect(subject.tags.size).to be_eql(0)
205
- subject.tags << FactoryBot.create(:tag)
290
+ new_tag = FactoryBot.create(:tag)
291
+ subject.tags << new_tag
206
292
 
207
293
  subject.tags.reload
208
294
  expect(subject.tags.size).to be_eql(1)
295
+ expect(subject.tags.first.id).to be_eql(new_tag.id)
296
+
297
+ record = Video.create(title: 'B', tags: [new_tag])
298
+ record.reload
299
+
300
+ expect(record.tags.size).to be_eql(1)
301
+ expect(record.tags.first.id).to be_eql(new_tag.id)
209
302
  end
210
303
 
211
304
  it 'can preload records' do
@@ -227,9 +320,8 @@ RSpec.describe 'BelongsToMany' do
227
320
 
228
321
  context "When record is not persisted" do
229
322
  let(:initial) { FactoryBot.create(:tag) }
230
- before { Video.belongs_to_many :tags }
323
+
231
324
  subject { Video.new(title: 'A', tags: [initial]) }
232
- after { Video._reflections = {} }
233
325
 
234
326
  it 'loads associated records' do
235
327
  expect(subject.tags.load).to be_a(ActiveRecord::Associations::CollectionProxy)