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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60b025f100c6a19b97cabb4a557d747b14c2abd434ef5910f1cf002ddcb0608a
4
- data.tar.gz: e03521e2ae32e3058153ba938c3a406dca43295eabc6b367a766600cfe6af222
3
+ metadata.gz: 9288e68d9c81a85ddd04dbac7bda54ed629dc5bcbf3862828234b416646b31e1
4
+ data.tar.gz: 9776a4f2a9d025f4b30c1c41c91e04213d767f696879b1507d27b34210e87617
5
5
  SHA512:
6
- metadata.gz: b74a99561d1ad83726ae9c01584e9e5272ac7d488a5ba710fe0e277184276ecb5a858e5073928d3481a51505b8f1cd206853d0d87f2ddd4b9c6a87fae759fc2e
7
- data.tar.gz: addb5e51d7116df423933bdcadf112fa8abd47d8cf536e59bba5929e776ceb793df8ee4a2da015f2660e5b03bb2dcd998fa517cdffc4699c82cc89c2ae5d307e
6
+ metadata.gz: 105265c744ee14f48a2f410839f4c6d075ec1a38a739c83275a86caf34ff24a17cb1270f1e0f01c6cf601cab741a75abfdb64976179ac2e8fb379f5b85703a28
7
+ data.tar.gz: 9d33bef69465070fc629b524004664fca7e256cc7dcc1a6c975a2349aaf8e9d192748fd596015e08714957feda9f8dbc61225186f8d3b35cab513d405d7f7689
data/README.md CHANGED
@@ -1,13 +1,85 @@
1
- # Active Record Querier
2
-
3
- > class UserQuerier < Querier
4
- > > @active_record_class = ApplicationRecord
5
- def initialize user_name:, active:
6
- > > > @query_template = "SELECT * FROM users WHERE name = ${user_name} AND active = ${active/no_quote}"
7
- super
8
- > >
9
- > > end
10
- > >
11
- > end
12
-
13
- > UserQuerier.new(user_name: 'foo', active: true).select_all.to_struct
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
- class Querier
4
- PARAM_NAME_INDEX = 0
5
- PARAM_VALUE_INDEX = 1
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
- @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
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.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))
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
@@ -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.2
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: 2022-03-17 00:00:00.000000000 Z
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.3.9
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: []