torque-postgresql 0.1.7 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|