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