torque-postgresql 3.4.1 → 4.0.0.rc1

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql/adapter/database_statements.rb +63 -84
  3. data/lib/torque/postgresql/adapter/oid/array.rb +17 -0
  4. data/lib/torque/postgresql/adapter/oid/line.rb +2 -6
  5. data/lib/torque/postgresql/adapter/oid/range.rb +4 -4
  6. data/lib/torque/postgresql/adapter/oid.rb +1 -23
  7. data/lib/torque/postgresql/adapter/quoting.rb +13 -7
  8. data/lib/torque/postgresql/adapter/schema_creation.rb +7 -28
  9. data/lib/torque/postgresql/adapter/schema_definitions.rb +36 -0
  10. data/lib/torque/postgresql/adapter/schema_dumper.rb +90 -34
  11. data/lib/torque/postgresql/adapter/schema_overrides.rb +45 -0
  12. data/lib/torque/postgresql/adapter/schema_statements.rb +64 -49
  13. data/lib/torque/postgresql/arel/infix_operation.rb +15 -28
  14. data/lib/torque/postgresql/arel/nodes.rb +2 -2
  15. data/lib/torque/postgresql/arel/operations.rb +7 -1
  16. data/lib/torque/postgresql/arel/visitors.rb +3 -9
  17. data/lib/torque/postgresql/associations/association_scope.rb +23 -31
  18. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +25 -0
  19. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +16 -0
  20. data/lib/torque/postgresql/attributes/builder/enum.rb +12 -9
  21. data/lib/torque/postgresql/attributes/builder/full_text_search.rb +121 -0
  22. data/lib/torque/postgresql/attributes/builder/period.rb +21 -21
  23. data/lib/torque/postgresql/attributes/builder.rb +49 -11
  24. data/lib/torque/postgresql/attributes/enum.rb +7 -7
  25. data/lib/torque/postgresql/attributes/enum_set.rb +7 -7
  26. data/lib/torque/postgresql/attributes/full_text_search.rb +19 -0
  27. data/lib/torque/postgresql/attributes/period.rb +2 -2
  28. data/lib/torque/postgresql/attributes.rb +0 -4
  29. data/lib/torque/postgresql/auxiliary_statement/recursive.rb +3 -3
  30. data/lib/torque/postgresql/base.rb +3 -10
  31. data/lib/torque/postgresql/collector.rb +1 -1
  32. data/lib/torque/postgresql/config.rb +95 -5
  33. data/lib/torque/postgresql/function.rb +61 -0
  34. data/lib/torque/postgresql/inheritance.rb +52 -36
  35. data/lib/torque/postgresql/predicate_builder/arel_attribute_handler.rb +33 -0
  36. data/lib/torque/postgresql/predicate_builder/array_handler.rb +47 -0
  37. data/lib/torque/postgresql/predicate_builder/enumerator_lazy_handler.rb +37 -0
  38. data/lib/torque/postgresql/predicate_builder/regexp_handler.rb +21 -0
  39. data/lib/torque/postgresql/predicate_builder.rb +35 -0
  40. data/lib/torque/postgresql/railtie.rb +112 -30
  41. data/lib/torque/postgresql/reflection/abstract_reflection.rb +12 -44
  42. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +4 -0
  43. data/lib/torque/postgresql/reflection/has_many_reflection.rb +4 -0
  44. data/lib/torque/postgresql/reflection/runtime_reflection.rb +1 -1
  45. data/lib/torque/postgresql/relation/inheritance.rb +4 -7
  46. data/lib/torque/postgresql/relation.rb +6 -10
  47. data/lib/torque/postgresql/schema_cache.rb +6 -12
  48. data/lib/torque/postgresql/version.rb +1 -1
  49. data/lib/torque/postgresql.rb +2 -1
  50. data/spec/initialize.rb +58 -0
  51. data/spec/mocks/cache_query.rb +21 -21
  52. data/spec/mocks/create_table.rb +6 -26
  53. data/spec/schema.rb +19 -12
  54. data/spec/spec_helper.rb +5 -1
  55. data/spec/tests/arel_spec.rb +32 -7
  56. data/spec/tests/auxiliary_statement_spec.rb +3 -3
  57. data/spec/tests/belongs_to_many_spec.rb +72 -5
  58. data/spec/tests/enum_set_spec.rb +12 -11
  59. data/spec/tests/enum_spec.rb +4 -2
  60. data/spec/tests/full_text_seach_test.rb +252 -0
  61. data/spec/tests/function_spec.rb +42 -0
  62. data/spec/tests/has_many_spec.rb +21 -8
  63. data/spec/tests/interval_spec.rb +1 -7
  64. data/spec/tests/period_spec.rb +61 -61
  65. data/spec/tests/predicate_builder_spec.rb +132 -0
  66. data/spec/tests/schema_spec.rb +2 -8
  67. data/spec/tests/table_inheritance_spec.rb +25 -26
  68. metadata +34 -39
