torque-postgresql 2.0.3 → 2.1.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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql.rb +1 -0
  3. data/lib/torque/postgresql/adapter.rb +23 -0
  4. data/lib/torque/postgresql/adapter/database_statements.rb +2 -0
  5. data/lib/torque/postgresql/adapter/oid.rb +3 -1
  6. data/lib/torque/postgresql/adapter/oid/box.rb +2 -0
  7. data/lib/torque/postgresql/adapter/oid/circle.rb +2 -0
  8. data/lib/torque/postgresql/adapter/oid/enum.rb +5 -0
  9. data/lib/torque/postgresql/adapter/oid/enum_set.rb +2 -0
  10. data/lib/torque/postgresql/adapter/oid/interval.rb +2 -0
  11. data/lib/torque/postgresql/adapter/oid/line.rb +2 -0
  12. data/lib/torque/postgresql/adapter/oid/range.rb +2 -0
  13. data/lib/torque/postgresql/adapter/oid/segment.rb +2 -0
  14. data/lib/torque/postgresql/adapter/quoting.rb +2 -0
  15. data/lib/torque/postgresql/adapter/schema_creation.rb +8 -1
  16. data/lib/torque/postgresql/adapter/schema_definitions.rb +2 -0
  17. data/lib/torque/postgresql/adapter/schema_dumper.rb +9 -3
  18. data/lib/torque/postgresql/adapter/schema_statements.rb +2 -0
  19. data/lib/torque/postgresql/arel/infix_operation.rb +5 -1
  20. data/lib/torque/postgresql/arel/join_source.rb +2 -0
  21. data/lib/torque/postgresql/arel/nodes.rb +2 -0
  22. data/lib/torque/postgresql/arel/operations.rb +2 -0
  23. data/lib/torque/postgresql/arel/select_manager.rb +2 -0
  24. data/lib/torque/postgresql/arel/visitors.rb +6 -3
  25. data/lib/torque/postgresql/associations/association.rb +14 -3
  26. data/lib/torque/postgresql/associations/association_scope.rb +2 -0
  27. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +168 -48
  28. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +8 -5
  29. data/lib/torque/postgresql/associations/builder/has_many.rb +2 -0
  30. data/lib/torque/postgresql/associations/preloader/association.rb +30 -1
  31. data/lib/torque/postgresql/attributes/builder.rb +3 -1
  32. data/lib/torque/postgresql/attributes/builder/enum.rb +10 -8
  33. data/lib/torque/postgresql/attributes/builder/period.rb +6 -4
  34. data/lib/torque/postgresql/attributes/enum.rb +6 -11
  35. data/lib/torque/postgresql/attributes/enum_set.rb +3 -1
  36. data/lib/torque/postgresql/attributes/lazy.rb +3 -1
  37. data/lib/torque/postgresql/attributes/period.rb +2 -0
  38. data/lib/torque/postgresql/autosave_association.rb +19 -16
  39. data/lib/torque/postgresql/auxiliary_statement.rb +2 -0
  40. data/lib/torque/postgresql/auxiliary_statement/settings.rb +2 -0
  41. data/lib/torque/postgresql/base.rb +11 -2
  42. data/lib/torque/postgresql/coder.rb +5 -3
  43. data/lib/torque/postgresql/collector.rb +2 -0
  44. data/lib/torque/postgresql/config.rb +5 -0
  45. data/lib/torque/postgresql/geometry_builder.rb +2 -0
  46. data/lib/torque/postgresql/i18n.rb +2 -0
  47. data/lib/torque/postgresql/inheritance.rb +2 -0
  48. data/lib/torque/postgresql/insert_all.rb +26 -0
  49. data/lib/torque/postgresql/migration/command_recorder.rb +2 -0
  50. data/lib/torque/postgresql/railtie.rb +2 -0
  51. data/lib/torque/postgresql/reflection.rb +2 -0
  52. data/lib/torque/postgresql/reflection/abstract_reflection.rb +13 -28
  53. data/lib/torque/postgresql/reflection/association_reflection.rb +24 -0
  54. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +2 -0
  55. data/lib/torque/postgresql/reflection/has_many_reflection.rb +2 -0
  56. data/lib/torque/postgresql/reflection/runtime_reflection.rb +2 -0
  57. data/lib/torque/postgresql/reflection/through_reflection.rb +2 -0
  58. data/lib/torque/postgresql/relation.rb +15 -11
  59. data/lib/torque/postgresql/relation/auxiliary_statement.rb +6 -1
  60. data/lib/torque/postgresql/relation/distinct_on.rb +2 -0
  61. data/lib/torque/postgresql/relation/inheritance.rb +2 -0
  62. data/lib/torque/postgresql/relation/merger.rb +2 -0
  63. data/lib/torque/postgresql/schema_cache.rb +2 -0
  64. data/lib/torque/postgresql/version.rb +3 -1
  65. data/spec/schema.rb +3 -2
  66. data/spec/tests/arel_spec.rb +3 -1
  67. data/spec/tests/belongs_to_many_spec.rb +139 -13
  68. data/spec/tests/enum_set_spec.rb +1 -1
  69. data/spec/tests/has_many_spec.rb +15 -1
  70. data/spec/tests/insert_all_spec.rb +89 -0
  71. data/spec/tests/table_inheritance_spec.rb +1 -1
  72. metadata +9 -6
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module InsertAll
6
+ attr_reader :where
7
+
8
+ def initialize(*args, where: nil, **xargs)
9
+ super(*args, **xargs)
10
+
11
+ @where = where
12
+ end
13
+ end
14
+
15
+ module InsertAll::Builder
16
+ delegate :where, to: :insert_all
17
+
18
+ def where_condition?
19
+ !where.nil?
20
+ end
21
+ end
22
+
23
+ ActiveRecord::InsertAll.prepend InsertAll
24
+ ActiveRecord::InsertAll::Builder.include InsertAll::Builder
25
+ end
26
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Migration
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  # = Torque PostgreSQL Railtie
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'reflection/abstract_reflection'
2
4
  require_relative 'reflection/association_reflection'
