torque-postgresql 2.0.2 → 2.1.0

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql.rb +1 -0
  3. data/lib/torque/postgresql/adapter.rb +23 -0
  4. data/lib/torque/postgresql/adapter/database_statements.rb +2 -0
  5. data/lib/torque/postgresql/adapter/oid.rb +3 -1
  6. data/lib/torque/postgresql/adapter/oid/box.rb +2 -0
  7. data/lib/torque/postgresql/adapter/oid/circle.rb +2 -0
  8. data/lib/torque/postgresql/adapter/oid/enum.rb +2 -0
  9. data/lib/torque/postgresql/adapter/oid/enum_set.rb +2 -0
  10. data/lib/torque/postgresql/adapter/oid/interval.rb +2 -0
  11. data/lib/torque/postgresql/adapter/oid/line.rb +2 -0
  12. data/lib/torque/postgresql/adapter/oid/range.rb +2 -0
  13. data/lib/torque/postgresql/adapter/oid/segment.rb +2 -0
  14. data/lib/torque/postgresql/adapter/quoting.rb +2 -0
  15. data/lib/torque/postgresql/adapter/schema_creation.rb +8 -1
  16. data/lib/torque/postgresql/adapter/schema_definitions.rb +2 -0
  17. data/lib/torque/postgresql/adapter/schema_dumper.rb +7 -1
  18. data/lib/torque/postgresql/adapter/schema_statements.rb +2 -0
  19. data/lib/torque/postgresql/arel/infix_operation.rb +5 -1
  20. data/lib/torque/postgresql/arel/join_source.rb +2 -0
  21. data/lib/torque/postgresql/arel/nodes.rb +2 -0
  22. data/lib/torque/postgresql/arel/operations.rb +2 -0
  23. data/lib/torque/postgresql/arel/select_manager.rb +2 -0
  24. data/lib/torque/postgresql/arel/visitors.rb +6 -3
  25. data/lib/torque/postgresql/associations/association.rb +14 -3
  26. data/lib/torque/postgresql/associations/association_scope.rb +2 -0
  27. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +169 -47
  28. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +8 -5
  29. data/lib/torque/postgresql/associations/builder/has_many.rb +2 -0
  30. data/lib/torque/postgresql/associations/preloader/association.rb +30 -1
  31. data/lib/torque/postgresql/attributes/builder.rb +3 -1
  32. data/lib/torque/postgresql/attributes/builder/enum.rb +5 -3
  33. data/lib/torque/postgresql/attributes/builder/period.rb +6 -4
  34. data/lib/torque/postgresql/attributes/enum.rb +5 -10
  35. data/lib/torque/postgresql/attributes/enum_set.rb +2 -0
  36. data/lib/torque/postgresql/attributes/lazy.rb +3 -1
  37. data/lib/torque/postgresql/attributes/period.rb +2 -0
  38. data/lib/torque/postgresql/autosave_association.rb +19 -16
  39. data/lib/torque/postgresql/auxiliary_statement.rb +2 -0
  40. data/lib/torque/postgresql/auxiliary_statement/settings.rb +2 -0
  41. data/lib/torque/postgresql/base.rb +11 -2
  42. data/lib/torque/postgresql/coder.rb +5 -3
  43. data/lib/torque/postgresql/collector.rb +2 -0
  44. data/lib/torque/postgresql/config.rb +5 -0
  45. data/lib/torque/postgresql/geometry_builder.rb +2 -0
  46. data/lib/torque/postgresql/i18n.rb +2 -0
  47. data/lib/torque/postgresql/inheritance.rb +2 -0
  48. data/lib/torque/postgresql/insert_all.rb +26 -0
  49. data/lib/torque/postgresql/migration/command_recorder.rb +2 -0
  50. data/lib/torque/postgresql/railtie.rb +2 -0
  51. data/lib/torque/postgresql/reflection.rb +2 -0
  52. data/lib/torque/postgresql/reflection/abstract_reflection.rb +13 -28
  53. data/lib/torque/postgresql/reflection/association_reflection.rb +24 -0
  54. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +18 -4
  55. data/lib/torque/postgresql/reflection/has_many_reflection.rb +2 -0
  56. data/lib/torque/postgresql/reflection/runtime_reflection.rb +2 -0
  57. data/lib/torque/postgresql/reflection/through_reflection.rb +2 -0
  58. data/lib/torque/postgresql/relation.rb +15 -11
  59. data/lib/torque/postgresql/relation/auxiliary_statement.rb +6 -1
  60. data/lib/torque/postgresql/relation/distinct_on.rb +2 -0
  61. data/lib/torque/postgresql/relation/inheritance.rb +2 -0
  62. data/lib/torque/postgresql/relation/merger.rb +2 -0
  63. data/lib/torque/postgresql/schema_cache.rb +2 -0
  64. data/lib/torque/postgresql/version.rb +3 -1
  65. data/spec/schema.rb +3 -2
  66. data/spec/tests/arel_spec.rb +3 -1
  67. data/spec/tests/belongs_to_many_spec.rb +134 -13
  68. data/spec/tests/enum_set_spec.rb +1 -1
  69. data/spec/tests/has_many_spec.rb +25 -1
  70. data/spec/tests/insert_all_spec.rb +89 -0
  71. data/spec/tests/table_inheritance_spec.rb +1 -1
  72. metadata +9 -6
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Associations
@@ -55,12 +57,9 @@ module Torque
55
57
  end