@@ -20,15 +20,14 @@ module Torque
20
20
  klass.find(self.id)
21
21
  end
22
22
 
23
- module ClassMethods
23
+ class_methods do
24
24
  delegate :_auto_cast_attribute, :_record_class_attribute, to: ActiveRecord::Relation
25
25
 
26
26
  # Get a full list of all attributes from a model and all its dependents
27
27
  def inheritance_merged_attributes
28
28
  @inheritance_merged_attributes ||= begin
29
- list = attribute_names
30
- list += casted_dependents.values.map(&:attribute_names)
31
- list.flatten.uniq.freeze
29
+ children = casted_dependents.values.flat_map(&:attribute_names)
30
+ attribute_names.to_set.merge(children).to_a.freeze
32
31
  end
33
32
  end
34
33
 
@@ -45,11 +44,11 @@ module Torque
45
44
  end
46
45
  end
47
46
 
48
- result = types.select do
49
- |_, types| types.each_with_object(types.shift).all?(&:==)
50
- end.keys + attribute_names
47
+ result = types.filter_map do |attribute, types|
48
+ attribute if types.each_with_object(types.shift).all?(&:==)
49
+ end
51
50
 
52
- result.freeze
51
+ (attribute_names + result).freeze
53
52
  end
54
53
  end
55
54
 
@@ -111,22 +110,19 @@ module Torque
111
110
  # For all main purposes, physical inherited classes should have
112
111
  # base_class as their own
113
112
  def base_class
114
- return super unless physically_inherited?
115
- self
113
+ physically_inherited? ? self : super
116
114
  end
117
115
 
118
116
  # Primary key is one exception when getting information about the class,
119
117
  # it must returns the superclass PK
120
118
  def primary_key
121
- return super unless physically_inherited?
122
- superclass.primary_key
119
+ physically_inherited? ? superclass.primary_key : super
123
120
  end
124
121
 
125
122
  # Add an additional check to return the name of the table even when the
126
123
  # class is inherited, but only if it is a physical inheritance
127
124
  def compute_table_name
128
- return super unless physically_inherited?
129
- decorated_table_name
125
+ physically_inherited? ? decorated_table_name : super
130
126
  end
131
127
 
132
128
  # Raises an error message saying that the giver record class was not
@@ -142,37 +138,57 @@ module Torque
142
138
 
143
139
  private
144
140
 
145
- def instantiate_instance_of(klass, attributes, column_types = {}, &block)
141
+ # If the class is physically inherited, the klass needs to be properly
142
+ # changed before moving forward
143
+ def instantiate_instance_of(klass, attributes, types = {}, &block)
146
144
  return super unless klass.physically_inheritances?
147
145
 
148
- auto_cast = _auto_cast_attribute.to_s
149
- record_class = _record_class_attribute.to_s
150
- return super unless attributes.key?(record_class) &&
151
- attributes.delete(auto_cast) && attributes[record_class] != table_name
152
-
153
- klass = casted_dependents[attributes[record_class]]
154
- raise_unable_to_cast(attributes[record_class]) if klass.nil?
155
- filter_attributes_for_cast(attributes, klass)
146
+ real_class = torque_discriminate_class_for_record(klass, attributes)
147
+ return super if real_class.nil?
156
148
 
157
- super(klass, attributes, column_types, &block)
149
+ attributes, types = sanitize_attributes(real_class, attributes, types)
150
+ super(real_class, attributes, types, &block)
158
151
  end
159
152
 