3
5
  require_relative 'reflection/belongs_to_many_reflection'
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Reflection
4
6
  module AbstractReflection
5
7
  AREL_ATTR = ::Arel::Attributes::Attribute
6
8
 
7
- ARR_NO_CAST = 'bigint'.freeze
8
- ARR_CAST = 'bigint[]'.freeze
9
+ ARR_NO_CAST = 'bigint'
10
+ ARR_CAST = 'bigint[]'
9
11
 
10
12
  # Check if the foreign key actually exists
11
13
  def connected_through_array?
@@ -68,11 +70,18 @@ module Torque
68
70
  klass_attr.overlaps(source_attr)
69
71
  end
70
72
 
73
+ if PostgreSQL::AR610
74
+ # TODO: Deprecate this method
75
+ def join_keys
76
+ OpenStruct.new(key: join_primary_key, foreign_key: join_foreign_key)
77
+ end
78
+ end
79
+
71
80
  private
72
81
 
73
82
  def build_id_constraint_between(table, foreign_table)
74
- klass_attr = table[join_keys.key.to_s]
75
- source_attr = foreign_table[join_keys.foreign_key.to_s]
83
+ klass_attr = table[join_primary_key]
84
+ source_attr = foreign_table[join_foreign_key]
76
85
 
77
86
  build_id_constraint(klass_attr, source_attr)
78
87
  end
@@ -94,30 +103,6 @@ module Torque
94
103
 
95
104
  ::Arel::Nodes::NamedFunction.new('ANY', [source_attr])
96
105
  end
97
-
98
- # returns either +nil+ or the inverse association name that it finds.
99
- def automatic_inverse_of
100
- return super unless connected_through_array?
101
-
102
- if can_find_inverse_of_automatically?(self)
103
- inverse_name = options[:as] || active_record.name.demodulize
104
- inverse_name = ActiveSupport::Inflector.underscore(inverse_name)
105
- inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
106
- inverse_name = inverse_name.to_sym
107
-
108
- begin
109
- reflection = klass._reflect_on_association(inverse_name)
110
- rescue NameError
111
- # Give up: we couldn't compute the klass type so we won't be able
112
- # to find any associations either.
113
- reflection = false
114
- end
115
-
116
- return inverse_name if reflection.connected_through_array? &&
117
- valid_inverse_reflection?(reflection)
118
- end
119
- end
120
-
121
106
  end
122
107
 
123
108
  ::ActiveRecord::Reflection::AbstractReflection.prepend(AbstractReflection)
@@ -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
@@ -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.3'
5
+ VERSION = '2.1.1'
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,9 +26,13 @@ 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)
@@ -93,13 +97,64 @@ RSpec.describe 'BelongsToMany' do
93
97
  expect(records.map(&:id).sort).to be_eql(ids.sort)
94
98
  end
95
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
+
96
150
  it 'can build an associated record' do
97
151
  record = subject.tags.build(name: 'Test')
98
152
  expect(record).to be_a(other)
99
153
  expect(record).not_to be_persisted
100
154
  expect(record.name).to be_eql('Test')
155
+ expect(subject.tags.target).to be_eql([record])
101
156
 
102
- expect(subject.save).to be_truthy
157
+ expect(subject.save && subject.reload).to be_truthy
103
158
  expect(subject.tag_ids).to be_eql([record.id])
104
159
  expect(subject.tags.size).to be_eql(1)
105
160
  end
@@ -120,7 +175,7 @@ RSpec.describe 'BelongsToMany' do
120
175
  expect(subject.tags.size).to be_eql(1)
121
176
 
122
177
  subject.tags.concat(other.new(name: 'Test'))
123
- subject.tags.reload
178
+ subject.reload
124
179
 
125
180
  expect(subject.tags.size).to be_eql(2)
126
181
  expect(subject.tag_ids.size).to be_eql(2)
@@ -131,10 +186,27 @@ RSpec.describe 'BelongsToMany' do
131
186
  subject.tags << FactoryBot.create(:tag)
