torque-postgresql 2.3.0 → 2.4.1

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: c2a382953b6a3c1d87e05837454cdf523f4be237fdd66c85f0b155278eafa203
4
- data.tar.gz: e72effb133a0ae945b85b1d46de72b16a098eb1d469f3720926b0267e7f3c11b
3
+ metadata.gz: 0e36ced2b0986c132e095bbc1799e4fab1f7bd6f320e83d74f8387c9ad23b798
4
+ data.tar.gz: 4807f5dc5146805a349a5495829e0b4aac83df495982b08a35c9c593d8323d42
5
5
  SHA512:
6
- metadata.gz: 554eb1ae3c30abd27acbbd9ce7e2f78d37e42c730ed811b5d617209cbb12a2c1bd34163f63ab25273f542e1cb6bfc36ee6d64484fd66f8a5b489e4aaea6ae628
7
- data.tar.gz: d018ee26970f64a83e552f1929e55e281235054064771a851e5b4fc714e69a95e6247e95338deffe8b1732d6ae2dad1bc2b5602146a1c4fb4d763ad19fc13c97
6
+ metadata.gz: 84cc6db0f08fcb8c6d0616784382f77ebca1440a396df0a06efa7c2b0e1e0ac6685d54c4b4f6ac5163a7ba42cbeb4b2f81fcb197e4836d894819e0f76d9545c4
7
+ data.tar.gz: 3624ca914923f61c0bf23a7eed17c8c7d71270b8d0853d3d1445b7a1e513a97d066d8a7041111e504a7b1e6aaaab391cc2502d8fb42e69052c46b97377f4dfb4
@@ -185,21 +185,20 @@ module Torque
185
185
 
186
186
  # Get the list of columns, and their definition, but only from the
187
187
  # actual table, does not include columns that comes from inherited table
188
- def column_definitions(table_name) # :nodoc:
189
- local_condition = 'AND a.attislocal IS TRUE' if @_dump_mode
188
+ def column_definitions(table_name)
189
+ local = 'AND a.attislocal' if @_dump_mode
190
+
190
191
  query(<<-SQL, 'SCHEMA')
191
- SELECT a.attname, format_type(a.atttypid, a.atttypmod),
192
- pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
193
- (SELECT c.collname FROM pg_collation c, pg_type t
194
- WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation),
195
- col_description(a.attrelid, a.attnum) AS comment
196
- FROM pg_attribute a
197
- LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
198
- WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
199
- AND a.attnum > 0
200
- AND a.attisdropped IS FALSE
201
- #{local_condition}
202
- ORDER BY a.attnum
192
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
193
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
194
+ c.collname, col_description(a.attrelid, a.attnum) AS comment
195
+ FROM pg_attribute a
196
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
197
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
198
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
199
+ WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
200
+ AND a.attnum > 0 AND NOT a.attisdropped #{local}
201
+ ORDER BY a.attnum
203
202
  SQL
204
203
  end
205
204
 
@@ -127,6 +127,11 @@ module Torque
127
127
 
128
128
  private
129
129
 
130
+ # Remove the schema from the sequence name
131
+ def sequence_name_from_parts(table_name, column_name, suffix)
132
+ super(table_name.split('.').last, column_name, suffix)
133
+ end
134
+
130
135
  def quote_enum_values(name, values, options)
131
136
  prefix = options[:prefix]