160
- # Filter the record attributes to be loaded to not included those from
161
- # another inherited dependent
162
- def filter_attributes_for_cast(record, klass)
163
- new_record = record.slice(*klass.attribute_names)
164
- table = new_record[_record_class_attribute.to_s] = klass.table_name
165
-
166
- # Recover aliased attributes
167
- (klass.attribute_names - inheritance_mergeable_attributes).each do |attribute|
168
- new_record[attribute] = record["#{table}__#{attribute}"]
153
+ # Unwrap the attributes and column types from the given class when
154
+ # there are unmergeable attributes
155
+ def sanitize_attributes(real_class, attributes, types)
156
+ skip = (inheritance_merged_attributes - real_class.attribute_names).to_set
157
+ skip.merge(real_class.attribute_names - inheritance_mergeable_attributes)
158
+ return [attributes, types] if skip.empty?
159
+
160
+ dropped = 0
161
+ new_types = {}
162
+
163
+ row = attributes.instance_variable_get(:@row).dup
164
+ indexes = attributes.instance_variable_get(:@column_indexes).dup
165
+ indexes = indexes.each_with_object({}) do |(column, index), new_indexes|
166
+ attribute, prefix = column.split('__', 2).reverse
167
+ current_index = index - dropped
168
+
169
+ if prefix != table_name && skip.include?(attribute)
170
+ row.delete_at(current_index)
171
+ dropped += 1
172
+ else
173
+ new_types.merge!(types.slice(attribute))
174
+ new_types[current_index] = types[index]
175
+ new_indexes[attribute] = current_index
176
+ end
169
177
  end
170
178
 
171
- # Add any additional columns and replace the record with the new record data
172
- new_record.merge!(record.slice(*(record.keys - inheritance_merged_attributes)))
173
- record.replace(new_record)
179
+ [ActiveRecord::Result::IndexedRow.new(indexes, row), new_types]
174
180
  end
175
181
 
182
+ # Get the real class when handling physical inheritances and casting
183
+ # the record when existing properly is present
184
+ def torque_discriminate_class_for_record(klass, record)
185
+ return if record[_auto_cast_attribute.to_s] == false
186
+
187
+ embedded_type = record[_record_class_attribute.to_s]
188
+ return if embedded_type.blank? || embedded_type == table_name
189
+
190
+ casted_dependents[embedded_type] || raise_unable_to_cast(embedded_type)
191
+ end
176
192
  end
177
193
  end
