torque-postgresql 2.0.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/torque/postgresql.rb +1 -0
- data/lib/torque/postgresql/adapter.rb +23 -0
- data/lib/torque/postgresql/adapter/database_statements.rb +2 -0
- data/lib/torque/postgresql/adapter/oid.rb +3 -1
- data/lib/torque/postgresql/adapter/oid/box.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/circle.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/enum.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/enum_set.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/interval.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/line.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/range.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/segment.rb +2 -0
- data/lib/torque/postgresql/adapter/quoting.rb +2 -0
- data/lib/torque/postgresql/adapter/schema_creation.rb +8 -1
- data/lib/torque/postgresql/adapter/schema_definitions.rb +2 -0
- data/lib/torque/postgresql/adapter/schema_dumper.rb +7 -1
- data/lib/torque/postgresql/adapter/schema_statements.rb +2 -0
- data/lib/torque/postgresql/arel/infix_operation.rb +5 -1
- data/lib/torque/postgresql/arel/join_source.rb +2 -0
- data/lib/torque/postgresql/arel/nodes.rb +2 -0
- data/lib/torque/postgresql/arel/operations.rb +2 -0
- data/lib/torque/postgresql/arel/select_manager.rb +2 -0
- data/lib/torque/postgresql/arel/visitors.rb +6 -3
- data/lib/torque/postgresql/associations/association.rb +14 -3
- data/lib/torque/postgresql/associations/association_scope.rb +2 -0
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +169 -47
- data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +8 -5
- data/lib/torque/postgresql/associations/builder/has_many.rb +2 -0
- data/lib/torque/postgresql/associations/preloader/association.rb +30 -1
- data/lib/torque/postgresql/attributes/builder.rb +3 -1
- data/lib/torque/postgresql/attributes/builder/enum.rb +5 -3
- data/lib/torque/postgresql/attributes/builder/period.rb +6 -4
- data/lib/torque/postgresql/attributes/enum.rb +5 -10
- data/lib/torque/postgresql/attributes/enum_set.rb +2 -0
- data/lib/torque/postgresql/attributes/lazy.rb +3 -1
- data/lib/torque/postgresql/attributes/period.rb +2 -0
- data/lib/torque/postgresql/autosave_association.rb +19 -16
- data/lib/torque/postgresql/auxiliary_statement.rb +2 -0
- data/lib/torque/postgresql/auxiliary_statement/settings.rb +2 -0
- data/lib/torque/postgresql/base.rb +11 -2
- data/lib/torque/postgresql/coder.rb +5 -3
- data/lib/torque/postgresql/collector.rb +2 -0
- data/lib/torque/postgresql/config.rb +5 -0
- data/lib/torque/postgresql/geometry_builder.rb +2 -0
- data/lib/torque/postgresql/i18n.rb +2 -0
- data/lib/torque/postgresql/inheritance.rb +2 -0
- data/lib/torque/postgresql/insert_all.rb +26 -0
- data/lib/torque/postgresql/migration/command_recorder.rb +2 -0
- data/lib/torque/postgresql/railtie.rb +2 -0
- data/lib/torque/postgresql/reflection.rb +2 -0
- data/lib/torque/postgresql/reflection/abstract_reflection.rb +13 -28
- data/lib/torque/postgresql/reflection/association_reflection.rb +24 -0
- data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +18 -4
- data/lib/torque/postgresql/reflection/has_many_reflection.rb +2 -0
- data/lib/torque/postgresql/reflection/runtime_reflection.rb +2 -0
- data/lib/torque/postgresql/reflection/through_reflection.rb +2 -0
- data/lib/torque/postgresql/relation.rb +15 -11
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +6 -1
- data/lib/torque/postgresql/relation/distinct_on.rb +2 -0
- data/lib/torque/postgresql/relation/inheritance.rb +2 -0
- data/lib/torque/postgresql/relation/merger.rb +2 -0
- data/lib/torque/postgresql/schema_cache.rb +2 -0
- data/lib/torque/postgresql/version.rb +3 -1
- data/spec/schema.rb +3 -2
- data/spec/tests/arel_spec.rb +3 -1
- data/spec/tests/belongs_to_many_spec.rb +134 -13
- data/spec/tests/enum_set_spec.rb +1 -1
- data/spec/tests/has_many_spec.rb +25 -1
- data/spec/tests/insert_all_spec.rb +89 -0
- data/spec/tests/table_inheritance_spec.rb +1 -1
- metadata +9 -6
@@ -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'
|
8
|
-
ARR_CAST = 'bigint[]'
|
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[
|
75
|
-
source_attr = foreign_table[
|
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[:
|
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[:
|
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
|
49
|
+
def derive_primary_key
|
36
50
|
klass.primary_key
|
37
51
|
end
|
38
52
|
|
39
|
-
def
|
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
|
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.
|
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(
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
147
|
-
|
148
|
-
|
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::
|
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
|
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 =
|
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
|
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
|
data/spec/tests/arel_spec.rb
CHANGED
@@ -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)
|
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
|
29
|
+
before { Video.belongs_to_many(:tags) }
|
30
30
|
subject { Video.create(title: 'A') }
|
31
|
-
|
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.
|
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
|
129
|
-
|
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
|
-
|
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.
|
183
|
-
expect(subject.tags.
|
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
|
-
|
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
|
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
|
-
|
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)
|