torque-postgresql 2.0.5 → 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe8259f8bb0f4c8c67c32919ec2bc2ca448c5372857670ed5fbd4d932e3160d6
4
- data.tar.gz: 3786a17977e0d4ffd81c1521c9e469fd411b6d15b413866941adc911beb8c2dd
3
+ metadata.gz: bd314ab034c3d38a227829faf2d706b13625a5e4cc6393004f6dbe2890e784a8
4
+ data.tar.gz: 035b0fc92e1ea2aedb507c18094e66025c0fe98838f8553f3abc64764a0b2f03
5
5
  SHA512:
6
- metadata.gz: f35b0eb94b260cc5c5c9d08a47dcba841d9044c9acc52d30b0ad13b1e0a6ecbe2ef10ca940828870fbf5f04b387827b9c56db43523e0585f77f950a72e368b51
7
- data.tar.gz: a3b899a4c7a26f9ff9a6a9af7f0d41a6ed193e96a76d96ae17a2423eb0b4bc6ea59138684cd324a28ccfad7374528c1cb72d4ae89a0ad710ce66655d3e355e50
6
+ metadata.gz: 6eeaffde8209891fee13de7518acea135cfe5f35ecbc5aa59124faad208ed0545a24c2ffb993f10eb5e306c2ccb96036e1dbc812a11177ecdda5e3ead62e0dd7
7
+ data.tar.gz: ad55cf8b8a5e9de71575c12e8452d440439529258752ceceb95da17111991b8a9ad90101806ab328d5993d328bec69a71feb4cc6b85431c9802d0c225557dbb5
@@ -18,6 +18,7 @@ require 'torque/postgresql/arel'
18
18
  require 'torque/postgresql/adapter'
19
19
  require 'torque/postgresql/associations'
20
20
  require 'torque/postgresql/attributes'
21
+ require 'torque/postgresql/autosave_association'
21
22
  require 'torque/postgresql/auxiliary_statement'
22
23
  require 'torque/postgresql/base'
23
24
  require 'torque/postgresql/inheritance'
@@ -78,6 +78,10 @@ module Torque
78
78
 
79
79
  # Scenic integration
80
80
  views(stream) if defined?(::Scenic)
81
+
82
+ # FX integration
83
+ functions(stream) if defined?(::Fx::SchemaDumper::Function)
84
+ triggers(stream) if defined?(::Fx::SchemaDumper::Trigger)
81
85
  end
82
86
 
83
87
  # Dump user defined types like enum
@@ -5,6 +5,8 @@ module Torque
5
5
  module Associations
6
6
  module Association
7
7
 
8
+ # There is no problem of adding temporary items on target because
9
+ # CollectionProxy will handle memory and persisted relationship
8
10
  def inversed_from(record)
9
11
  return super unless reflection.connected_through_array?
10
12
 
@@ -13,19 +15,20 @@ module Torque
13
15
  @inversed = self.target.present?
14
16
  end
15
17
 
18
+ # The binds and the cache are getting mixed and caching the wrong query
16
19
  def skip_statement_cache?(*)
17
20
  super || reflection.connected_through_array?
18
21
  end
19
22
 
20
23
  private
21
24
 
25
+ # This is mainly for the has many when connect through an array to add
26
+ # its id to the list of the inverse belongs to many association
22
27
  def set_owner_attributes(record)
23
28
  return super unless reflection.connected_through_array?
24
29
 
25
30
  add_id = owner[reflection.active_record_primary_key]
26
- record_fk = reflection.foreign_key
27
-
28
- list = record[record_fk] ||= []
31
+ list = record[reflection.foreign_key] ||= []
29
32
  list.push(add_id) unless list.include?(add_id)
30
33
  end
31
34
 
@@ -9,10 +9,68 @@ module Torque
9
9
  class BelongsToManyAssociation < ::ActiveRecord::Associations::CollectionAssociation
10
10
  include ::ActiveRecord::Associations::ForeignAssociation
11
11
 