178
194
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module PredicateBuilder
6
+ class ArelAttributeHandler
7
+ # Shortcut
8
+ def self.call(*args)
9
+ new.call(*args)
10
+ end
11
+
12
+ def initialize(*)
13
+ # There is no need to use or save the predicate builder here
14
+ end
15
+
16
+ def call(attribute, value)
17
+ case
18
+ when array_typed?(attribute) && array_typed?(value) then attribute.overlaps(value)
19
+ when array_typed?(attribute) then value.eq(FN.any(attribute))
20
+ when array_typed?(value) then attribute.eq(FN.any(value))
21
+ else attribute.eq(value)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def array_typed?(attribute)
28
+ attribute.able_to_type_cast? && attribute.type_caster.is_a?(ARRAY_OID)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module PredicateBuilder
6
+ module ArrayHandler
7
+ def call(attribute, value)
8
+ return super unless array_attribute?(attribute) &&
9
+ PostgreSQL.config.predicate_builder.handle_array_attributes
10
+
11
+ call_for_array(attribute, value)
12
+ end
13
+
14
+ def call_for_array(attribute, value)
15
+ if !value.is_a?(::Array)
16
+ call_with_value(attribute, value)
17
+ elsif value.any?
18
+ call_with_array(attribute, value)
19
+ else
20
+ call_with_empty(attribute)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def call_with_value(attribute, value)
27
+ FN.infix(:"=", FN.bind_with(attribute, value), FN.any(attribute))
28
+ end
29
+
30
+ def call_with_array(attribute, value)
31
+ attribute.overlaps(FN.bind_with(attribute, value))
32
+ end
33
+
34
+ def call_with_empty(attribute)
35
+ FN.cardinality(attribute).eq(0)
36
+ end
37
+
38
+ def array_attribute?(attribute)
39
+ attribute.type_caster.is_a?(ARRAY_OID)
40
+ end
41
+ end
42
+
43
+ ::ActiveRecord::PredicateBuilder::ArrayHandler.prepend(ArrayHandler)
44
+ ::ActiveRecord::PredicateBuilder::BasicObjectHandler.prepend(ArrayHandler)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module PredicateBuilder
6
+ class EnumeratorLazyHandler < ::ActiveRecord::PredicateBuilder::ArrayHandler
7
+ Timeout = Class.new(::Timeout::Error)
8
+
9
+ def call(attribute, value)
10
+ with_timeout do
11
+ super(attribute, limit.nil? ? value.force : value.first(limit))
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def with_timeout
18
+ return yield if timeout.nil?
19
+
20
+ begin
21
+ ::Timeout.timeout(timeout) { yield }
22
+ rescue ::Timeout::Error
23
+ raise Timeout, "Lazy predicate builder timed out after #{timeout} seconds"
24
+ end
25
+ end
26
+
27
+ def timeout
28
+ PostgreSQL.config.predicate_builder.lazy_timeout
29
+ end
30
+
31
+ def limit
32
+ PostgreSQL.config.predicate_builder.lazy_limit
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module PredicateBuilder
6
+ class RegexpHandler
7
+ def initialize(predicate_builder)
8
+ @predicate_builder = predicate_builder
9
+ end
10
+
11
+ def call(attribute, value)
12
+ operator = value.casefold? ? :"~*" : :"~"
13
+ FN.infix(operator, attribute, FN.bind_with(attribute, value.source))
14
+ end
15
+
16
+ private
17
+ attr_reader :predicate_builder
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'predicate_builder/array_handler'
4
+
5
+ require_relative 'predicate_builder/regexp_handler'
6
+ require_relative 'predicate_builder/arel_attribute_handler'
7
+ require_relative 'predicate_builder/enumerator_lazy_handler'
8
+
9
+ module Torque
10
+ module PostgreSQL
11
+ module PredicateBuilder
12
+ ARRAY_OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array
13
+
14
+ def initialize(*)
15
+ super
16
+
17
+ handlers = Array.wrap(PostgreSQL.config.predicate_builder.enabled).inquiry
18
+
19
+ if handlers.regexp?
20
+ register_handler(Regexp, RegexpHandler.new(self))
21
+ end
22
+
23
+ if handlers.enumerator_lazy?
24
+ register_handler(Enumerator::Lazy, EnumeratorLazyHandler.new(self))
25
+ end
26
+
27
+ if handlers.arel_attribute?
28
+ register_handler(::Arel::Attributes::Attribute, ArelAttributeHandler.new(self))
29
+ end
30
+ end
31
+ end
32
+
33
+ ::ActiveRecord::PredicateBuilder.prepend(PredicateBuilder)
34
+ end
35
+ end
@@ -4,41 +4,123 @@ module Torque
4
4
  module PostgreSQL
5
5
  # = Torque PostgreSQL Railtie
6
6
  class Railtie < Rails::Railtie # :nodoc:
7
-
8
7
  # Get information from the running rails app
9
8
  initializer 'torque-postgresql' do |app|
10
- torque_config = Torque::PostgreSQL.config
11
- torque_config.eager_load = app.config.eager_load
12
-
13
- # Include enum on ActiveRecord::Base so it can have the correct enum
14
- # initializer
15
- Torque::PostgreSQL::Attributes::Enum.include_on(ActiveRecord::Base)
16
- Torque::PostgreSQL::Attributes::EnumSet.include_on(ActiveRecord::Base)
17
- Torque::PostgreSQL::Attributes::Period.include_on(ActiveRecord::Base)
18
-
19
- # Setup belongs_to_many association
20
- ActiveRecord::Base.belongs_to_many_required_by_default = torque_config.associations
21
- .belongs_to_many_required_by_default
22
-
23
- # Define a method to find enumaerators based on the namespace
24
- torque_config.enum.namespace.define_singleton_method(:const_missing) do |name|
25
- Torque::PostgreSQL::Attributes::Enum.lookup(name)
26
- end
9
+ ActiveSupport.on_load(:active_record_postgresqladapter) do
10
+ ActiveSupport.on_load(:active_record) do
11
+ torque_config = Torque::PostgreSQL.config
12
+ torque_config.eager_load = app.config.eager_load
27
13
 
