torque-postgresql 1.0.1 → 1.1.0
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/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
|