132
137
  prefix = name if prefix === true
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ class AuxiliaryStatement
6
+ class Recursive < AuxiliaryStatement
7
+ # Setup any additional option in the recursive mode
8
+ def initialize(*, **options)
9
+ super
10
+
11
+ @connect = options[:connect]&.to_a&.first
12
+ @union_all = options[:union_all]
13
+ @sub_query = options[:sub_query]
14
+
15
+ if options.key?(:with_depth)
16
+ @depth = options[:with_depth].values_at(:name, :start, :as)
17
+ @depth[0] ||= 'depth'
18
+ end
19
+
20
+ if options.key?(:with_path)
21
+ @path = options[:with_path].values_at(:name, :source, :as)
22
+ @path[0] ||= 'path'
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ # Build the string or arel query
29
+ def build_query(base)
30
+ # Expose columns and get the list of the ones for select
31
+ columns = expose_columns(base, @query.try(:arel_table))
32
+ sub_columns = columns.dup
33
+ type = @union_all.present? ? 'all' : ''
34
+
35
+ # Build any extra columns that are dynamic and from the recursion
36
+ extra_columns(base, columns, sub_columns)
37
+
38
+ # Prepare the query depending on its type
39
+ if @query.is_a?(String) && @sub_query.is_a?(String)
40
+ args = @args.each_with_object({}) { |h, (k, v)| h[k] = base.connection.quote(v) }
41
+ ::Arel.sql("(#{@query} UNION #{type.upcase} #{@sub_query})" % args)
42
+ elsif relation_query?(@query)
43
+ @query = @query.where(@where) if @where.present?
44
+ @bound_attributes.concat(@query.send(:bound_attributes))
45
+
46
+ if relation_query?(@sub_query)
47
+ @bound_attributes.concat(@sub_query.send(:bound_attributes))
48
+
49
+ sub_query = @sub_query.select(*sub_columns).arel
50
+ sub_query.from([@sub_query.arel_table, table])
51
+ else
52
+ sub_query = ::Arel.sql(@sub_query)
53
+ end
54
+
55
+ @query.select(*columns).arel.union(type, sub_query)
56
+ else
57
+ raise ArgumentError, <<-MSG.squish
58
+ Only String and ActiveRecord::Base objects are accepted as query and sub query
59
+ objects, #{@query.class.name} given for #{self.class.name}.
60
+ MSG
61
+ end
62
+ end
63
+
64
+ # Setup the statement using the class configuration
65
+ def prepare(base, settings)
66
+ super
67
+
68
+ prepare_sub_query(base, settings)
69
+ end
70
+
71
+ # Make sure that both parts of the union are ready
72
+ def prepare_sub_query(base, settings)
73
+ @union_all = settings.union_all if @union_all.nil?
74
+ @sub_query ||= settings.sub_query
75
+ @depth ||= settings.depth
76
+ @path ||= settings.path
77
+
78
+ # Collect the connection
79
+ @connect ||= settings.connect || begin
80
+ key = base.primary_key
81
+ [key.to_sym, :"parent_#{key}"] unless key.nil?
82
+ end
83
+
84
+ raise ArgumentError, <<-MSG.squish if @sub_query.nil? && @query.is_a?(String)
85
+ Unable to generate sub query from a string query. Please provide a `sub_query`
86
+ property on the "#{table_name}" settings.
87
+ MSG
88
+
89
+ if @sub_query.nil?
90
+ raise ArgumentError, <<-MSG.squish if @connect.blank?
91
+ Unable to generate sub query without setting up a proper way to connect it
92
+ with the main query. Please provide a `connect` property on the "#{table_name}"
93
+ settings.
94
+ MSG
95
+
96
+ left, right = @connect.map(&:to_s)
97
+ condition = @query.arel_table[right].eq(table[left])
98
+
99
+ if @query.where_values_hash.key?(right)
100
+ @sub_query = @query.unscope(where: right.to_sym).where(condition)
101
+ else
102
+ @sub_query = @query.where(condition)
103
+ @query = @query.where(right => nil)
104
+ end
105
+ elsif @sub_query.respond_to?(:call)
106
+ # Call a proc to get the real sub query
107
+ call_args = @sub_query.try(:arity) === 0 ? [] : [OpenStruct.new(@args)]
108
+ @sub_query = @sub_query.call(*call_args)
109
+ end
110
+ end
111
+
112
+ # Add depth and path if they were defined in settings
113
+ def extra_columns(base, columns, sub_columns)
114
+ return if @query.is_a?(String) || @sub_query.is_a?(String)
115
+
116
+ # Add the connect attribute to the query
117
+ if defined?(@connect)
118
+ columns.unshift(@query.arel_table[@connect[0]])
119
+ sub_columns.unshift(@sub_query.arel_table[@connect[0]])
120
+ end
121
+
122
+ # Build a column to represent the depth of the recursion
123
+ if @depth.present?
124
+ name, start, as = @depth
125
+ col = table[name]
126
+ base.select_extra_values += [col.as(as)] unless as.nil?
127
+
128
+ columns << ::Arel.sql(start.to_s).as(name)
129
+ sub_columns << (col + ::Arel.sql('1')).as(name)
130
+ end
131
+
132
+ # Build a column to represent the path of the record access
133
+ if @path.present?
134
+ name, source, as = @path
135
+ source = @query.arel_table[source || @connect[0]]
136
+
137
+ col = table[name]
138
+ base.select_extra_values += [col.as(as)] unless as.nil?
139
+ parts = [col, source.cast(:varchar)]
140
+
141
+ columns << ::Arel.array([source]).cast(:varchar, true).as(name)
142
+ sub_columns << ::Arel::Nodes::NamedFunction.new('array_append', parts).as(name)
143
+ end
144
+ end
145
+
146
+ end
147
+ end
148
+ end
149
+ end
@@ -4,9 +4,9 @@ module Torque
4
4
  module PostgreSQL
