querier 0.4.5 → 0.5.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/LICENSE +21 -0
- data/README.md +118 -50
- data/lib/querier.rb +86 -19
- metadata +73 -13
- data/lib/querier_bkp.rb +0 -93
- data/lib/querier_bkp2.rb +0 -105
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a9d661e9272bd71551c319bb3bbb2937a0b385c9ded9cc01cff1f78a6fe1dd5
|
4
|
+
data.tar.gz: f9df13887085ef293df3f8630386e817542278615b867e84396653dbf986962b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22de9fbcea5a9a08186a6f3743aeff6dc4da8e97ed4bcf392e7f7c54bb1cbb82782b05c6f1b67d21fcbff2dd2d7fc74ae18e6c99641eabce34bdbbaf283d6035
|
7
|
+
data.tar.gz: a2619f2ee3d5ab6a651208b93778c72298079575cd51bfebe60ced41fe708158498806ac8c16409bae4415e71773f65bde956bcd135e5ba1139e158a45f8b46b
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Gedean Dias
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -1,85 +1,153 @@
|
|
1
|
-
# Querier
|
1
|
+
# Querier: Execução Parametrizada de SQL com ActiveRecord
|
2
2
|
|
3
|
-
|
3
|
+
Gem Ruby para executar consultas SQL customizadas de forma segura e elegante usando ActiveRecord, com suporte para templates parametrizados, DSL fluente e formatos práticos de resultado.
|
4
4
|
|
5
|
-
##
|
5
|
+
## Instalação
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
- `as_struct`: Converts each record into an `OpenStruct` instance, allowing attribute access using dot notation.
|
7
|
+
```ruby
|
8
|
+
gem 'querier'
|
9
|
+
```
|
11
10
|
|
12
|
-
##
|
11
|
+
## Recursos Principais
|
13
12
|
|
14
|
-
|
13
|
+
- ✅ Substituição segura de parâmetros contra SQL injection
|
14
|
+
- ✅ DSL fluente para construção de queries
|
15
|
+
- ✅ Validação automática de parâmetros obrigatórios
|
16
|
+
- ✅ Métodos de conveniência para diferentes formatos de resultado
|
17
|
+
- ✅ Tratamento de erros aprimorado
|
18
|
+
- ✅ Suporte para EXPLAIN e COUNT
|
19
|
+
- ✅ Extensões úteis para resultados (as_hash, as_struct, pluck)
|
15
20
|
|
16
|
-
|
21
|
+
## Uso Básico
|
17
22
|
|
18
|
-
|
23
|
+
### Criando uma Query Simples
|
19
24
|
|
20
|
-
|
25
|
+
```ruby
|
26
|
+
class UserQuerier < Querier
|
27
|
+
def initialize(name:, active:)
|
28
|
+
@query_template = 'SELECT * FROM users WHERE name = ${name} AND active = ${active}'
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
21
32
|
|
22
|
-
|
33
|
+
query = UserQuerier.new(name: 'João', active: true)
|
34
|
+
users = query.select_all.as_hash
|
35
|
+
```
|
23
36
|
|
24
|
-
|
25
|
-
- **`@active_record_class`**: Initialized with the ActiveRecord class defined by the class or superclass. This allows the `Querier` class to be reused for different ActiveRecord models.
|
37
|
+
### Usando DSL Fluente
|
26
38
|
|
27
|
-
|
39
|
+
```ruby
|
40
|
+
class ProductQuerier < Querier
|
41
|
+
query_template 'SELECT * FROM products WHERE price BETWEEN ${min} AND ${max}'
|
42
|
+
param :min, :max
|
43
|
+
end
|
28
44
|
|
29
|
-
|
45
|
+
products = ProductQuerier.new
|
46
|
+
.min(10.0)
|
47
|
+
.max(100.0)
|
48
|
+
.select_all
|
49
|
+
.as_struct
|
50
|
+
```
|
30
51
|
|
31
|
-
|
32
|
-
- `exec_query`: Executes the query and returns results as an `ActiveRecord::Result`, extended with the `QuerierDatasetExtensions` module.
|
33
|
-
- `select_all`, `select_one`, `select_rows`, `select_values`, `select_value`: These methods provide different formats for returning the query results, such as all records, a single record, rows, values, or a specific value.
|
52
|
+
## Métodos de Execução
|
34
53
|
|
35
|
-
|
54
|
+
- `execute` - Executa a query (sem retorno estruturado)
|
55
|
+
- `exec_query` - Retorna `ActiveRecord::Result` com métodos extras
|
56
|
+
- `select_all` - Retorna array de hashes
|
57
|
+
- `select_one` - Retorna um único hash
|
58
|
+
- `select_rows` - Retorna array de arrays
|
59
|
+
- `select_values` - Retorna array de valores de uma coluna
|
60
|
+
- `select_value` - Retorna um único valor
|
61
|
+
- `to_sql` - Retorna a query SQL gerada
|
62
|
+
- `explain` - Retorna o plano de execução
|
63
|
+
- `count` - Retorna contagem de registros
|
36
64
|
|
37
|
-
|
65
|
+
## Extensões de Dataset
|
38
66
|
|
39
|
-
|
40
|
-
|
41
|
-
- `${param_name/no_quote}`: This placeholder substitutes the value directly without escaping, which is useful when the value is not susceptible to SQL injection (e.g., column names).
|
67
|
+
```ruby
|
68
|
+
result = query.select_all
|
42
69
|
|
43
|
-
|
70
|
+
# Converte para hash com chaves simbólicas
|
71
|
+
result.as_hash
|
44
72
|
|
45
|
-
|
73
|
+
# Converte para OpenStruct
|
74
|
+
result.as_struct
|
46
75
|
|
47
|
-
|
76
|
+
# Extrai valores de colunas específicas
|
77
|
+
result.pluck(:name) # ['João', 'Maria']
|
78
|
+
result.pluck(:name, :email) # [['João', 'j@email.com'], ['Maria', 'm@email.com']]
|
48
79
|
|
49
|
-
|
80
|
+
# Primeiro valor do primeiro registro
|
81
|
+
result.first_value # 'João'
|
82
|
+
```
|
50
83
|
|
51
|
-
|
84
|
+
## Parâmetros Seguros vs Não-Quotados
|
52
85
|
|
53
|
-
|
86
|
+
```ruby
|
87
|
+
# ${param} - Sempre quotado (seguro para valores do usuário)
|
88
|
+
@query_template = 'SELECT * FROM users WHERE email = ${email}'
|
54
89
|
|
55
|
-
|
90
|
+
# ${param/no_quote} - Não quotado (apenas para valores validados)
|
91
|
+
@query_template = 'SELECT * FROM ${table/no_quote} WHERE id IN (${ids/no_quote})'
|
92
|
+
```
|
56
93
|
|
57
|
-
|
94
|
+
## Exemplo Avançado
|
58
95
|
|
59
|
-
|
96
|
+
```ruby
|
97
|
+
class ReportQuerier < Querier
|
98
|
+
query_template <<~SQL
|
99
|
+
SELECT
|
100
|
+
DATE(created_at) as date,
|
101
|
+
COUNT(*) as total,
|
102
|
+
SUM(amount) as revenue
|
103
|
+
FROM orders
|
104
|
+
WHERE created_at BETWEEN ${start_date} AND ${end_date}
|
105
|
+
AND status = ${status}
|
106
|
+
GROUP BY DATE(created_at)
|
107
|
+
ORDER BY date DESC
|
108
|
+
SQL
|
109
|
+
|
110
|
+
param :start_date, :end_date, :status
|
111
|
+
|
112
|
+
def last_30_days
|
113
|
+
start_date(30.days.ago.to_date.to_s)
|
114
|
+
end_date(Date.today.to_s)
|
115
|
+
self
|
116
|
+
end
|
117
|
+
end
|
60
118
|
|
61
|
-
|
119
|
+
# Uso
|
120
|
+
report = ReportQuerier.new
|
121
|
+
.last_30_days
|
122
|
+
.status('completed')
|
62
123
|
|
63
|
-
|
64
|
-
|
65
|
-
QUERY_TEMPLATE = <<-END_TEMPLATE
|
66
|
-
SELECT * FROM users WHERE id = ${user_id}
|
67
|
-
END_TEMPLATE
|
124
|
+
# Visualizar SQL
|
125
|
+
puts report.to_sql
|
68
126
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
127
|
+
# Executar e formatar
|
128
|
+
results = report.select_all.as_struct
|
129
|
+
results.each do |day|
|
130
|
+
puts "#{day.date}: #{day.total} pedidos, R$ #{day.revenue}"
|
73
131
|
end
|
132
|
+
```
|
133
|
+
|
134
|
+
## Tratamento de Erros
|
74
135
|
|
75
|
-
|
76
|
-
|
77
|
-
|
136
|
+
```ruby
|
137
|
+
begin
|
138
|
+
query.select_all
|
139
|
+
rescue Querier::QueryError => e
|
140
|
+
puts "Erro na query: #{e.message}"
|
141
|
+
end
|
78
142
|
```
|
79
143
|
|
80
|
-
|
144
|
+
## Segurança
|
145
|
+
|
146
|
+
- Use `${param}` para todos os valores dinâmicos vindos do usuário
|
147
|
+
- Use `${param/no_quote}` apenas para valores pré-validados (nomes de colunas, listas processadas)
|
148
|
+
- A gem valida automaticamente se todos os parâmetros necessários foram fornecidos
|
81
149
|
|
82
|
-
##
|
150
|
+
## Licença
|
83
151
|
|
84
|
-
|
152
|
+
MIT
|
85
153
|
|
data/lib/querier.rb
CHANGED
@@ -1,16 +1,39 @@
|
|
1
1
|
require 'active_record'
|
2
|
+
require 'ostruct'
|
2
3
|
|
3
4
|
module QuerierDatasetExtensions
|
4
5
|
def as_hash = map(&:symbolize_keys)
|
5
|
-
def as_struct = map {
|
6
|
+
def as_struct = map { OpenStruct.new(it) }
|
7
|
+
def pluck(*columns) = map { |row| columns.one? ? row[columns.first.to_s] : columns.map { row[it.to_s] } }
|
8
|
+
def first_value = first&.values&.first
|
6
9
|
end
|
7
10
|
|
8
11
|
class Querier
|
12
|
+
class QueryError < StandardError; end
|
13
|
+
|
9
14
|
@active_record_class = ActiveRecord::Base
|
10
|
-
|
11
|
-
# solution here: https://www.ruby-lang.org/en/documentation/faq/8/
|
15
|
+
|
12
16
|
class << self
|
13
17
|
attr_accessor :active_record_class
|
18
|
+
|
19
|
+
def query_template(template = nil)
|
20
|
+
@query_template = template if template
|
21
|
+
@query_template
|
22
|
+
end
|
23
|
+
|
24
|
+
def param(*names)
|
25
|
+
@param_names = names
|
26
|
+
names.each do |name|
|
27
|
+
define_method(name) do |value|
|
28
|
+
@query_params[name] = value
|
29
|
+
self
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def param_names
|
35
|
+
@param_names || []
|
36
|
+
end
|
14
37
|
end
|
15
38
|
|
16
39
|
attr_reader :query, :query_template, :query_params
|
@@ -18,30 +41,74 @@ class Querier
|
|
18
41
|
def initialize(template_query_params = {})
|
19
42
|
@active_record_class = self.class.active_record_class || self.class.superclass.active_record_class
|
20
43
|
@query_params = template_query_params
|
21
|
-
@
|
44
|
+
@query_template ||= self.class.query_template
|
45
|
+
validate_template!
|
22
46
|
end
|
23
47
|
|
24
|
-
def execute =
|
25
|
-
def exec_query =
|
26
|
-
def select_all =
|
27
|
-
def select_one =
|
28
|
-
def select_rows =
|
29
|
-
def select_values =
|
30
|
-
def select_value =
|
48
|
+
def execute = wrap_errors { build_and_execute { connection.execute(@query) } }
|
49
|
+
def exec_query = wrap_errors { build_and_execute { connection.exec_query(@query).extend(QuerierDatasetExtensions) } }
|
50
|
+
def select_all = wrap_errors { build_and_execute { connection.select_all(@query).extend(QuerierDatasetExtensions) } }
|
51
|
+
def select_one = wrap_errors { build_and_execute { connection.select_one(@query) } }
|
52
|
+
def select_rows = wrap_errors { build_and_execute { connection.select_rows(@query) } }
|
53
|
+
def select_values = wrap_errors { build_and_execute { connection.select_values(@query) } }
|
54
|
+
def select_value = wrap_errors { build_and_execute { connection.select_value(@query) } }
|
55
|
+
|
56
|
+
def to_sql
|
57
|
+
build_query
|
58
|
+
@query
|
59
|
+
end
|
60
|
+
|
61
|
+
def explain
|
62
|
+
build_query
|
63
|
+
explained_query = "EXPLAIN #{@query}"
|
64
|
+
connection.select_all(explained_query).map { it['QUERY PLAN'] }.join("\n")
|
65
|
+
end
|
66
|
+
|
67
|
+
def count
|
68
|
+
build_query
|
69
|
+
count_query = "SELECT COUNT(*) FROM (#{@query}) AS count_query"
|
70
|
+
connection.select_value(count_query).to_i
|
71
|
+
end
|
31
72
|
|
32
73
|
private
|
33
74
|
|
34
|
-
def
|
35
|
-
query = @query_template.dup
|
75
|
+
def connection = @active_record_class.connection
|
36
76
|
|
37
|
-
|
38
|
-
|
39
|
-
|
77
|
+
def validate_template!
|
78
|
+
raise ArgumentError, "Query template não pode ser vazio" if @query_template.nil? || @query_template.strip.empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_and_execute
|
82
|
+
build_query
|
83
|
+
yield
|
84
|
+
end
|
40
85
|
|
41
|
-
|
42
|
-
|
86
|
+
def build_query
|
87
|
+
return if @query
|
88
|
+
|
89
|
+
required_params = extract_params
|
90
|
+
provided_params = @query_params.keys.map(&:to_s)
|
91
|
+
missing_params = required_params - provided_params
|
92
|
+
|
93
|
+
if missing_params.any? && self.class.param_names.empty?
|
94
|
+
raise ArgumentError, "Parâmetros faltando: #{missing_params.join(', ')}"
|
43
95
|
end
|
96
|
+
|
97
|
+
@query = @query_params.reduce(@query_template.dup) do |q, (param_name, param_value)|
|
98
|
+
quoted_value = connection.quote(param_value.to_s)
|
99
|
+
q.gsub!("${#{param_name}}", quoted_value)
|
100
|
+
q.gsub!("${#{param_name}/no_quote}", param_value.to_s)
|
101
|
+
q
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def extract_params
|
106
|
+
@query_template.scan(/\$\{(\w+)(?:\/no_quote)?\}/).map(&:first).uniq
|
107
|
+
end
|
44
108
|
|
45
|
-
|
109
|
+
def wrap_errors
|
110
|
+
yield
|
111
|
+
rescue ActiveRecord::StatementInvalid => e
|
112
|
+
raise QueryError, "Erro ao executar query: #{e.message}\nQuery: #{@query}"
|
46
113
|
end
|
47
114
|
end
|
metadata
CHANGED
@@ -1,30 +1,91 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: querier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gedean Dias
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
13
|
-
|
10
|
+
date: 2025-06-28 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: activerecord
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: ostruct
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: rspec
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3.12'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.12'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: rake
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '13.0'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '13.0'
|
68
|
+
description: Gem que simplifica a execução de consultas SQL customizadas usando ActiveRecord,
|
69
|
+
com suporte para templates parametrizados e formatos práticos de resultado. Permite
|
70
|
+
substituição segura de parâmetros e retorno de dados como hash ou struct.
|
14
71
|
email: gedean.dias@gmail.com
|
15
72
|
executables: []
|
16
73
|
extensions: []
|
17
74
|
extra_rdoc_files: []
|
18
75
|
files:
|
76
|
+
- LICENSE
|
19
77
|
- README.md
|
20
78
|
- lib/querier.rb
|
21
|
-
- lib/querier_bkp.rb
|
22
|
-
- lib/querier_bkp2.rb
|
23
79
|
homepage: https://github.com/gedean/querier
|
24
80
|
licenses:
|
25
81
|
- MIT
|
26
|
-
metadata:
|
27
|
-
|
82
|
+
metadata:
|
83
|
+
bug_tracker_uri: https://github.com/gedean/querier/issues
|
84
|
+
changelog_uri: https://github.com/gedean/querier/blob/main/CHANGELOG.md
|
85
|
+
documentation_uri: https://github.com/gedean/querier#readme
|
86
|
+
homepage_uri: https://github.com/gedean/querier
|
87
|
+
source_code_uri: https://github.com/gedean/querier
|
88
|
+
rubygems_mfa_required: 'true'
|
28
89
|
rdoc_options: []
|
29
90
|
require_paths:
|
30
91
|
- lib
|
@@ -32,15 +93,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
32
93
|
requirements:
|
33
94
|
- - ">="
|
34
95
|
- !ruby/object:Gem::Version
|
35
|
-
version: '3.
|
96
|
+
version: '3.4'
|
36
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
98
|
requirements:
|
38
99
|
- - ">="
|
39
100
|
- !ruby/object:Gem::Version
|
40
101
|
version: '0'
|
41
102
|
requirements: []
|
42
|
-
rubygems_version: 3.
|
43
|
-
signing_key:
|
103
|
+
rubygems_version: 3.6.9
|
44
104
|
specification_version: 4
|
45
|
-
summary:
|
105
|
+
summary: Execução segura de queries SQL parametrizadas com ActiveRecord
|
46
106
|
test_files: []
|
data/lib/querier_bkp.rb
DELETED
@@ -1,93 +0,0 @@
|
|
1
|
-
require 'active_record'
|
2
|
-
|
3
|
-
class Querier
|
4
|
-
PARAM_NAME_INDEX = 0
|
5
|
-
PARAM_VALUE_INDEX = 1
|
6
|
-
|
7
|
-
@active_record_class = ActiveRecord::Base
|
8
|
-
# based on rubocop's tips at: https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/ClassVars
|
9
|
-
# solution here: https://www.ruby-lang.org/en/documentation/faq/8/
|
10
|
-
class << self
|
11
|
-
attr_accessor :active_record_class
|
12
|
-
end
|
13
|
-
|
14
|
-
attr_reader :query_execution_count, :query_template, :query_params
|
15
|
-
|
16
|
-
def initialize(template_query_params = {})
|
17
|
-
@active_record_class = self.class.active_record_class || self.class.superclass.active_record_class
|
18
|
-
@query_execution_count = 0
|
19
|
-
@query_params = template_query_params
|
20
|
-
end
|
21
|
-
|
22
|
-
def execute
|
23
|
-
@query_execution_count += 1
|
24
|
-
@execution_cached_result = @active_record_class.connection.select_all(fill_query_params(query_template: @query_template,
|
25
|
-
query_params: @query_params)).map(&:symbolize_keys!)
|
26
|
-
end
|
27
|
-
|
28
|
-
def cached_result(format: :hash)
|
29
|
-
raise 'query not executed yet' if @query_execution_count.eql?(0)
|
30
|
-
|
31
|
-
case format
|
32
|
-
when :hash
|
33
|
-
@execution_cached_result
|
34
|
-
when :open_struct
|
35
|
-
hash_to_open_struct(dataset: @execution_cached_result)
|
36
|
-
else
|
37
|
-
raise 'invalid value type'
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def structured_results
|
42
|
-
hash_to_open_struct(dataset: execute)
|
43
|
-
end
|
44
|
-
|
45
|
-
def to_sql
|
46
|
-
fill_query_params(query_template: @query_template, query_params: @query_params)
|
47
|
-
end
|
48
|
-
|
49
|
-
def to_file
|
50
|
-
file_name = "querier #{Time.now.strftime '[%d-%m-%Y]-[%Hh %Mm %Ss]'}.sql"
|
51
|
-
File.write "tmp/#{file_name}", to_sql
|
52
|
-
end
|
53
|
-
|
54
|
-
def field_group_and_count(field_name:, sort_element_index: nil, reverse_sort: true)
|
55
|
-
count_result = cached_result(format: :open_struct).group_by(&field_name).map { |k, v| [k, v.count] }
|
56
|
-
|
57
|
-
unless sort_element_index.nil?
|
58
|
-
count_result = count_result.sort_by { |el| el[sort_element_index] }
|
59
|
-
count_result.reverse! if reverse_sort.eql? true
|
60
|
-
end
|
61
|
-
|
62
|
-
count_result
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
def hash_to_open_struct(dataset:)
|
68
|
-
dataset.map { |record| OpenStruct.new(record.symbolize_keys!) }
|
69
|
-
end
|
70
|
-
|
71
|
-
def get_param_value(raw_query_param:, quotefy_param: true)
|
72
|
-
# where's String#quote when we need it?
|
73
|
-
raw_query_param.instance_of?(String) && quotefy_param ? "'#{raw_query_param.to_s}'" : raw_query_param.to_s
|
74
|
-
end
|
75
|
-
|
76
|
-
def fill_query_params(query_template:, query_params:)
|
77
|
-
query = query_template
|
78
|
-
|
79
|
-
query_params.each_pair do |query_param|
|
80
|
-
query_param_name = query_param[PARAM_NAME_INDEX].to_s
|
81
|
-
|
82
|
-
query.gsub!(/\${#{query_param_name}}/,
|
83
|
-
get_param_value(raw_query_param: query_param[PARAM_VALUE_INDEX],
|
84
|
-
quotefy_param: true))
|
85
|
-
|
86
|
-
query.gsub!(/\${#{query_param_name}\/no_quote}/,
|
87
|
-
get_param_value(raw_query_param: query_param[PARAM_VALUE_INDEX],
|
88
|
-
quotefy_param: false))
|
89
|
-
end
|
90
|
-
|
91
|
-
query
|
92
|
-
end
|
93
|
-
end
|
data/lib/querier_bkp2.rb
DELETED
@@ -1,105 +0,0 @@
|
|
1
|
-
require 'active_record'
|
2
|
-
|
3
|
-
class Querier
|
4
|
-
PARAM_NAME_INDEX = 0
|
5
|
-
PARAM_VALUE_INDEX = 1
|
6
|
-
|
7
|
-
@active_record_class = ActiveRecord::Base
|
8
|
-
# based on rubocop's tips at: https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/ClassVars
|
9
|
-
# solution here: https://www.ruby-lang.org/en/documentation/faq/8/
|
10
|
-
class << self
|
11
|
-
attr_accessor :active_record_class
|
12
|
-
end
|
13
|
-
|
14
|
-
attr_reader :query, :query_template, :query_params
|
15
|
-
|
16
|
-
def initialize(template_query_params = {})
|
17
|
-
@active_record_class = self.class.active_record_class || self.class.superclass.active_record_class
|
18
|
-
@query_params = template_query_params
|
19
|
-
@query = fill_query_params
|
20
|
-
end
|
21
|
-
|
22
|
-
def execute
|
23
|
-
@active_record_class.connection.execute(@query)
|
24
|
-
end
|
25
|
-
|
26
|
-
def exec_query
|
27
|
-
decorate_dataset_format(@active_record_class.connection.exec_query(@query))
|
28
|
-
end
|
29
|
-
|
30
|
-
def select_all
|
31
|
-
decorate_dataset_format(@active_record_class.connection.select_all(@query))
|
32
|
-
end
|
33
|
-
|
34
|
-
def select_one
|
35
|
-
@active_record_class.connection.select_one(@query)
|
36
|
-
end
|
37
|
-
|
38
|
-
def select_sole
|
39
|
-
@active_record_class.connection.select_sole(@query)
|
40
|
-
end
|
41
|
-
|
42
|
-
def select_values
|
43
|
-
@active_record_class.connection.select_values(@query)
|
44
|
-
end
|
45
|
-
|
46
|
-
def select_rows
|
47
|
-
@active_record_class.connection.select_rows(@query)
|
48
|
-
end
|
49
|
-
|
50
|
-
def select_value
|
51
|
-
@active_record_class.connection.select_value(@query)
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def decorate_dataset_format(dataset)
|
57
|
-
def dataset.as_hash
|
58
|
-
map(&:symbolize_keys)
|
59
|
-
end
|
60
|
-
|
61
|
-
def dataset.as_struct
|
62
|
-
map { |record| OpenStruct.new(record) }
|
63
|
-
end
|
64
|
-
|
65
|
-
dataset
|
66
|
-
end
|
67
|
-
|
68
|
-
def get_param_value(raw_query_param:, quotefy_param: true)
|
69
|
-
if raw_query_param.instance_of?(String) && quotefy_param
|
70
|
-
@active_record_class.connection.quote(raw_query_param.to_s)
|
71
|
-
else
|
72
|
-
raw_query_param.to_s
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def fill_query_params
|
77
|
-
query = @query_template.dup
|
78
|
-
|
79
|
-
@query_params.each_pair do |query_param|
|
80
|
-
query_param_name = query_param[PARAM_NAME_INDEX].to_s
|
81
|
-
|
82
|
-
query.gsub!(/\${#{query_param_name}}/,
|
83
|
-
get_param_value(raw_query_param: query_param[PARAM_VALUE_INDEX],
|
84
|
-
quotefy_param: true))
|
85
|
-
|
86
|
-
query.gsub!(/\${#{query_param_name}\/no_quote}/,
|
87
|
-
get_param_value(raw_query_param: query_param[PARAM_VALUE_INDEX],
|
88
|
-
quotefy_param: false))
|
89
|
-
end
|
90
|
-
|
91
|
-
query
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
|
96
|
-
# def fill_query_params
|
97
|
-
# query = @query_template.dup
|
98
|
-
|
99
|
-
# @query_params.each do |param_name, param_value|
|
100
|
-
# placeholder = "${#{param_name}}"
|
101
|
-
# query.gsub!(placeholder, param_value.to_s)
|
102
|
-
# end
|
103
|
-
|
104
|
-
# query
|
105
|
-
# end
|