arel_rest 1.2.0 → 1.2.2
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/parser.rb +83 -0
- data/lib/predications/between_operator.rb +46 -0
- data/lib/predications/does_not_match_operator.rb +11 -0
- data/lib/predications/eq_operator.rb +12 -0
- data/lib/predications/gt_operator.rb +12 -0
- data/lib/predications/gteq_operator.rb +12 -0
- data/lib/predications/in_operator.rb +12 -0
- data/lib/predications/lt_operator.rb +12 -0
- data/lib/predications/lteq_operator.rb +12 -0
- data/lib/predications/matches_operator.rb +10 -0
- data/lib/predications/not_eq_operator.rb +13 -0
- data/lib/predications/not_in_operator.rb +12 -0
- data/lib/predications/order_operator.rb +13 -0
- data/lib/query.rb +145 -0
- metadata +23 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 248cd74204296375871f7e5de8358609ebf10042f25b2bd542fac98ccc69f332
|
4
|
+
data.tar.gz: ece38b072cff0716453e5a97b1a4f8a1296a3366e7ac06e64d3d6297f9dbbf18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54b64f1e8678c878a5152d9a24479602380f2a612483ac6370d9218e24e927c40f64a80bed6f4684390054c8b57862851aa5bd5712355c8af9c48aca74143293
|
7
|
+
data.tar.gz: 85054aa333add5050dcb2e2edbda9de70da7422d255a0f33f7df324d19da550f0972a9acab3687618f09cda9a4cb72b5a3d0187992be3d14477ddf688f2eb528
|
data/lib/parser.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'predications/eq_operator'
|
2
|
+
require 'predications/not_eq_operator'
|
3
|
+
require 'predications/gt_operator'
|
4
|
+
require 'predications/gteq_operator'
|
5
|
+
require 'predications/lt_operator'
|
6
|
+
require 'predications/lteq_operator'
|
7
|
+
require 'predications/in_operator'
|
8
|
+
require 'predications/not_in_operator'
|
9
|
+
require 'predications/between_operator'
|
10
|
+
require 'predications/matches_operator'
|
11
|
+
require 'predications/does_not_match_operator'
|
12
|
+
require 'predications/order_operator'
|
13
|
+
|
14
|
+
module ArelRest
|
15
|
+
class Parser
|
16
|
+
TEMPLATE_OPERATORS = {
|
17
|
+
"eq" => Predications::EqOperator,
|
18
|
+
"not_eq" => Predications::NotEqOperator,
|
19
|
+
"gt" => Predications::GtOperator,
|
20
|
+
"gteq" => Predications::GteqOperator,
|
21
|
+
"lt" => Predications::LtOperator,
|
22
|
+
"lteq" => Predications::LteqOperator,
|
23
|
+
"in" => Predications::InOperator,
|
24
|
+
"not_in" => Predications::NotInOperator,
|
25
|
+
"between" => Predications::BetweenOperator,
|
26
|
+
"matches" => Predications::MatchesOperator,
|
27
|
+
"does_not_match" => Predications::DoesNotMatchOperator,
|
28
|
+
}
|
29
|
+
|
30
|
+
# Recevi object query and return array with
|
31
|
+
# string query and value.
|
32
|
+
# Ex: query object: {attribute: "a", operator:"=", values: [1]}
|
33
|
+
# >> ["a = ?", [1]]
|
34
|
+
|
35
|
+
class OperatorNotFound < StandardError
|
36
|
+
attr_accessor :column
|
37
|
+
|
38
|
+
def initialize(column)
|
39
|
+
@column = column
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def message
|
44
|
+
"Operator #{@column} not found"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.build_pair_query_string_and_values(q)
|
49
|
+
operator = TEMPLATE_OPERATORS[q[:operator].to_s]
|
50
|
+
|
51
|
+
raise(Parser::OperatorNotFound, q[:operator].to_s) unless operator
|
52
|
+
|
53
|
+
operator.process(q)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.query_builder(query)
|
57
|
+
conector = query.keys.detect{|connector| [:or, :and].include?(connector.to_sym)}
|
58
|
+
pair_query_string_and_values = query[conector].map do |query_obj|
|
59
|
+
if query_obj.keys.map(&:to_sym).any?{|key| [:or,:and].include?(key)}
|
60
|
+
nested_query = query_builder(query_obj) # Recursive
|
61
|
+
builded = nested_query
|
62
|
+
else
|
63
|
+
builded = build_pair_query_string_and_values(query_obj)
|
64
|
+
end
|
65
|
+
|
66
|
+
builded
|
67
|
+
end.inject do |query_composited, query_node|
|
68
|
+
if query_composited.nil?
|
69
|
+
query_composited = query_node
|
70
|
+
else
|
71
|
+
query_composited = query_composited.send(conector, query_node)
|
72
|
+
end
|
73
|
+
query_composited
|
74
|
+
end
|
75
|
+
|
76
|
+
pair_query_string_and_values
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.parse_filter_to_arel(query)
|
80
|
+
query_builder(query)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ArelRest::Predications
|
2
|
+
class BetweenOperator
|
3
|
+
class << self
|
4
|
+
def process(query)
|
5
|
+
table = Arel::Table.new(query[:attribute].split(".")[0])
|
6
|
+
column = query[:attribute].split(".")[1]
|
7
|
+
|
8
|
+
range = Range.new(
|
9
|
+
transform_data_for_first(query[:values][0]),
|
10
|
+
transform_data_for_last(query[:values][1])
|
11
|
+
)
|
12
|
+
table[column].between(range)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def transform_data_for_first(first)
|
18
|
+
# Try obtein date in format iso8601
|
19
|
+
case first
|
20
|
+
when /^\d{4}-\d{2}-\d{2}$/ # somente com data
|
21
|
+
Time.use_zone(ArelRest.time_zone) { Time.zone.parse(first).beginning_of_day }
|
22
|
+
when /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/ # com data e hora
|
23
|
+
Time.use_zone(ArelRest.time_zone) { Time.zone.parse(first) }
|
24
|
+
when /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})$/ # com data, hora e fuso horário
|
25
|
+
Time.use_zone(ArelRest.time_zone) { Time.zone.parse(first) }
|
26
|
+
else
|
27
|
+
first
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def transform_data_for_last(last)
|
32
|
+
case last
|
33
|
+
when /^\d{4}-\d{2}-\d{2}$/ # somente com data
|
34
|
+
Time.use_zone(ArelRest.time_zone) { Time.zone.parse(last).beginning_of_day }
|
35
|
+
when /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/ # com data e hora
|
36
|
+
Time.use_zone(ArelRest.time_zone) { Time.zone.parse(last) }
|
37
|
+
when /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})$/ # com data, hora e fuso horário
|
38
|
+
Time.use_zone(ArelRest.time_zone) { Time.zone.parse(last) }
|
39
|
+
else
|
40
|
+
last
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ArelRest::Predications
|
2
|
+
class DoesNotMatchOperator
|
3
|
+
class << self
|
4
|
+
def process(query)
|
5
|
+
table = Arel::Table.new(query[:attribute].split(".")[0])
|
6
|
+
column = query[:attribute].split(".")[1]
|
7
|
+
table[column].does_not_match("%#{query[:values]}%")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ArelRest::Predications
|
2
|
+
class EqOperator
|
3
|
+
class << self
|
4
|
+
def process(query)
|
5
|
+
values = query[:values] == '=null=' ? nil : query[:values]
|
6
|
+
|
7
|
+
table = Arel::Table.new(query[:attribute].split(".")[0])
|
8
|
+
table[query[:attribute].split(".")[1]].eq(values)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ArelRest::Predications
|
2
|
+
class InOperator
|
3
|
+
class << self
|
4
|
+
def process(query)
|
5
|
+
values = query[:values].dup.map{|value| value==('=null=') ? nil : value}
|
6
|
+
|
7
|
+
table = Arel::Table.new(query[:attribute].split(".")[0])
|
8
|
+
table[query[:attribute].split(".")[1]].in(values)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ArelRest::Predications
|
2
|
+
class NotEqOperator
|
3
|
+
class << self
|
4
|
+
def process(query)
|
5
|
+
values = query[:values] == '=null=' ? nil : query[:values]
|
6
|
+
|
7
|
+
table = Arel::Table.new(query[:attribute].split(".")[0])
|
8
|
+
column = query[:attribute].split(".")[1]
|
9
|
+
table[column].not_eq(values)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ArelRest::Predications
|
2
|
+
class NotInOperator
|
3
|
+
class << self
|
4
|
+
def process(query)
|
5
|
+
values = query[:values].dup.map{|value| value==('=null=') ? nil : value}
|
6
|
+
|
7
|
+
table = Arel::Table.new(query[:attribute].split(".")[0])
|
8
|
+
table[query[:attribute].split(".")[1]].not_in(values)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ArelRest::Predications
|
2
|
+
class OrderOperator
|
3
|
+
class << self
|
4
|
+
def process(query)
|
5
|
+
table = Arel::Table.new(query[:attribute].split(".")[0])
|
6
|
+
column = query[:attribute].split(".")[1]
|
7
|
+
|
8
|
+
return table[column].asc if query[:values] == 'asc'
|
9
|
+
return table[column].desc if query[:values] == 'desc'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/query.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
#TODO: Criar uma forma de checar a syntax dos consultas. Ex: Não aceitar dimenssões sem <table_name>.<column>
|
3
|
+
module ArelRest
|
4
|
+
module Query
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
included do
|
7
|
+
WHITE_LIST_MENSURE_OP = {
|
8
|
+
"count" => :count,
|
9
|
+
"average" => :average,
|
10
|
+
"minimum" => :minimum,
|
11
|
+
"maximum" => :maximum,
|
12
|
+
"sum" => :sum
|
13
|
+
}
|
14
|
+
|
15
|
+
@relationship_tree = {}
|
16
|
+
|
17
|
+
def self.schema
|
18
|
+
@relationship_tree = yield
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.query(_rest_query)
|
22
|
+
# Conditional methods chain
|
23
|
+
self
|
24
|
+
.then do |method_chain|
|
25
|
+
_rest_query[:filters] ? method_chain.filter(_rest_query[:filters]) : method_chain
|
26
|
+
end
|
27
|
+
.then do |method_chain|
|
28
|
+
_rest_query[:sort] ? method_chain.order_by_dimensions(_rest_query[:sort]) : method_chain
|
29
|
+
end
|
30
|
+
.then do |method_chain|
|
31
|
+
_rest_query[:dimensions] ? method_chain.group_by_dimensions(_rest_query[:dimensions]) : method_chain
|
32
|
+
end
|
33
|
+
.then do |method_chain|
|
34
|
+
_rest_query[:page] ? method_chain.offset(_rest_query[:page]) : method_chain.offset(0)
|
35
|
+
end
|
36
|
+
.then do |method_chain|
|
37
|
+
_rest_query[:size] ? method_chain.limit(_rest_query[:size]) : method_chain.limit(100)
|
38
|
+
end
|
39
|
+
.then do |method_chain|
|
40
|
+
if _rest_query[:measures]
|
41
|
+
mensure_op = _rest_query[:measures].split('.')[0]
|
42
|
+
column = _rest_query[:measures].split('.')[1]
|
43
|
+
method_chain.send(WHITE_LIST_MENSURE_OP[mensure_op], column)
|
44
|
+
else
|
45
|
+
method_chain
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.filter(query)
|
51
|
+
return where({}) unless query.present?
|
52
|
+
query_nodes = ArelRest::Parser.parse_filter_to_arel(query)
|
53
|
+
tables_from_arel_node = collect_tables_from_arel(query_nodes).reject{|table| table == self.table_name}
|
54
|
+
|
55
|
+
paths = tables_from_arel_node.map do |table|
|
56
|
+
build_join_hash find_path_to_relation(relationship_tree, table)
|
57
|
+
end
|
58
|
+
where(query_nodes).joins(paths)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.group_by_dimensions(query)
|
62
|
+
return group({}) unless query.present?
|
63
|
+
paths = query
|
64
|
+
# TODO: Estudar syntaxe p/ entender se essa extração de nome de tabela é correta
|
65
|
+
.map{|dimension| dimension.split('.')[0]}
|
66
|
+
.map{|table| find_path_to_relation(relationship_tree, table)}.compact
|
67
|
+
.map{|path| build_join_hash path}
|
68
|
+
|
69
|
+
group(query).joins(paths)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.order_by_dimensions(query)
|
73
|
+
return order({}) unless query.present?
|
74
|
+
paths = query
|
75
|
+
# TODO: Estudar syntaxe p/ entender se essa extração de nome de tabela é correta
|
76
|
+
.keys
|
77
|
+
.map(&:to_s)
|
78
|
+
.map{|dimension| dimension.split('.')[0]}
|
79
|
+
.map{|table| find_path_to_relation(relationship_tree, table)}.compact
|
80
|
+
.map{|path| build_join_hash path}
|
81
|
+
|
82
|
+
order(
|
83
|
+
ArelRest::Predications::OrderOperator
|
84
|
+
.process({
|
85
|
+
attribute: query.keys.first.to_s,
|
86
|
+
values: query.values.first
|
87
|
+
})
|
88
|
+
).joins(paths)
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.relationship_tree
|
92
|
+
@relationship_tree
|
93
|
+
end
|
94
|
+
|
95
|
+
# Busca o caminho até uma associação específica (ex: :comments)
|
96
|
+
def self.find_path_to_relation(tree, target_table, current_path = [])
|
97
|
+
tree.each do |model_name, associations|
|
98
|
+
associations.each do |assoc_name, subtree|
|
99
|
+
table_name_assoc = model_name.to_s.constantize.reflect_on_association(assoc_name).table_name
|
100
|
+
path_with_assoc = current_path + [model_name, assoc_name]
|
101
|
+
return path_with_assoc + [subtree.keys.first] if (table_name_assoc == target_table)
|
102
|
+
|
103
|
+
result = find_path_to_relation(subtree, target_table, path_with_assoc)
|
104
|
+
return result if result
|
105
|
+
end
|
106
|
+
end
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
|
110
|
+
# Converte caminho [:posts, :comments] em hash encadeado { posts: :comments }
|
111
|
+
def self.build_join_hash(path)
|
112
|
+
path ||= []
|
113
|
+
join_path = path.select.with_index { |_, i| i.odd? }
|
114
|
+
join_path.reverse.reduce { |acc, key| { key => acc } }
|
115
|
+
end
|
116
|
+
|
117
|
+
# Recebe um nó(Arel::Node) e busca o nome de todas as tabelas a partir desse nó
|
118
|
+
# TODO: Talvez seja mais fácil obter toda a expressão e obter todas as tabelas a partir
|
119
|
+
# da string
|
120
|
+
def self.collect_tables_from_arel(node, tables = Set.new)
|
121
|
+
return tables unless node.is_a?(Arel::Nodes::Node) || node.is_a?(Arel::Attributes::Attribute)
|
122
|
+
|
123
|
+
# Se for atributo, pega o nome da tabela
|
124
|
+
if node.is_a?(Arel::Attributes::Attribute)
|
125
|
+
tables << node.relation.name.to_s
|
126
|
+
end
|
127
|
+
|
128
|
+
# Percorre children, expressions, left, right, grouping, etc
|
129
|
+
children = []
|
130
|
+
children += node.children if node.respond_to?(:children)
|
131
|
+
children << node.left if node.respond_to?(:left) && node.left
|
132
|
+
children << node.right if node.respond_to?(:right) && node.right
|
133
|
+
children << node.expr if node.respond_to?(:expr) && node.expr
|
134
|
+
children += node.expressions if node.respond_to?(:expressions)
|
135
|
+
|
136
|
+
children.compact.each do |child|
|
137
|
+
collect_tables_from_arel(child, tables)
|
138
|
+
end
|
139
|
+
|
140
|
+
tables
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arel_rest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luiz Filipe, Lucas Hunter, Leonardo Baptista, Rafael C. Abreu
|
@@ -14,28 +14,22 @@ dependencies:
|
|
14
14
|
name: activesupport
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '6'
|
20
|
-
- - "~>"
|
17
|
+
- - ">="
|
21
18
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
23
|
-
- - "
|
19
|
+
version: '6.0'
|
20
|
+
- - "<"
|
24
21
|
- !ruby/object:Gem::Version
|
25
|
-
version: '
|
22
|
+
version: '9.0'
|
26
23
|
type: :runtime
|
27
24
|
prerelease: false
|
28
25
|
version_requirements: !ruby/object:Gem::Requirement
|
29
26
|
requirements:
|
30
|
-
- - "
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '6'
|
33
|
-
- - "~>"
|
27
|
+
- - ">="
|
34
28
|
- !ruby/object:Gem::Version
|
35
|
-
version: '
|
36
|
-
- - "
|
29
|
+
version: '6.0'
|
30
|
+
- - "<"
|
37
31
|
- !ruby/object:Gem::Version
|
38
|
-
version: '
|
32
|
+
version: '9.0'
|
39
33
|
- !ruby/object:Gem::Dependency
|
40
34
|
name: rake
|
41
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -115,6 +109,20 @@ extensions: []
|
|
115
109
|
extra_rdoc_files: []
|
116
110
|
files:
|
117
111
|
- lib/arel_rest.rb
|
112
|
+
- lib/parser.rb
|
113
|
+
- lib/predications/between_operator.rb
|
114
|
+
- lib/predications/does_not_match_operator.rb
|
115
|
+
- lib/predications/eq_operator.rb
|
116
|
+
- lib/predications/gt_operator.rb
|
117
|
+
- lib/predications/gteq_operator.rb
|
118
|
+
- lib/predications/in_operator.rb
|
119
|
+
- lib/predications/lt_operator.rb
|
120
|
+
- lib/predications/lteq_operator.rb
|
121
|
+
- lib/predications/matches_operator.rb
|
122
|
+
- lib/predications/not_eq_operator.rb
|
123
|
+
- lib/predications/not_in_operator.rb
|
124
|
+
- lib/predications/order_operator.rb
|
125
|
+
- lib/query.rb
|
118
126
|
homepage: https://github.com/luizfilipecosta/arel_rest
|
119
127
|
licenses:
|
120
128
|
- MIT
|