12
+ ## CUSTOM
13
+ def ids_reader
14
+ if loaded?
15
+ target.pluck(reflection.association_primary_key)
16
+ elsif !target.empty?
17
+ load_target.pluck(reflection.association_primary_key)
18
+ else
19
+ stale_state
20
+ end
21
+ end
22
+
23
+ def ids_writer(ids)
24
+ owner.write_attribute(source_attr, ids.presence)
25
+ return unless owner.persisted? && owner.attribute_changed?(source_attr)
26
+
27
+ owner.update_attribute(source_attr, ids.presence)
28
+ end
29
+
30
+ def size
31
+ if loaded?
32
+ target.size
33
+ elsif !target.empty?
34
+ unsaved_records = target.select(&:new_record?)
35
+ unsaved_records.size + stale_state.size
36
+ else
37
+ stale_state&.size || 0
38
+ end
39
+ end
40
+
41
+ def empty?
42
+ size.zero?
43
+ end
44
+
45
+ def include?(record)
46
+ return false unless record.is_a?(reflection.klass)
47
+ return include_in_memory?(record) if record.new_record?
48
+
49
+ (!target.empty? && target.include?(record)) ||
50
+ stale_state&.include?(record.read_attribute(klass_attr))
51
+ end
52
+
53
+ def load_target
54
+ if stale_target? || find_target?
55
+ @target = merge_target_lists(find_target, target)
56
+ end
57
+
58
+ loaded!
59
+ target
60
+ end
61
+
62
+ def build_changes
63
+ @_building_changes = true
64
+ yield.tap { ids_writer(ids_reader) }
65
+ ensure
66
+ @_building_changes = nil
67
+ end
68
+
69
+ ## HAS MANY
12
70
  def handle_dependency
13
71
  case options[:dependent]
14
72
  when :restrict_with_exception
15
- raise ::ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
73
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
16
74
 
17
75
  when :restrict_with_error
18
76
  unless empty?
@@ -22,86 +80,89 @@ module Torque
22
80
  end
23
81
 
24
82
  when :destroy
25
- # No point in executing the counter update since we're going to destroy the parent anyway
26
83
  load_target.each { |t| t.destroyed_by_association = reflection }
27
84
  destroy_all
85
+ when :destroy_async
86
+ load_target.each do |t|
87
+ t.destroyed_by_association = reflection
88
+ end
89
+
90
+ unless target.empty?
91
+ association_class = target.first.class
92
+ primary_key_column = association_class.primary_key.to_sym
93
+
94
+ ids = target.collect do |assoc|
95
+ assoc.public_send(primary_key_column)
96
+ end
97
+
98
+ enqueue_destroy_association(
99
+ owner_model_name: owner.class.to_s,
100
+ owner_id: owner.id,
101
+ association_class: association_class.to_s,
102
+ association_ids: ids,
103
+ association_primary_key_column: primary_key_column,
104
+ ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
105
+ )
106
+ end
28
107
  else
29
108
  delete_all
30
109
  end
31
110
  end
32
111
 
33
- def ids_reader
34
- owner[source_attr]
35
- end
36
-
37
- def ids_writer(new_ids)
38
- column = source_attr
39
- command = owner.persisted? ? :update_column : :write_attribute
40
- owner.public_send(command, column, new_ids.presence)
41
- @association_scope = nil
42
- end
43
-
44
112
  def insert_record(record, *)
45
- super
46
-
47
- attribute = (ids_reader || owner[source_attr] = [])
48
- attribute.push(record[klass_attr])
49
- record
113
+ super.tap do |saved|
114
+ ids_rewriter(record.read_attribute(klass_attr), :<<) if saved
115
+ end
50
116
  end
51
117
 
52
- def empty?
53
- size.zero?
54
- end
55
-
56
- def include?(record)
57
- list = owner[source_attr]
58
- ids_reader && ids_reader.include?(record[klass_attr])
118
+ ## BELONGS TO
119
+ def default(&block)
120
+ writer(owner.instance_exec(&block)) if reader.nil?
59
121
  end
60
122
 
61
123
  private
62
124
 
63
- # Returns the number of records in this collection, which basically
64
- # means count the number of entries in the +primary_key+
65
- def count_records
66
- ids_reader&.size || (@target ||= []).size
125
+ ## CUSTOM
126
+ def _create_record(attributes, raises = false, &block)
127
+ if attributes.is_a?(Array)
128
+ attributes.collect { |attr| _create_record(attr, raises, &block) }
129
+ else
130
+ build_record(attributes, &block).tap do |record|
131
+ transaction do
132
+ result = nil
133
+ add_to_target(record) do
134
+ result = insert_record(record, true, raises) { @_was_loaded = loaded? }
135
+ end
136
+ raise ActiveRecord::Rollback unless result
137
+ end
138
+ end
139
+ end
67
140
  end
68
141
 
69
- # When the idea is to nulligy the association, then just set the owner
142
+ # When the idea is to nullify the association, then just set the owner
70
143
  # +primary_key+ as empty