5
5
  class AuxiliaryStatement
6
6
  class Settings < Collector.new(:attributes, :join, :join_type, :query, :requires,
7
- :polymorphic, :through)
7
+ :polymorphic, :through, :union_all, :connect)
8
8
 
9
- attr_reader :base, :source
9
+ attr_reader :base, :source, :depth, :path
10
10
  alias_method :select, :attributes
11
11
  alias_method :cte, :source
12
12
 
@@ -14,9 +14,10 @@ module Torque
14
14
  delegate :table, :table_name, to: :@source
15
15
  delegate :sql, to: ::Arel
16
16
 
17
- def initialize(base, source)
17
+ def initialize(base, source, recursive = false)
18
18
  @base = base
19
19
  @source = source
20
+ @recursive = recursive
20
21
  end
21
22
 
22
23
  def base_name
@@ -27,6 +28,39 @@ module Torque
27
28
  @base.arel_table
28
29
  end
29
30
 
31
+
32
+ def recursive?
33
+ @recursive
34
+ end
35
+
36
+ def depth?
37
+ defined?(@depth)
38
+ end
39
+
40
+ def path?
41
+ defined?(@path)
42
+ end
43
+
44
+ # Add an attribute to the result showing the depth of each iteration
45
+ def with_depth(name = 'depth', start: 0, as: nil)
46
+ @depth = [name.to_s, start, as&.to_s] if recursive?
47
+ end
48
+
49
+ # Add an attribute to the result showing the path of each record
50
+ def with_path(name = 'path', source: nil, as: nil)
51
+ @path = [name.to_s, source&.to_s, as&.to_s] if recursive?
52
+ end
53
+
54
+ # Set recursive operation to use union all
55
+ def union_all!
56
+ @union_all = true if recursive?
57
+ end
58
+
59
+ # Add both depth and path to the result
60
+ def with_depth_and_path
61
+ with_depth && with_path
62
+ end
63
+
30
64
  # Get the arel version of the table set on the query
31
65
  def query_table
32
66
  raise StandardError, 'The query is not defined yet' if query.nil?
@@ -41,36 +75,55 @@ module Torque
41
75
 
42
76
  alias column col
43
77
 
44
- # There are two ways of setting the query:
78
+ # There are three ways of setting the query:
45
79
  # - A simple relation based on a Model
46
80
  # - A Arel-based select manager
47
- # - A string or a proc that requires the table name as first argument
81
+ # - A string or a proc
48
82
  def query(value = nil, command = nil)
49
83
  return @query if value.nil?
50
- return @query = value if relation_query?(value)
51
84
 
52
- if value.is_a?(::Arel::SelectManager)
53
- @query = value
54
- @query_table = value.source.left.name
55
- return
56
- end
85
+ @query = sanitize_query(value, command)
86
+ end
57
87
 
58
- valid_type = command.respond_to?(:call) || command.is_a?(String)
88
+ # Same as query, but for the second part of the union for recursive cte
89
+ def sub_query(value = nil, command = nil)
90
+ return unless recursive?
91
+ return @sub_query if value.nil?
59
92
 
60
- raise ArgumentError, <<-MSG.squish if command.nil?
61
- To use proc or string as query, you need to provide the table name
62
- as the first argument
63
- MSG
93
+ @sub_query = sanitize_query(value, command)
94
+ end
95
+
96
+ # Assume `parent_` as the other part if provided a Symbol or String
97
+ def connect(value = nil)
98
+ return @connect if value.nil?
64
99
 