56
58
  end
57
59
 
58
- unless reflection.counter_cache_column
59
- model.after_create callback.call(:saved_changes), if: :saved_changes?
60
- model.after_destroy callback.call(:changes_to_save)
61
- end
62
-
60
+ model.after_create callback.call(:saved_changes), if: :saved_changes?
63
61
  model.after_update callback.call(:saved_changes), if: :saved_changes?
62
+ model.after_destroy callback.call(:changes_to_save)
64
63
  model.after_touch callback.call(:changes_to_save)
65
64
  end
66
65
 
@@ -95,6 +94,10 @@ module Torque
95
94
  end
96
95
  end
97
96
 
97
+ def self.add_destroy_callbacks(model, reflection)
98
+ model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
99
+ end
100
+
98
101
  def self.define_validations(model, reflection)
99
102
  if reflection.options.key?(:required)
100
103
  reflection.options[:optional] = !reflection.options.delete(:required)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Associations
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Associations
@@ -33,16 +35,43 @@ module Torque
33
35
  ids.each { |id| records[id].concat(Array.wrap(record)) }
34
36
  end
35
37
 
38
+ records.default_proc = nil
36
39
  owners.each do |owner|
37
40
  associate_records_to_owner(owner, records[owner[owner_key_name]] || [])
38
41
  end
39
42
  end
40
43
 
44
+ if PostgreSQL::AR610
45
+ # This is how Rails 6.1 now load the records
46
+ def load_records
47
+ return super unless connected_through_array?
48
+
49
+ @records_by_owner = {}.compare_by_identity
50
+ raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
51
+
52
+ @preloaded_records = raw_records.select do |record|
53
+ assignments = false
54
+
55
+ ids = convert_key(record[association_key_name])
56
+ owners_by_key.values_at(*ids).flat_map do |owner|
57
+ entries = (@records_by_owner[owner] ||= [])
58
+
59
+ if reflection.collection? || entries.empty?
60
+ entries << record
61
+ assignments = true
62
+ end
63
+ end
64
+
65
+ assignments
66
+ end
67
+ end
68
+ end
69
+
41
70
  # Build correctly the constraint condition in order to get the
42
71
  # associated ids
43
72
  def records_for(ids, &block)
44
73
  return super unless connected_through_array?
45
- condition = scope.arel_attribute(association_key_name)
74
+ condition = scope.arel_table[association_key_name]
46
75
  condition = reflection.build_id_constraint(condition, ids.flatten.uniq)
47
76
  scope.where(condition).load(&block)
48
77
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'builder/enum'
2
4
  require_relative 'builder/period'
3
5
 
@@ -7,7 +9,7 @@ module Torque
7
9
  module Builder
8
10
  def self.include_on(klass, method_name, builder_klass, **extra, &block)
9
11
  klass.define_singleton_method(method_name) do |*args, **options|
10
- return unless connection.table_exists?(table_name)
12
+ return unless table_exists?
11
13
 
12
14
  args.each do |attribute|
13
15
  begin
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Attributes
@@ -153,12 +155,12 @@ module Torque
153
155
  cast_type = subtype.name.chomp('[]')
154
156
  klass_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
155
157
  def has_#{attribute.pluralize}(*values) # def has_roles(*values)
156
- attr = arel_attribute('#{attribute}') # attr = arel_attribute('role')
158
+ attr = arel_table['#{attribute}'] # attr = arel_table['role']
157
159
  where(attr.contains(::Arel.array(values, cast: '#{cast_type}'))) # where(attr.contains(::Arel.array(values, cast: 'roles')))
158
160
  end # end
159
161
 