71
- def delete_count(method, scope, ids = nil)
72
- ids ||= scope.pluck(klass_attr)
73
- scope.delete_all if method == :delete_all
74
- remove_stash_records(ids)
144
+ def delete_count(method, scope, ids)
145
+ size_cache = scope.delete_all if method == :delete_all
146
+ (size_cache || ids.size).tap { ids_rewriter(ids, :-) }
75
147
  end
76
148
 
77
149
  def delete_or_nullify_all_records(method)
78
- delete_count(method, scope)
150
+ delete_count(method, scope, ids_reader)
79
151
  end
80
152
 
81
153
  # Deletes the records according to the <tt>:dependent</tt> option.
82
154
  def delete_records(records, method)
83
- ids = Array.wrap(records).each_with_object(klass_attr).map(&:[])
155
+ ids = read_records_ids(records)
84
156
 
85
157
  if method == :destroy
86
158
  records.each(&:destroy!)
87
- remove_stash_records(ids)
159
+ ids_rewriter(ids, :-)
88
160
  else
89
161
  scope = self.scope.where(klass_attr => records)
90
162
  delete_count(method, scope, ids)
91
163
  end
92
164
  end
93
165
 
94
- def concat_records(*)
95
- result = super
96
- ids_writer(ids_reader)
97
- result
98
- end
99
-
100
- def remove_stash_records(ids)
101
- return if ids_reader.nil?
102
- ids_writer(ids_reader - Array.wrap(ids))
103
- end
104
-
105
166
  def source_attr
106
167
  reflection.foreign_key
107
168
  end
@@ -110,6 +171,33 @@ module Torque
110
171
  reflection.active_record_primary_key
111
172
  end
112
173
 
174
+ def read_records_ids(records)
175
+ return unless records.present?
176
+ Array.wrap(records).each_with_object(klass_attr).map(&:read_attribute).presence
177
+ end
178
+
179
+ def ids_rewriter(ids, operator)
180
+ list = owner[source_attr] ||= []
181
+ list = list.public_send(operator, ids)
182
+ owner[source_attr] = list.uniq.compact.presence
183
+
184
+ return if @_building_changes || !owner.persisted?
185
+ owner.update_attribute(source_attr, list)
186
+ end
187
+
188
+ ## HAS MANY
189
+ def replace_records(*)
190
+ build_changes { super }
191
+ end
192
+
193
+ def concat_records(*)
194
+ build_changes { super }
195
+ end
196
+
197
+ def delete_or_destroy(*)
198
+ build_changes { super }
199
+ end
200
+
113
201
  def difference(a, b)
114
202
  a - b
115
203
  end
@@ -117,6 +205,28 @@ module Torque
117
205
  def intersection(a, b)
118
206
  a & b
119
207
  end
208
+
209
+ ## BELONGS TO
210
+ def scope_for_create
211
+ super.except!(klass.primary_key)
212
+ end
213
+
214
+ def find_target?
215
+ !loaded? && foreign_key_present? && klass
216
+ end
217
+
218
+ def foreign_key_present?
219
+ stale_state.present?
220
+ end
221
+
222
+ def invertible_for?(record)
223
+ inverse = inverse_reflection_for(record)
224
+ inverse && (inverse.has_many? && inverse.connected_through_array?)
225
+ end
226
+
227
+ def stale_state
228
+ owner.read_attribute(source_attr)
229
+ end
120
230
  end
121
231
 
122
232
  ::ActiveRecord::Associations.const_set(:BelongsToManyAssociation, BelongsToManyAssociation)
@@ -57,12 +57,9 @@ module Torque
57
57
  end
58
58
  end
59
59
 
60
- unless reflection.counter_cache_column
61
- model.after_create callback.call(:saved_changes), if: :saved_changes?
62
- model.after_destroy callback.call(:changes_to_save)
63
- end
64
-
60
+ model.after_create callback.call(:saved_changes), if: :saved_changes?
65
61
  model.after_update callback.call(:saved_changes), if: :saved_changes?
62
+ model.after_destroy callback.call(:changes_to_save)
66
63
  model.after_touch callback.call(:changes_to_save)
67
64
  end
68
65
 
@@ -97,6 +94,10 @@ module Torque
97
94
  end
98
95
  end
99
96
 
97
+ def self.add_destroy_callbacks(model, reflection)
98
+ model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
99
+ end
100
+
100
101
  def self.define_validations(model, reflection)
