torque-postgresql 2.0.4 → 2.1.2
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 -1
- data/lib/torque/postgresql/adapter.rb +16 -0
- data/lib/torque/postgresql/adapter/database_statements.rb +1 -15
- data/lib/torque/postgresql/adapter/schema_dumper.rb +6 -2
- data/lib/torque/postgresql/associations/association.rb +10 -3
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +165 -48
- data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +6 -5
- data/lib/torque/postgresql/associations/preloader/association.rb +1 -0
- data/lib/torque/postgresql/attributes/builder.rb +1 -1
- data/lib/torque/postgresql/attributes/builder/enum.rb +5 -5
- data/lib/torque/postgresql/attributes/enum.rb +1 -1
- data/lib/torque/postgresql/attributes/enum_set.rb +1 -1
- data/lib/torque/postgresql/autosave_association.rb +16 -19
- data/lib/torque/postgresql/base.rb +9 -2
- data/lib/torque/postgresql/insert_all.rb +26 -0
- 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/factories/item.rb +5 -0
- data/spec/models/item.rb +3 -0
- data/spec/schema.rb +9 -1
- data/spec/tests/belongs_to_many_spec.rb +149 -13
- data/spec/tests/has_many_spec.rb +14 -0
- data/spec/tests/insert_all_spec.rb +89 -0
- metadata +17 -13
- data/lib/torque/postgresql/coder.rb +0 -133
- data/spec/tests/coder_spec.rb +0 -367
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83fb6f6620a916f7ef85af51f4ba4fa6e2a1c6b3597ee5884466e4d46b6544c6
|
4
|
+
data.tar.gz: 7503abd9ce29bd38349d88527502abc51182bb5e158e0f1960b71817d65cd559
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13c371dccc04ee5663e2d249a443f398005a1d2e2cabea904c03f34c379ee4119c5ec5c7edeee52ef009d5223df9b514b6444b1ffeff9fc3231adbba93aebd5a
|
7
|
+
data.tar.gz: c8308c9290b8bac1ee2d990ec64e128cdeed903d09dbc391b23eb5867930c41febc1affaf043840c81675642bebd027e1a6bdf7400c4375c23cfe111496ee174
|
data/lib/torque/postgresql.rb
CHANGED
@@ -22,7 +22,7 @@ require 'torque/postgresql/autosave_association'
|
|
22
22
|
require 'torque/postgresql/auxiliary_statement'
|
23
23
|
require 'torque/postgresql/base'
|
24
24
|
require 'torque/postgresql/inheritance'
|
25
|
-
require 'torque/postgresql/
|
25
|
+
require 'torque/postgresql/insert_all'
|
26
26
|
require 'torque/postgresql/migration'
|
27
27
|
require 'torque/postgresql/relation'
|
28
28
|
require 'torque/postgresql/reflection'
|
@@ -15,6 +15,8 @@ module Torque
|
|
15
15
|
include DatabaseStatements
|
16
16
|
include SchemaStatements
|
17
17
|
|
18
|
+
INJECT_WHERE_REGEX = /(DO UPDATE SET.*excluded\.[^ ]+) RETURNING/.freeze
|
19
|
+
|
18
20
|
# Get the current PostgreSQL version as a Gem Version.
|
19
21
|
def version
|
20
22
|
@version ||= Gem::Version.new(
|
@@ -26,6 +28,20 @@ module Torque
|
|
26
28
|
def extract_table_options!(options)
|
27
29
|
super.merge(options.extract!(:inherits))
|
28
30
|
end
|
31
|
+
|
32
|
+
# Allow filtered bulk insert by adding the where clause. This method is only used by
|
33
|
+
# +InsertAll+, so it somewhat safe to override it
|
34
|
+
def build_insert_sql(insert)
|
35
|
+
super.tap do |sql|
|
36
|
+
if insert.update_duplicates? && insert.where_condition?
|
37
|
+
if insert.returning
|
38
|
+
sql.gsub!(INJECT_WHERE_REGEX, "\\1 WHERE #{insert.where} RETURNING")
|
39
|
+
else
|
40
|
+
sql << " WHERE #{insert.where}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
29
45
|
end
|
30
46
|
|
31
47
|
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend Adapter
|
@@ -123,7 +123,7 @@ module Torque
|
|
123
123
|
SQL
|
124
124
|
|
125
125
|
tables.map do |(table, refs)|
|
126
|
-
[table,
|
126
|
+
[table, PG::TextDecoder::Array.new.decode(refs)]
|
127
127
|
end.to_h
|
128
128
|
end
|
129
129
|
|
@@ -147,20 +147,6 @@ module Torque
|
|
147
147
|
SQL
|
148
148
|
end
|
149
149
|
|
150
|
-
# Extracts the value from a PostgreSQL column default definition.
|
151
|
-
def extract_value_from_default(default)
|
152
|
-
case default
|
153
|
-
# Array elements
|
154
|
-
when /\AARRAY\[(.*)\]\z/
|
155
|
-
# TODO: Improve this since it's not the most safe approach
|
156
|
-
eval(default.gsub(/ARRAY|::\w+(\[\])?/, ''))
|
157
|
-
else
|
158
|
-
super
|
159
|
-
end
|
160
|
-
rescue SyntaxError
|
161
|
-
# If somethin goes wrong with the eval, just return nil
|
162
|
-
end
|
163
|
-
|
164
150
|
end
|
165
151
|
end
|
166
152
|
end
|
@@ -43,13 +43,13 @@ module Torque
|
|
43
43
|
inherited_tables = @connection.inherited_tables
|
44
44
|
sorted_tables = @connection.tables.sort - @connection.views
|
45
45
|
|
46
|
-
stream.puts " # These are the common tables
|
46
|
+
stream.puts " # These are the common tables"
|
47
47
|
(sorted_tables - inherited_tables.keys).each do |table_name|
|
48
48
|
table(table_name, stream) unless ignored?(table_name)
|
49
49
|
end
|
50
50
|
|
51
51
|
if inherited_tables.present?
|
52
|
-
stream.puts " # These are tables that
|
52
|
+
stream.puts " # These are tables that have inheritance"
|
53
53
|
inherited_tables.each do |table_name, inherits|
|
54
54
|
next if ignored?(table_name)
|
55
55
|
|
@@ -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,15 +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
|
19
|
+
def skip_statement_cache?(*)
|
20
|
+
super || reflection.connected_through_array?
|
21
|
+
end
|
22
|
+
|
16
23
|
private
|
17
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
|
18
27
|
def set_owner_attributes(record)
|
19
28
|
return super unless reflection.connected_through_array?
|
20
29
|
|
21
30
|
add_id = owner[reflection.active_record_primary_key]
|
22
|
-
|
23
|
-
|
24
|
-
list = record[record_fk] ||= []
|
31
|
+
list = record[reflection.foreign_key] ||= []
|
25
32
|
list.push(add_id) unless list.include?(add_id)
|
26
33
|
end
|
27
34
|
|
@@ -9,10 +9,71 @@ 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 || column_default_value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def ids_writer(ids)
|
24
|
+
ids = ids.presence || column_default_value
|
25
|
+
owner.write_attribute(source_attr, ids)
|
26
|
+
return unless owner.persisted? && owner.attribute_changed?(source_attr)
|
27
|
+
|
28
|
+
owner.update_attribute(source_attr, ids)
|
29
|
+
end
|
30
|
+
|
31
|
+
def size
|
32
|
+
if loaded?
|
33
|
+
target.size
|
34
|
+
elsif !target.empty?
|
35
|
+
unsaved_records = target.select(&:new_record?)
|
36
|
+
unsaved_records.size + stale_state.size
|
37
|
+
else
|
38
|
+
stale_state&.size || 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def empty?
|
43
|
+
size.zero?
|
44
|
+
end
|
45
|
+
|
46
|
+
def include?(record)
|
47
|
+
return false unless record.is_a?(reflection.klass)
|
48
|
+
return include_in_memory?(record) if record.new_record?
|
49
|
+
|
50
|
+
(!target.empty? && target.include?(record)) ||
|
51
|
+
stale_state&.include?(record.read_attribute(klass_attr))
|
52
|
+
end
|
53
|
+
|
54
|
+
def load_target
|
55
|
+
if stale_target? || find_target?
|
56
|
+
@target = merge_target_lists(find_target, target)
|
57
|
+
end
|
58
|
+
|
59
|
+
loaded!
|
60
|
+
target
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_changes(from_target = false)
|
64
|
+
return yield if defined?(@_building_changes) && @_building_changes
|
65
|
+
|
66
|
+
@_building_changes = true
|
67
|
+
yield.tap { ids_writer(from_target ? ids_reader : stale_state) }
|
68
|
+
ensure
|
69
|
+
@_building_changes = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
## HAS MANY
|
12
73
|
def handle_dependency
|
13
74
|
case options[:dependent]
|
14
75
|
when :restrict_with_exception
|
15
|
-
raise
|
76
|
+
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
|
16
77
|
|
17
78
|
when :restrict_with_error
|
18
79
|
unless empty?
|
@@ -22,86 +83,89 @@ module Torque
|
|
22
83
|
end
|
23
84
|
|
24
85
|
when :destroy
|
25
|
-
# No point in executing the counter update since we're going to destroy the parent anyway
|
26
86
|
load_target.each { |t| t.destroyed_by_association = reflection }
|
27
87
|
destroy_all
|
88
|
+
when :destroy_async
|
89
|
+
load_target.each do |t|
|
90
|
+
t.destroyed_by_association = reflection
|
91
|
+
end
|
92
|
+
|
93
|
+
unless target.empty?
|
94
|
+
association_class = target.first.class
|
95
|
+
primary_key_column = association_class.primary_key.to_sym
|
96
|
+
|
97
|
+
ids = target.collect do |assoc|
|
98
|
+
assoc.public_send(primary_key_column)
|
99
|
+
end
|
100
|
+
|
101
|
+
enqueue_destroy_association(
|
102
|
+
owner_model_name: owner.class.to_s,
|
103
|
+
owner_id: owner.id,
|
104
|
+
association_class: association_class.to_s,
|
105
|
+
association_ids: ids,
|
106
|
+
association_primary_key_column: primary_key_column,
|
107
|
+
ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
|
108
|
+
)
|
109
|
+
end
|
28
110
|
else
|
29
111
|
delete_all
|
30
112
|
end
|
31
113
|
end
|
32
114
|
|
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
115
|
def insert_record(record, *)
|
45
|
-
super
|
46
|
-
|
47
|
-
|
48
|
-
attribute.push(record[klass_attr])
|
49
|
-
record
|
50
|
-
end
|
51
|
-
|
52
|
-
def empty?
|
53
|
-
size.zero?
|
116
|
+
(record.persisted? || super).tap do |saved|
|
117
|
+
ids_rewriter(record.read_attribute(klass_attr), :<<) if saved
|
118
|
+
end
|
54
119
|
end
|
55
120
|
|
56
|
-
|
57
|
-
|
58
|
-
|
121
|
+
## BELONGS TO
|
122
|
+
def default(&block)
|
123
|
+
writer(owner.instance_exec(&block)) if reader.nil?
|
59
124
|
end
|
60
125
|
|
61
126
|
private
|
62
127
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
128
|
+
## CUSTOM
|
129
|
+
def _create_record(attributes, raises = false, &block)
|
130
|
+
if attributes.is_a?(Array)
|
131
|
+
attributes.collect { |attr| _create_record(attr, raises, &block) }
|
132
|
+
else
|
133
|
+
build_record(attributes, &block).tap do |record|
|
134
|
+
transaction do
|
135
|
+
result = nil
|
136
|
+
add_to_target(record) do
|
137
|
+
result = insert_record(record, true, raises) { @_was_loaded = loaded? }
|
138
|
+
end
|
139
|
+
raise ActiveRecord::Rollback unless result
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
67
143
|
end
|
68
144
|
|
69
|
-
# When the idea is to
|
145
|
+
# When the idea is to nullify the association, then just set the owner
|
70
146
|
# +primary_key+ as empty
|
71
|
-
def delete_count(method, scope, ids
|
72
|
-
|
73
|
-
|
74
|
-
remove_stash_records(ids)
|
147
|
+
def delete_count(method, scope, ids)
|
148
|
+
size_cache = scope.delete_all if method == :delete_all
|
149
|
+
(size_cache || ids.size).tap { ids_rewriter(ids, :-) }
|
75
150
|
end
|
76
151
|
|
77
152
|
def delete_or_nullify_all_records(method)
|
78
|
-
delete_count(method, scope)
|
153
|
+
delete_count(method, scope, ids_reader)
|
79
154
|
end
|
80
155
|
|
81
156
|
# Deletes the records according to the <tt>:dependent</tt> option.
|
82
157
|
def delete_records(records, method)
|
83
|
-
ids =
|
158
|
+
ids = read_records_ids(records)
|
84
159
|
|
85
160
|
if method == :destroy
|
86
161
|
records.each(&:destroy!)
|
87
|
-
|
162
|
+
ids_rewriter(ids, :-)
|
88
163
|
else
|
89
164
|
scope = self.scope.where(klass_attr => records)
|
90
165
|
delete_count(method, scope, ids)
|
91
166
|
end
|
92
167
|
end
|
93
168
|
|
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
169
|
def source_attr
|
106
170
|
reflection.foreign_key
|
107
171
|
end
|
@@ -110,6 +174,37 @@ module Torque
|
|
110
174
|
reflection.active_record_primary_key
|
111
175
|
end
|
112
176
|
|
177
|
+
def read_records_ids(records)
|
178
|
+
return unless records.present?
|
179
|
+
Array.wrap(records).each_with_object(klass_attr).map(&:read_attribute).presence
|
180
|
+
end
|
181
|
+
|
182
|
+
def ids_rewriter(ids, operator)
|
183
|
+
list = owner[source_attr] ||= []
|
184
|
+
list = list.public_send(operator, ids)
|
185
|
+
owner[source_attr] = list.uniq.compact.presence || column_default_value
|
186
|
+
|
187
|
+
return if @_building_changes || !owner.persisted?
|
188
|
+
owner.update_attribute(source_attr, list)
|
189
|
+
end
|
190
|
+
|
191
|
+
def column_default_value
|
192
|
+
owner.class.columns_hash[source_attr].default
|
193
|
+
end
|
194
|
+
|
195
|
+
## HAS MANY
|
196
|
+
def replace_records(*)
|
197
|
+
build_changes(true) { super }
|
198
|
+
end
|
199
|
+
|
200
|
+
def concat_records(*)
|
201
|
+
build_changes(true) { super }
|
202
|
+
end
|
203
|
+
|
204
|
+
def delete_or_destroy(*)
|
205
|
+
build_changes(true) { super }
|
206
|
+
end
|
207
|
+
|
113
208
|
def difference(a, b)
|
114
209
|
a - b
|
115
210
|
end
|
@@ -117,6 +212,28 @@ module Torque
|
|
117
212
|
def intersection(a, b)
|
118
213
|
a & b
|
119
214
|
end
|
215
|
+
|
216
|
+
## BELONGS TO
|
217
|
+
def scope_for_create
|
218
|
+
super.except!(klass.primary_key)
|
219
|
+
end
|
220
|
+
|
221
|
+
def find_target?
|
222
|
+
!loaded? && foreign_key_present? && klass
|
223
|
+
end
|
224
|
+
|
225
|
+
def foreign_key_present?
|
226
|
+
stale_state.present?
|
227
|
+
end
|
228
|
+
|
229
|
+
def invertible_for?(record)
|
230
|
+
inverse = inverse_reflection_for(record)
|
231
|
+
inverse && (inverse.has_many? && inverse.connected_through_array?)
|
232
|
+
end
|
233
|
+
|
234
|
+
def stale_state
|
235
|
+
owner.read_attribute(source_attr)
|
236
|
+
end
|
120
237
|
end
|
121
238
|
|
122
239
|
::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)
|