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