torque-postgresql 2.0.2 → 2.1.0

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 +2 -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 +7 -1
  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 +169 -47
  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 +5 -3
  33. data/lib/torque/postgresql/attributes/builder/period.rb +6 -4
  34. data/lib/torque/postgresql/attributes/enum.rb +5 -10
  35. data/lib/torque/postgresql/attributes/enum_set.rb +2 -0
  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 +18 -4
  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 +134 -13
  68. data/spec/tests/enum_set_spec.rb +1 -1
  69. data/spec/tests/has_many_spec.rb +25 -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
@@ -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
@@ -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.2'
5
+ VERSION = '2.1.0'
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
@@ -225,11 +318,39 @@ RSpec.describe 'BelongsToMany' do
225
318
  expect { query.load }.not_to raise_error
226
319
  end
227
320
 
228
- context "When record is not persisted" do
321
+ context 'When the attribute has a default value' do
322
+ after(:all) { Video.reset_column_information }
323
+ let(:sql) { %{ALTER TABLE "videos" ALTER COLUMN "tag_ids" SET DEFAULT '{}'::bigint[]} }
324
+
325
+ before do
326
+ Video.connection.execute(sql)
327
+ Video.reset_column_information
328
+ end
329
+
330
+ it 'will always return the column default value' do
331
+ expect(subject.tag_ids).to be_a(Array)
332
+ expect(subject.tag_ids).to be_empty
333
+ end
334
+
335
+ it 'will keep the value as an array even when the association is cleared' do
336
+ records = FactoryBot.create_list(:tag, 5)
337
+ subject.tags.concat(records)
338
+
339
+ subject.reload
340
+ expect(subject.tag_ids).to be_a(Array)
341
+ expect(subject.tag_ids).not_to be_empty
342
+
343
+ subject.tags.clear
344
+ subject.reload
345
+ expect(subject.tag_ids).to be_a(Array)
346
+ expect(subject.tag_ids).to be_empty
347
+ end
348
+ end
349
+
350
+ context 'When record is not persisted' do
229
351
  let(:initial) { FactoryBot.create(:tag) }
230
- before { Video.belongs_to_many :tags }
352
+
231
353
  subject { Video.new(title: 'A', tags: [initial]) }
232
- after { Video._reflections = {} }
233
354
 
234
355
  it 'loads associated records' do
235
356
  expect(subject.tags.load).to be_a(ActiveRecord::Associations::CollectionProxy)