torque-postgresql 2.0.3 → 2.1.1

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