torque-postgresql 2.4.4 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.rdoc +0 -17
- data/lib/torque/postgresql/adapter/database_statements.rb +32 -74
- data/lib/torque/postgresql/adapter/oid/enum_set.rb +1 -1
- data/lib/torque/postgresql/adapter/oid.rb +0 -3
- data/lib/torque/postgresql/adapter/quoting.rb +12 -20
- data/lib/torque/postgresql/adapter/schema_creation.rb +1 -2
- data/lib/torque/postgresql/adapter/schema_definitions.rb +0 -37
- data/lib/torque/postgresql/adapter/schema_dumper.rb +2 -60
- data/lib/torque/postgresql/adapter/schema_statements.rb +2 -74
- data/lib/torque/postgresql/adapter.rb +2 -11
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +7 -6
- data/lib/torque/postgresql/associations/{association.rb → foreign_association.rb} +1 -4
- data/lib/torque/postgresql/associations/preloader/association.rb +53 -26
- data/lib/torque/postgresql/associations/preloader/loader_query.rb +36 -0
- data/lib/torque/postgresql/associations/preloader.rb +1 -0
- data/lib/torque/postgresql/associations.rb +6 -1
- data/lib/torque/postgresql/attributes/builder/period.rb +6 -2
- data/lib/torque/postgresql/auxiliary_statement/settings.rb +22 -75
- data/lib/torque/postgresql/auxiliary_statement.rb +40 -39
- data/lib/torque/postgresql/base.rb +13 -33
- data/lib/torque/postgresql/config.rb +3 -30
- data/lib/torque/postgresql/inheritance.rb +1 -3
- data/lib/torque/postgresql/migration/command_recorder.rb +2 -12
- data/lib/torque/postgresql/railtie.rb +1 -5
- data/lib/torque/postgresql/reflection/abstract_reflection.rb +44 -20
- data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +2 -2
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +15 -28
- data/lib/torque/postgresql/relation.rb +10 -12
- data/lib/torque/postgresql/schema_cache.rb +2 -7
- data/lib/torque/postgresql/version.rb +1 -1
- data/lib/torque/postgresql.rb +1 -2
- data/lib/torque-postgresql.rb +0 -1
- data/spec/schema.rb +14 -30
- data/spec/spec_helper.rb +1 -2
- data/spec/tests/arel_spec.rb +2 -4
- data/spec/tests/auxiliary_statement_spec.rb +35 -374
- data/spec/tests/belongs_to_many_spec.rb +2 -99
- data/spec/tests/distinct_on_spec.rb +1 -1
- data/spec/tests/enum_set_spec.rb +10 -10
- data/spec/tests/enum_spec.rb +0 -90
- data/spec/tests/has_many_spec.rb +0 -46
- data/spec/tests/relation_spec.rb +1 -1
- data/spec/tests/table_inheritance_spec.rb +15 -11
- metadata +11 -37
- data/lib/torque/postgresql/auxiliary_statement/recursive.rb +0 -149
- data/lib/torque/postgresql/table_name.rb +0 -41
- data/lib/torque/range.rb +0 -22
- data/spec/models/category.rb +0 -2
- data/spec/models/internal/user.rb +0 -5
- data/spec/tests/range_spec.rb +0 -36
- data/spec/tests/schema_spec.rb +0 -134
@@ -11,8 +11,61 @@ module Torque
|
|
11
11
|
# For reflections connected through an array, make sure to properly
|
12
12
|
# decuple the list of ids and set them as associated with the owner
|
13
13
|
def run
|
14
|
+
return self if run?
|
14
15
|
return super unless connected_through_array?
|
16
|
+
|
17
|
+
@run = true
|
15
18
|
send("run_array_for_#{@reflection.macro}")
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
# Correctly correlate records when they are connected theough an array
|
23
|
+
def set_inverse(record)
|
24
|
+
return super unless connected_through_array? && @reflection.macro == :has_many
|
25
|
+
|
26
|
+
# Only the first owner is associated following the same instruction
|
27
|
+
# on the original implementation
|
28
|
+
convert_key(record[association_key_name])&.each do |key|
|
29
|
+
if owners = owners_by_key[key]
|
30
|
+
association = owners.first.association(reflection.name)
|
31
|
+
association.set_inverse_instance(record)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Requires a slight change when running on has many since the value
|
37
|
+
# of the foreign key being an array
|
38
|
+
def load_records(raw_records = nil)
|
39
|
+
return super unless connected_through_array? && @reflection.macro == :has_many
|
40
|
+
|
41
|
+
@records_by_owner = {}.compare_by_identity
|
42
|
+
raw_records ||= loader_query.records_for([self])
|
43
|
+
|
44
|
+
@preloaded_records = raw_records.select do |record|
|
45
|
+
assignments = false
|
46
|
+
|
47
|
+
keys = convert_key(record[association_key_name]) || []
|
48
|
+
owners_by_key.values_at(*keys).each do |owner|
|
49
|
+
entries = (@records_by_owner[owner] ||= [])
|
50
|
+
|
51
|
+
if reflection.collection? || entries.empty?
|
52
|
+
entries << record
|
53
|
+
assignments = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
assignments
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Make sure to change the process when connected through an array
|
62
|
+
def owners_by_key
|
63
|
+
return super unless connected_through_array?
|
64
|
+
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
65
|
+
Array.wrap(convert_key(owner[owner_key_name])).each do |key|
|
66
|
+
(result[key] ||= []) << owner
|
67
|
+
end
|
68
|
+
end
|
16
69
|
end
|
17
70
|
|
18
71
|
private
|
@@ -41,32 +94,6 @@ module Torque
|
|
41
94
|
end
|
42
95
|
end
|
43
96
|
|
44
|
-
if PostgreSQL::AR604
|
45
|
-
# This is how Rails 6.0.4 and 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
|
-
|
70
97
|
# Build correctly the constraint condition in order to get the
|
71
98
|
# associated ids
|
72
99
|
def records_for(ids, &block)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
module Associations
|
6
|
+
module Preloader
|
7
|
+
module LoaderQuery
|
8
|
+
def foreign_column
|
9
|
+
@foreign_column ||= scope.columns_hash[association_key_name]
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_records_for_keys(keys, &block)
|
13
|
+
condition = query_condition_for(keys)
|
14
|
+
scope.where(condition).load(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def query_condition_for(keys)
|
18
|
+
if connected_through_array?
|
19
|
+
value = scope.cast_for_condition(foreign_column, keys.to_a)
|
20
|
+
scope.table[association_key_name].overlaps(value)
|
21
|
+
else
|
22
|
+
{ association_key_name => keys }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def connected_through_array?
|
27
|
+
foreign_column.array?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
::ActiveRecord::Associations::Preloader::Association::LoaderQuery
|
32
|
+
.prepend(LoaderQuery)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,5 +1,10 @@
|
|
1
|
-
require_relative 'associations/association'
|
2
1
|
require_relative 'associations/association_scope'
|
3
2
|
require_relative 'associations/belongs_to_many_association'
|
3
|
+
require_relative 'associations/foreign_association'
|
4
|
+
|
4
5
|
require_relative 'associations/builder'
|
5
6
|
require_relative 'associations/preloader'
|
7
|
+
|
8
|
+
association_mod = Torque::PostgreSQL::Associations::ForeignAssociation
|
9
|
+
::ActiveRecord::Associations::HasManyAssociation.prepend(association_mod)
|
10
|
+
::ActiveRecord::Associations::BelongsToManyAssociation.prepend(association_mod)
|
@@ -220,7 +220,7 @@ module Torque
|
|
220
220
|
@arel_threshold_value ||= begin
|
221
221
|
case threshold
|
222
222
|
when Symbol, String
|
223
|
-
"
|
223
|
+
"arel_table['#{threshold}']"
|
224
224
|
when ActiveSupport::Duration
|
225
225
|
value = "'#{threshold.to_i} seconds'"
|
226
226
|
"::Arel.sql(\"#{value}\").cast(:interval)"
|
@@ -453,7 +453,11 @@ module Torque
|
|
453
453
|
attr_value = threshold.present? ? method_names[:real] : attribute
|
454
454
|
default_value = default.inspect
|
455
455
|
|
456
|
-
|
456
|
+
[
|
457
|
+
"return #{default_value} if #{attr_value}.nil?",
|
458
|
+
"(#{attr_value}.min.try(:infinite?) || #{attr_value}.min <= value) &&",
|
459
|
+
" (#{attr_value}.max.try(:infinite?) || #{attr_value}.max > value)",
|
460
|
+
].join("\n")
|
457
461
|
end
|
458
462
|
|
459
463
|
def instance_start
|
@@ -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)
|
8
8
|
|
9
|
-
attr_reader :base, :source
|
9
|
+
attr_reader :base, :source
|
10
10
|
alias_method :select, :attributes
|
11
11
|
alias_method :cte, :source
|
12
12
|
|
@@ -14,10 +14,9 @@ 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)
|
18
18
|
@base = base
|
19
19
|
@source = source
|
20
|
-
@recursive = recursive
|
21
20
|
end
|
22
21
|
|
23
22
|
def base_name
|
@@ -28,39 +27,6 @@ module Torque
|
|
28
27
|
@base.arel_table
|
29
28
|
end
|
30
29
|
|
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
|
-
|
64
30
|
# Get the arel version of the table set on the query
|
65
31
|
def query_table
|
66
32
|
raise StandardError, 'The query is not defined yet' if query.nil?
|
@@ -75,55 +41,36 @@ module Torque
|
|
75
41
|
|
76
42
|
alias column col
|
77
43
|
|
78
|
-
# There are
|
44
|
+
# There are two ways of setting the query:
|
79
45
|
# - A simple relation based on a Model
|
80
46
|
# - A Arel-based select manager
|
81
|
-
# - A string or a proc
|
47
|
+
# - A string or a proc that requires the table name as first argument
|
82
48
|
def query(value = nil, command = nil)
|
83
49
|
return @query if value.nil?
|
50
|
+
return @query = value if relation_query?(value)
|
84
51
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
return unless recursive?
|
91
|
-
return @sub_query if value.nil?
|
52
|
+
if value.is_a?(::Arel::SelectManager)
|
53
|
+
@query = value
|
54
|
+
@query_table = value.source.left.name
|
55
|
+
return
|
56
|
+
end
|
92
57
|
|
93
|
-
|
94
|
-
end
|
58
|
+
valid_type = command.respond_to?(:call) || command.is_a?(String)
|
95
59
|
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
99
64
|
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
103
69
|
|
104
|
-
@
|
70
|
+
@query = command
|
71
|
+
@query_table = ::Arel::Table.new(value)
|
105
72
|
end
|
106
73
|
|
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
|
-
|
127
74
|
end
|
128
75
|
end
|
129
76
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'auxiliary_statement/settings'
|
4
|
-
require_relative 'auxiliary_statement/recursive'
|
5
4
|
|
6
5
|
module Torque
|
7
6
|
module PostgreSQL
|
@@ -9,20 +8,17 @@ module Torque
|
|
9
8
|
TABLE_COLUMN_AS_STRING = /\A(?:"?(\w+)"?\.)?"?(\w+)"?\z/.freeze
|
10
9
|
|
11
10
|
class << self
|
12
|
-
attr_reader :config
|
11
|
+
attr_reader :config
|
13
12
|
|
14
13
|
# Find or create the class that will handle statement
|
15
14
|
def lookup(name, base)
|
16
15
|
const = name.to_s.camelize << '_' << self.name.demodulize
|
17
16
|
return base.const_get(const, false) if base.const_defined?(const, false)
|
18
|
-
|
19
|
-
base.const_set(const, Class.new(self)).tap do |klass|
|
20
|
-
klass.instance_variable_set(:@table_name, name.to_s)
|
21
|
-
end
|
17
|
+
base.const_set(const, Class.new(AuxiliaryStatement))
|
22
18
|
end
|
23
19
|
|
24
20
|
# Create a new instance of an auxiliary statement
|
25
|
-
def instantiate(statement, base,
|
21
|
+
def instantiate(statement, base, options = nil)
|
26
22
|
klass = while base < ActiveRecord::Base
|
27
23
|
list = base.auxiliary_statements_list
|
28
24
|
break list[statement] if list.present? && list.key?(statement)
|
@@ -30,15 +26,15 @@ module Torque
|
|
30
26
|
base = base.superclass
|
31
27
|
end
|
32
28
|
|
33
|
-
return klass.new(
|
29
|
+
return klass.new(options) unless klass.nil?
|
34
30
|
raise ArgumentError, <<-MSG.squish
|
35
31
|
There's no '#{statement}' auxiliary statement defined for #{base.class.name}.
|
36
32
|
MSG
|
37
33
|
end
|
38
34
|
|
39
35
|
# Fast access to statement build
|
40
|
-
def build(statement, base, bound_attributes = [], join_sources = []
|
41
|
-
klass = instantiate(statement, base,
|
36
|
+
def build(statement, base, options = nil, bound_attributes = [], join_sources = [])
|
37
|
+
klass = instantiate(statement, base, options)
|
42
38
|
result = klass.build(base)
|
43
39
|
|
44
40
|
bound_attributes.concat(klass.bound_attributes)
|
@@ -60,7 +56,7 @@ module Torque
|
|
60
56
|
# A way to create auxiliary statements outside of models configurations,
|
61
57
|
# being able to use on extensions
|
62
58
|
def create(table_or_settings, &block)
|
63
|
-
klass = Class.new(
|
59
|
+
klass = Class.new(AuxiliaryStatement)
|
64
60
|
|
65
61
|
if block_given?
|
66
62
|
klass.instance_variable_set(:@table_name, table_or_settings)
|
@@ -93,8 +89,7 @@ module Torque
|
|
93
89
|
def configure(base, instance)
|
94
90
|
return @config unless @config.respond_to?(:call)
|
95
91
|
|
96
|
-
|
97
|
-
settings = Settings.new(base, instance, recursive)
|
92
|
+
settings = Settings.new(base, instance)
|
98
93
|
settings.instance_exec(settings, &@config)
|
99
94
|
settings
|
100
95
|
end
|
@@ -103,6 +98,11 @@ module Torque
|
|
103
98
|
def table
|
104
99
|
@table ||= ::Arel::Table.new(table_name)
|
105
100
|
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,14 +111,15 @@ 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(
|
114
|
+
def initialize(*args)
|
115
|
+
options = args.extract_options!
|
115
116
|
args_key = Torque::PostgreSQL.config.auxiliary_statement.send_arguments_key
|
116
117
|
|
117
118
|
@join = options.fetch(:join, {})
|
118
119
|
@args = options.fetch(args_key, {})
|
119
120
|
@where = options.fetch(:where, {})
|
120
121
|
@select = options.fetch(:select, {})
|
121
|
-
@join_type = options
|
122
|
+
@join_type = options.fetch(:join_type, nil)
|
122
123
|
|
123
124
|
@bound_attributes = []
|
124
125
|
@join_sources = []
|
@@ -130,7 +131,7 @@ module Torque
|
|
130
131
|
@join_sources.clear
|
131
132
|
|
132
133
|
# Prepare all the data for the statement
|
133
|
-
prepare(base
|
134
|
+
prepare(base)
|
134
135
|
|
135
136
|
# Add the join condition to the list
|
136
137
|
@join_sources << build_join(base)
|
@@ -141,7 +142,8 @@ module Torque
|
|
141
142
|
|
142
143
|
private
|
143
144
|
# Setup the statement using the class configuration
|
144
|
-
def prepare(base
|
145
|
+
def prepare(base)
|
146
|
+
settings = configure(base, self)
|
145
147
|
requires = Array.wrap(settings.requires).flatten.compact
|
146
148
|
@dependencies = ensure_dependencies(requires, base).flatten.compact
|
147
149
|
|
@@ -149,13 +151,14 @@ module Torque
|
|
149
151
|
@query = settings.query
|
150
152
|
|
151
153
|
# Call a proc to get the real query
|
152
|
-
if @query.
|
154
|
+
if @query.methods.include?(:call)
|
153
155
|
call_args = @query.try(:arity) === 0 ? [] : [OpenStruct.new(@args)]
|
154
156
|
@query = @query.call(*call_args)
|
155
157
|
@args = []
|
156
158
|
end
|
157
159
|
|
158
|
-
#
|
160
|
+
# Manually set the query table when it's not an relation query
|
161
|
+
@query_table = settings.query_table unless relation_query?(@query)
|
159
162
|
@select = settings.attributes.merge(@select) if settings.attributes.present?
|
160
163
|
|
161
164
|
# Merge join settings
|
@@ -165,7 +168,7 @@ module Torque
|
|
165
168
|
@association = settings.through.to_s
|
166
169
|
elsif relation_query?(@query)
|
167
170
|
@association = base.reflections.find do |name, reflection|
|
168
|
-
break name if @query.klass.eql?
|
171
|
+
break name if @query.klass.eql? reflection.klass
|
169
172
|
end
|
170
173
|
end
|
171
174
|
end
|
@@ -231,6 +234,15 @@ module Torque
|
|
231
234
|
as a query object on #{self.class.name}.
|
232
235
|
MSG
|
233
236
|
|
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
|
+
|
234
246
|
# Build the join based on the join type
|
235
247
|
arel_join.new(table, table.create_on(conditions))
|
236
248
|
end
|
@@ -251,31 +263,21 @@ module Torque
|
|
251
263
|
|
252
264
|
# Mount the list of selected attributes
|
253
265
|
def expose_columns(base, query_table = nil)
|
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
|
259
|
-
end
|
260
|
-
|
261
266
|
# Add select columns to the query and get exposed columns
|
262
|
-
|
263
|
-
base.select_extra_values += [table[right.to_s]]
|
264
|
-
|
265
|
-
|
266
|
-
col = project(left, query_table)
|
267
|
-
right.nil? ? col : col.as(right.to_s)
|
268
|
-
end.compact
|
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
|
270
|
+
end
|
269
271
|
end
|
270
272
|
|
271
273
|
# Ensure that all the dependencies are loaded in the base relation
|
272
274
|
def ensure_dependencies(list, base)
|
273
275
|
with_options = list.extract_options!.to_a
|
274
|
-
(list + with_options).map do |
|
275
|
-
dependent_klass = base.model.auxiliary_statements_list[
|
276
|
+
(list + with_options).map do |dependent, options|
|
277
|
+
dependent_klass = base.model.auxiliary_statements_list[dependent]
|
276
278
|
|
277
279
|
raise ArgumentError, <<-MSG.squish if dependent_klass.nil?
|
278
|
-
The '#{
|
280
|
+
The '#{dependent}' auxiliary statement dependency can't found on
|
279
281
|
#{self.class.name}.
|
280
282
|
MSG
|
281
283
|
|
@@ -283,8 +285,7 @@ module Torque
|
|
283
285
|
cte.is_a?(dependent_klass)
|
284
286
|
end
|
285
287
|
|
286
|
-
options
|
287
|
-
AuxiliaryStatement.build(name, base, bound_attributes, join_sources, **options)
|
288
|
+
AuxiliaryStatement.build(dependent, base, options, bound_attributes, join_sources)
|
288
289
|
end
|
289
290
|
end
|
290
291
|
|
@@ -5,27 +5,15 @@ module Torque
|
|
5
5
|
module Base
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
|
-
##
|
9
|
-
# :singleton-method: schema
|
10
|
-
# :call-seq: schema
|
11
|
-
#
|
12
|
-
# The schema to which the table belongs to.
|
13
|
-
|
14
8
|
included do
|
15
9
|
mattr_accessor :belongs_to_many_required_by_default, instance_accessor: false
|
16
|
-
class_attribute :schema, instance_writer: false
|
17
10
|
end
|
18
11
|
|
19
12
|
module ClassMethods
|
20
13
|
delegate :distinct_on, :with, :itself_only, :cast_records, to: :all
|
21
14
|
|
22
|
-
#
|
23
|
-
|
24
|
-
self.table_name = TableName.new(self, super)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Whenever the base model is inherited, add a list of auxiliary
|
28
|
-
# statements like the one that loads inherited records' relname
|
15
|
+
# Wenever it's inherited, add a new list of auxiliary statements
|
16
|
+
# It also adds an auxiliary statement to load inherited records' relname
|
29
17
|
def inherited(subclass)
|
30
18
|
super
|
31
19
|
|
@@ -36,11 +24,6 @@ module Torque
|
|
36
24
|
|
37
25
|
# Define helper methods to return the class of the given records
|
38
26
|
subclass.auxiliary_statement record_class do |cte|
|
39
|
-
ActiveSupport::Deprecation.warn(<<~MSG.squish)
|
40
|
-
Inheritance does not use this auxiliary statement and it can be removed.
|
41
|
-
You can replace it with `model.select_extra_values << 'tableoid::regclass'`.
|
42
|
-
MSG
|
43
|
-
|
44
27
|
pg_class = ::Arel::Table.new('pg_class')
|
45
28
|
arel_query = ::Arel::SelectManager.new(pg_class)
|
46
29
|
arel_query.project(pg_class['oid'], pg_class['relname'].as(record_class.to_s))
|
@@ -53,11 +36,18 @@ module Torque
|
|
53
36
|
# Define the dynamic attribute that returns the same information as
|
54
37
|
# the one provided by the auxiliary statement
|
55
38
|
subclass.dynamic_attribute(record_class) do
|
56
|
-
|
57
|
-
|
39
|
+
next self.class.table_name unless self.class.physically_inheritances?
|
40
|
+
|
41
|
+
pg_class = ::Arel::Table.new('pg_class')
|
42
|
+
source = ::Arel::Table.new(subclass.table_name, as: 'source')
|
43
|
+
quoted_id = ::Arel::Nodes::Quoted.new(id)
|
58
44
|
|
59
|
-
query =
|
60
|
-
query.
|
45
|
+
query = ::Arel::SelectManager.new(pg_class)
|
46
|
+
query.join(source).on(pg_class['oid'].eq(source['tableoid']))
|
47
|
+
query.where(source[subclass.primary_key].eq(quoted_id))
|
48
|
+
query.project(pg_class['relname'])
|
49
|
+
|
50
|
+
self.class.connection.select_value(query)
|
61
51
|
end
|
62
52
|
end
|
63
53
|
|
@@ -309,16 +299,6 @@ module Torque
|
|
309
299
|
klass.configurator(block)
|
310
300
|
end
|
311
301
|
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
|
322
302
|
end
|
323
303
|
end
|
324
304
|
|
@@ -4,11 +4,6 @@ module Torque
|
|
4
4
|
module PostgreSQL
|
5
5
|
include ActiveSupport::Configurable
|
6
6
|
|
7
|
-
# Stores a version check for compatibility purposes
|
8
|
-
AR604 = (ActiveRecord.gem_version >= Gem::Version.new('6.0.4'))
|
9
|
-
AR610 = (ActiveRecord.gem_version >= Gem::Version.new('6.1.0'))
|
10
|
-
AR615 = (ActiveRecord.gem_version >= Gem::Version.new('6.1.5'))
|
11
|
-
|
12
7
|
# Use the same logger as the Active Record one
|
13
8
|
def self.logger
|
14
9
|
ActiveRecord::Base.logger
|
@@ -27,11 +22,6 @@ module Torque
|
|
27
22
|
# same configuration is set to true
|
28
23
|
config.eager_load = false
|
29
24
|
|
30
|
-
# This allows default values to have extended values like arrays and casted
|
31
|
-
# values. Extended defaults are still experimental, so enable and test it
|
32
|
-
# before using it in prod
|
33
|
-
config.use_extended_defaults = false
|
34
|
-
|
35
25
|
# Set a list of irregular model name when associated with table names
|
36
26
|
config.irregular_models = {}
|
37
27
|
def config.irregular_models=(hash)
|
@@ -40,19 +30,6 @@ module Torque
|
|
40
30
|
end.to_h
|
41
31
|
end
|
42
32
|
|
43
|
-
# Configure multiple schemas
|
44
|
-
config.nested(:schemas) do |schemas|
|
45
|
-
|
46
|
-
# Defines a list of LIKE-based schemas to not consider for a multiple
|
47
|
-
# schema database
|
48
|
-
schemas.blacklist = %w[information_schema pg_%]
|
49
|
-
|
50
|
-
# Defines a list of LIKE-based schemas to consider for a multiple schema
|
51
|
-
# database
|
52
|
-
schemas.whitelist = %w[public]
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
33
|
# Configure associations features
|
57
34
|
config.nested(:associations) do |assoc|
|
58
35
|
|
@@ -69,14 +46,10 @@ module Torque
|
|
69
46
|
# arguments to format string or send on a proc
|
70
47
|
cte.send_arguments_key = :args
|
71
48
|
|
72
|
-
# Estipulate a class name (which may contain namespace) that
|
49
|
+
# Estipulate a class name (which may contain namespace) that expose the
|
73
50
|
# auxiliary statement in order to perform detached CTEs
|
74
51
|
cte.exposed_class = 'TorqueCTE'
|
75
52
|
|
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
|
-
|
80
53
|
end
|
81
54
|
|
82
55
|
# Configure ENUM features
|
@@ -84,11 +57,11 @@ module Torque
|
|
84
57
|
|
85
58
|
# The name of the method to be used on any ActiveRecord::Base to
|
86
59
|
# initialize model-based enum features
|
87
|
-
enum.base_method = :
|
60
|
+
enum.base_method = :torque_enum
|
88
61
|
|
89
62
|
# The name of the method to be used on any ActiveRecord::Base to
|
90
63
|
# initialize model-based enum set features
|
91
|
-
enum.set_method = :
|
64
|
+
enum.set_method = :torque_enum_set
|
92
65
|
|
93
66
|
# Indicates if bang methods like 'disabled!' should update the record on
|
94
67
|
# database or not
|
@@ -55,9 +55,7 @@ module Torque
|
|
55
55
|
|
56
56
|
# Check if the model's table depends on any inheritance
|
57
57
|
def physically_inherited?
|
58
|
-
|
59
|
-
|
60
|
-
@physically_inherited = connection.schema_cache.dependencies(
|
58
|
+
@physically_inherited ||= connection.schema_cache.dependencies(
|
61
59
|
defined?(@table_name) ? @table_name : decorated_table_name,
|
62
60
|
).present?
|
63
61
|
rescue ActiveRecord::ConnectionNotEstablished
|