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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9288e68d9c81a85ddd04dbac7bda54ed629dc5bcbf3862828234b416646b31e1
4
- data.tar.gz: 9776a4f2a9d025f4b30c1c41c91e04213d767f696879b1507d27b34210e87617
3
+ metadata.gz: 7a9d661e9272bd71551c319bb3bbb2937a0b385c9ded9cc01cff1f78a6fe1dd5
4
+ data.tar.gz: f9df13887085ef293df3f8630386e817542278615b867e84396653dbf986962b
5
5
  SHA512:
6
- metadata.gz: 105265c744ee14f48a2f410839f4c6d075ec1a38a739c83275a86caf34ff24a17cb1270f1e0f01c6cf601cab741a75abfdb64976179ac2e8fb379f5b85703a28
7
- data.tar.gz: 9d33bef69465070fc629b524004664fca7e256cc7dcc1a6c975a2349aaf8e9d192748fd596015e08714957feda9f8dbc61225186f8d3b35cab513d405d7f7689
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 Class for ActiveRecord Custom SQL Queries
1
+ # Querier: Execução Parametrizada de SQL com ActiveRecord
2
2
 
3
- This README explains the functionality and use of the `Querier` class, designed to facilitate the construction and execution of custom SQL queries in a Ruby on Rails environment using ActiveRecord.
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
- ## Module: `QuerierDatasetExtensions`
5
+ ## Instalação
6
6
 
7
- The `QuerierDatasetExtensions` module extends the results returned by SQL queries with utility methods for easy data transformation:
8
-
9
- - `as_hash`: Converts each record into a hash with symbolized keys, making it easier to work with the data.
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
- ## Class: `Querier`
11
+ ## Recursos Principais
13
12
 
14
- The `Querier` class is designed to simplify the execution of custom SQL queries on a specific ActiveRecord model. Below are the detailed explanations of its components:
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
- ### 1. Class Variable and Singleton Attribute
21
+ ## Uso Básico
17
22
 
18
- The class uses a singleton attribute (`@active_record_class`) to maintain a reference to the ActiveRecord class that will be used to execute queries.
23
+ ### Criando uma Query Simples
19
24
 
20
- Instead of using class variables (`@@`), which are generally discouraged due to inheritance and concurrency issues, the implementation uses the recommended approach of using singleton class attributes with `attr_accessor` defined within the `class << self` context.
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
- ### 2. Attributes and Initialization
33
+ query = UserQuerier.new(name: 'João', active: true)
34
+ users = query.select_all.as_hash
35
+ ```
23
36
 
24
- - **`@query_params`**: Stores the parameters that will be used to fill in the query template.
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
- ### 3. Public Execution Methods
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
- The `Querier` class provides several methods to execute SQL queries, wrapping the ActiveRecord connection methods:
45
+ products = ProductQuerier.new
46
+ .min(10.0)
47
+ .max(100.0)
48
+ .select_all
49
+ .as_struct
50
+ ```
30
51
 
31
- - `execute`: Executes the query without returning structured records.
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
- ### 4. Filling Query Parameters (`fill_query_params`)
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
- The private `fill_query_params` method is responsible for parameter interpolation within the `@query_template`.
65
+ ## Extensões de Dataset
38
66
 
39
- - There are two types of placeholders for parameters in the query:
40
- - `${param_name}`: This placeholder is replaced by the parameter value, escaped using the ActiveRecord connection's `quote` method to prevent SQL injection.
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
- ### Security Considerations
70
+ # Converte para hash com chaves simbólicas
71
+ result.as_hash
44
72
 
45
- - The use of `${param_name/no_quote}` should be done cautiously, as it may introduce vulnerabilities if used with unvalidated inputs. It is recommended to restrict its use to validated column names only.
73
+ # Converte para OpenStruct
74
+ result.as_struct
46
75
 
47
- ### Error Handling
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
- Currently, there is no error handling. Adding a `begin-rescue` block in the execution methods could improve robustness, handling issues such as connection failures or SQL syntax errors gracefully.
80
+ # Primeiro valor do primeiro registro
81
+ result.first_value # 'João'
82
+ ```
50
83
 
51
- ### Query Template Separation
84
+ ## Parâmetros Seguros vs Não-Quotados
52
85
 
53
- The `@query_template` is not explicitly defined in the provided code. It is recommended to use subclasses or clearly pass a query template, following good design practices to reuse query templates effectively.
86
+ ```ruby
87
+ # ${param} - Sempre quotado (seguro para valores do usuário)
88
+ @query_template = 'SELECT * FROM users WHERE email = ${email}'
54
89
 
55
- ### Interface Improvement
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
- Adding methods that accept queries directly as arguments could make the class more flexible and easier to use in scenarios where a dynamic query is needed.
94
+ ## Exemplo Avançado
58
95
 
59
- ## Example Usage
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
- Below is an example of how to use the `Querier` class:
119
+ # Uso
120
+ report = ReportQuerier.new
121
+ .last_30_days
122
+ .status('completed')
62
123
 
63
- ```ruby
64
- class MyCustomQuery < Querier
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
- def initialize(user_id)
70
- @query_template = QUERY_TEMPLATE
71
- super
72
- end
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
- query = MyCustomQuery.new(1)
76
- result = query.select_one
77
- puts result # => Returns the user record with id 1
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
- This example demonstrates how the `Querier` class can be used to create specific queries for different purposes, making it easier to organize and reuse SQL queries.
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
- ## Summary
150
+ ## Licença
83
151
 
84
- The `Querier` class provides a convenient way to build and execute custom SQL queries while keeping the process organized. It also enables centralized control for constructing and executing SQL within the context of ActiveRecord. The addition of the `QuerierDatasetExtensions` module further enhances the ease of transforming and using query results.
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 { |record| OpenStruct.new(record) }
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
- # based on rubocop's tips at: https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/ClassVars
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
- @query = fill_query_params
44
+ @query_template ||= self.class.query_template
45
+ validate_template!
22
46
  end
23
47
 
24
- def execute = @active_record_class.connection.execute(@query)
25
- def exec_query = @active_record_class.connection.exec_query(@query).extend(QuerierDatasetExtensions)
26
- def select_all = @active_record_class.connection.select_all(@query).extend(QuerierDatasetExtensions)
27
- def select_one = @active_record_class.connection.select_one(@query)
28
- def select_rows = @active_record_class.connection.select_rows(@query)
29
- def select_values = @active_record_class.connection.select_values(@query)
30
- def select_value = @active_record_class.connection.select_value(@query)
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 fill_query_params
35
- query = @query_template.dup
75
+ def connection = @active_record_class.connection
36
76
 
37
- @query_params.each do |param_name, param_value|
38
- placeholder = "${#{param_name}}"
39
- placeholder_no_quote = "${#{param_name}/no_quote}"
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
- query.gsub!(placeholder, @active_record_class.connection.quote(param_value.to_s))
42
- query.gsub!(placeholder_no_quote, param_value.to_s)
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
- query
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.5
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: 2024-10-04 00:00:00.000000000 Z
12
- dependencies: []
13
- description: Active Record queries with variable number of params
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
- post_install_message:
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.1'
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.5.21
43
- signing_key:
103
+ rubygems_version: 3.6.9
44
104
  specification_version: 4
45
- summary: Active Record Querier
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