65
- raise ArgumentError, <<-MSG.squish unless valid_type
66
- Only relation, string and proc are valid object types for query,
67
- #{command.inspect} given.
68
- MSG
100
+ value = [value.to_sym, :"parent_#{value}"] \
101
+ if value.is_a?(String) || value.is_a?(Symbol)
102
+ value = value.to_a.first if value.is_a?(Hash)
69
103
 
70
- @query = command
71
- @query_table = ::Arel::Table.new(value)
104
+ @connect = value
72
105
  end
73
106
 
107
+ alias connect= connect
108
+
109
+ private
110
+
111
+ # Get the query and table from the params
112
+ def sanitize_query(value, command = nil)
113
+ return value if relation_query?(value)
114
+ return value if value.is_a?(::Arel::SelectManager)
115
+
116
+ command = value if command.nil? # For compatibility purposes
117
+ valid_type = command.respond_to?(:call) || command.is_a?(String)
118
+
119
+ raise ArgumentError, <<-MSG.squish unless valid_type
120
+ Only relation, string and proc are valid object types for query,
121
+ #{command.inspect} given.
122
+ MSG
123
+
124
+ command
125
+ end
126
+
74
127
  end
75
128
  end
76
129
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'auxiliary_statement/settings'
4
+ require_relative 'auxiliary_statement/recursive'
4
5
 
5
6
  module Torque
6
7
  module PostgreSQL
@@ -8,17 +9,20 @@ module Torque
8
9
  TABLE_COLUMN_AS_STRING = /\A(?:"?(\w+)"?\.)?"?(\w+)"?\z/.freeze
9
10
 
10
11
  class << self
11
- attr_reader :config
12
+ attr_reader :config, :table_name
12
13
 
13
14
  # Find or create the class that will handle statement
14
15
  def lookup(name, base)
15
16
  const = name.to_s.camelize << '_' << self.name.demodulize
16
17
  return base.const_get(const, false) if base.const_defined?(const, false)
17
- base.const_set(const, Class.new(AuxiliaryStatement))
18
+
19
+ base.const_set(const, Class.new(self)).tap do |klass|
20
+ klass.instance_variable_set(:@table_name, name.to_s)
21
+ end
18
22
  end
19
23
 
20
24
  # Create a new instance of an auxiliary statement
21
- def instantiate(statement, base, options = nil)
25
+ def instantiate(statement, base, **options)
22
26
  klass = while base < ActiveRecord::Base
23
27
  list = base.auxiliary_statements_list
24
28
  break list[statement] if list.present? && list.key?(statement)
@@ -26,15 +30,15 @@ module Torque
26
30
  base = base.superclass
27
31
  end
28
32
 
29
- return klass.new(options) unless klass.nil?
33
+ return klass.new(**options) unless klass.nil?
30
34
  raise ArgumentError, <<-MSG.squish
31
35
  There's no '#{statement}' auxiliary statement defined for #{base.class.name}.
32
36
  MSG
33
37
  end
34
38
 
35
39
  # Fast access to statement build
36
- def build(statement, base, options = nil, bound_attributes = [], join_sources = [])
37
- klass = instantiate(statement, base, options)
40
+ def build(statement, base, bound_attributes = [], join_sources = [], **options)
41
+ klass = instantiate(statement, base, **options)
38
42
  result = klass.build(base)
39
43
 
40
44
  bound_attributes.concat(klass.bound_attributes)
@@ -56,7 +60,7 @@ module Torque
56
60
  # A way to create auxiliary statements outside of models configurations,
57
61
  # being able to use on extensions
58
62
  def create(table_or_settings, &block)
59
- klass = Class.new(AuxiliaryStatement)
63
+ klass = Class.new(self)
60
64
 
61
65
  if block_given?
62
66
  klass.instance_variable_set(:@table_name, table_or_settings)
@@ -89,7 +93,8 @@ module Torque
89
93
  def configure(base, instance)
90
94
  return @config unless @config.respond_to?(:call)
91
95
 
92
- settings = Settings.new(base, instance)
96
+ recursive = self < AuxiliaryStatement::Recursive
97
+ settings = Settings.new(base, instance, recursive)
93
98
  settings.instance_exec(settings, &@config)
