torque-postgresql 2.0.1 → 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql/adapter.rb +7 -0
  3. data/lib/torque/postgresql/adapter/database_statements.rb +2 -0
  4. data/lib/torque/postgresql/adapter/oid.rb +3 -1
  5. data/lib/torque/postgresql/adapter/oid/box.rb +2 -0
  6. data/lib/torque/postgresql/adapter/oid/circle.rb +2 -0
  7. data/lib/torque/postgresql/adapter/oid/enum.rb +2 -0
  8. data/lib/torque/postgresql/adapter/oid/enum_set.rb +2 -0
  9. data/lib/torque/postgresql/adapter/oid/interval.rb +2 -0
  10. data/lib/torque/postgresql/adapter/oid/line.rb +2 -0
  11. data/lib/torque/postgresql/adapter/oid/range.rb +2 -0
  12. data/lib/torque/postgresql/adapter/oid/segment.rb +2 -0
  13. data/lib/torque/postgresql/adapter/quoting.rb +2 -0
  14. data/lib/torque/postgresql/adapter/schema_creation.rb +8 -1
  15. data/lib/torque/postgresql/adapter/schema_definitions.rb +2 -0
  16. data/lib/torque/postgresql/adapter/schema_dumper.rb +11 -2
  17. data/lib/torque/postgresql/adapter/schema_statements.rb +2 -0
  18. data/lib/torque/postgresql/arel/infix_operation.rb +5 -1
  19. data/lib/torque/postgresql/arel/join_source.rb +2 -0
  20. data/lib/torque/postgresql/arel/nodes.rb +2 -0
  21. data/lib/torque/postgresql/arel/operations.rb +2 -0
  22. data/lib/torque/postgresql/arel/select_manager.rb +2 -0
  23. data/lib/torque/postgresql/arel/visitors.rb +6 -3
  24. data/lib/torque/postgresql/associations/association.rb +14 -3
  25. data/lib/torque/postgresql/associations/association_scope.rb +2 -0
  26. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +164 -47
  27. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +8 -5
  28. data/lib/torque/postgresql/associations/builder/has_many.rb +2 -0
  29. data/lib/torque/postgresql/associations/preloader/association.rb +30 -1
  30. data/lib/torque/postgresql/attributes/builder.rb +3 -1
  31. data/lib/torque/postgresql/attributes/builder/enum.rb +5 -3
  32. data/lib/torque/postgresql/attributes/builder/period.rb +6 -4
  33. data/lib/torque/postgresql/attributes/enum.rb +5 -10
  34. data/lib/torque/postgresql/attributes/enum_set.rb +2 -0
  35. data/lib/torque/postgresql/attributes/lazy.rb +3 -1
  36. data/lib/torque/postgresql/attributes/period.rb +2 -0
  37. data/lib/torque/postgresql/autosave_association.rb +19 -16
  38. data/lib/torque/postgresql/auxiliary_statement.rb +2 -0
  39. data/lib/torque/postgresql/auxiliary_statement/settings.rb +2 -0
  40. data/lib/torque/postgresql/base.rb +5 -2
  41. data/lib/torque/postgresql/coder.rb +5 -3
  42. data/lib/torque/postgresql/collector.rb +2 -0
  43. data/lib/torque/postgresql/config.rb +5 -0
  44. data/lib/torque/postgresql/geometry_builder.rb +2 -0
  45. data/lib/torque/postgresql/i18n.rb +2 -0
  46. data/lib/torque/postgresql/inheritance.rb +2 -0
  47. data/lib/torque/postgresql/migration/command_recorder.rb +2 -0
  48. data/lib/torque/postgresql/railtie.rb +2 -0
  49. data/lib/torque/postgresql/reflection.rb +2 -0
  50. data/lib/torque/postgresql/reflection/abstract_reflection.rb +13 -28
  51. data/lib/torque/postgresql/reflection/association_reflection.rb +24 -0
  52. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +18 -4
  53. data/lib/torque/postgresql/reflection/has_many_reflection.rb +2 -0
  54. data/lib/torque/postgresql/reflection/runtime_reflection.rb +2 -0
  55. data/lib/torque/postgresql/reflection/through_reflection.rb +2 -0
  56. data/lib/torque/postgresql/relation.rb +15 -11
  57. data/lib/torque/postgresql/relation/auxiliary_statement.rb +6 -1
  58. data/lib/torque/postgresql/relation/distinct_on.rb +2 -0
  59. data/lib/torque/postgresql/relation/inheritance.rb +2 -0
  60. data/lib/torque/postgresql/relation/merger.rb +2 -0
  61. data/lib/torque/postgresql/schema_cache.rb +2 -0
  62. data/lib/torque/postgresql/version.rb +3 -1
  63. data/spec/schema.rb +3 -2
  64. data/spec/tests/arel_spec.rb +3 -1
  65. data/spec/tests/belongs_to_many_spec.rb +104 -12
  66. data/spec/tests/enum_set_spec.rb +1 -1
  67. data/spec/tests/has_many_spec.rb +25 -1
  68. data/spec/tests/table_inheritance_spec.rb +1 -1
  69. metadata +7 -7
