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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f108bfb672743d7edd701b77eb102aa6f9164b4981e1f366d2354d9e8cf3360b
4
- data.tar.gz: 60d7208f0798acbde69aee78fca7a92d4824c75dcecc44acd16dcf59568f0e04
3
+ metadata.gz: 83fb6f6620a916f7ef85af51f4ba4fa6e2a1c6b3597ee5884466e4d46b6544c6
4
+ data.tar.gz: 7503abd9ce29bd38349d88527502abc51182bb5e158e0f1960b71817d65cd559
5
5
  SHA512:
6
- metadata.gz: 0a463f94c5a09f74bbb2ba57fe9fb2f7fe649f243c239f7709180c29988f2b5424bffec68d0ec1d2c4ae8ad1191d6156e96dd00c2a66a2a473e02411c563e21f
7
- data.tar.gz: 1568915cd13e2b93b77a702cfd346326aa9e07605aac3deca0f3dacbe82b24762a84f3007a4dffc493888c94802724defbfc01037d0bad984e3735783a790f35
6
+ metadata.gz: 13c371dccc04ee5663e2d249a443f398005a1d2e2cabea904c03f34c379ee4119c5ec5c7edeee52ef009d5223df9b514b6444b1ffeff9fc3231adbba93aebd5a
7
+ data.tar.gz: c8308c9290b8bac1ee2d990ec64e128cdeed903d09dbc391b23eb5867930c41febc1affaf043840c81675642bebd027e1a6bdf7400c4375c23cfe111496ee174
@@ -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/coder'
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, Coder.decode(refs)]
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 managed"
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 has inheritance"
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
- record_fk = reflection.foreign_key
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 ::ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
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
- attribute = (ids_reader || owner[source_attr] = [])
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
- def include?(record)
57
- list = owner[source_attr]
58
- ids_reader && ids_reader.include?(record[klass_attr])
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
- # 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
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 nulligy the association, then just set the owner
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 = nil)
72
- ids ||= scope.pluck(klass_attr)
73
- scope.delete_all if method == :delete_all
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 = Array.wrap(records).each_with_object(klass_attr).map(&:[])
158
+ ids = read_records_ids(records)
84
159
 
85
160
  if method == :destroy
86
161
  records.each(&:destroy!)
87
- remove_stash_records(ids)
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
- 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)
@@ -35,6 +35,7 @@ module Torque
35
35
  ids.each { |id| records[id].concat(Array.wrap(record)) }
36
36
  end
37
37
 
38
+ records.default_proc = nil
38
39
  owners.each do |owner|
39
40
  associate_records_to_owner(owner, records[owner[owner_key_name]] || [])
40
41
  end