torque-postgresql 0.1.7 → 0.2.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 +4 -4
- data/README.rdoc +74 -0
- data/lib/torque/postgresql/adapter/database_statements.rb +44 -2
- data/lib/torque/postgresql/adapter/schema_creation.rb +55 -0
- data/lib/torque/postgresql/adapter/schema_definitions.rb +26 -1
- data/lib/torque/postgresql/adapter/schema_statements.rb +20 -0
- data/lib/torque/postgresql/adapter.rb +1 -1
- data/lib/torque/postgresql/arel/join_source.rb +15 -0
- data/lib/torque/postgresql/arel/select_manager.rb +21 -0
- data/lib/torque/postgresql/arel/using.rb +10 -0
- data/lib/torque/postgresql/arel/visitors.rb +18 -1
- data/lib/torque/postgresql/arel.rb +4 -0
- data/lib/torque/postgresql/attributes/enum.rb +7 -1
- data/lib/torque/postgresql/attributes.rb +9 -1
- data/lib/torque/postgresql/auxiliary_statement/settings.rb +7 -0
- data/lib/torque/postgresql/auxiliary_statement.rb +19 -10
- data/lib/torque/postgresql/base.rb +76 -7
- data/lib/torque/postgresql/coder.rb +132 -0
- data/lib/torque/postgresql/collector.rb +2 -0
- data/lib/torque/postgresql/config.rb +35 -1
- data/lib/torque/postgresql/inheritance.rb +133 -0
- data/lib/torque/postgresql/railtie.rb +16 -0
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +26 -19
- data/lib/torque/postgresql/relation/distinct_on.rb +9 -7
- data/lib/torque/postgresql/relation/inheritance.rb +112 -0
- data/lib/torque/postgresql/relation/merger.rb +48 -0
- data/lib/torque/postgresql/relation.rb +67 -2
- data/lib/torque/postgresql/schema_cache.rb +192 -0
- data/lib/torque/postgresql/schema_dumper.rb +49 -1
- data/lib/torque/postgresql/version.rb +1 -1
- data/lib/torque/postgresql.rb +6 -4
- metadata +14 -8
@@ -0,0 +1,132 @@
|
|
1
|
+
module Torque
|
2
|
+
module PostgreSQL
|
3
|
+
module Coder
|
4
|
+
|
5
|
+
# This class represents an Record to be encoded, instead of a literal Array
|
6
|
+
class Record < Array
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
NEED_QUOTE_FOR = /[\\"(){}, \t\n\r\v\f]/m
|
12
|
+
DELIMITER = ','.freeze
|
13
|
+
|
14
|
+
# This method replace the +read_array+ method from PG gem
|
15
|
+
# See https://github.com/ged/ruby-pg/blob/master/ext/pg_text_decoder.c#L177
|
16
|
+
# for more information
|
17
|
+
def decode(value)
|
18
|
+
# TODO: Use StringScanner
|
19
|
+
# See http://ruby-doc.org/stdlib-1.9.3/libdoc/strscan/rdoc/StringScanner.html
|
20
|
+
_decode(::StringIO.new(value))
|
21
|
+
end
|
22
|
+
|
23
|
+
# This method replace the ++ method from PG gem
|
24
|
+
# See https://github.com/ged/ruby-pg/blob/master/ext/pg_text_encoder.c#L398
|
25
|
+
# for more information
|
26
|
+
def encode(value)
|
27
|
+
_encode(value)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def _decode(stream)
|
33
|
+
quoted = 0
|
34
|
+
escaped = false
|
35
|
+
result = []
|
36
|
+
part = ''
|
37
|
+
|
38
|
+
# Always start getting the non-collection character, the second char
|
39
|
+
stream.getc if stream.pos == 0
|
40
|
+
|
41
|
+
# Check for an empty list
|
42
|
+
return result if %w[} )].include?(stream.getc)
|
43
|
+
|
44
|
+
# If it's not an empty list, return one position before iterating
|
45
|
+
stream.pos -= 1
|
46
|
+
stream.each_char do |c|
|
47
|
+
|
48
|
+
case
|
49
|
+
when quoted < 1
|
50
|
+
case
|
51
|
+
when c == DELIMITER, c == '}', c == ')'
|
52
|
+
|
53
|
+
unless escaped
|
54
|
+
# Non-quoted empty string or NULL as extense
|
55
|
+
part = nil if quoted == 0 && ( part.length == 0 || part == 'NULL' )
|
56
|
+
result << part
|
57
|
+
end
|
58
|
+
|
59
|
+
return result unless c == DELIMITER
|
60
|
+
|
61
|
+
escaped = false
|
62
|
+
quoted = 0
|
63
|
+
part = ''
|
64
|
+
|
65
|
+
when c == '"'
|
66
|
+
quoted = 1
|
67
|
+
when c == '{', c == '('
|
68
|
+
result << _decode(stream)
|
69
|
+
escaped = true
|
70
|
+
else
|
71
|
+
part << c
|
72
|
+
end
|
73
|
+
when escaped
|
74
|
+
escaped = false
|
75
|
+
part << c
|
76
|
+
when c == '\\'
|
77
|
+
escaped = true
|
78
|
+
when c == '"'
|
79
|
+
if stream.getc == '"'
|
80
|
+
part << c
|
81
|
+
else
|
82
|
+
stream.pos -= 1
|
83
|
+
quoted = -1
|
84
|
+
end
|
85
|
+
else
|
86
|
+
if ( c == '"' || c == "'" ) && stream.getc != c
|
87
|
+
stream.pos -= 1
|
88
|
+
quoted = -1
|
89
|
+
else
|
90
|
+
part << c
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def _encode(list)
|
98
|
+
is_record = list.is_a?(Record)
|
99
|
+
list.map! do |part|
|
100
|
+
case part
|
101
|
+
when NilClass
|
102
|
+
is_record ? '' : 'NULL'
|
103
|
+
when Array
|
104
|
+
_encode(part)
|
105
|
+
else
|
106
|
+
_quote(part.to_s)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
result = is_record ? '(%s)' : '{%s}'
|
111
|
+
result % list.join(DELIMITER)
|
112
|
+
end
|
113
|
+
|
114
|
+
def _quote(string)
|
115
|
+
len = string.length
|
116
|
+
|
117
|
+
# Fast results
|
118
|
+
return '""' if len == 0
|
119
|
+
return '"NULL"' if len == 4 && string == 'NULL'
|
120
|
+
|
121
|
+
# Check if the string don't need quotes
|
122
|
+
return string unless string =~ NEED_QUOTE_FOR
|
123
|
+
|
124
|
+
# Use the original string escape function
|
125
|
+
PG::Connection.escape_string(string).inspect
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -3,12 +3,26 @@ module Torque
|
|
3
3
|
include ActiveSupport::Configurable
|
4
4
|
|
5
5
|
# Allow nested configurations
|
6
|
+
# :TODO: Rely on +inheritable_copy+ to make nested configurations
|
6
7
|
config.define_singleton_method(:nested) do |name, &block|
|
7
8
|
klass = Class.new(ActiveSupport::Configurable::Configuration).new
|
8
9
|
block.call(klass) if block
|
9
10
|
send("#{name}=", klass)
|
10
11
|
end
|
11
12
|
|
13
|
+
# Set if any information that requires querying and searching or collectiong
|
14
|
+
# information shuld be eager loaded. This automatically changes when rails
|
15
|
+
# same configuration is set to true
|
16
|
+
config.eager_load = false
|
17
|
+
|
18
|
+
# Set a list of irregular model name when associated with table names
|
19
|
+
config.irregular_models = {}
|
20
|
+
def config.irregular_models=(hash)
|
21
|
+
PostgreSQL.config[:irregular_models] = hash.map do |(table, model)|
|
22
|
+
[table.to_s, model.to_s]
|
23
|
+
end.to_h
|
24
|
+
end
|
25
|
+
|
12
26
|
# Configure ENUM features
|
13
27
|
config.nested(:enum) do |enum|
|
14
28
|
|
@@ -51,7 +65,27 @@ module Torque
|
|
51
65
|
|
52
66
|
# Define the key that is used on auxiliary statements to send extra
|
53
67
|
# arguments to format string or send on a proc
|
54
|
-
cte.send_arguments_key = :
|
68
|
+
cte.send_arguments_key = :args
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
# Configure inheritance features
|
73
|
+
config.nested(:inheritance) do |inheritance|
|
74
|
+
|
75
|
+
# Define the lookup of models from their given name to be inverted, which
|
76
|
+
# means that they are going to be form the last namespaced one to the
|
77
|
+
# most namespaced one
|
78
|
+
inheritance.inverse_lookup = true
|
79
|
+
|
80
|
+
# Determines the name of the column used to collect the table of each
|
81
|
+
# record. When the table has inheritance tables, this column will return
|
82
|
+
# the name of the table that actually holds the record
|
83
|
+
inheritance.record_class_column_name = :_record_class
|
84
|
+
|
85
|
+
# Determines the name of the column used when identifying that the loaded
|
86
|
+
# records should be casted to its correctly model. This will be TRUE for
|
87
|
+
# the records mentioned on `cast_records`
|
88
|
+
inheritance.auto_cast_column_name = :_auto_cast
|
55
89
|
|
56
90
|
end
|
57
91
|
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Torque
|
2
|
+
module PostgreSQL
|
3
|
+
InheritanceError = Class.new(ArgumentError)
|
4
|
+
|
5
|
+
module Inheritance
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Cast the given object to its correct class
|
9
|
+
def cast_record
|
10
|
+
record_class_value = send(self.class._record_class_attribute)
|
11
|
+
|
12
|
+
return self unless self.class.table_name != record_class_value
|
13
|
+
klass = self.class.casted_dependents[record_class_value]
|
14
|
+
self.class.raise_unable_to_cast(record_class_value) if klass.nil?
|
15
|
+
|
16
|
+
# The record need to be re-queried to have its attributes loaded
|
17
|
+
# :TODO: Improve this by only loading the necessary extra columns
|
18
|
+
klass.find(self.id)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def using_single_table_inheritance?(record) # :nodoc:
|
24
|
+
self.class.physically_inherited? || super
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
|
29
|
+
delegate :_auto_cast_attribute, :_record_class_attribute, to: ActiveRecord::Relation
|
30
|
+
|
31
|
+
# Manually set the model name associated with tables name in order to
|
32
|
+
# facilitates the identification of inherited records
|
33
|
+
def inherited(subclass)
|
34
|
+
super
|
35
|
+
|
36
|
+
return unless Torque::PostgreSQL.config.eager_load &&
|
37
|
+
!subclass.abstract_class?
|
38
|
+
|
39
|
+
connection.schema_cache.add_model_name(table_name, subclass)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get a full list of all attributes from a model and all its dependents
|
43
|
+
def inheritance_merged_attributes
|
44
|
+
@inheritance_merged_attributes ||= begin
|
45
|
+
list = attribute_names
|
46
|
+
list += casted_dependents.values.map(&:attribute_names)
|
47
|
+
list.flatten.to_set.freeze
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Check if the model's table depends on any inheritance
|
52
|
+
def physically_inherited?
|
53
|
+
@physically_inherited ||= connection.schema_cache.dependencies(
|
54
|
+
defined?(@table_name) ? @table_name : decorated_table_name,
|
55
|
+
).present?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get the list of all tables directly or indirectly dependent of the
|
59
|
+
# current one
|
60
|
+
def inheritance_dependents
|
61
|
+
connection.schema_cache.associations(table_name) || []
|
62
|
+
end
|
63
|
+
|
64
|
+
# Check whether the model's table has directly or indirectly dependents
|
65
|
+
def physically_inheritances?
|
66
|
+
inheritance_dependents.present?
|
67
|
+
end
|
68
|
+
|
69
|
+
# Get the list of all ActiveRecord classes directly or indirectly
|
70
|
+
# associated by inheritance
|
71
|
+
def casted_dependents
|
72
|
+
@casted_dependents ||= inheritance_dependents.map do |table_name|
|
73
|
+
[table_name, connection.schema_cache.lookup_model(table_name)]
|
74
|
+
end.to_h
|
75
|
+
end
|
76
|
+
|
77
|
+
# Get the final decorated table, regardless of any special condition
|
78
|
+
def decorated_table_name
|
79
|
+
if parent < Base && !parent.abstract_class?
|
80
|
+
contained = parent.table_name
|
81
|
+
contained = contained.singularize if parent.pluralize_table_names
|
82
|
+
contained += "_"
|
83
|
+
end
|
84
|
+
|
85
|
+
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}"
|
86
|
+
end
|
87
|
+
|
88
|
+
# Add an additional check to return the name of the table even when the
|
89
|
+
# class is inherited, but only if it is a physical inheritance
|
90
|
+
def compute_table_name
|
91
|
+
return super unless physically_inherited?
|
92
|
+
decorated_table_name
|
93
|
+
end
|
94
|
+
|
95
|
+
# Raises an error message saying that the giver record class was not
|
96
|
+
# able to be casted since the model was not identified
|
97
|
+
def raise_unable_to_cast(record_class_value)
|
98
|
+
raise InheritanceError.new(<<~MSG.squish)
|
99
|
+
An record was not able to be casted to type '#{record_class_value}'.
|
100
|
+
If this table name doesn't represent a guessable model,
|
101
|
+
please use 'Torque::PostgreSQL.conf.irregular_models =
|
102
|
+
{ '#{record_class_value}' => 'ModelName' }'.
|
103
|
+
MSG
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def discriminate_class_for_record(record) # :nodoc:
|
109
|
+
auto_cast = _auto_cast_attribute.to_s
|
110
|
+
record_class = _record_class_attribute.to_s
|
111
|
+
|
112
|
+
return super unless record.key?(record_class) &&
|
113
|
+
record[auto_cast] === true && record[record_class] != table_name
|
114
|
+
|
115
|
+
klass = casted_dependents[record[record_class]]
|
116
|
+
raise_unable_to_cast(record[record_class]) if klass.nil?
|
117
|
+
filter_attributes_for_cast(record, klass)
|
118
|
+
klass
|
119
|
+
end
|
120
|
+
|
121
|
+
# Filter the record attributes to be loaded to not included those from
|
122
|
+
# another inherited dependent
|
123
|
+
def filter_attributes_for_cast(record, klass)
|
124
|
+
remove_attrs = (inheritance_merged_attributes - klass.attribute_names)
|
125
|
+
record.reject!{ |attribute| remove_attrs.include?(attribute) }
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
ActiveRecord::Base.include Inheritance
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Torque
|
2
|
+
module PostgreSQL
|
3
|
+
# = Torque PostgreSQL Railtie
|
4
|
+
class Railtie < Rails::Railtie # :nodoc:
|
5
|
+
|
6
|
+
# Eger load PostgreSQL namespace
|
7
|
+
config.eager_load_namespaces << Torque::PostgreSQL
|
8
|
+
|
9
|
+
# Get information from the running rails app
|
10
|
+
runner do |app|
|
11
|
+
Torque::PostgreSQL.config.eager_load = app.config.eager_load
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -3,7 +3,10 @@ module Torque
|
|
3
3
|
module Relation
|
4
4
|
module AuxiliaryStatement
|
5
5
|
|
6
|
-
|
6
|
+
# :nodoc:
|
7
|
+
def auxiliary_statements_values; get_value(:auxiliary_statements); end
|
8
|
+
# :nodoc:
|
9
|
+
def auxiliary_statements_values=(value); set_value(:auxiliary_statements, value); end
|
7
10
|
|
8
11
|
# Set use of an auxiliary statement already configurated on the model
|
9
12
|
def with(*args)
|
@@ -13,46 +16,50 @@ module Torque
|
|
13
16
|
# Like #with, but modifies relation in place.
|
14
17
|
def with!(*args)
|
15
18
|
options = args.extract_options!
|
16
|
-
self.auxiliary_statements ||= {}
|
17
19
|
args.each do |table|
|
18
|
-
instance =
|
20
|
+
instance = table.is_a?(PostgreSQL::AuxiliaryStatement) \
|
21
|
+
? table.class.new(options) \
|
22
|
+
: PostgreSQL::AuxiliaryStatement.instantiate(table, self, options)
|
19
23
|
instance.ensure_dependencies!(self)
|
20
|
-
self.
|
24
|
+
self.auxiliary_statements_values |= [instance]
|
21
25
|
end
|
22
26
|
|
23
27
|
self
|
24
28
|
end
|
25
29
|
|
30
|
+
alias_method :auxiliary_statements, :with
|
31
|
+
alias_method :auxiliary_statements!, :with!
|
32
|
+
|
26
33
|
# Get all auxiliary statements bound attributes and the base bound
|
27
34
|
# attributes as well
|
28
35
|
def bound_attributes
|
29
|
-
return super unless self.
|
30
|
-
bindings = self.
|
36
|
+
return super unless self.auxiliary_statements_values.present?
|
37
|
+
bindings = self.auxiliary_statements_values.map(&:bound_attributes)
|
31
38
|
(bindings + super).flatten
|
32
39
|
end
|
33
40
|
|
34
41
|
private
|
35
|
-
delegate :instantiate, to: PostgreSQL::AuxiliaryStatement
|
36
42
|
|
37
43
|
# Hook arel build to add the distinct on clause
|
38
44
|
def build_arel
|
39
45
|
arel = super
|
46
|
+
build_auxiliary_statements(arel)
|
47
|
+
arel
|
48
|
+
end
|
40
49
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
columns << klass.columns
|
45
|
-
klass.build_arel(arel, self)
|
46
|
-
end
|
50
|
+
# Build all necessary data for auxiliary statements
|
51
|
+
def build_auxiliary_statements(arel)
|
52
|
+
return unless self.auxiliary_statements_values.present?
|
47
53
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
54
|
+
columns = []
|
55
|
+
subqueries = self.auxiliary_statements_values.map do |klass|
|
56
|
+
columns << klass.columns
|
57
|
+
klass.build_arel(arel, self)
|
53
58
|
end
|
54
59
|
|
55
|
-
|
60
|
+
columns.flatten!
|
61
|
+
arel.with(subqueries.flatten)
|
62
|
+
arel.project(*columns) if columns.any?
|
56
63
|
end
|
57
64
|
|
58
65
|
# Throw an error showing that an auxiliary statement of the given
|
@@ -3,10 +3,13 @@ module Torque
|
|
3
3
|
module Relation
|
4
4
|
module DistinctOn
|
5
5
|
|
6
|
-
|
6
|
+
# :nodoc:
|
7
|
+
def distinct_on_values; get_value(:distinct_on); end
|
8
|
+
# :nodoc:
|
9
|
+
def distinct_on_values=(value); set_value(:distinct_on, value); end
|
7
10
|
|
8
|
-
# Specifies whether the records should be unique or not by a given set
|
9
|
-
# For example:
|
11
|
+
# Specifies whether the records should be unique or not by a given set
|
12
|
+
# of fields. For example:
|
10
13
|
#
|
11
14
|
# User.distinct_on(:name)
|
12
15
|
# # Returns 1 record per distinct name
|
@@ -22,7 +25,7 @@ module Torque
|
|
22
25
|
|
23
26
|
# Like #distinct_on, but modifies relation in place.
|
24
27
|
def distinct_on!(*value)
|
25
|
-
self.
|
28
|
+
self.distinct_on_values = value
|
26
29
|
self
|
27
30
|
end
|
28
31
|
|
@@ -31,9 +34,8 @@ module Torque
|
|
31
34
|
# Hook arel build to add the distinct on clause
|
32
35
|
def build_arel
|
33
36
|
arel = super
|
34
|
-
|
35
|
-
value
|
36
|
-
arel.distinct_on(resolve_column(value)) unless value.nil?
|
37
|
+
value = self.distinct_on_values
|
38
|
+
arel.distinct_on(resolve_column(value)) if value.present?
|
37
39
|
arel
|
38
40
|
end
|
39
41
|
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Torque
|
2
|
+
module PostgreSQL
|
3
|
+
module Relation
|
4
|
+
module Inheritance
|
5
|
+
|
6
|
+
# :nodoc:
|
7
|
+
def cast_records_value; get_value(:cast_records); end
|
8
|
+
# :nodoc:
|
9
|
+
def cast_records_value=(value); set_value(:cast_records, value); end
|
10
|
+
|
11
|
+
# :nodoc:
|
12
|
+
def itself_only_value; get_value(:itself_only); end
|
13
|
+
# :nodoc:
|
14
|
+
def itself_only_value=(value); set_value(:itself_only, value); end
|
15
|
+
|
16
|
+
delegate :quote_table_name, :quote_column_name, to: :connection
|
17
|
+
|
18
|
+
# Specify that the results should come only from the table that the
|
19
|
+
# entries were created on. For example:
|
20
|
+
#
|
21
|
+
# Activity.itself_only
|
22
|
+
# # Does not return entries for inherited tables
|
23
|
+
def itself_only
|
24
|
+
spawn.itself_only!
|
25
|
+
end
|
26
|
+
|
27
|
+
# Like #itself_only, but modifies relation in place.
|
28
|
+
def itself_only!(*)
|
29
|
+
self.itself_only_value = true
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Enables the casting of all returned records. The result will include
|
34
|
+
# all the information needed to instantiate the inherited models
|
35
|
+
#
|
36
|
+
# Activity.cast_records
|
37
|
+
# # The result list will have many different classes, for all
|
38
|
+
# # inherited models of activities
|
39
|
+
def cast_records(*types, **options)
|
40
|
+
spawn.cast_records!(*types, **options)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Like #cast_records, but modifies relation in place
|
44
|
+
def cast_records!(*types, **options)
|
45
|
+
record_class = self.class._record_class_attribute
|
46
|
+
|
47
|
+
with!(record_class)
|
48
|
+
where!(record_class => types.map(&:table_name)) if options[:filter]
|
49
|
+
|
50
|
+
self.cast_records_value = (types.present? ? types : model.casted_dependents.values)
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Hook arel build to add any necessary table
|
57
|
+
def build_arel
|
58
|
+
arel = super
|
59
|
+
arel.only if self.itself_only_value === true
|
60
|
+
build_inheritances(arel)
|
61
|
+
arel
|
62
|
+
end
|
63
|
+
|
64
|
+
# Build all necessary data for inheritances
|
65
|
+
def build_inheritances(arel)
|
66
|
+
return unless self.cast_records_value.present?
|
67
|
+
|
68
|
+
columns = build_inheritances_joins(arel, self.cast_records_value)
|
69
|
+
columns = columns.map do |column, arel_tables|
|
70
|
+
next arel_tables.first[column] if arel_tables.size == 1
|
71
|
+
list = arel_tables.each_with_object(column).map(&:[])
|
72
|
+
::Arel::Nodes::NamedFunction.new('COALESCE', list).as(column)
|
73
|
+
end
|
74
|
+
|
75
|
+
columns.push(build_auto_caster_marker(arel, self.cast_records_value))
|
76
|
+
arel.project(*columns) if columns.any?
|
77
|
+
end
|
78
|
+
|
79
|
+
# Build as many left outer join as necessary for each dependent table
|
80
|
+
def build_inheritances_joins(arel, types)
|
81
|
+
columns = Hash.new{ |h, k| h[k] = [] }
|
82
|
+
primary_key = quoted_primary_key
|
83
|
+
base_attributes = model.attribute_names
|
84
|
+
|
85
|
+
# Iterate over each casted dependent calculating the columns
|
86
|
+
types.each.with_index do |model, idx|
|
87
|
+
join_table = model.arel_table.alias("\"i_#{idx}\"")
|
88
|
+
arel.outer_join(join_table).using(primary_key)
|
89
|
+
(model.attribute_names - base_attributes).each do |column|
|
90
|
+
columns[column] << join_table
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Return the list of needed columns
|
95
|
+
columns.default_proc = nil
|
96
|
+
columns
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_auto_caster_marker(arel, types)
|
100
|
+
types = types.map(&:table_name)
|
101
|
+
type_attribute = self.class._record_class_attribute.to_s
|
102
|
+
auto_cast_attribute = self.class._auto_cast_attribute.to_s
|
103
|
+
|
104
|
+
table = ::Arel::Table.new(type_attribute.camelize.underscore)
|
105
|
+
column = table[type_attribute].in(types)
|
106
|
+
::Arel::Nodes::SqlLiteral.new(column.to_sql).as(auto_cast_attribute)
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Torque
|
2
|
+
module PostgreSQL
|
3
|
+
module Relation
|
4
|
+
module Merger
|
5
|
+
|
6
|
+
def merge # :nodoc:
|
7
|
+
super
|
8
|
+
|
9
|
+
merge_distinct_on
|
10
|
+
merge_auxiliary_statements
|
11
|
+
merge_inheritance
|
12
|
+
|
13
|
+
relation
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Merge distinct on columns
|
19
|
+
def merge_distinct_on
|
20
|
+
return if other.distinct_on_values.blank?
|
21
|
+
relation.distinct_on_values += other.distinct_on_values
|
22
|
+
end
|
23
|
+
|
24
|
+
# Merge auxiliary statements activated by +with+
|
25
|
+
def merge_auxiliary_statements
|
26
|
+
return if other.auxiliary_statements_values.blank?
|
27
|
+
|
28
|
+
current = relation.auxiliary_statements_values.map{ |cte| cte.class }
|
29
|
+
other.auxiliary_statements_values.each do |other|
|
30
|
+
next if current.include?(other.class)
|
31
|
+
relation.auxiliary_statements_values += [other]
|
32
|
+
current << other.class
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Merge settings related to inheritance tables
|
37
|
+
def merge_inheritance
|
38
|
+
relation.itself_only_value = true if other.itself_only_value
|
39
|
+
relation.cast_records_value.concat(other.cast_records_value).uniq! \
|
40
|
+
if other.cast_records_value.present?
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
ActiveRecord::Relation::Merger.prepend Merger
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|