101
102
  if reflection.options.key?(:required)
102
103
  reflection.options[:optional] = !reflection.options.delete(:required)
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module AutosaveAssociation
6
+ module ClassMethods
7
+ # Since belongs to many is a collection, the callback would normally go
8
+ # to +after_create+. However, since it is a +belongs_to+ kind of
9
+ # association, it neds to be executed +before_save+
10
+ def add_autosave_association_callbacks(reflection)
11
+ return super unless reflection.macro.eql?(:belongs_to_many)
12
+
13
+ save_method = :"autosave_associated_records_for_#{reflection.name}"
14
+ define_non_cyclic_method(save_method) do
15
+ save_belongs_to_many_association(reflection)
16
+ end
17
+
18
+ before_save(save_method)
19
+
20
+ define_autosave_validation_callbacks(reflection)
21
+ end
22
+ end
23
+
24
+ # Ensure the right way to execute +save_collection_association+ and also
25
+ # keep it as a single change using +build_changes+
26
+ def save_belongs_to_many_association(reflection)
27
+ previously_new_record_before_save = (@new_record_before_save ||= false)
28
+ @new_record_before_save = new_record?
29
+
30
+ association = association_instance_get(reflection.name)
31
+ association&.build_changes { save_collection_association(reflection) }
32
+ rescue ::ActiveRecord::RecordInvalid
33
+ throw(:abort)
34
+ ensure
35
+ @new_record_before_save = previously_new_record_before_save
36
+ end
37
+ end
38
+
39
+ ::ActiveRecord::Base.singleton_class.prepend(AutosaveAssociation::ClassMethods)
40
+ ::ActiveRecord::Base.include(AutosaveAssociation)
41
+ end
42
+ end
@@ -200,8 +200,9 @@ module Torque
200
200
  # belongs_to_many :tags, dependent: :nullify
201
201
  # belongs_to_many :tags, required: true, touch: true
202
202
  # belongs_to_many :tags, default: -> { Tag.default }
203
- def belongs_to_many(name, scope = nil, **options)
204
- reflection = Associations::Builder::BelongsToMany.build(self, name, scope, options)
203
+ def belongs_to_many(name, scope = nil, **options, &extension)
204
+ klass = Associations::Builder::BelongsToMany
205
+ reflection = klass.build(self, name, scope, options, &extension)
205
206
  ::ActiveRecord::Reflection.add_reflection(self, name, reflection)
206
207
  end
207
208
 
@@ -103,29 +103,6 @@ module Torque
103
103
 
104
104
  ::Arel::Nodes::NamedFunction.new('ANY', [source_attr])
105
105
  end
106
-
107
- # returns either +nil+ or the inverse association name that it finds.
108
- def automatic_inverse_of
109
- return super unless connected_through_array?
110
-
111
- if can_find_inverse_of_automatically?(self)
112
- inverse_name = options[:as] || active_record.name.demodulize
113
- inverse_name = ActiveSupport::Inflector.underscore(inverse_name)
114
- inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
115
- inverse_name = inverse_name.to_sym
116
-
117
- begin
118
- reflection = klass._reflect_on_association(inverse_name)
119
- rescue NameError
120
- # Give up: we couldn't compute the klass type so we won't be able
121
- # to find any associations either.
122
- reflection = false
123
- end
124
-
125
- return inverse_name if reflection.connected_through_array? &&
126
- valid_inverse_reflection?(reflection)
127
- end
128
- end
129
106
  end
130
107
 
131
108
  ::ActiveRecord::Reflection::AbstractReflection.prepend(AbstractReflection)
@@ -24,6 +24,28 @@ module Torque
24
24
  result
25
25
  end
26
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
+
27
49
  end
28
50
 
29
51
  ::ActiveRecord::Reflection::AssociationReflection.prepend(AssociationReflection)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Torque
4
4
  module PostgreSQL
5
- VERSION = '2.0.5'
5
+ VERSION = '2.0.6'
6
6
  end
7
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 = 62
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
@@ -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
@@ -182,11 +254,15 @@ RSpec.describe 'BelongsToMany' do
182
254
  it 'can check if a record is included on the list' do
183
255
  outside = FactoryBot.create(:tag)
184
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
+
185
261
  subject.tags << inside
186
262
 
187
263
  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
264
+ expect(subject.tags).to be_include(inside)
265
+ expect(subject.tags).not_to be_include(outside)
190
266
  end
191
267
 
192
268
  it 'can append records' do
