torque-postgresql 2.0.4 → 2.1.2
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 -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)
|