94
99
  settings
95
100
  end
@@ -98,11 +103,6 @@ module Torque
98
103
  def table
99
104
  @table ||= ::Arel::Table.new(table_name)
100
105
  end
101
-
102
- # Get the name of the table of the configurated statement
103
- def table_name
104
- @table_name ||= self.name.demodulize.split('_').first.underscore
105
- end
106
106
  end
107
107
 
108
108
  delegate :config, :table, :table_name, :relation, :configure, :relation_query?,
@@ -111,15 +111,14 @@ module Torque
111
111
  attr_reader :bound_attributes, :join_sources
112
112
 
113
113
  # Start a new auxiliary statement giving extra options
114
- def initialize(*args)
115
- options = args.extract_options!
114
+ def initialize(*, **options)
116
115
  args_key = Torque::PostgreSQL.config.auxiliary_statement.send_arguments_key
117
116
 
118
117
  @join = options.fetch(:join, {})
119
118
  @args = options.fetch(args_key, {})
120
119
  @where = options.fetch(:where, {})
121
120
  @select = options.fetch(:select, {})
122
- @join_type = options.fetch(:join_type, nil)
121
+ @join_type = options[:join_type]
123
122
 
124
123
  @bound_attributes = []
125
124
  @join_sources = []
@@ -131,7 +130,7 @@ module Torque
131
130
  @join_sources.clear
132
131
 
133
132
  # Prepare all the data for the statement
134
- prepare(base)
133
+ prepare(base, configure(base, self))
135
134
 
136
135
  # Add the join condition to the list
137
136
  @join_sources << build_join(base)
@@ -142,8 +141,7 @@ module Torque
142
141
 
143
142
  private
144
143
  # Setup the statement using the class configuration
145
- def prepare(base)
146
- settings = configure(base, self)
144
+ def prepare(base, settings)
147
145
  requires = Array.wrap(settings.requires).flatten.compact
148
146
  @dependencies = ensure_dependencies(requires, base).flatten.compact
149
147
 
@@ -151,14 +149,13 @@ module Torque
151
149
  @query = settings.query
152
150
 
153
151
  # Call a proc to get the real query
154
- if @query.methods.include?(:call)
152
+ if @query.respond_to?(:call)
155
153
  call_args = @query.try(:arity) === 0 ? [] : [OpenStruct.new(@args)]
156
154
  @query = @query.call(*call_args)
157
155
  @args = []
158
156
  end
159
157
 
160
- # Manually set the query table when it's not an relation query
161
- @query_table = settings.query_table unless relation_query?(@query)
158
+ # Merge select attributes provided on the instance creation
162
159
  @select = settings.attributes.merge(@select) if settings.attributes.present?
163
160
 
164
161
  # Merge join settings
@@ -168,7 +165,7 @@ module Torque
168
165
  @association = settings.through.to_s
169
166
  elsif relation_query?(@query)
170
167
  @association = base.reflections.find do |name, reflection|
171
- break name if @query.klass.eql? reflection.klass
168
+ break name if @query.klass.eql?(reflection.klass)
172
169
  end
173
170
  end
174
171
  end
@@ -234,15 +231,6 @@ module Torque
234
231
  as a query object on #{self.class.name}.
235
232
  MSG
236
233
 
237
- # Expose join columns
238
- if relation_query?(@query)
239
- query_table = @query.arel_table
240
- conditions.children.each do |item|
241
- @query.select_values += [query_table[item.left.name]] \
242
- if item.left.relation.eql?(table)
243
- end
244
- end
245
-
246
234
  # Build the join based on the join type
247
235
  arel_join.new(table, table.create_on(conditions))
248
236
  end
@@ -263,21 +251,31 @@ module Torque
263
251
 
264
252
  # Mount the list of selected attributes
265
253
  def expose_columns(base, query_table = nil)
266
- # Add select columns to the query and get exposed columns
267
- @select.map do |left, right|
268
- base.select_extra_values += [table[right.to_s]]
269
- project(left, query_table).as(right.to_s) if query_table
254
+ # Add the columns necessary for the join
255
+ list = @join_sources.each_with_object(@select) do |join, hash|
256
+ join.right.expr.children.each do |item|
257
+ hash[item.left.name] = nil if item.left.relation.eql?(table)
258
+ end
270
259
  end