@@ -194,6 +270,9 @@ RSpec.describe 'BelongsToMany' do
194
270
  expect(subject.tags.size).to be_eql(1)
195
271
 
196
272
  subject.tags << other.new(name: 'Test 2')
273
+ subject.update(title: 'B')
274
+ subject.reload
275
+
197
276
  expect(subject.tags.size).to be_eql(2)
198
277
  expect(subject.tags.last.name).to be_eql('Test 2')
199
278
  end
@@ -208,10 +287,18 @@ RSpec.describe 'BelongsToMany' do
208
287
 
209
288
  it 'can reload records' do
210
289
  expect(subject.tags.size).to be_eql(0)
211
- subject.tags << FactoryBot.create(:tag)
290
+ new_tag = FactoryBot.create(:tag)
291
+ subject.tags << new_tag
212
292
 
213
293
  subject.tags.reload
214
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)
215
302
  end
216
303
 
217
304
  it 'can preload records' do
@@ -233,9 +320,8 @@ RSpec.describe 'BelongsToMany' do
233
320
 
234
321
  context "When record is not persisted" do
235
322
  let(:initial) { FactoryBot.create(:tag) }
236
- before { Video.belongs_to_many :tags }
323
+
237
324
  subject { Video.new(title: 'A', tags: [initial]) }
238
- after { Video._reflections = {} }
239
325
 
240
326
  it 'loads associated records' do
241
327
  expect(subject.tags.load).to be_a(ActiveRecord::Associations::CollectionProxy)
@@ -291,6 +291,20 @@ RSpec.describe 'HasMany' do
291
291
  expect(record.tag_ids).to be_eql([subject.id])
292
292
  end
293
293
 
294
+ it 'can perist after accessed in after_create' do
295
+ other.belongs_to_many(:tags)
296
+ other.after_create { self.tags.to_a }
297
+
298
+ video = FactoryBot.create(:video)
299
+ subject.videos << video
300
+
301
+ expect(subject.reload.videos.size).to eql(1)
302
+ expect(video.reload.tags.size).to eql(1)
303
+
304
+ other.reset_callbacks(:create)
305
+ other._reflections = {}
306
+ end
307
+
294
308
  it 'can concat records' do
295
309
  FactoryBot.create(:video, tag_ids: [subject.id])
296
310
  expect(subject.videos.size).to be_eql(1)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: torque-postgresql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 2.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Silva
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-27 00:00:00.000000000 Z
11
+ date: 2021-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -211,6 +211,7 @@ files:
211
211
  - lib/torque/postgresql/attributes/enum_set.rb
212
212
  - lib/torque/postgresql/attributes/lazy.rb
213
213
  - lib/torque/postgresql/attributes/period.rb
214
+ - lib/torque/postgresql/autosave_association.rb
214
215
  - lib/torque/postgresql/auxiliary_statement.rb
215
216
  - lib/torque/postgresql/auxiliary_statement/settings.rb
216
217
  - lib/torque/postgresql/base.rb
@@ -332,10 +333,9 @@ test_files:
332
333
  - spec/factories/videos.rb
333
334
  - spec/tests/geometric_builder_spec.rb
334
335
  - spec/tests/range_spec.rb
335
- - spec/tests/table_inheritance_spec.rb
336
+ - spec/tests/arel_spec.rb
336
337
  - spec/tests/enum_spec.rb
337
- - spec/tests/auxiliary_statement_spec.rb
338
- - spec/tests/enum_set_spec.rb
338
+ - spec/tests/period_spec.rb
339
339
  - spec/tests/coder_spec.rb
340
340
  - spec/tests/collector_spec.rb
341
341
  - spec/tests/distinct_on_spec.rb
@@ -343,10 +343,11 @@ test_files:
343
343
  - spec/tests/lazy_spec.rb
344
344
  - spec/tests/quoting_spec.rb
345
345
  - spec/tests/relation_spec.rb
346
- - spec/tests/belongs_to_many_spec.rb
347
- - spec/tests/period_spec.rb
348
- - spec/tests/arel_spec.rb
346
+ - spec/tests/auxiliary_statement_spec.rb
347
+ - spec/tests/enum_set_spec.rb
349
348
  - spec/tests/has_many_spec.rb
349
+ - spec/tests/belongs_to_many_spec.rb
350
+ - spec/tests/table_inheritance_spec.rb
350
351
  - spec/mocks/cache_query.rb
351
352
  - spec/mocks/create_table.rb
352
353
  - spec/en.yml