28
- # Define a helper method to get a sample value
29
- torque_config.enum.namespace.define_singleton_method(:sample) do |name|
30
- Torque::PostgreSQL::Attributes::Enum.lookup(name).sample
31
- end
14
+ # TODO: Only load files that have their features enabled, like CTE
15
+
16
+ ar_type = ActiveRecord::Type
17
+
18
+ # Setup belongs_to_many association
19
+ ActiveRecord::Base.belongs_to_many_required_by_default =
20
+ torque_config.associations.belongs_to_many_required_by_default
21
+
22
+ ## Schemas Enabled Setup
23
+ if (config = torque_config.schemas).enabled
24
+ require_relative 'adapter/schema_overrides'
25
+ end
26
+
27
+ ## CTE Enabled Setup
28
+ if (config = torque_config.auxiliary_statement).enabled
29
+ require_relative 'auxiliary_statement'
30
+ require_relative 'relation/auxiliary_statement'
31
+ Relation.include(Relation::AuxiliaryStatement)
32
+
33
+ # Define the exposed constant for both types of auxiliary statements
34
+ if config.exposed_class.present?
35
+ *ns, name = config.exposed_class.split('::')
36
+ base = ns.present? ? ::Object.const_get(ns.join('::')) : ::Object
37
+ base.const_set(name, AuxiliaryStatement)
38
+
39
+ *ns, name = config.exposed_recursive_class.split('::')
40
+ base = ns.present? ? ::Object.const_get(ns.join('::')) : ::Object
41
+ base.const_set(name, AuxiliaryStatement::Recursive)
42
+ end
43
+ end
44
+
45
+ ## Enum Enabled Setup
46
+ if (config = torque_config.enum).enabled
47
+ require_relative 'adapter/oid/enum'
48
+ require_relative 'adapter/oid/enum_set'
49
+
50
+ require_relative 'attributes/enum'
51
+ require_relative 'attributes/enum_set'
52
+
53
+ Attributes::Enum.include_on(ActiveRecord::Base)
54
+ Attributes::EnumSet.include_on(ActiveRecord::Base)
55
+
56
+ ar_type.register(:enum, Adapter::OID::Enum, adapter: :postgresql)
57
+ ar_type.register(:enum_set, Adapter::OID::EnumSet, adapter: :postgresql)
58
+
59
+ if config.namespace == false
60
+ # TODO: Allow enum classes to exist without a namespace
61
+ config.namespace = PostgreSQL.const_set('Enum', Module.new)
62
+ else
63
+ config.namespace ||= ::Object.const_set('Enum', Module.new)
64
+
65
+ # Define a method to find enumerators based on the namespace
66
+ config.namespace.define_singleton_method(:const_missing) do |name|
67
+ Attributes::Enum.lookup(name)
68
+ end
69
+
70
+ # Define a helper method to get a sample value
71
+ config.namespace.define_singleton_method(:sample) do |name|
72
+ Attributes::Enum.lookup(name).sample
73
+ end
74
+ end
75
+ end
76
+
77
+ ## Geometry Enabled Setup
78
+ if (config = torque_config.geometry).enabled
79
+ require_relative 'adapter/oid/box'
80
+ require_relative 'adapter/oid/circle'
81
+ require_relative 'adapter/oid/line'
82
+ require_relative 'adapter/oid/segment'
83
+
84
+ ar_type.register(:box, Adapter::OID::Box, adapter: :postgresql)
85
+ ar_type.register(:circle, Adapter::OID::Circle, adapter: :postgresql)
86
+ ar_type.register(:line, Adapter::OID::Line, adapter: :postgresql)
87
+ ar_type.register(:segment, Adapter::OID::Segment, adapter: :postgresql)
88
+ end
89
+
90
+ ## Period Enabled Setup
91
+ if (config = torque_config.period).enabled
92
+ require_relative 'attributes/period'
93
+ Attributes::Period.include_on(ActiveRecord::Base)
94
+ end
95
+
96
+ ## Interval Enabled Setup
97
+ if (config = torque_config.interval).enabled
98
+ require_relative 'adapter/oid/interval'
99
+ ar_type.register(:interval, Adapter::OID::Interval, adapter: :postgresql)
100
+ end
101
+
102
+ ## Full Text Search Enabled Setup
103
+ if (config = torque_config.full_text_search).enabled
104
+ require_relative 'attributes/full_text_search'
105
+ Attributes::FullTextSearch.include_on(ActiveRecord::Base)
106
+ end
107
+
108
+ ## Arel Setup
109
+ PostgreSQL::Arel.build_operations(torque_config.arel.infix_operators)
110
+ if (mod = torque_config.arel.expose_function_helper_on&.to_s)
111
+ parent, _, name = mod.rpartition('::')
112
+ parent.constantize.const_set(name, PostgreSQL::FN)
113
+ end
32
114
 