132
187
  expect(subject.tags.size).to be_eql(1)
133
188
 
134
- subject.tags.replace([other.new(name: 'Test 1'), other.new(name: 'Test 2')])
135
- 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)
136
193
  expect(subject.tags[0].name).to be_eql('Test 1')
137
- 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)
138
210
  end
139
211
 
140
212
  it 'can delete all records' do
@@ -153,6 +225,17 @@ RSpec.describe 'BelongsToMany' do
153
225
  expect(subject.tags.size).to be_eql(0)
154
226
  end
155
227
 
228
+ it 'can clear the array' do
229
+ record = Video.create(title: 'B', tags: [initial])
230
+ expect(record.tags.size).to be_eql(1)
231
+
232
+ record.update(tag_ids: [])
233
+ record.reload
234
+
235
+ expect(record.tag_ids).to be_nil
236
+ expect(record.tags.size).to be_eql(0)
237
+ end
238
+
156
239
  it 'can have sum operations' do
157
240
  records = FactoryBot.create_list(:tag, 5)
158
241
  subject.tags.concat(records)
@@ -182,11 +265,15 @@ RSpec.describe 'BelongsToMany' do
182
265
  it 'can check if a record is included on the list' do
183
266
  outside = FactoryBot.create(:tag)
184
267
  inside = FactoryBot.create(:tag)
268
+
269
+ expect(subject.tags).not_to be_include(inside)
270
+ expect(subject.tags).not_to be_include(outside)
271
+
185
272
  subject.tags << inside
186
273
 
187
274
  expect(subject.tags).to respond_to(:include?)
188
- expect(subject.tags.include?(inside)).to be_truthy
189
- expect(subject.tags.include?(outside)).to be_falsey
275
+ expect(subject.tags).to be_include(inside)
276
+ expect(subject.tags).not_to be_include(outside)
190
277
  end
191
278
 
192
279
  it 'can append records' do
@@ -194,6 +281,9 @@ RSpec.describe 'BelongsToMany' do
194
281
  expect(subject.tags.size).to be_eql(1)
195
282
 
196
283
  subject.tags << other.new(name: 'Test 2')
284
+ subject.update(title: 'B')
285
+ subject.reload
286
+
197
287
  expect(subject.tags.size).to be_eql(2)
198
288
  expect(subject.tags.last.name).to be_eql('Test 2')
199
289
  end
@@ -208,10 +298,18 @@ RSpec.describe 'BelongsToMany' do
208
298
 
209
299
  it 'can reload records' do
210
300
  expect(subject.tags.size).to be_eql(0)
211
- subject.tags << FactoryBot.create(:tag)
301
+ new_tag = FactoryBot.create(:tag)
302
+ subject.tags << new_tag
212
303
 
213
304
  subject.tags.reload
214
305
  expect(subject.tags.size).to be_eql(1)
306
+ expect(subject.tags.first.id).to be_eql(new_tag.id)
307
+
308
+ record = Video.create(title: 'B', tags: [new_tag])
309
+ record.reload
310
+
311
+ expect(record.tags.size).to be_eql(1)
312
+ expect(record.tags.first.id).to be_eql(new_tag.id)
215
313
  end
216
314
 
217
315
  it 'can preload records' do
@@ -231,11 +329,39 @@ RSpec.describe 'BelongsToMany' do
231
329
  expect { query.load }.not_to raise_error
232
330
  end
233
331
 
234
- context "When record is not persisted" do
332
+ context 'When the attribute has a default value' do
333
+ after(:all) { Video.reset_column_information }
334
+ let(:sql) { %{ALTER TABLE "videos" ALTER COLUMN "tag_ids" SET DEFAULT '{}'::bigint[]} }
335
+
336
+ before do
337
+ Video.connection.execute(sql)
338
+ Video.reset_column_information
339
+ end
340
+
341
+ it 'will always return the column default value' do
342
+ expect(subject.tag_ids).to be_a(Array)
343
+ expect(subject.tag_ids).to be_empty
344
+ end
345
+
346
+ it 'will keep the value as an array even when the association is cleared' do
347
+ records = FactoryBot.create_list(:tag, 5)
348
+ subject.tags.concat(records)
349
+
350
+ subject.reload
351
+ expect(subject.tag_ids).to be_a(Array)
352
+ expect(subject.tag_ids).not_to be_empty
353
+
354
+ subject.tags.clear
355
+ subject.reload
356
+ expect(subject.tag_ids).to be_a(Array)
357
+ expect(subject.tag_ids).to be_empty
358
+ end
359
+ end
360
+
361
+ context 'When record is not persisted' do
235
362
  let(:initial) { FactoryBot.create(:tag) }
236
- before { Video.belongs_to_many :tags }
363
+
237
364
  subject { Video.new(title: 'A', tags: [initial]) }
238
- after { Video._reflections = {} }
239
365
 
240
366
  it 'loads associated records' do
241
367
  expect(subject.tags.load).to be_a(ActiveRecord::Associations::CollectionProxy)