260
+
261
+ # Add select columns to the query and get exposed columns
262
+ list.map do |left, right|
263
+ base.select_extra_values += [table[right.to_s]] unless right.nil?
264
+ next unless query_table
265
+
266
+ col = project(left, query_table)
267
+ right.nil? ? col : col.as(right.to_s)
268
+ end.compact
271
269
  end
272
270
 
273
271
  # Ensure that all the dependencies are loaded in the base relation
274
272
  def ensure_dependencies(list, base)
275
273
  with_options = list.extract_options!.to_a
276
- (list + with_options).map do |dependent, options|
277
- dependent_klass = base.model.auxiliary_statements_list[dependent]
274
+ (list + with_options).map do |name, options|
275
+ dependent_klass = base.model.auxiliary_statements_list[name]
278
276
 
279
277
  raise ArgumentError, <<-MSG.squish if dependent_klass.nil?
280
- The '#{dependent}' auxiliary statement dependency can't found on
278
+ The '#{name}' auxiliary statement dependency can't found on
281
279
  #{self.class.name}.
282
280
  MSG
283
281
 
@@ -285,7 +283,8 @@ module Torque
285
283
  cte.is_a?(dependent_klass)
286
284
  end
287
285
 
288
- AuxiliaryStatement.build(dependent, base, options, bound_attributes, join_sources)
286
+ options ||= {}
287
+ AuxiliaryStatement.build(name, base, bound_attributes, join_sources, **options)
289
288
  end
290
289
  end
291
290
 
@@ -309,6 +309,16 @@ module Torque
309
309
  klass.configurator(block)
310
310
  end
311
311
  alias cte auxiliary_statement
312
+
313
+ # Creates a new recursive auxiliary statement (CTE) under the base
314
+ # Very similar to the regular auxiliary statement, but with two-part
315
+ # query where one is executed first and the second recursively
316
+ def recursive_auxiliary_statement(table, &block)
317
+ klass = AuxiliaryStatement::Recursive.lookup(table, self)
318
+ auxiliary_statements_list[table.to_sym] = klass
319
+ klass.configurator(block)
320
+ end
321
+ alias recursive_cte recursive_auxiliary_statement
312
322
  end
313
323
  end
314
324
 
@@ -69,10 +69,14 @@ module Torque
69
69
  # arguments to format string or send on a proc
70
70
  cte.send_arguments_key = :args
71
71
 
72
- # Estipulate a class name (which may contain namespace) that expose the
72
+ # Estipulate a class name (which may contain namespace) that exposes the
73
73
  # auxiliary statement in order to perform detached CTEs
74
74
  cte.exposed_class = 'TorqueCTE'
75
75
 
76
+ # Estipulate a class name (which may contain namespace) that exposes the
77
+ # recursive auxiliary statement in order to perform detached CTEs
78
+ cte.exposed_recursive_class = 'TorqueRecursiveCTE'
79
+
76
80
  end
77
81
 
78
82
  # Configure ENUM features
@@ -55,7 +55,9 @@ module Torque
55
55
 
56
56
  # Check if the model's table depends on any inheritance
57
57
  def physically_inherited?
58
- @physically_inherited ||= connection.schema_cache.dependencies(
58
+ return @physically_inherited if defined?(@physically_inherited)
59
+
60
+ @physically_inherited = connection.schema_cache.dependencies(
59
61
  defined?(@table_name) ? @table_name : decorated_table_name,
60
62
  ).present?
61
63
  rescue ActiveRecord::ConnectionNotEstablished
@@ -30,11 +30,15 @@ module Torque
30
30
  Torque::PostgreSQL::Attributes::Enum.lookup(name).sample
31
31
  end
32
32
 
33
- # Define the exposed constant for auxiliary statements
33
+ # Define the exposed constant for both types of auxiliary statements
34
34
  if torque_config.auxiliary_statement.exposed_class.present?
35
35
  *ns, name = torque_config.auxiliary_statement.exposed_class.split('::')
36
36
  base = ns.present? ? Object.const_get(ns.join('::')) : Object
37
37
  base.const_set(name, Torque::PostgreSQL::AuxiliaryStatement)
38
+
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)
38
42
  end
39
43
  end
40
44
  end