33
- # Define the exposed constant for both types of auxiliary statements
34
- if torque_config.auxiliary_statement.exposed_class.present?
35
- *ns, name = torque_config.auxiliary_statement.exposed_class.split('::')
36
- base = ns.present? ? Object.const_get(ns.join('::')) : Object
37
- base.const_set(name, Torque::PostgreSQL::AuxiliaryStatement)
115
+ # Make sure to load all the types that are handled by this gem on
116
+ # each individual PG connection
117
+ adapter = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
118
+ ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
119
+ next unless pool.db_config.adapter_class.is_a?(adapter)
38
120
 
39
- *ns, name = torque_config.auxiliary_statement.exposed_recursive_class.split('::')
40
- base = ns.present? ? Object.const_get(ns.join('::')) : Object
41
- base.const_set(name, Torque::PostgreSQL::AuxiliaryStatement::Recursive)
121
+ pool.with_connection { |conn| conn.torque_load_additional_types }
122
+ end
123
+ end
42
124
  end
43
125
  end
44
126
  end
@@ -5,29 +5,25 @@ module Torque
5
5
  module Reflection
6
6
  module AbstractReflection
7
7
  AREL_ATTR = ::Arel::Attributes::Attribute
8
-
9
- ARR_NO_CAST = 'bigint'
10
- ARR_CAST = 'bigint[]'
8
+ AREL_NODE = ::Arel::Nodes::Node
11
9
 
12
10
  # Check if the foreign key actually exists
13
11
  def connected_through_array?
14
12
  false
15
13
  end
16
14
 
17
- # Fix where the join_scope method is the one now responsible for
18
- # building the join condition
15
+ # Connection through an array-like attribute is more complex then just
16
+ # a simple eq. This needs to go through the channel that handles larger
17
+ # situations
19
18
  def join_scope(table, foreign_table, foreign_klass)
20
19
  return super unless connected_through_array?
21
20
 
22
- predicate_builder = predicate_builder(table)
21
+ table_md = ActiveRecord::TableMetadata.new(klass, table)
22
+ predicate_builder = klass.predicate_builder.with(table_md)
23
23
  scope_chain_items = join_scopes(table, predicate_builder)
24
24
  klass_scope = klass_join_scope(table, predicate_builder)
25
25
 
26
26
  klass_scope.where!(build_id_constraint_between(table, foreign_table))
27
- klass_scope.where!(type => foreign_klass.polymorphic_name) if type
28
- klass_scope.where!(klass.send(:type_condition, table)) \
29
- if klass.finder_needs_type_condition?
30
-
31
27
  scope_chain_items.inject(klass_scope, &:merge!)
32
28
  end
33
29
 
@@ -40,43 +36,15 @@ module Torque
40
36
  result
41
37
  end
42
38
 
43
- # Build the id constraint checking if both types are perfect matching.
44
- # The klass attribute (left side) will always be a column attribute
45
- def build_id_constraint(klass_attr, source_attr)
46
- return klass_attr.eq(source_attr) unless connected_through_array?
47
-
48
- # Klass and key are associated with the reflection Class
49
- klass_type = klass.columns_hash[join_keys.key.to_s]
50
-
51
- # Apply an ANY operation which checks if the single value on the left
52
- # side exists in the array on the right side
53
- if source_attr.is_a?(AREL_ATTR)
54
- any_value = [klass_attr, source_attr]
55
- any_value.reverse! if klass_type.try(:array?)
56
- return any_value.shift.eq(::Arel::Nodes::NamedFunction.new('ANY', any_value))
57
- end
58
-
59
- # If the left side is not an array, just use the IN condition
60
- return klass_attr.in(source_attr) unless klass_type.try(:array)
61
-
62
- # Build the overlap condition (array && array) ensuring that the right
63
- # side has the same type as the left side
64
- source_attr = ::Arel::Nodes.build_quoted(Array.wrap(source_attr))
65
- klass_attr.overlaps(source_attr.cast(klass_type.sql_type_metadata.sql_type))
66
- end
67
-
68
- # TODO: Deprecate this method
69
- def join_keys
70
- OpenStruct.new(key: join_primary_key, foreign_key: join_foreign_key)
71
- end
72
-
73
39
  private
