querier 0.4.2 → 0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +85 -13
- data/lib/querier.rb +19 -65
- data/lib/querier_bkp.rb +0 -0
- data/lib/querier_bkp2.rb +105 -0
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9288e68d9c81a85ddd04dbac7bda54ed629dc5bcbf3862828234b416646b31e1
|
4
|
+
data.tar.gz: 9776a4f2a9d025f4b30c1c41c91e04213d767f696879b1507d27b34210e87617
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 105265c744ee14f48a2f410839f4c6d075ec1a38a739c83275a86caf34ff24a17cb1270f1e0f01c6cf601cab741a75abfdb64976179ac2e8fb379f5b85703a28
|
7
|
+
data.tar.gz: 9d33bef69465070fc629b524004664fca7e256cc7dcc1a6c975a2349aaf8e9d192748fd596015e08714957feda9f8dbc61225186f8d3b35cab513d405d7f7689
|
data/README.md
CHANGED
@@ -1,13 +1,85 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
1
|
+
# Querier Class for ActiveRecord Custom SQL Queries
|
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.
|
4
|
+
|
5
|
+
## Module: `QuerierDatasetExtensions`
|
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.
|
11
|
+
|
12
|
+
## Class: `Querier`
|
13
|
+
|
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:
|
15
|
+
|
16
|
+
### 1. Class Variable and Singleton Attribute
|
17
|
+
|
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.
|
19
|
+
|
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.
|
21
|
+
|
22
|
+
### 2. Attributes and Initialization
|
23
|
+
|
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.
|
26
|
+
|
27
|
+
### 3. Public Execution Methods
|
28
|
+
|
29
|
+
The `Querier` class provides several methods to execute SQL queries, wrapping the ActiveRecord connection methods:
|
30
|
+
|
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.
|
34
|
+
|
35
|
+
### 4. Filling Query Parameters (`fill_query_params`)
|
36
|
+
|
37
|
+
The private `fill_query_params` method is responsible for parameter interpolation within the `@query_template`.
|
38
|
+
|
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).
|
42
|
+
|
43
|
+
### Security Considerations
|
44
|
+
|
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.
|
46
|
+
|
47
|
+
### Error Handling
|
48
|
+
|
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.
|
50
|
+
|
51
|
+
### Query Template Separation
|
52
|
+
|
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.
|
54
|
+
|
55
|
+
### Interface Improvement
|
56
|
+
|
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.
|
58
|
+
|
59
|
+
## Example Usage
|
60
|
+
|
61
|
+
Below is an example of how to use the `Querier` class:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
class MyCustomQuery < Querier
|
65
|
+
QUERY_TEMPLATE = <<-END_TEMPLATE
|
66
|
+
SELECT * FROM users WHERE id = ${user_id}
|
67
|
+
END_TEMPLATE
|
68
|
+
|
69
|
+
def initialize(user_id)
|
70
|
+
@query_template = QUERY_TEMPLATE
|
71
|
+
super
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
query = MyCustomQuery.new(1)
|
76
|
+
result = query.select_one
|
77
|
+
puts result # => Returns the user record with id 1
|
78
|
+
```
|
79
|
+
|
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.
|
81
|
+
|
82
|
+
## Summary
|
83
|
+
|
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.
|
85
|
+
|
data/lib/querier.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
module QuerierDatasetExtensions
|
4
|
+
def as_hash = map(&:symbolize_keys)
|
5
|
+
def as_struct = map { |record| OpenStruct.new(record) }
|
6
|
+
end
|
6
7
|
|
8
|
+
class Querier
|
7
9
|
@active_record_class = ActiveRecord::Base
|
8
10
|
# based on rubocop's tips at: https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/ClassVars
|
9
11
|
# solution here: https://www.ruby-lang.org/en/documentation/faq/8/
|
@@ -19,75 +21,27 @@ class Querier
|
|
19
21
|
@query = fill_query_params
|
20
22
|
end
|
21
23
|
|
22
|
-
def execute
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
def
|
27
|
-
|
28
|
-
|
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
|
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)
|
53
31
|
|
54
32
|
private
|
55
33
|
|
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
34
|
def fill_query_params
|
77
35
|
query = @query_template.dup
|
78
36
|
|
79
|
-
@query_params.
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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))
|
37
|
+
@query_params.each do |param_name, param_value|
|
38
|
+
placeholder = "${#{param_name}}"
|
39
|
+
placeholder_no_quote = "${#{param_name}/no_quote}"
|
40
|
+
|
41
|
+
query.gsub!(placeholder, @active_record_class.connection.quote(param_value.to_s))
|
42
|
+
query.gsub!(placeholder_no_quote, param_value.to_s)
|
89
43
|
end
|
90
|
-
|
44
|
+
|
91
45
|
query
|
92
46
|
end
|
93
47
|
end
|
data/lib/querier_bkp.rb
CHANGED
File without changes
|
data/lib/querier_bkp2.rb
ADDED
@@ -0,0 +1,105 @@
|
|
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
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: querier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gedean Dias
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-10-04 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Active Record queries with variable number of params
|
14
14
|
email: gedean.dias@gmail.com
|
@@ -19,11 +19,12 @@ files:
|
|
19
19
|
- README.md
|
20
20
|
- lib/querier.rb
|
21
21
|
- lib/querier_bkp.rb
|
22
|
+
- lib/querier_bkp2.rb
|
22
23
|
homepage: https://github.com/gedean/querier
|
23
24
|
licenses:
|
24
25
|
- MIT
|
25
26
|
metadata: {}
|
26
|
-
post_install_message:
|
27
|
+
post_install_message:
|
27
28
|
rdoc_options: []
|
28
29
|
require_paths:
|
29
30
|
- lib
|
@@ -38,8 +39,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
38
39
|
- !ruby/object:Gem::Version
|
39
40
|
version: '0'
|
40
41
|
requirements: []
|
41
|
-
rubygems_version: 3.
|
42
|
-
signing_key:
|
42
|
+
rubygems_version: 3.5.21
|
43
|
+
signing_key:
|
43
44
|
specification_version: 4
|
44
45
|
summary: Active Record Querier
|
45
46
|
test_files: []
|