@@ -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,8 +200,9 @@ 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
 
@@ -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)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Migration
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  # = Torque PostgreSQL Railtie
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'reflection/abstract_reflection'
2
4
  require_relative 'reflection/association_reflection'
3
5
  require_relative 'reflection/belongs_to_many_reflection'
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Torque
2
4
  module PostgreSQL
3
5
  module Reflection
4
6
  module AbstractReflection
5
7
  AREL_ATTR = ::Arel::Attributes::Attribute
6
8
 
7
- ARR_NO_CAST = 'bigint'.freeze
8
- ARR_CAST = 'bigint[]'.freeze
9
+ ARR_NO_CAST = 'bigint'
10
+ ARR_CAST = 'bigint[]'
9
11
 
10
12
  # Check if the foreign key actually exists
11
13
  def connected_through_array?
@@ -68,11 +70,18 @@ module Torque
68
70
  klass_attr.overlaps(source_attr)
69
71
  end
70
72
 
73
+ if PostgreSQL::AR610
74
+ # TODO: Deprecate this method
75
+ def join_keys
76
+ OpenStruct.new(key: join_primary_key, foreign_key: join_foreign_key)
77
+ end
78
+ end
79
+
71
80
  private
72
81
 
73
82
  def build_id_constraint_between(table, foreign_table)
74
- klass_attr = table[join_keys.key.to_s]
75
- source_attr = foreign_table[join_keys.foreign_key.to_s]
83
+ klass_attr = table[join_primary_key]
84
+ source_attr = foreign_table[join_foreign_key]
76
85
 
77
86
  build_id_constraint(klass_attr, source_attr)
78
87
  end
@@ -94,30 +103,6 @@ module Torque
94
103
 
95
104
  ::Arel::Nodes::NamedFunction.new('ANY', [source_attr])
96
105
  end
97
-
98
- # returns either +nil+ or the inverse association name that it finds.
99
- def automatic_inverse_of
100
- return super unless connected_through_array?
101
-
102
- if can_find_inverse_of_automatically?(self)
103
- inverse_name = options[:as] || active_record.name.demodulize
104
- inverse_name = ActiveSupport::Inflector.underscore(inverse_name)
105
- inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
106
- inverse_name = inverse_name.to_sym
107
-
108
- begin
109
- reflection = klass._reflect_on_association(inverse_name)
110
- rescue NameError
111
- # Give up: we couldn't compute the klass type so we won't be able
112
- # to find any associations either.
113
- reflection = false
114
- end
115
-
116
- return inverse_name if reflection.connected_through_array? &&
117
- valid_inverse_reflection?(reflection)
118
- end
119
- end
120
-
121
106
  end
122
107
 
123
108
  ::ActiveRecord::Reflection::AbstractReflection.prepend(AbstractReflection)