74
40
 
41
+ # This one is a lot simpler, now that we have a predicate builder that
42
+ # knows exactly what to do with 2 array-like attributes
75
43
  def build_id_constraint_between(table, foreign_table)
76
- klass_attr = table[join_primary_key]
77
- source_attr = foreign_table[join_foreign_key]
78
-
79
- build_id_constraint(klass_attr, source_attr)
44
+ PredicateBuilder::ArelAttributeHandler.call(
45
+ table[join_primary_key],
46
+ foreign_table[join_foreign_key],
47
+ )
80
48
  end
81
49
  end
82
50
 
@@ -44,6 +44,10 @@ module Torque
44
44
  foreign_key
45
45
  end
46
46
 
47
+ def array_attribute
48
+ active_record.arel_table[foreign_key]
49
+ end
50
+
47
51
  private
48
52
 
49
53
  def derive_primary_key
@@ -7,6 +7,10 @@ module Torque
7
7
  def connected_through_array?
8
8
  options[:array]
9
9
  end
10
+
11
+ def array_attribute
12
+ klass.arel_table[foreign_key]
13
+ end
10
14
  end
11
15
 
12
16
  ::ActiveRecord::Reflection::HasManyReflection.include(HasManyReflection)
@@ -5,7 +5,7 @@ module Torque
5
5
  module Reflection
6
6
  module RuntimeReflection
7
7
  delegate :klass, :active_record, :connected_through_array?, :macro, :name,
8
- :build_id_constraint, to: :@reflection
8
+ :array_attribute, to: :@reflection
9
9
  end
10
10
 
11
11
  ::ActiveRecord::Reflection::RuntimeReflection.include(RuntimeReflection)
@@ -5,8 +5,6 @@ module Torque
5
5
  module Relation
6
6
  module Inheritance
7
7
 
8
- # REGCLASS = ::Arel.sql('tableoid').cast('regclass')
9
-
10
8
  # :nodoc:
11
9
  def cast_records_value; get_value(:cast_records); end
12
10
  # :nodoc:
@@ -46,7 +44,7 @@ module Torque
46
44
 
47
45
  # Like #cast_records, but modifies relation in place
48
46
  def cast_records!(*types, **options)
49
- where!(regclass.cast(:varchar).in(types.map(&:table_name))) if options[:filter]
47
+ where!(regclass.pg_cast(:varchar).in(types.map(&:table_name))) if options[:filter]
50
48
  self.select_extra_values += [regclass.as(_record_class_attribute.to_s)]
51
49
  self.cast_records_value = (types.present? ? types : model.casted_dependents.values)
52
50
  self
@@ -73,8 +71,7 @@ module Torque
73
71
  next arel_tables.first[column] if arel_tables.size == 1
74
72
 
75
73
  if mergeable.include?(column)
76
- list = arel_tables.each_with_object(column).map(&:[])
77
- ::Arel::Nodes::NamedFunction.new('COALESCE', list).as(column)
74
+ FN.coalesce(*arel_tables.each_with_object(column).map(&:[])).as(column)
78
75
  else
79
76
  arel_tables.map { |table| table[column].as("#{table.left.name}__#{column}") }
80
77
  end
@@ -105,12 +102,12 @@ module Torque
105
102
  end
106
103
 
107
104
  def build_auto_caster_marker(arel, types)
108
- attribute = regclass.cast(:varchar).in(types.map(&:table_name))
105
+ attribute = regclass.pg_cast(:varchar).in(types.map(&:table_name))
109
106
  attribute.as(self.class._auto_cast_attribute.to_s)
110
107
  end
111
108
 
112
109
  def regclass
113
- arel_table['tableoid'].cast(:regclass)
110
+ arel_table['tableoid'].pg_cast(:regclass)
114
111
  end
115
112
 
116
113
  end