torque-postgresql 2.0.5 → 2.0.6

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.
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