params_ready 0.0.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 +7 -0
- data/lib/arel/cte_name.rb +20 -0
- data/lib/params_ready.rb +36 -0
- data/lib/params_ready/builder.rb +140 -0
- data/lib/params_ready/error.rb +31 -0
- data/lib/params_ready/extensions/class_reader_writer.rb +33 -0
- data/lib/params_ready/extensions/collection.rb +43 -0
- data/lib/params_ready/extensions/delegation.rb +25 -0
- data/lib/params_ready/extensions/finalizer.rb +26 -0
- data/lib/params_ready/extensions/freezer.rb +49 -0
- data/lib/params_ready/extensions/hash.rb +46 -0
- data/lib/params_ready/extensions/late_init.rb +38 -0
- data/lib/params_ready/extensions/registry.rb +44 -0
- data/lib/params_ready/extensions/undefined.rb +15 -0
- data/lib/params_ready/format.rb +130 -0
- data/lib/params_ready/helpers/arel_builder.rb +68 -0
- data/lib/params_ready/helpers/conditional_block.rb +31 -0
- data/lib/params_ready/helpers/find_in_hash.rb +22 -0
- data/lib/params_ready/helpers/key_map.rb +176 -0
- data/lib/params_ready/helpers/memo.rb +42 -0
- data/lib/params_ready/helpers/options.rb +39 -0
- data/lib/params_ready/helpers/parameter_definer_class_methods.rb +39 -0
- data/lib/params_ready/helpers/parameter_storage_class_methods.rb +36 -0
- data/lib/params_ready/helpers/parameter_user_class_methods.rb +31 -0
- data/lib/params_ready/helpers/relation_builder_wrapper.rb +35 -0
- data/lib/params_ready/helpers/rule.rb +57 -0
- data/lib/params_ready/helpers/storage.rb +30 -0
- data/lib/params_ready/helpers/usage_rule.rb +18 -0
- data/lib/params_ready/input_context.rb +31 -0
- data/lib/params_ready/intent.rb +70 -0
- data/lib/params_ready/marshaller/array_marshallers.rb +132 -0
- data/lib/params_ready/marshaller/builder_module.rb +9 -0
- data/lib/params_ready/marshaller/collection.rb +165 -0
- data/lib/params_ready/marshaller/definition_module.rb +63 -0
- data/lib/params_ready/marshaller/hash_marshallers.rb +100 -0
- data/lib/params_ready/marshaller/hash_set_marshallers.rb +96 -0
- data/lib/params_ready/marshaller/parameter_module.rb +11 -0
- data/lib/params_ready/marshaller/polymorph_marshallers.rb +67 -0
- data/lib/params_ready/marshaller/tuple_marshallers.rb +103 -0
- data/lib/params_ready/ordering/column.rb +60 -0
- data/lib/params_ready/ordering/ordering.rb +276 -0
- data/lib/params_ready/output_parameters.rb +127 -0
- data/lib/params_ready/pagination/abstract_pagination.rb +18 -0
- data/lib/params_ready/pagination/cursor.rb +171 -0
- data/lib/params_ready/pagination/direction.rb +148 -0
- data/lib/params_ready/pagination/keyset_pagination.rb +254 -0
- data/lib/params_ready/pagination/keysets.rb +70 -0
- data/lib/params_ready/pagination/nulls.rb +31 -0
- data/lib/params_ready/pagination/offset_pagination.rb +130 -0
- data/lib/params_ready/pagination/tendency.rb +28 -0
- data/lib/params_ready/parameter/abstract_hash_parameter.rb +204 -0
- data/lib/params_ready/parameter/array_parameter.rb +197 -0
- data/lib/params_ready/parameter/definition.rb +264 -0
- data/lib/params_ready/parameter/hash_parameter.rb +63 -0
- data/lib/params_ready/parameter/hash_set_parameter.rb +101 -0
- data/lib/params_ready/parameter/parameter.rb +456 -0
- data/lib/params_ready/parameter/polymorph_parameter.rb +172 -0
- data/lib/params_ready/parameter/state.rb +132 -0
- data/lib/params_ready/parameter/tuple_parameter.rb +152 -0
- data/lib/params_ready/parameter/value_parameter.rb +182 -0
- data/lib/params_ready/parameter_definer.rb +14 -0
- data/lib/params_ready/parameter_user.rb +43 -0
- data/lib/params_ready/query/array_grouping.rb +68 -0
- data/lib/params_ready/query/custom_predicate.rb +102 -0
- data/lib/params_ready/query/exists_predicate.rb +103 -0
- data/lib/params_ready/query/fixed_operator_predicate.rb +77 -0
- data/lib/params_ready/query/grouping.rb +177 -0
- data/lib/params_ready/query/join_clause.rb +87 -0
- data/lib/params_ready/query/nullness_predicate.rb +71 -0
- data/lib/params_ready/query/polymorph_predicate.rb +77 -0
- data/lib/params_ready/query/predicate.rb +203 -0
- data/lib/params_ready/query/predicate_operator.rb +132 -0
- data/lib/params_ready/query/relation.rb +337 -0
- data/lib/params_ready/query/structured_grouping.rb +58 -0
- data/lib/params_ready/query/variable_operator_predicate.rb +125 -0
- data/lib/params_ready/query_context.rb +21 -0
- data/lib/params_ready/restriction.rb +252 -0
- data/lib/params_ready/result.rb +109 -0
- data/lib/params_ready/value/coder.rb +181 -0
- data/lib/params_ready/value/constraint.rb +198 -0
- data/lib/params_ready/value/custom.rb +56 -0
- data/lib/params_ready/value/validator.rb +68 -0
- metadata +181 -0
@@ -0,0 +1,203 @@
|
|
1
|
+
require_relative '../extensions/registry'
|
2
|
+
require_relative '../parameter/array_parameter'
|
3
|
+
require_relative '../parameter/hash_set_parameter'
|
4
|
+
require_relative '../helpers/arel_builder'
|
5
|
+
|
6
|
+
module ParamsReady
|
7
|
+
module Query
|
8
|
+
class AbstractPredicateBuilder < AbstractBuilder
|
9
|
+
module HavingType
|
10
|
+
def type(type_name, *args, **opts, &block)
|
11
|
+
name, altn = data_object_handles
|
12
|
+
builder = type_builder_instance(type_name, name, *args, altn: altn, **opts)
|
13
|
+
builder.instance_eval(&block) unless block.nil?
|
14
|
+
@definition.set_type builder.fetch
|
15
|
+
end
|
16
|
+
|
17
|
+
def type_builder_instance(type_name, name, *args, altn:, **opts)
|
18
|
+
AbstractPredicateBuilder.type(type_name)
|
19
|
+
.instance(name, *args, altn: altn, **opts)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module HavingAttribute
|
24
|
+
def associations(*arr)
|
25
|
+
arr.each do |name|
|
26
|
+
@definition.add_association name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def attribute(name: nil, expression: nil, &block)
|
31
|
+
expression = if expression
|
32
|
+
raise ParamsReadyError, 'Block unexpected' unless block.nil?
|
33
|
+
expression
|
34
|
+
else
|
35
|
+
raise ParamsReadyError, 'Expression unexpected' unless expression.nil?
|
36
|
+
block
|
37
|
+
end
|
38
|
+
@definition.set_attribute(name, expression)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
include HavingArelTable
|
43
|
+
|
44
|
+
extend Extensions::Registry
|
45
|
+
registry :types, as: :type, getter: true
|
46
|
+
register_type :value, Parameter::ValueParameterBuilder
|
47
|
+
register_type :array, Parameter::ArrayParameterBuilder
|
48
|
+
register_type :hash_set, Parameter::HashSetParameterBuilder
|
49
|
+
end
|
50
|
+
|
51
|
+
class AbstractPredicateDefinition < Parameter::AbstractDefinition
|
52
|
+
late_init :arel_table, obligatory: false, freeze: false
|
53
|
+
|
54
|
+
module HavingAttribute
|
55
|
+
def self.included(base)
|
56
|
+
base.collection :associations, :association
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_attribute(name, select_expression)
|
60
|
+
@attribute_name = name
|
61
|
+
@select_expression = select_expression
|
62
|
+
end
|
63
|
+
|
64
|
+
def attribute_name
|
65
|
+
@attribute_name || @name
|
66
|
+
end
|
67
|
+
|
68
|
+
def select_expression
|
69
|
+
@select_expression || attribute_name
|
70
|
+
end
|
71
|
+
|
72
|
+
def build_select_expression(arel_table, context)
|
73
|
+
arel_builder = Helpers::ArelBuilder.instance(select_expression, arel_table: @arel_table)
|
74
|
+
arel = arel_builder.to_arel(arel_table, context, self)
|
75
|
+
|
76
|
+
arel
|
77
|
+
end
|
78
|
+
|
79
|
+
def alias_select_expression(arel_table, context)
|
80
|
+
build_select_expression(arel_table, context).as(attribute_name.to_s)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module Predicate
|
86
|
+
module HavingChildren
|
87
|
+
def context_for_predicates(context)
|
88
|
+
intent_for_children(context)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
module HavingAssociations
|
93
|
+
def dig(record, associations)
|
94
|
+
associations.reduce(record) do |record, assoc|
|
95
|
+
next record if record.nil?
|
96
|
+
|
97
|
+
record.send assoc
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
module HavingAttribute
|
103
|
+
extend Forwardable
|
104
|
+
def_delegators :definition, :build_select_expression, :alias_select_expression
|
105
|
+
include HavingAssociations
|
106
|
+
|
107
|
+
def to_query(arel_table, context: Restriction.blanket_permission)
|
108
|
+
table = definition.arel_table || arel_table
|
109
|
+
select_expression = build_select_expression(table, context)
|
110
|
+
build_query(select_expression, context: context)
|
111
|
+
end
|
112
|
+
|
113
|
+
def context_for_predicates(context)
|
114
|
+
# We consider a an attribute having parameter atomic
|
115
|
+
# so it's permitted per se including its contents
|
116
|
+
context.permit_all
|
117
|
+
end
|
118
|
+
|
119
|
+
def test(record)
|
120
|
+
return nil unless is_definite?
|
121
|
+
|
122
|
+
attribute_name = definition.attribute_name
|
123
|
+
record = dig(record, definition.associations)
|
124
|
+
|
125
|
+
perform_test(record, attribute_name)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
module DelegatingPredicate
|
130
|
+
def self.included(base)
|
131
|
+
base.include Parameter::DelegatingParameter
|
132
|
+
end
|
133
|
+
|
134
|
+
def eligible_for_query?(_table, context)
|
135
|
+
return false unless context.permitted? self
|
136
|
+
|
137
|
+
is_definite?
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_query_if_eligible(arel_table, context:)
|
141
|
+
return unless eligible_for_query?(arel_table, context)
|
142
|
+
|
143
|
+
context = context_for_predicates(context)
|
144
|
+
to_query(arel_table, context: context)
|
145
|
+
end
|
146
|
+
attr_reader :data
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class PredicateRegistry
|
152
|
+
extend Extensions::Registry
|
153
|
+
|
154
|
+
registry :operator_names, as: :operator_by_name, name_method: :name
|
155
|
+
registry :operator_alt_names, as: :operator_by_alt_name, name_method: :altn
|
156
|
+
registry :predicates, as: :predicate, getter: true
|
157
|
+
|
158
|
+
def self.operator_by(identifier, format)
|
159
|
+
if format.alternative?
|
160
|
+
@@operator_alt_names[identifier]
|
161
|
+
else
|
162
|
+
@@operator_names[identifier]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.operator(identifier, format, collision_check = false)
|
167
|
+
operator = find_operator(identifier, format, collision_check)
|
168
|
+
if operator.nil? && !collision_check
|
169
|
+
raise("No such operator: #{identifier}")
|
170
|
+
end
|
171
|
+
|
172
|
+
operator
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.find_operator(identifier, format, collision_check)
|
176
|
+
operator = operator_by(identifier, format)
|
177
|
+
if operator.nil?
|
178
|
+
name_as_string = identifier.to_s
|
179
|
+
invertor = if format.alternative?
|
180
|
+
'n_'
|
181
|
+
else
|
182
|
+
'not_'
|
183
|
+
end
|
184
|
+
if !collision_check && name_as_string.start_with?(invertor)
|
185
|
+
bare_name = name_as_string[invertor.length..-1].to_sym
|
186
|
+
inverted = PredicateRegistry.operator(bare_name, format)
|
187
|
+
if inverted.nil?
|
188
|
+
nil
|
189
|
+
elsif inverted.inverse_of.nil?
|
190
|
+
Not.new(inverted)
|
191
|
+
else
|
192
|
+
operator(inverted.inverse_of, format)
|
193
|
+
end
|
194
|
+
else
|
195
|
+
nil
|
196
|
+
end
|
197
|
+
else
|
198
|
+
operator
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require_relative 'predicate'
|
2
|
+
|
3
|
+
module ParamsReady
|
4
|
+
module Query
|
5
|
+
class PredicateOperator
|
6
|
+
def self.dup
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.define_operator(name, altn, arel: nil, test: nil, inverse_of: nil)
|
11
|
+
define_singleton_method :name do
|
12
|
+
name
|
13
|
+
end
|
14
|
+
|
15
|
+
define_singleton_method :altn do
|
16
|
+
altn
|
17
|
+
end
|
18
|
+
|
19
|
+
if arel
|
20
|
+
define_singleton_method :to_query do |attribute, value|
|
21
|
+
attribute.send(arel, value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
if test
|
26
|
+
define_singleton_method :test do |record, attribute_name, value|
|
27
|
+
attribute = record.send attribute_name
|
28
|
+
attribute.send test, value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
define_singleton_method :inverse_of do
|
33
|
+
inverse_of
|
34
|
+
end
|
35
|
+
|
36
|
+
unless PredicateRegistry.operator(self.name, Format.instance(:backend), true).nil?
|
37
|
+
raise ParamsReadyError, "Operator name taken #{self.name}"
|
38
|
+
end
|
39
|
+
|
40
|
+
unless PredicateRegistry.operator(self.altn, Format.instance(:frontend), true).nil?
|
41
|
+
raise ParamsReadyError, "Operator altn taken #{self.name}"
|
42
|
+
end
|
43
|
+
|
44
|
+
PredicateRegistry.register_operator_by_name self
|
45
|
+
PredicateRegistry.register_operator_by_alt_name self
|
46
|
+
end
|
47
|
+
|
48
|
+
def altn
|
49
|
+
self.class.altn
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class In < PredicateOperator
|
54
|
+
define_operator :in, :in, arel: :in
|
55
|
+
|
56
|
+
def self.test(record, attribute_name, values)
|
57
|
+
attribute = record.send attribute_name
|
58
|
+
values.include? attribute
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
class ComparisonPredicateOperator < PredicateOperator; end
|
64
|
+
|
65
|
+
class Like < ComparisonPredicateOperator
|
66
|
+
define_operator :like, :lk
|
67
|
+
|
68
|
+
def self.to_query(attribute_name, value)
|
69
|
+
attribute_name.matches("%#{value}%")
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.test(record, attribute_name, value)
|
73
|
+
attribute = record.send attribute_name
|
74
|
+
result = Regexp.new(value, Regexp::IGNORECASE) =~ attribute
|
75
|
+
result.nil? ? false : true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Equal < ComparisonPredicateOperator
|
80
|
+
define_operator :equal, :eq, arel: :eq, test: :==
|
81
|
+
end
|
82
|
+
|
83
|
+
class NotEqual < ComparisonPredicateOperator
|
84
|
+
define_operator :not_equal, :neq, arel: :not_eq, test: :!=
|
85
|
+
end
|
86
|
+
|
87
|
+
class GreaterThan < ComparisonPredicateOperator
|
88
|
+
define_operator :greater_than, :gt, arel: :gt, test: :>, inverse_of: :less_than_or_equal
|
89
|
+
|
90
|
+
def self.test(record, attribute_name, value)
|
91
|
+
attribute = record.send attribute_name
|
92
|
+
attribute > value
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class LessThan < ComparisonPredicateOperator
|
97
|
+
define_operator :less_than, :lt, arel: :lt, test: :<, inverse_of: :greater_than_or_equal
|
98
|
+
end
|
99
|
+
|
100
|
+
class GraterThanOrEqual < ComparisonPredicateOperator
|
101
|
+
define_operator :greater_than_or_equal, :gteq, arel: :gteq, test: :>=, inverse_of: :less_than
|
102
|
+
end
|
103
|
+
|
104
|
+
class LessThanOrEqual < ComparisonPredicateOperator
|
105
|
+
define_operator :less_than_or_equal, :lteq, arel: :lteq, test: :<=, inverse_of: :greater_than
|
106
|
+
end
|
107
|
+
|
108
|
+
class Not
|
109
|
+
def initialize(operator)
|
110
|
+
@operator = operator
|
111
|
+
end
|
112
|
+
|
113
|
+
def name
|
114
|
+
"not_#{@operator.name}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def altn
|
118
|
+
"n#{@operator.altn}"
|
119
|
+
end
|
120
|
+
|
121
|
+
def test(*args)
|
122
|
+
result = @operator.test *args
|
123
|
+
!result
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_query(*args)
|
127
|
+
result = @operator.to_query(*args)
|
128
|
+
result.not
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,337 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require_relative 'structured_grouping'
|
3
|
+
require_relative 'join_clause'
|
4
|
+
require_relative '../pagination/offset_pagination'
|
5
|
+
require_relative '../pagination/keyset_pagination'
|
6
|
+
require_relative '../pagination/direction'
|
7
|
+
require_relative '../ordering/ordering'
|
8
|
+
|
9
|
+
module ParamsReady
|
10
|
+
module Query
|
11
|
+
class Relation < StructuredGrouping
|
12
|
+
module PageAccessors
|
13
|
+
def page_accessor(name, delegate = nil)
|
14
|
+
delegate ||= "#{name}_page"
|
15
|
+
|
16
|
+
define_method name do |*args|
|
17
|
+
send(delegate, *args)&.for_frontend
|
18
|
+
end
|
19
|
+
ruby2_keywords name
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.extended(mod)
|
23
|
+
mod.page_accessor :current
|
24
|
+
mod.page_accessor :first
|
25
|
+
mod.page_accessor :last
|
26
|
+
mod.page_accessor :previous
|
27
|
+
mod.page_accessor :next
|
28
|
+
mod.page_accessor :before
|
29
|
+
mod.page_accessor :after
|
30
|
+
mod.page_accessor :limit_at, :limited_at
|
31
|
+
mod.page_accessor :toggle, :toggled_order
|
32
|
+
mod.page_accessor :reorder, :reordered
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
extend PageAccessors
|
37
|
+
extend Forwardable
|
38
|
+
|
39
|
+
def_delegators :pagination, :offset, :limit, :num_pages, :page_no, :has_previous?, :has_next?, :has_page?
|
40
|
+
|
41
|
+
def child_is_definite?(name)
|
42
|
+
return false unless definition.has_child?(name)
|
43
|
+
return false if self[name].nil?
|
44
|
+
return false unless self[name].is_definite?
|
45
|
+
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def pagination
|
50
|
+
self[:pagination]
|
51
|
+
end
|
52
|
+
|
53
|
+
def ordering
|
54
|
+
self[:ordering]
|
55
|
+
end
|
56
|
+
|
57
|
+
def ordering_or_nil
|
58
|
+
return unless child_is_definite?(:ordering)
|
59
|
+
|
60
|
+
self[:ordering]
|
61
|
+
end
|
62
|
+
|
63
|
+
def new_offset(delta)
|
64
|
+
pagination.new_offset(delta)
|
65
|
+
end
|
66
|
+
|
67
|
+
def page(delta, count: nil)
|
68
|
+
return nil unless pagination.can_yield_page?(delta, count: count)
|
69
|
+
return self if delta == 0
|
70
|
+
|
71
|
+
new_offset = pagination.new_offset(delta)
|
72
|
+
|
73
|
+
update_in(new_offset, [:pagination, 0])
|
74
|
+
end
|
75
|
+
|
76
|
+
def current_page(count: nil)
|
77
|
+
page(0, count: count)
|
78
|
+
end
|
79
|
+
|
80
|
+
def first_page
|
81
|
+
value = pagination.first_page_value
|
82
|
+
update_in(value, [:pagination])
|
83
|
+
end
|
84
|
+
|
85
|
+
def last_page(count:)
|
86
|
+
value = pagination.last_page_value(count: count)
|
87
|
+
return if value.nil?
|
88
|
+
update_in(value, [:pagination])
|
89
|
+
end
|
90
|
+
|
91
|
+
def previous_page(delta = 1)
|
92
|
+
value = pagination.previous_page_value(delta)
|
93
|
+
return if value.nil?
|
94
|
+
update_in(value, [:pagination])
|
95
|
+
end
|
96
|
+
|
97
|
+
def next_page(delta = 1, count: nil)
|
98
|
+
value = pagination.next_page_value(delta, count: count)
|
99
|
+
return if value.nil?
|
100
|
+
page(delta, count: count)
|
101
|
+
end
|
102
|
+
|
103
|
+
def before_page(keyset)
|
104
|
+
tuple = { direction: :bfr, limit: limit, keyset: keyset }
|
105
|
+
update_in(tuple, [:pagination])
|
106
|
+
end
|
107
|
+
|
108
|
+
def after_page(keyset)
|
109
|
+
tuple = { direction: :aft, limit: limit, keyset: keyset }
|
110
|
+
update_in(tuple, [:pagination])
|
111
|
+
end
|
112
|
+
|
113
|
+
def limited_at(limit)
|
114
|
+
update_in(limit, [:pagination, pagination.limit_key])
|
115
|
+
end
|
116
|
+
|
117
|
+
def toggled_order(column)
|
118
|
+
new_order = ordering.toggled_order_value(column)
|
119
|
+
toggled = update_in(new_order, [:ordering])
|
120
|
+
toggled.update_in(0, [:pagination, 0])
|
121
|
+
end
|
122
|
+
|
123
|
+
def reordered(column, direction)
|
124
|
+
new_order = ordering.reordered_value(column, direction)
|
125
|
+
reordered = update_in(new_order, [:ordering])
|
126
|
+
reordered.update_in(0, [:pagination, 0])
|
127
|
+
end
|
128
|
+
|
129
|
+
def model_class(default_model_class)
|
130
|
+
default_model_class || definition.model_class
|
131
|
+
end
|
132
|
+
|
133
|
+
def arel_table(default_model_class)
|
134
|
+
model_class(default_model_class).arel_table
|
135
|
+
end
|
136
|
+
|
137
|
+
def perform_count(scope: nil, context: Restriction.blanket_permission)
|
138
|
+
scope ||= definition.model_class if definition.model_class_defined?
|
139
|
+
group = predicate_group(scope.arel_table, context: context)
|
140
|
+
relation = scope.where(group)
|
141
|
+
relation = perform_joins(relation, context)
|
142
|
+
relation.count
|
143
|
+
end
|
144
|
+
|
145
|
+
def keysets(limit, direction, keyset, scope: nil, context: Restriction.blanket_permission, &block)
|
146
|
+
model_class = scope || definition.model_class
|
147
|
+
group = predicate_group(model_class.arel_table, context: context)
|
148
|
+
relation = model_class.where(group)
|
149
|
+
relation = perform_joins(relation, context)
|
150
|
+
|
151
|
+
sql_literal = pagination.keysets_for_relation(relation, limit, direction, keyset, ordering, context, &block)
|
152
|
+
|
153
|
+
array = model_class.connection.execute(sql_literal.to_s).to_a
|
154
|
+
Pagination::Direction.instance(direction).keysets(keyset, array, &block)
|
155
|
+
end
|
156
|
+
|
157
|
+
def build_relation(scope: nil, include: [], context: Restriction.blanket_permission, paginate: true)
|
158
|
+
model_class = scope || definition.model_class
|
159
|
+
group = predicate_group(model_class.arel_table, context: context)
|
160
|
+
relation = model_class.where(group)
|
161
|
+
relation = relation.includes(*include) unless include.empty?
|
162
|
+
relation = perform_joins(relation, context)
|
163
|
+
|
164
|
+
order_and_paginate_relation(relation, context, paginate)
|
165
|
+
end
|
166
|
+
|
167
|
+
def perform_joins(relation, context)
|
168
|
+
return relation if definition.joins.empty?
|
169
|
+
|
170
|
+
sql = joined_tables(relation.arel_table, context).join_sources.map(&:to_sql).join(' ')
|
171
|
+
relation.joins(sql)
|
172
|
+
end
|
173
|
+
|
174
|
+
def to_count(model_class: nil, context: Restriction.blanket_permission)
|
175
|
+
model_class = model_class || definition.model_class
|
176
|
+
|
177
|
+
arel_table = joined_tables(model_class.arel_table, context)
|
178
|
+
|
179
|
+
group = self.predicate_group(model_class.arel_table, context: context)
|
180
|
+
|
181
|
+
query = if group.nil?
|
182
|
+
arel_table
|
183
|
+
else
|
184
|
+
arel_table.where(group)
|
185
|
+
end
|
186
|
+
query.project(arel_table[:id].count)
|
187
|
+
end
|
188
|
+
|
189
|
+
def build_select(model_class: nil, context: Restriction.blanket_permission, select_list: Arel.star, paginate: true)
|
190
|
+
arel_table, query = build_query(model_class, context)
|
191
|
+
query = order_and_paginate_query(query, arel_table, context, paginate)
|
192
|
+
query.project(select_list)
|
193
|
+
end
|
194
|
+
|
195
|
+
def build_keyset_query(limit, direction, keyset, model_class: nil, context: Restriction.blanket_permission)
|
196
|
+
arel_table, query = build_query(model_class, context)
|
197
|
+
pagination.select_keysets(query, limit, direction, keyset, ordering, arel_table, context)
|
198
|
+
end
|
199
|
+
|
200
|
+
def build_query(model_class, context)
|
201
|
+
arel_table = arel_table(model_class)
|
202
|
+
|
203
|
+
group = self.predicate_group(arel_table, context: context)
|
204
|
+
joined = joined_tables(arel_table, context)
|
205
|
+
|
206
|
+
query = if group.nil?
|
207
|
+
joined
|
208
|
+
else
|
209
|
+
joined.where(group)
|
210
|
+
end
|
211
|
+
|
212
|
+
[arel_table, query]
|
213
|
+
end
|
214
|
+
|
215
|
+
def order_if_applicable(arel_table, context)
|
216
|
+
if child_is_definite?(:ordering) && (context.permitted?(ordering) || ordering.required?)
|
217
|
+
ordering = self.ordering.to_arel(arel_table, context: context)
|
218
|
+
yield ordering if ordering.length > 0
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def paginate_if_applicable(paginate)
|
223
|
+
if paginate && child_is_definite?(:pagination)
|
224
|
+
pagination = self.pagination
|
225
|
+
yield pagination
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def order_and_paginate_relation(relation, context, paginate)
|
230
|
+
paginate_if_applicable(paginate) do |pagination|
|
231
|
+
relation = pagination.paginate_relation(relation, ordering_or_nil, context)
|
232
|
+
end
|
233
|
+
|
234
|
+
order_if_applicable(relation.arel_table, context) do |ordering|
|
235
|
+
relation = relation.order(ordering)
|
236
|
+
end
|
237
|
+
relation
|
238
|
+
end
|
239
|
+
|
240
|
+
def order_and_paginate_query(query, arel_table, context, paginate)
|
241
|
+
paginate_if_applicable(paginate) do |pagination|
|
242
|
+
query = pagination.paginate_query(query, ordering_or_nil, arel_table, context)
|
243
|
+
end
|
244
|
+
|
245
|
+
order_if_applicable(arel_table, context) do |ordering|
|
246
|
+
query = query.order(*ordering)
|
247
|
+
end
|
248
|
+
query
|
249
|
+
end
|
250
|
+
|
251
|
+
def joined_tables(proper_table, context)
|
252
|
+
definition.joins.reduce(proper_table) do |table, join|
|
253
|
+
join.to_arel(table, context, self)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
class RelationParameterBuilder < Builder
|
259
|
+
include GroupingLike
|
260
|
+
include Parameter::AbstractHashParameterBuilder::HashLike
|
261
|
+
include HavingModel
|
262
|
+
|
263
|
+
def self.instance(name, altn: nil)
|
264
|
+
new RelationDefinition.new(name, altn: altn)
|
265
|
+
end
|
266
|
+
|
267
|
+
register :relation
|
268
|
+
DEFAULT_LIMIT = 10
|
269
|
+
|
270
|
+
def paginate(limit = DEFAULT_LIMIT, max_limit = nil, method: :offset, &block)
|
271
|
+
case method
|
272
|
+
when :offset
|
273
|
+
raise ParamsReadyError, 'Block not expected' unless block.nil?
|
274
|
+
add Pagination::OffsetPaginationDefinition.new(0, limit, max_limit).finish
|
275
|
+
when :keyset
|
276
|
+
ordering_builder = @definition.init_ordering_builder(empty: true)
|
277
|
+
rcpb = Pagination::KeysetPaginationBuilder.new ordering_builder, limit, max_limit
|
278
|
+
add rcpb.build(&block)
|
279
|
+
else
|
280
|
+
raise "Unimplemented pagination method '#{method}'"
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def order(&proc)
|
285
|
+
ordering_builder = @definition.init_ordering_builder(empty: false)
|
286
|
+
ordering_builder.instance_eval(&proc) unless proc.nil?
|
287
|
+
ordering = ordering_builder.build
|
288
|
+
add ordering
|
289
|
+
end
|
290
|
+
|
291
|
+
def join_table(arel_table, type, &block)
|
292
|
+
join = Join.new arel_table, type, &block
|
293
|
+
join.freeze
|
294
|
+
@definition.add_join(join)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
class RelationDefinition < StructuredGroupingDefinition
|
299
|
+
late_init :model_class, obligatory: false, freeze: false, getter: false
|
300
|
+
collection :joins, :join
|
301
|
+
|
302
|
+
def model_class
|
303
|
+
raise ParamsReadyError, "Model class not set for #{name}" if @model_class.nil?
|
304
|
+
@model_class
|
305
|
+
end
|
306
|
+
|
307
|
+
def init_ordering_builder(empty:)
|
308
|
+
raise ParamsReadyError, 'Ordering already defined' if empty == true && !@ordering_builder.nil?
|
309
|
+
@ordering_builder ||= Ordering::OrderingParameterBuilder.instance
|
310
|
+
end
|
311
|
+
|
312
|
+
def arel_table
|
313
|
+
model_class.arel_table
|
314
|
+
end
|
315
|
+
|
316
|
+
def model_class_defined?
|
317
|
+
!@model_class.nil?
|
318
|
+
end
|
319
|
+
|
320
|
+
attr_reader :joins
|
321
|
+
|
322
|
+
def initialize(*args, **opts)
|
323
|
+
@joins = []
|
324
|
+
@ordering_builder = nil
|
325
|
+
super
|
326
|
+
end
|
327
|
+
|
328
|
+
def finish
|
329
|
+
raise ParamsReadyError, 'Ordering must be explicitly declared' if @ordering_builder&.open?
|
330
|
+
@ordering_builder = nil
|
331
|
+
super
|
332
|
+
end
|
333
|
+
|
334
|
+
parameter_class Relation
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|