torque-postgresql 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/torque/postgresql/adapter/oid/enum.rb +10 -1
- data/lib/torque/postgresql/adapter/oid/enum_set.rb +12 -5
- data/lib/torque/postgresql/adapter/oid/range.rb +7 -0
- data/lib/torque/postgresql/adapter/quoting.rb +1 -1
- data/lib/torque/postgresql/adapter/schema_statements.rb +4 -4
- data/lib/torque/postgresql/attributes.rb +0 -20
- data/lib/torque/postgresql/attributes/builder.rb +28 -0
- data/lib/torque/postgresql/attributes/builder/enum.rb +7 -6
- data/lib/torque/postgresql/attributes/builder/period.rb +365 -305
- data/lib/torque/postgresql/attributes/enum.rb +4 -17
- data/lib/torque/postgresql/attributes/enum_set.rb +4 -20
- data/lib/torque/postgresql/attributes/period.rb +3 -17
- data/lib/torque/postgresql/config.rb +42 -17
- data/lib/torque/postgresql/inheritance.rb +34 -4
- data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +8 -3
- data/lib/torque/postgresql/relation/inheritance.rb +10 -3
- data/lib/torque/postgresql/version.rb +1 -1
- data/lib/torque/range.rb +0 -18
- metadata +2 -3
- data/lib/torque/postgresql/attributes/type_map.rb +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a161d5be06adc9e93a5888b4b16b1a07b6f4a613
|
4
|
+
data.tar.gz: 51c1aae61c09bfa681e02daeb523551da9ef1139
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99892a8ca40bf830e81accb0d4f15d87fbcb44697b95b4f9854a81b2e5d733c80c1a23f7312009407741ea11e7a6cb4d29acae8b03284226d618fadfbaa6a9da
|
7
|
+
data.tar.gz: 53e5888be0f1c89d2e30c85acc2ad350e34ad5efe6b6d899315a4cd18affe9366978d5cfd7e4ddad7843c0856ca11f7243f106d5d4ad2f538a86e2c11d5551ce
|
@@ -4,7 +4,7 @@ module Torque
|
|
4
4
|
module OID
|
5
5
|
class Enum < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Enum
|
6
6
|
|
7
|
-
attr_reader :name, :klass
|
7
|
+
attr_reader :name, :klass, :set_klass, :enum_klass
|
8
8
|
|
9
9
|
def self.create(row, type_map)
|
10
10
|
name = row['typname']
|
@@ -13,6 +13,7 @@ module Torque
|
|
13
13
|
|
14
14
|
oid_klass = Enum.new(name)
|
15
15
|
oid_set_klass = EnumSet.new(name, oid_klass.klass)
|
16
|
+
oid_klass.instance_variable_set(:@set_klass, oid_set_klass)
|
16
17
|
|
17
18
|
type_map.register_type(oid, oid_klass)
|
18
19
|
type_map.register_type(arr_oid, oid_set_klass)
|
@@ -21,6 +22,8 @@ module Torque
|
|
21
22
|
def initialize(name)
|
22
23
|
@name = name
|
23
24
|
@klass = Attributes::Enum.lookup(name)
|
25
|
+
|
26
|
+
@enum_klass = self
|
24
27
|
end
|
25
28
|
|
26
29
|
def hash
|
@@ -42,6 +45,12 @@ module Torque
|
|
42
45
|
cast_value(value).to_sym.inspect
|
43
46
|
end
|
44
47
|
|
48
|
+
def ==(other)
|
49
|
+
self.class == other.class &&
|
50
|
+
other.klass == klass &&
|
51
|
+
other.type == type
|
52
|
+
end
|
53
|
+
|
45
54
|
private
|
46
55
|
|
47
56
|
def cast_value(value)
|
@@ -3,12 +3,11 @@ module Torque
|
|
3
3
|
module Adapter
|
4
4
|
module OID
|
5
5
|
class EnumSet < Enum
|
6
|
-
|
7
|
-
attr_reader :enum_klass
|
8
|
-
|
9
6
|
def initialize(name, enum_klass)
|
10
7
|
@name = name + '[]'
|
11
8
|
@klass = Attributes::EnumSet.lookup(name, enum_klass)
|
9
|
+
|
10
|
+
@set_klass = self
|
12
11
|
@enum_klass = enum_klass
|
13
12
|
end
|
14
13
|
|
@@ -16,10 +15,18 @@ module Torque
|
|
16
15
|
:enum_set
|
17
16
|
end
|
18
17
|
|
18
|
+
def deserialize(value)
|
19
|
+
return unless value.present?
|
20
|
+
value = value[1..-2].split(',') if value.is_a?(String)
|
21
|
+
cast_value(value)
|
22
|
+
end
|
23
|
+
|
19
24
|
def serialize(value)
|
20
25
|
return if value.blank?
|
21
26
|
value = cast_value(value)
|
22
|
-
|
27
|
+
|
28
|
+
return if value.blank?
|
29
|
+
"{#{value.map(&:to_s).join(',')}}"
|
23
30
|
end
|
24
31
|
|
25
32
|
# Always use symbol values for schema dumper
|
@@ -33,7 +40,7 @@ module Torque
|
|
33
40
|
return if value.blank?
|
34
41
|
return value if value.is_a?(@klass)
|
35
42
|
@klass.new(value)
|
36
|
-
rescue Attributes::EnumSet::
|
43
|
+
rescue Attributes::EnumSet::EnumSetError
|
37
44
|
nil
|
38
45
|
end
|
39
46
|
|
@@ -9,7 +9,7 @@ module Torque
|
|
9
9
|
def drop_type(name, options = {})
|
10
10
|
force = options.fetch(:force, '').upcase
|
11
11
|
check = 'IF EXISTS' if options.fetch(:check, true)
|
12
|
-
execute <<-SQL
|
12
|
+
execute <<-SQL.squish
|
13
13
|
DROP TYPE #{check}
|
14
14
|
#{quote_type_name(name, options[:schema])} #{force}
|
15
15
|
SQL
|
@@ -17,7 +17,7 @@ module Torque
|
|
17
17
|
|
18
18
|
# Renames a type.
|
19
19
|
def rename_type(type_name, new_name)
|
20
|
-
execute <<-SQL
|
20
|
+
execute <<-SQL.squish
|
21
21
|
ALTER TYPE #{quote_type_name(type_name)}
|
22
22
|
RENAME TO #{Quoting::Name.new(nil, new_name.to_s).quoted}
|
23
23
|
SQL
|
@@ -32,7 +32,7 @@ module Torque
|
|
32
32
|
# create_enum 'status', ['foo', 'bar'], force: true
|
33
33
|
def create_enum(name, values, options = {})
|
34
34
|
drop_type(name, options) if options[:force]
|
35
|
-
execute <<-SQL
|
35
|
+
execute <<-SQL.squish
|
36
36
|
CREATE TYPE #{quote_type_name(name, options[:schema])} AS ENUM
|
37
37
|
(#{quote_enum_values(name, values, options).join(', ')})
|
38
38
|
SQL
|
@@ -56,7 +56,7 @@ module Torque
|
|
56
56
|
quote_enum_values(name, values, options).each do |value|
|
57
57
|
reference = "BEFORE #{before}" unless before == false
|
58
58
|
reference = "AFTER #{after}" unless after == false
|
59
|
-
execute <<-SQL
|
59
|
+
execute <<-SQL.squish
|
60
60
|
ALTER TYPE #{quote_type_name(name, options[:schema])}
|
61
61
|
ADD VALUE #{value} #{reference}
|
62
62
|
SQL
|
@@ -1,6 +1,4 @@
|
|
1
|
-
require_relative 'attributes/type_map'
|
2
1
|
require_relative 'attributes/lazy'
|
3
|
-
|
4
2
|
require_relative 'attributes/builder'
|
5
3
|
|
6
4
|
require_relative 'attributes/enum'
|
@@ -17,24 +15,6 @@ module Torque
|
|
17
15
|
class_attribute :enum_save_on_bang, instance_accessor: true
|
18
16
|
self.enum_save_on_bang = Torque::PostgreSQL.config.enum.save_on_bang
|
19
17
|
end
|
20
|
-
|
21
|
-
module ClassMethods
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
# If the attributes are not loaded,
|
26
|
-
def method_missing(method_name, *args, &block)
|
27
|
-
return super unless define_attribute_methods
|
28
|
-
self.send(method_name, *args, &block)
|
29
|
-
end
|
30
|
-
|
31
|
-
# Use local type map to identify attribute decorator
|
32
|
-
def define_attribute_method(attribute)
|
33
|
-
type = attribute_types[attribute]
|
34
|
-
super unless TypeMap.lookup(type, self, attribute)
|
35
|
-
end
|
36
|
-
|
37
|
-
end
|
38
18
|
end
|
39
19
|
|
40
20
|
ActiveRecord::Base.include Attributes
|
@@ -1,2 +1,30 @@
|
|
1
1
|
require_relative 'builder/enum'
|
2
2
|
require_relative 'builder/period'
|
3
|
+
|
4
|
+
module Torque
|
5
|
+
module PostgreSQL
|
6
|
+
module Attributes
|
7
|
+
module Builder
|
8
|
+
def self.include_on(klass, method_name, builder_klass, &block)
|
9
|
+
klass.define_singleton_method(method_name) do |*args, **options|
|
10
|
+
return unless connection.table_exists?(table_name)
|
11
|
+
|
12
|
+
args.each do |attribute|
|
13
|
+
begin
|
14
|
+
# Generate methods on self class
|
15
|
+
builder = builder_klass.new(self, attribute, options)
|
16
|
+
builder.conflicting?
|
17
|
+
builder.build
|
18
|
+
|
19
|
+
# Additional settings for the builder
|
20
|
+
instance_exec(builder, &block) if block.present?
|
21
|
+
rescue Interrupt
|
22
|
+
# Not able to build the attribute, maybe pending migrations
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -3,15 +3,18 @@ module Torque
|
|
3
3
|
module Attributes
|
4
4
|
module Builder
|
5
5
|
class Enum
|
6
|
+
VALID_TYPES = %i[enum enum_set].freeze
|
7
|
+
|
6
8
|
attr_accessor :klass, :attribute, :subtype, :options, :values, :enum_module
|
7
9
|
|
8
10
|
# Start a new builder of methods for enum values on ActiveRecord::Base
|
9
|
-
def initialize(klass, attribute,
|
11
|
+
def initialize(klass, attribute, options)
|
10
12
|
@klass = klass
|
11
13
|
@attribute = attribute.to_s
|
12
|
-
@subtype =
|
14
|
+
@subtype = klass.attribute_types[@attribute]
|
13
15
|
@options = options
|
14
16
|
|
17
|
+
raise Interrupt unless subtype.respond_to?(:klass)
|
15
18
|
@values = subtype.klass.values
|
16
19
|
|
17
20
|
if @options[:only]
|
@@ -49,7 +52,7 @@ module Torque
|
|
49
52
|
# Check if any of the methods that will be created get in conflict
|
50
53
|
# with the base class methods
|
51
54
|
def conflicting?
|
52
|
-
return
|
55
|
+
return if options[:force] == true
|
53
56
|
attributes = attribute.pluralize
|
54
57
|
|
55
58
|
dangerous?(attributes, true)
|
@@ -60,11 +63,9 @@ module Torque
|
|
60
63
|
values_methods.each do |attr, list|
|
61
64
|
list.map(&method(:dangerous?))
|
62
65
|
end
|
63
|
-
|
64
|
-
return false
|
65
66
|
rescue Interrupt => err
|
66
67
|
raise ArgumentError, <<-MSG.squish
|
67
|
-
#{subtype.
|
68
|
+
Enum #{subtype.name} was not able to generate requested
|
68
69
|
methods because the method #{err} already exists in
|
69
70
|
#{klass.name}.
|
70
71
|
MSG
|
@@ -2,8 +2,10 @@ module Torque
|
|
2
2
|
module PostgreSQL
|
3
3
|
module Attributes
|
4
4
|
module Builder
|
5
|
-
# TODO: Allow
|
5
|
+
# TODO: Allow documenting by building the methods outside and importing
|
6
|
+
# only the raw string
|
6
7
|
class Period
|
8
|
+
DIRECT_ACCESS_REGEX = /_?%s_?/
|
7
9
|
SUPPORTED_TYPES = %i[daterange tsrange tstzrange].freeze
|
8
10
|
CURRENT_GETTERS = {
|
9
11
|
daterange: 'Date.today',
|
@@ -17,58 +19,77 @@ module Torque
|
|
17
19
|
tstzrange: :timestamp,
|
18
20
|
}.freeze
|
19
21
|
|
20
|
-
attr_accessor :klass, :attribute, :options, :type, :
|
21
|
-
:
|
22
|
-
:period_module
|
22
|
+
attr_accessor :klass, :attribute, :options, :type, :default, :current_getter,
|
23
|
+
:type_caster, :threshold, :dynamic_threshold, :klass_module, :instance_module
|
23
24
|
|
24
25
|
# Start a new builder of methods for period values on
|
25
26
|
# ActiveRecord::Base
|
26
|
-
def initialize(klass, attribute,
|
27
|
+
def initialize(klass, attribute, options)
|
27
28
|
@klass = klass
|
28
29
|
@attribute = attribute.to_s
|
29
30
|
@options = options
|
30
31
|
@type = klass.attribute_types[@attribute].type
|
31
32
|
|
32
|
-
|
33
|
+
raise ArgumentError, <<-MSG.squish unless SUPPORTED_TYPES.include?(type)
|
34
|
+
Period cannot be generated for #{attribute} because its type
|
35
|
+
#{type} is not supported. Only #{SUPPORTED_TYPES.join(', ')} are supported.
|
36
|
+
MSG
|
37
|
+
|
33
38
|
@current_getter = CURRENT_GETTERS[type]
|
34
39
|
@type_caster = TYPE_CASTERS[type]
|
35
40
|
|
36
|
-
@default
|
37
|
-
|
41
|
+
@default = options[:pessimistic].blank?
|
42
|
+
end
|
38
43
|
|
39
|
-
|
44
|
+
# Check if can identify a threshold field
|
45
|
+
def threshold
|
46
|
+
@threshold ||= begin
|
47
|
+
option = options[:threshold]
|
48
|
+
return if option.eql?(false)
|
40
49
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
50
|
+
unless option.eql?(true)
|
51
|
+
return option.is_a?(String) ? option.to_sym : option
|
52
|
+
end
|
53
|
+
|
54
|
+
attributes = klass.attribute_names
|
55
|
+
default_name = Torque::PostgreSQL.config.period.auto_threshold.to_s
|
56
|
+
raise ArgumentError, <<-MSG.squish unless attributes.include?(default_name)
|
57
|
+
Unable to find the #{default_name} to use as threshold for period
|
58
|
+
features for #{attribute} in #{klass.name} model.
|
59
|
+
MSG
|
60
|
+
|
61
|
+
check_type = klass.attribute_types[default_name].type
|
62
|
+
raise ArgumentError, <<-MSG.squish unless check_type.eql?(:interval)
|
63
|
+
The #{default_name} has the wrong type to be used as threshold.
|
64
|
+
Expected :interval got #{check_type.inspect} in #{klass.name} model.
|
65
|
+
MSG
|
66
|
+
|
67
|
+
default_name.to_sym
|
68
|
+
end
|
45
69
|
end
|
46
70
|
|
47
71
|
# Generate all the method names
|
48
72
|
def method_names
|
49
|
-
@method_names ||= options.fetch(:methods, {})
|
50
|
-
.reverse_merge(default_method_names)
|
73
|
+
@method_names ||= default_method_names.merge(options.fetch(:methods, {}))
|
51
74
|
end
|
52
75
|
|
53
76
|
# Get the list of methods associated withe the class
|
54
77
|
def klass_method_names
|
55
|
-
@klass_method_names ||= method_names.to_a[0..
|
78
|
+
@klass_method_names ||= method_names.to_a[0..22].to_h
|
56
79
|
end
|
57
80
|
|
58
81
|
# Get the list of methods associated withe the instances
|
59
82
|
def instance_method_names
|
60
|
-
@instance_method_names ||= method_names.to_a[
|
83
|
+
@instance_method_names ||= method_names.to_a[23..29].to_h
|
61
84
|
end
|
62
85
|
|
63
86
|
# Check if any of the methods that will be created get in conflict
|
64
87
|
# with the base class methods
|
65
88
|
def conflicting?
|
66
|
-
return
|
89
|
+
return if options[:force] == true
|
67
90
|
|
68
91
|
klass_method_names.values.each { |name| dangerous?(name, true) }
|
69
92
|
instance_method_names.values.each { |name| dangerous?(name) }
|
70
|
-
|
71
|
-
return false
|
72
93
|
rescue Interrupt => err
|
73
94
|
raise ArgumentError, <<-MSG.squish
|
74
95
|
#{subtype.class.name} was not able to generate requested
|
@@ -79,99 +100,84 @@ module Torque
|
|
79
100
|
|
80
101
|
# Create all methods needed
|
81
102
|
def build
|
82
|
-
@
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
103
|
+
@klass_module = Module.new
|
104
|
+
@instance_module = Module.new
|
105
|
+
|
106
|
+
value_args = ['value']
|
107
|
+
left_right_args = ['left', 'right = nil']
|
108
|
+
|
109
|
+
## Klass methods
|
110
|
+
build_method_helper :klass, :current_on, value_args # 00
|
111
|
+
build_method_helper :klass, :current # 01
|
112
|
+
build_method_helper :klass, :not_current # 02
|
113
|
+
build_method_helper :klass, :containing, value_args # 03
|
114
|
+
build_method_helper :klass, :not_containing, value_args # 04
|
115
|
+
build_method_helper :klass, :overlapping, left_right_args # 05
|
116
|
+
build_method_helper :klass, :not_overlapping, left_right_args # 06
|
117
|
+
build_method_helper :klass, :starting_after, value_args # 07
|
118
|
+
build_method_helper :klass, :starting_before, value_args # 08
|
119
|
+
build_method_helper :klass, :finishing_after, value_args # 09
|
120
|
+
build_method_helper :klass, :finishing_before, value_args # 10
|
121
|
+
|
122
|
+
if threshold.present?
|
123
|
+
build_method_helper :klass, :real_containing, value_args # 11
|
124
|
+
build_method_helper :klass, :real_overlapping, left_right_args # 12
|
125
|
+
build_method_helper :klass, :real_starting_after, value_args # 13
|
126
|
+
build_method_helper :klass, :real_starting_before, value_args # 14
|
127
|
+
build_method_helper :klass, :real_finishing_after, value_args # 15
|
128
|
+
build_method_helper :klass, :real_finishing_before, value_args # 16
|
100
129
|
end
|
101
130
|
|
102
|
-
|
103
|
-
|
131
|
+
unless type.eql?(:daterange)
|
132
|
+
build_method_helper :klass, :containing_date, value_args # 17
|
133
|
+
build_method_helper :klass, :not_containing_date, value_args # 18
|
134
|
+
build_method_helper :klass, :overlapping_date, left_right_args # 19
|
135
|
+
build_method_helper :klass, :not_overlapping_date, left_right_args # 20
|
104
136
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
# Create an arel version of +nullif+ function
|
111
|
-
def arel_nullif(*args)
|
112
|
-
named_function('nullif', args)
|
113
|
-
end
|
114
|
-
|
115
|
-
# Create an arel version of +coalesce+ function
|
116
|
-
def arel_coalesce(*args)
|
117
|
-
named_function('coalesce', args)
|
118
|
-
end
|
119
|
-
|
120
|
-
# Create an arel version of the type with the following values
|
121
|
-
def arel_convert_to_type(left, right = nil, set_type = nil)
|
122
|
-
named_function(set_type || type, [left, right || left])
|
123
|
-
end
|
137
|
+
if threshold.present?
|
138
|
+
build_method_helper :klass, :real_containing_date, value_args # 21
|
139
|
+
build_method_helper :klass, :real_overlapping_date, left_right_args # 22
|
140
|
+
end
|
141
|
+
end
|
124
142
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
named_function(:upper, real_arel_attribute).cast(:date),
|
131
|
-
)
|
132
|
-
end
|
143
|
+
## Instance methods
|
144
|
+
build_method_helper :instance, :current? # 23
|
145
|
+
build_method_helper :instance, :current_on?, value_args # 24
|
146
|
+
build_method_helper :instance, :start # 25
|
147
|
+
build_method_helper :instance, :finish # 26
|
133
148
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
149
|
+
if threshold.present?
|
150
|
+
build_method_helper :instance, :real # 27
|
151
|
+
build_method_helper :instance, :real_start # 28
|
152
|
+
build_method_helper :instance, :real_finish # 29
|
153
|
+
end
|
138
154
|
|
139
|
-
|
140
|
-
|
141
|
-
named_function(:not, value)
|
155
|
+
klass.extend klass_module
|
156
|
+
klass.include instance_module
|
142
157
|
end
|
143
158
|
|
144
|
-
|
145
|
-
|
146
|
-
|
159
|
+
def build_method_helper(type, key, args = [])
|
160
|
+
method_name = method_names[key]
|
161
|
+
return if method_name.nil?
|
147
162
|
|
148
|
-
|
149
|
-
|
150
|
-
arel_coalesce(checker, default_sql)
|
151
|
-
end
|
163
|
+
method_content = send("#{type}_#{key}")
|
164
|
+
method_content = define_string_method(method_name, method_content, args)
|
152
165
|
|
153
|
-
|
154
|
-
|
155
|
-
@threshold_value ||= begin
|
156
|
-
case threshold
|
157
|
-
when Symbol, String
|
158
|
-
klass.arel_table[threshold]
|
159
|
-
when ActiveSupport::Duration
|
160
|
-
::Arel.sql("'#{threshold.to_i} seconds'").cast(:interval)
|
161
|
-
when Numeric
|
162
|
-
value = threshold.to_i.to_s
|
163
|
-
value << type_caster.eql?(:date) ? ' days' : ' seconds'
|
164
|
-
::Arel.sql("'#{value}'").cast(:interval)
|
165
|
-
end
|
166
|
-
end
|
166
|
+
source_module = send("#{type}_module")
|
167
|
+
source_module.class_eval(method_content)
|
167
168
|
end
|
168
169
|
|
169
170
|
private
|
170
171
|
|
171
172
|
# Generates the default method names
|
172
173
|
def default_method_names
|
173
|
-
Torque::PostgreSQL.config.period.method_names.
|
174
|
-
|
174
|
+
list = Torque::PostgreSQL.config.period.method_names.dup
|
175
|
+
|
176
|
+
if options.fetch(:prefixed, true)
|
177
|
+
list.transform_values { |value| format(value, attribute) }
|
178
|
+
else
|
179
|
+
list = list.merge(Torque::PostgreSQL.config.period.direct_method_names)
|
180
|
+
list.transform_values { |value| value.gsub(DIRECT_ACCESS_REGEX, '') }
|
175
181
|
end
|
176
182
|
end
|
177
183
|
|
@@ -188,262 +194,316 @@ module Torque
|
|
188
194
|
end
|
189
195
|
end
|
190
196
|
|
191
|
-
|
192
|
-
def
|
193
|
-
|
194
|
-
|
197
|
+
## BUILDER HELPERS
|
198
|
+
def define_string_method(name, body, args = [])
|
199
|
+
headline = "def #{name}"
|
200
|
+
headline += "(#{args.join(', ')})"
|
201
|
+
[headline, body, 'end'].join("\n")
|
202
|
+
end
|
195
203
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
end
|
204
|
+
def arel_attribute
|
205
|
+
@arel_attribute ||= "arel_attribute(#{attribute.inspect})"
|
206
|
+
end
|
200
207
|
|
201
|
-
|
202
|
-
|
203
|
-
|
208
|
+
def arel_default_sql
|
209
|
+
@arel_default_sql ||= arel_sql_quote(@default.inspect)
|
210
|
+
end
|
204
211
|
|
205
|
-
|
206
|
-
|
207
|
-
|
212
|
+
def arel_sql_quote(value)
|
213
|
+
"::Arel.sql(connection.quote(#{value}))"
|
214
|
+
end
|
215
|
+
|
216
|
+
# Check how to provide the threshold value
|
217
|
+
def arel_threshold_value
|
218
|
+
@arel_threshold_value ||= begin
|
219
|
+
case threshold
|
220
|
+
when Symbol, String
|
221
|
+
"arel_attribute('#{threshold}')"
|
222
|
+
when ActiveSupport::Duration
|
223
|
+
value = "'#{threshold.to_i} seconds'"
|
224
|
+
"::Arel.sql(\"#{value}\").cast(:interval)"
|
225
|
+
when Numeric
|
226
|
+
value = threshold.to_i.to_s
|
227
|
+
value << type_caster.eql?(:date) ? ' days' : ' seconds'
|
228
|
+
value = "'#{value}'"
|
229
|
+
"::Arel.sql(\"#{value}\").cast(:interval)"
|
230
|
+
end
|
208
231
|
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Start at version of the value
|
235
|
+
def arel_start_at
|
236
|
+
@arel_start_at ||= arel_named_function('lower', arel_attribute)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Finish at version of the value
|
240
|
+
def arel_finish_at
|
241
|
+
@arel_finish_at ||= arel_named_function('upper', arel_attribute)
|
242
|
+
end
|
209
243
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
244
|
+
# Start at version of the value with threshold
|
245
|
+
def arel_real_start_at
|
246
|
+
return arel_start_at unless threshold.present?
|
247
|
+
@arel_real_start_at ||= begin
|
248
|
+
result = "(#{arel_start_at} - #{arel_threshold_value})"
|
249
|
+
result << '.cast(:date)' if type.eql?(:daterange)
|
250
|
+
result
|
214
251
|
end
|
252
|
+
end
|
215
253
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
254
|
+
# Finish at version of the value with threshold
|
255
|
+
def arel_real_finish_at
|
256
|
+
return arel_finish_at unless threshold.present?
|
257
|
+
@arel_real_finish_at ||= begin
|
258
|
+
result = "(#{arel_finish_at} + #{arel_threshold_value})"
|
259
|
+
result << '.cast(:date)' if type.eql?(:daterange)
|
260
|
+
result
|
220
261
|
end
|
262
|
+
end
|
221
263
|
|
222
|
-
|
223
|
-
|
264
|
+
# When the time has a threshold, then the real attribute is complex
|
265
|
+
def arel_real_attribute
|
266
|
+
return arel_attribute unless threshold.present?
|
267
|
+
@arel_real_attribute ||= arel_named_function(
|
268
|
+
type, arel_real_start_at, arel_real_finish_at,
|
269
|
+
)
|
270
|
+
end
|
224
271
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
elsif !value.respond_to?(:cast)
|
230
|
-
value = ::Arel.sql(connection.quote(value))
|
231
|
-
end
|
272
|
+
# Create an arel version of the type with the following values
|
273
|
+
def arel_convert_to_type(left, right = nil, set_type = nil)
|
274
|
+
arel_named_function(set_type || type, left, right || left)
|
275
|
+
end
|
232
276
|
|
233
|
-
|
234
|
-
|
277
|
+
# Create an arel named function
|
278
|
+
def arel_named_function(name, *args)
|
279
|
+
result = "::Arel::Nodes::NamedFunction.new(#{name.to_s.inspect}"
|
280
|
+
result << ', [' << args.join(', ') << ']' if args.present?
|
281
|
+
result << ')'
|
282
|
+
end
|
235
283
|
|
236
|
-
|
237
|
-
|
284
|
+
# Create an arel version of +nullif+ function
|
285
|
+
def arel_nullif(*args)
|
286
|
+
arel_named_function('nullif', *args)
|
287
|
+
end
|
238
288
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
elsif !value.respond_to?(:cast)
|
244
|
-
value = ::Arel.sql(connection.quote(value))
|
245
|
-
end
|
289
|
+
# Create an arel version of +coalesce+ function
|
290
|
+
def arel_coalesce(*args)
|
291
|
+
arel_named_function('coalesce', *args)
|
292
|
+
end
|
246
293
|
|
247
|
-
|
248
|
-
|
294
|
+
# Create an arel version of an empty value for the range
|
295
|
+
def arel_empty_value
|
296
|
+
arel_convert_to_type('::Arel.sql(\'NULL\')')
|
297
|
+
end
|
249
298
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
299
|
+
# Convert timestamp range to date range format
|
300
|
+
def arel_daterange(real = false)
|
301
|
+
arel_named_function(
|
302
|
+
'daterange',
|
303
|
+
(real ? arel_real_start_at : arel_start_at) + '.cast(:date)',
|
304
|
+
(real ? arel_real_finish_at : arel_finish_at) + '.cast(:date)',
|
305
|
+
'::Arel.sql("\'[]\'")',
|
306
|
+
)
|
307
|
+
end
|
254
308
|
|
255
|
-
|
256
|
-
|
309
|
+
def arel_check_condition(type)
|
310
|
+
checker = arel_nullif(arel_real_attribute, arel_empty_value)
|
311
|
+
checker << ".#{type}(value.cast(#{type_caster.inspect}))"
|
312
|
+
arel_coalesce(checker, arel_default_sql)
|
313
|
+
end
|
257
314
|
|
258
|
-
|
259
|
-
|
260
|
-
value =
|
261
|
-
|
315
|
+
def arel_formatting_value(condition = nil, value = 'value', cast: nil)
|
316
|
+
[
|
317
|
+
"#{value} = arel_table[#{value}] if #{value}.is_a?(Symbol)",
|
318
|
+
"unless #{value}.respond_to?(:cast)",
|
319
|
+
" #{value} = ::Arel.sql(connection.quote(#{value}))",
|
320
|
+
(" #{value} = #{value}.cast(#{cast.inspect})" if cast),
|
321
|
+
'end',
|
322
|
+
condition,
|
323
|
+
].compact.join("\n")
|
324
|
+
end
|
262
325
|
|
263
|
-
|
264
|
-
|
326
|
+
def arel_formatting_left_right(condition, set_type = nil, cast: nil)
|
327
|
+
[
|
328
|
+
arel_formatting_value(nil, 'left', cast: cast),
|
329
|
+
'',
|
330
|
+
'if right.present?',
|
331
|
+
' ' + arel_formatting_value(nil, 'right', cast: cast),
|
332
|
+
" value = #{arel_convert_to_type('left', 'right', set_type)}",
|
333
|
+
'else',
|
334
|
+
' value = left',
|
335
|
+
'end',
|
336
|
+
'',
|
337
|
+
condition,
|
338
|
+
].join("\n")
|
339
|
+
end
|
265
340
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
341
|
+
## METHOD BUILDERS
|
342
|
+
def klass_current_on
|
343
|
+
arel_formatting_value("where(#{arel_check_condition(:contains)})")
|
344
|
+
end
|
270
345
|
|
271
|
-
|
272
|
-
|
346
|
+
def klass_current
|
347
|
+
[
|
348
|
+
"value = #{arel_sql_quote(current_getter)}",
|
349
|
+
"where(#{arel_check_condition(:contains)})",
|
350
|
+
].join("\n")
|
351
|
+
end
|
273
352
|
|
274
|
-
|
275
|
-
|
276
|
-
value =
|
277
|
-
|
353
|
+
def klass_not_current
|
354
|
+
[
|
355
|
+
"value = #{arel_sql_quote(current_getter)}",
|
356
|
+
"where.not(#{arel_check_condition(:contains)})",
|
357
|
+
].join("\n")
|
358
|
+
end
|
278
359
|
|
279
|
-
|
280
|
-
|
360
|
+
def klass_containing
|
361
|
+
arel_formatting_value("where(#{arel_attribute}.contains(value))")
|
362
|
+
end
|
281
363
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
value = ::Arel.sql(connection.quote(value)) unless value.respond_to?(:cast)
|
286
|
-
where(builder.real_arel_attribute.contains(value))
|
287
|
-
end
|
364
|
+
def klass_not_containing
|
365
|
+
arel_formatting_value("where.not(#{arel_attribute}.contains(value))")
|
366
|
+
end
|
288
367
|
|
289
|
-
|
290
|
-
|
368
|
+
def klass_overlapping
|
369
|
+
arel_formatting_left_right("where(#{arel_attribute}.overlaps(value))")
|
370
|
+
end
|
291
371
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
value = builder.arel_convert_to_type(value, right)
|
296
|
-
elsif !value.respond_to?(:cast)
|
297
|
-
value = ::Arel.sql(connection.quote(value))
|
298
|
-
end
|
372
|
+
def klass_not_overlapping
|
373
|
+
arel_formatting_left_right("where.not(#{arel_attribute}.overlaps(value))")
|
374
|
+
end
|
299
375
|
|
300
|
-
|
301
|
-
|
376
|
+
def klass_starting_after
|
377
|
+
arel_formatting_value("where((#{arel_start_at}).gt(value))")
|
378
|
+
end
|
302
379
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
condition -= builder.threshold_value
|
307
|
-
condition = condition.cast(:date) if builder.type.eql?(:daterange)
|
308
|
-
where(condition.gt(value))
|
309
|
-
end
|
380
|
+
def klass_starting_before
|
381
|
+
arel_formatting_value("where((#{arel_start_at}).lt(value))")
|
382
|
+
end
|
310
383
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
condition -= builder.threshold_value
|
315
|
-
condition = condition.cast(:date) if builder.type.eql?(:daterange)
|
316
|
-
where(condition.lt(value))
|
317
|
-
end
|
384
|
+
def klass_finishing_after
|
385
|
+
arel_formatting_value("where((#{arel_finish_at}).gt(value))")
|
386
|
+
end
|
318
387
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
condition += builder.threshold_value
|
323
|
-
condition = condition.cast(:date) if builder.type.eql?(:daterange)
|
324
|
-
where(condition.gt(value))
|
325
|
-
end
|
388
|
+
def klass_finishing_before
|
389
|
+
arel_formatting_value("where((#{arel_finish_at}).lt(value))")
|
390
|
+
end
|
326
391
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
condition += builder.threshold_value
|
331
|
-
condition = condition.cast(:date) if builder.type.eql?(:daterange)
|
332
|
-
where(condition.lt(value))
|
333
|
-
end
|
334
|
-
end
|
392
|
+
def klass_real_containing
|
393
|
+
arel_formatting_value("where(#{arel_real_attribute}.contains(value))")
|
394
|
+
end
|
335
395
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
value = ::Arel.sql(connection.quote(value)) unless value.respond_to?(:cast)
|
340
|
-
where(builder.arel_daterange.contains(value))
|
341
|
-
end
|
396
|
+
def klass_real_overlapping
|
397
|
+
arel_formatting_left_right("where(#{arel_real_attribute}.overlaps(value))")
|
398
|
+
end
|
342
399
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
where.not(builder.arel_daterange.contains(value))
|
347
|
-
end
|
400
|
+
def klass_real_starting_after
|
401
|
+
arel_formatting_value("where(#{arel_real_start_at}.gt(value))")
|
402
|
+
end
|
348
403
|
|
349
|
-
|
350
|
-
|
404
|
+
def klass_real_starting_before
|
405
|
+
arel_formatting_value("where(#{arel_real_start_at}.lt(value))")
|
406
|
+
end
|
351
407
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
value = builder.arel_convert_to_type(value, right, :daterange)
|
356
|
-
elsif !value.respond_to?(:cast)
|
357
|
-
value = ::Arel.sql(connection.quote(value))
|
358
|
-
end
|
408
|
+
def klass_real_finishing_after
|
409
|
+
arel_formatting_value("where(#{arel_real_finish_at}.gt(value))")
|
410
|
+
end
|
359
411
|
|
360
|
-
|
361
|
-
|
412
|
+
def klass_real_finishing_before
|
413
|
+
arel_formatting_value("where(#{arel_real_finish_at}.lt(value))")
|
414
|
+
end
|
362
415
|
|
363
|
-
|
364
|
-
|
416
|
+
def klass_containing_date
|
417
|
+
arel_formatting_value("where(#{arel_daterange}.contains(value))",
|
418
|
+
cast: :date)
|
419
|
+
end
|
365
420
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
elsif !value.respond_to?(:cast)
|
371
|
-
value = ::Arel.sql(connection.quote(value))
|
372
|
-
end
|
421
|
+
def klass_not_containing_date
|
422
|
+
arel_formatting_value("where.not(#{arel_daterange}.contains(value))",
|
423
|
+
cast: :date)
|
424
|
+
end
|
373
425
|
|
374
|
-
|
375
|
-
|
376
|
-
|
426
|
+
def klass_overlapping_date
|
427
|
+
arel_formatting_left_right("where(#{arel_daterange}.overlaps(value))",
|
428
|
+
:daterange, cast: :date)
|
429
|
+
end
|
430
|
+
|
431
|
+
def klass_not_overlapping_date
|
432
|
+
arel_formatting_left_right("where.not(#{arel_daterange}.overlaps(value))",
|
433
|
+
:daterange, cast: :date)
|
377
434
|
end
|
378
435
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
436
|
+
def klass_real_containing_date
|
437
|
+
arel_formatting_value("where(#{arel_daterange(true)}.contains(value))",
|
438
|
+
cast: :date)
|
439
|
+
end
|
383
440
|
|
384
|
-
|
385
|
-
|
386
|
-
|
441
|
+
def klass_real_overlapping_date
|
442
|
+
arel_formatting_left_right("where(#{arel_daterange(true)}.overlaps(value))",
|
443
|
+
:daterange, cast: :date)
|
444
|
+
end
|
387
445
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
public_send(builder.method_names[:current_on?], eval(builder.current_getter))
|
392
|
-
end
|
446
|
+
def instance_current?
|
447
|
+
"#{method_names[:current_on?]}(#{current_getter})"
|
448
|
+
end
|
393
449
|
|
394
|
-
|
395
|
-
|
396
|
-
|
450
|
+
def instance_current_on?
|
451
|
+
attr_value = threshold.present? ? method_names[:real] : attribute
|
452
|
+
default_value = default.inspect
|
453
|
+
[
|
454
|
+
"return #{default_value} if #{attr_value}.nil?",
|
455
|
+
"return #{default_value} if #{attr_value}.min.try(:infinite?)",
|
456
|
+
"return #{default_value} if #{attr_value}.max.try(:infinite?)",
|
457
|
+
"#{attr_value}.min < value && #{attr_value}.max > value",
|
458
|
+
].join("\n")
|
459
|
+
end
|
397
460
|
|
398
|
-
|
399
|
-
|
461
|
+
def instance_start
|
462
|
+
"#{attribute}&.min"
|
463
|
+
end
|
400
464
|
|
401
|
-
|
402
|
-
|
465
|
+
def instance_finish
|
466
|
+
"#{attribute}&.max"
|
467
|
+
end
|
403
468
|
|
404
|
-
|
405
|
-
|
406
|
-
|
469
|
+
def instance_real
|
470
|
+
left = method_names[:real_start]
|
471
|
+
right = method_names[:real_finish]
|
407
472
|
|
408
|
-
|
409
|
-
|
410
|
-
|
473
|
+
[
|
474
|
+
"left = #{left}",
|
475
|
+
"right = #{right}",
|
476
|
+
'return unless left || right',
|
477
|
+
'((left || -::Float::INFINITY)..(right || ::Float::INFINITY))',
|
478
|
+
].join("\n")
|
479
|
+
end
|
411
480
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
define_method(builder.method_names[:real_finish]) do
|
440
|
-
threshold = attr_threshold
|
441
|
-
threshold = public_send(threshold) if threshold.is_a?(Symbol)
|
442
|
-
result = public_send(attr)&.max.try(:+, threshold)
|
443
|
-
builder.type.eql?(:daterange) ? result&.to_date : result
|
444
|
-
end
|
445
|
-
end
|
446
|
-
end
|
481
|
+
def instance_real_start
|
482
|
+
suffix = type.eql?(:daterange) ? '.to_date' : ''
|
483
|
+
threshold_value = threshold.is_a?(Symbol) \
|
484
|
+
? threshold.to_s \
|
485
|
+
: threshold.to_i.to_s + '.seconds'
|
486
|
+
|
487
|
+
[
|
488
|
+
"return if #{method_names[:start]}.nil?",
|
489
|
+
"value = #{method_names[:start]}",
|
490
|
+
"value -= (#{threshold_value} || 0)",
|
491
|
+
"value#{suffix}"
|
492
|
+
].join("\n")
|
493
|
+
end
|
494
|
+
|
495
|
+
def instance_real_finish
|
496
|
+
suffix = type.eql?(:daterange) ? '.to_date' : ''
|
497
|
+
threshold_value = threshold.is_a?(Symbol) \
|
498
|
+
? threshold.to_s \
|
499
|
+
: threshold.to_i.to_s + '.seconds'
|
500
|
+
|
501
|
+
[
|
502
|
+
"return if #{method_names[:finish]}.nil?",
|
503
|
+
"value = #{method_names[:finish]}",
|
504
|
+
"value += (#{threshold_value} || 0)",
|
505
|
+
"value#{suffix}"
|
506
|
+
].join("\n")
|
447
507
|
end
|
448
508
|
end
|
449
509
|
end
|