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 +4 -4
- data/lib/torque/postgresql.rb +1 -0
- data/lib/torque/postgresql/adapter/schema_dumper.rb +4 -0
- data/lib/torque/postgresql/associations/association.rb +6 -3
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +158 -48
- data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +6 -5
- data/lib/torque/postgresql/autosave_association.rb +42 -0
- data/lib/torque/postgresql/base.rb +3 -2
- data/lib/torque/postgresql/reflection/abstract_reflection.rb +0 -23
- data/lib/torque/postgresql/reflection/association_reflection.rb +22 -0
- data/lib/torque/postgresql/version.rb +1 -1
- data/spec/schema.rb +2 -1
- data/spec/tests/belongs_to_many_spec.rb +98 -12
- data/spec/tests/has_many_spec.rb +14 -0
- metadata +9 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd314ab034c3d38a227829faf2d706b13625a5e4cc6393004f6dbe2890e784a8
|
4
|
+
data.tar.gz: 035b0fc92e1ea2aedb507c18094e66025c0fe98838f8553f3abc64764a0b2f03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6eeaffde8209891fee13de7518acea135cfe5f35ecbc5aa59124faad208ed0545a24c2ffb993f10eb5e306c2ccb96036e1dbc812a11177ecdda5e3ead62e0dd7
|
7
|
+
data.tar.gz: ad55cf8b8a5e9de71575c12e8452d440439529258752ceceb95da17111991b8a9ad90101806ab328d5993d328bec69a71feb4cc6b85431c9802d0c225557dbb5
|
data/lib/torque/postgresql.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
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
|
72
|
-
|
73
|
-
|
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 =
|
155
|
+
ids = read_records_ids(records)
|
84
156
|
|
85
157
|
if method == :destroy
|
86
158
|
records.each(&:destroy!)
|
87
|
-
|
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
|
-
|
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
|
-
|
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)
|
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
|
@@ -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
|
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)
|
@@ -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.
|
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
|
135
|
-
|
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
|
-
|
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.
|
189
|
-
expect(subject.tags.
|
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
|
-
|
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
|
-
|
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)
|
data/spec/tests/has_many_spec.rb
CHANGED
@@ -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.
|
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-
|
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/
|
336
|
+
- spec/tests/arel_spec.rb
|
336
337
|
- spec/tests/enum_spec.rb
|
337
|
-
- spec/tests/
|
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/
|
347
|
-
- spec/tests/
|
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
|