160
162
  def has_any_#{attribute.pluralize}(*values) # def has_roles(*values)
161
- attr = arel_attribute('#{attribute}') # attr = arel_attribute('role')
163
+ attr = arel_table['#{attribute}'] # attr = arel_table['role']
162
164
  where(attr.overlaps(::Arel.array(values, cast: '#{cast_type}'))) # where(attr.overlaps(::Arel.array(values, cast: 'roles')))
163
165
  end # end
164
166
  RUBY
@@ -184,7 +186,7 @@ module Torque
184
186
  values_methods.each do |key, (scope, ask, bang, val)|
185
187
  klass_content += <<-RUBY
186
188
  def #{scope} # def admin
187
- attr = arel_attribute('#{attribute}') # attr = arel_attribute('role')
189
+ attr = arel_table['#{attribute}'] # attr = arel_table['role']
188
190
  where(::#{enum_klass}.scope(attr, '#{val}')) # where(Enum::Roles.scope(attr, 'admin'))
189
191
  end # end
190
192
  RUBY
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Attributes
@@ -202,7 +204,7 @@ module Torque
202
204
  end
203
205
 
204
206
  def arel_attribute
205
- @arel_attribute ||= "arel_attribute(#{attribute.inspect})"
207
+ @arel_attribute ||= "arel_table[#{attribute.inspect}]"
206
208
  end
207
209
 
208
210
  def arel_default_sql
@@ -245,7 +247,7 @@ module Torque
245
247
  def arel_real_start_at
246
248
  return arel_start_at unless threshold.present?
247
249
  @arel_real_start_at ||= begin
248
- result = "(#{arel_start_at} - #{arel_threshold_value})"
250
+ result = +"(#{arel_start_at} - #{arel_threshold_value})"
249
251
  result << '.cast(:date)' if type.eql?(:daterange)
250
252
  result
251
253
  end
@@ -255,7 +257,7 @@ module Torque
255
257
  def arel_real_finish_at
256
258
  return arel_finish_at unless threshold.present?
257
259
  @arel_real_finish_at ||= begin
258
- result = "(#{arel_finish_at} + #{arel_threshold_value})"
260
+ result = +"(#{arel_finish_at} + #{arel_threshold_value})"
259
261
  result << '.cast(:date)' if type.eql?(:daterange)
260
262
  result
261
263
  end
@@ -276,7 +278,7 @@ module Torque
276
278
 
277
279
  # Create an arel named function
278
280
  def arel_named_function(name, *args)
279
- result = "::Arel::Nodes::NamedFunction.new(#{name.to_s.inspect}"
281
+ result = +"::Arel::Nodes::NamedFunction.new(#{name.to_s.inspect}"
280
282
  result << ', [' << args.join(', ') << ']' if args.present?
281
283
  result << ')'
282
284
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Attributes
@@ -31,11 +33,6 @@ module Torque
31
33
  end
32
34
  end
33
35
 
34
- # You can specify the connection name for each enum
35
- def connection_specification_name
36
- return self == Enum ? 'primary' : superclass.connection_specification_name
37
- end
38
-
39
36
  # Overpass new so blank values return only nil
40
37
  def new(value)
41
38
  return Lazy.new(self, LAZY_VALUE) if value.blank?
@@ -45,9 +42,7 @@ module Torque
45
42
  # Load the list of values in a lazy way
46
43
  def values
47
44
  @values ||= self == Enum ? nil : begin
48
- conn_name = connection_specification_name
49
- conn = connection(conn_name)
50
- conn.enum_values(type_name).freeze
45
+ connection.enum_values(type_name).freeze
51
46
  end
52
47
  end
53
48
 
@@ -110,8 +105,8 @@ module Torque
110
105
  end
111
106
 
112
107
  # Get a connection based on its name
113
- def connection(name)
114
- ActiveRecord::Base.connection_handler.retrieve_connection(name)
108
+ def connection
109
+ ::ActiveRecord::Base.connection
115
110
  end
116
111
 
117
112
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Attributes
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Attributes
@@ -16,7 +18,7 @@ module Torque
16
18
  end
17
19
 
18
20
  def inspect
19
- 'nil'.freeze
21
+ 'nil'
20
22
  end
21
23
 
22
24
  def __class__
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Attributes
@@ -1,35 +1,38 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module AutosaveAssociation
4
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+
5
10
  def add_autosave_association_callbacks(reflection)
6
11
  return super unless reflection.macro.eql?(:belongs_to_many)
7
12
 
8
13
  save_method = :"autosave_associated_records_for_#{reflection.name}"
9
- define_non_cyclic_method(save_method) { save_belongs_to_many_array(reflection) }
10
-
11
- before_save(:before_save_collection_association)
12
- after_save(:after_save_collection_association) if ::ActiveRecord::Base
13
- .instance_methods.include?(:after_save_collection_association)
14
+ define_non_cyclic_method(save_method) do
15
+ save_belongs_to_many_association(reflection)
16
+ end
14
17
 
15
- before_create(save_method)
16
- before_update(save_method)
18
+ before_save(save_method)
17
19
 
18
20
  define_autosave_validation_callbacks(reflection)
19
21
  end
20
22
  end
21
23
 
22
- def save_belongs_to_many_array(reflection)
23
- save_collection_association(reflection)
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?
24
29
 
25
30
  association = association_instance_get(reflection.name)
26
- return unless association
27
-
28
- klass_fk = reflection.foreign_key
29
- acpk = reflection.active_record_primary_key
30
-
31
- records = association.target.each_with_object(klass_fk)
32
- write_attribute(acpk, records.map(&:read_attribute).compact)
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
33
36
  end
34
37
  end
35
38
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'auxiliary_statement/settings'
2
4
 
3
5
  module Torque
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  class AuxiliaryStatement
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Base
@@ -198,11 +200,18 @@ module Torque
198
200
  # belongs_to_many :tags, dependent: :nullify
199
201
  # belongs_to_many :tags, required: true, touch: true
200
202
  # belongs_to_many :tags, default: -> { Tag.default }
201
- def belongs_to_many(name, scope = nil, **options)
202
- reflection = Associations::Builder::BelongsToMany.build(self, name, scope, options)
203
+ def belongs_to_many(name, scope = nil, **options, &extension)
204
+ klass = Associations::Builder::BelongsToMany
205
+ reflection = klass.build(self, name, scope, options, &extension)
203
206
  ::ActiveRecord::Reflection.add_reflection(self, name, reflection)
204
207
  end
205
208
 
209
+ # Allow extra keyword arguments to be sent to +InsertAll+
210
+ def upsert_all(attributes, **xargs)
211
+ xargs = xargs.merge(on_duplicate: :update)
212
+ ::ActiveRecord::InsertAll.new(self, attributes, **xargs).execute
213
+ end
214
+
206
215
  protected
207
216
 
208
217
  # Allow optional select attributes to be loaded manually when they are
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Coder
@@ -8,7 +10,7 @@ module Torque
8
10
  class << self
9
11
 
10
12
  NEED_QUOTE_FOR = /[\\"(){}, \t\n\r\v\f]/m
11
- DELIMITER = ','.freeze
13
+ DELIMITER = ','
12
14
 
13
15
  # This method replace the +read_array+ method from PG gem
14
16
  # See https://github.com/ged/ruby-pg/blob/master/ext/pg_text_decoder.c#L177
@@ -32,7 +34,7 @@ module Torque
32
34
  quoted = 0
33
35
  escaped = false
34
36
  result = []
35
- part = ''
37
+ part = String.new
36
38
 
37
39
  # Always start getting the non-collection character, the second char
38
40
  stream.getc if stream.pos == 0
@@ -59,7 +61,7 @@ module Torque
59
61
 
60
62
  escaped = false
61
63
  quoted = 0
62
- part = ''
64
+ part = String.new
63
65
 
64
66
  when c == '"'
65
67
  quoted = 1
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Collector
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  include ActiveSupport::Configurable
4
6
 
7
+ # Stores a version check for compatibility purposes
8
+ AR610 = (ActiveRecord.gem_version >= Gem::Version.new('6.1.0'))
9
+
5
10
  # Use the same logger as the Active Record one
6
11
  def self.logger
7
12
  ActiveRecord::Base.logger
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  class GeometryBuilder < ActiveModel::Type::Value
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module I18n
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  InheritanceError = Class.new(ArgumentError)
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module InsertAll
6
+ attr_reader :where
7
+
8
+ def initialize(*args, where: nil, **xargs)
9
+ super(*args, **xargs)
10
+
11
+ @where = where
12
+ end
13
+ end
14
+
15
+ module InsertAll::Builder
16
+ delegate :where, to: :insert_all
17
+
18
+ def where_condition?
19
+ !where.nil?
20
+ end
21
+ end
22
+
23
+ ActiveRecord::InsertAll.prepend InsertAll
24
+ ActiveRecord::InsertAll::Builder.include InsertAll::Builder
25
+ end
26
+ end