query_helper 0.0.0 → 0.1.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: 9748c7b95202e187a62f4e889db450dbd6a406aac6f342d6fb0ae5f532e77f98
4
- data.tar.gz: bc3f05f3093a0730758d05d7fab7485960b4d0dfbd489982e3dbbe996f51fb15
3
+ metadata.gz: 93112cb7974e069da9db1dbf08d4a0f3511076a6991195e669747567f2d2caa7
4
+ data.tar.gz: a242199dcea3eaa67c50792cf0b76cbe9fc9826a81f231649c81120969923cc9
5
5
  SHA512:
6
- metadata.gz: 833e99acbbac34e55b2c99c03977c707792c1e1ed1b86d65a347d2184c01f96894cc5e41c40dab0e81f4b7463511ab63cd1ade1d9f7b36a6b689a9bfcece5df7
7
- data.tar.gz: 73ccb427fc984585951c099fd311ebe5f83ff20d927f6e196b69e0d5f899b7e55e42a027de87424c3e986b94e767aa3faceab106f8aacd07cae1341f6b78c79e
6
+ metadata.gz: ab24ee9a256fbb9282e3adbe79db6436235000a7be0e1f166b570d6b413c412a9f3e2d5b285938630671db1efadd7e4ed9bf13e4aa84326fb28efb060b4d2df7
7
+ data.tar.gz: c08788615159a1da5fb4a218095ca5b7497360dbf7a074522d9f5f46cdffcca96cb40b4b9ba27ce905112b909dae0de42e8682292af0f0840d807b32831456d0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- query_helper (0.0.0)
4
+ query_helper (0.1.0)
5
5
  activerecord (~> 5.0)
6
6
  activesupport (~> 5.0)
7
7
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  [![TravisCI](https://travis-ci.org/iserve-products/query_helper.svg?branch=master)](https://travis-ci.org/iserve-products/query_helper)
3
3
  [![Gem Version](https://badge.fury.io/rb/query_helper.svg)](https://badge.fury.io/rb/query_helper)
4
4
 
5
- Ruby Gem developed and used at Pattern to paginate, sort, filter, and include associations on sql and active record queries.
5
+ QueryHelper is a ruby gem used to paginate, sort, and filter your API calls in Ruby on Rails using URL params in your HTTP requests. It currently only supports Postgres.
6
6
 
7
7
  ## Installation
8
8
 
@@ -20,278 +20,118 @@ Or install it yourself as:
20
20
 
21
21
  $ gem install query_helper
22
22
 
23
- ## Use
23
+ ## Quick Use
24
24
 
25
- ### SQL Queries
25
+ ### Step 1: Update Base Controller to use the QueryHelper Concern
26
26
 
27
- #### Initialize
28
-
29
- To create a new sql query object run
30
-
31
- ```ruby
32
- QueryHelper::Sql.new(
33
- model:, # required
34
- query:, # required
35
- query_params: , # optional
36
- column_mappings: , # optional
37
- filters: , # optional
38
- sorts: , # optional
39
- page: , # optional
40
- per_page: , # optional
41
- single_record: , # optional, default: false
42
- associations: , # optional
43
- as_json_options: , # optional
44
- run: # optional, default: true
45
- )
46
- ```
47
-
48
- The following arguments are accepted when creating a new objects
49
-
50
- <table>
51
- <tr>
52
- <th>Argument</th>
53
- <th>Description</th>
54
- <th>Example</th>
55
- </tr>
56
- <tr>
57
- <td>model</td>
58
- <td>the model to run the query against</td>
59
- <td>
60
- <pre lang="ruby">
61
- Parent
62
- </pre>
63
- </td>
64
- </tr>
65
- <tr>
66
- <td>query</td>
67
- <td>the custom sql string to be executed</td>
68
- <td>
69
- <pre lang="ruby">
70
- 'select * from parents'
71
- </pre>
72
- </td>
73
- </tr>
74
- <tr>
75
- <td>query_params</td>
76
- <td>a hash of bind variables to be embedded into the sql query</td>
77
- <td>
78
- <pre lang="ruby">
79
- {
80
- age: 20,
81
- name: 'John'
82
- }
83
- </pre>
84
- </td>
85
- </tr>
86
- <tr>
87
- <td>column_mappings</td>
88
- <td>A hash that translates aliases to sql expressions</td>
89
- <td>
90
- <pre lang="ruby">
91
- {
92
- "age" => "parents.age"
93
- "children_count" => {
94
- sql_expression: "count(children.id)",
95
- aggregate: true
96
- }
97
- }
98
- </pre>
99
- </td>
100
- </tr>
101
- <tr>
102
- <td>filters</td>
103
- <td>a list of filters in the form of `{"comparate_alias"=>{"operator_code"=>"value"}}`</td>
104
- <td>
105
- <pre lang="ruby">
106
- {
107
- "age" => { "lt" => 100 },
108
- "children_count" => { "gt" => 0 }
109
- }
110
- </pre>
111
- </td>
112
- </tr>
113
- <tr>
114
- <td>sorts</td>
115
- <td>a comma separated string with a list of sort values</td>
116
- <td>
117
- <pre lang="ruby">
118
- "age:desc,name:asc:lowercase"
119
- </pre>
120
- </td>
121
- </tr>
122
- <tr>
123
- <td>page</td>
124
- <td>the page you want returned</td>
125
- <td>
126
- <pre lang="ruby">
127
- 5
128
- </pre>
129
- </td>
130
- </tr>
131
- <tr>
132
- <td>per_page</td>
133
- <td>the number of results per page</td>
134
- <td>
135
- <pre lang="ruby">
136
- 20
137
- </pre>
138
- </td>
139
- </tr>
140
- <tr>
141
- <td>single_record</td>
142
- <td>whether or not you expect the record to return a single result, if toggled, only the first result will be returned</td>
143
- <td>
144
- <pre lang="ruby">
145
- false
146
- </pre>
147
- </td>
148
- </tr>
149
- <tr>
150
- <td>associations</td>
151
- <td>a list of activerecord associations you'd like included in the payload </td>
152
- <td>
153
- <pre lang="ruby">
154
-
155
- </pre>
156
- </td>
157
- </tr>
158
- <tr>
159
- <td>as_json_options</td>
160
- <td>a list of as_json options you'd like run before returning the payload</td>
161
- <td>
162
- <pre lang="ruby">
163
-
164
- </pre>
165
- </td>
166
- </tr>
167
- <tr>
168
- <td>run</td>
169
- <td>whether or not you'd like to run the query on initilization</td>
170
- <td>
171
- <pre lang="ruby">
172
- false
173
- </pre>
174
- </td>
175
- </tr>
176
- </table>
177
-
178
- ### Active Record Queries
179
-
180
- To run an active record query execute
181
27
  ```ruby
182
- QueryHelper.run_active_record_query(active_record_call, query_helpers, valid_columns, single_record)
28
+ class ApplicationController < ActionController::API
29
+ include QueryHelper::QueryHelperConcern
30
+ before_action :create_query_helper
31
+ end
183
32
  ```
184
- active_record_call: Valid active record syntax (i.e. ```Object.where(state: 'Active')```)
185
- query_helpers: See docs below
186
- valid_columns: Default is []. Pass in an array of columns you want to allow sorting and filtering on.
187
- single_record: Default is false. Pass in true to format payload as a single object instead of a list of objects
188
33
 
34
+ Adding this code creates a `QueryHelper` object preloaded with pagination, filtering, sorting, and association information included in the URL. This object can be accessed by using the `@query_helper` instance variable from within your controllers.
189
35
 
190
- model: A valid ActiveRecord model
191
- query: A string containing your custom SQL query
192
- query_params: a symbolized hash of binds to be included in your SQL query
193
- query_helpers: See docs below
194
- valid_columns: Default is []. Pass in an array of columns you want to allow sorting and filtering on.
195
- single_record: Default is false. Pass in true to format payload as a single object instead of a list of objects
36
+ ### Step 2: Use QueryHelper to run active record and sql queries within your controller
196
37
 
197
- ## Query Helpers
198
- query_helpers is a symbolized hash passed in with information about pagination, associations, filtering and sorting.
199
-
200
- ### Pagination
201
- There are two pagination keys you can pass in as part of the query_helpers objects
38
+ #### Active Record Example
202
39
 
203
40
  ```ruby
204
- {
205
- page: 1,
206
- per_page: 20
207
- }
208
- ```
41
+ class ResourceController < ApplicationController
209
42
 
210
- If at least one of these keys is present, paginated results will be returned.
43
+ def index
44
+ @query_helper.update(
45
+ model: UserNotificationSetting,
46
+ query: "select * from resources r where r.user_id = :user_id",
47
+ bind_variables: { user_id: current_user().id }
48
+ )
211
49
 
212
- ### Sorting
213
- Sorting is controlled by the `sort` key in the query_helpers object
50
+ render json: @query_helper.results()
51
+ end
214
52
 
215
- ```ruby
216
- {
217
- sort: "column_name:sort_direction"
218
- }
53
+ end
219
54
  ```
220
- Sort direction can be either asc or desc. If you wish to lowercase string before sorting include the following:
55
+
56
+ #### Raw SQL Example
57
+
221
58
  ```ruby
222
- {
223
- sort: "name:desc:lowercase"
224
- }
59
+ class ResourceController < ApplicationController
60
+
61
+ def index
62
+ @query_helper.query = Resource.all
63
+ render json: @query_helper.results()
64
+ end
65
+
66
+ end
225
67
  ```
226
68
 
227
- ### Filtering
228
- Filtering is controlled by the `filter` object in the query_helpers hash
69
+ You can also use the `@query_helper.update()` method to update the QueryHelper with an ActiveRecord object
229
70
 
230
71
  ```ruby
231
- {
232
- filter: {
233
- "column_1" => {
234
- "gte" => 20,
235
- "lt" => 40
236
- },
237
- "column_2" => {
238
- "eql" => "my_string"
239
- },
240
- "column_3" => {
241
- "like" => "my_string%"
242
- },
243
- "column_4" => {
244
- "in" => "item1,item2,item3"
245
- }
246
- }
72
+ @query_helper.update(
73
+ query: Resource.all
74
+ )
247
75
  ```
248
76
 
249
- The following operator codes are valid
77
+ ### Step 3: Paginate, Sort, Filter, and Include Associations using URL params
250
78
 
251
- ```
252
- “gte”: >=
253
- “lte”: <=
254
- “gt”: >
255
- “lt”: <
256
- “eql”: =
257
- “noteql”: !=
258
- "like": like
259
- “in”: in
260
- “notin” not in
261
- “null”: “is null” or “is not null” (pass in true or false as the value)
262
- ```
79
+ #### Pagination
263
80
 
264
- ### Associations
81
+ `page=1`
265
82
 
266
- To include associated objects in the payload, pass in the following as part of the query_helpers hash:
83
+ `per_page=20`
267
84
 
268
- ```ruby
269
- {
270
- include: ['associated_object_1', 'associated_object_2']
271
- }
272
- ```
85
+ `http://www.example.com/resources?page=1&per_page=25`
273
86
 
274
- ### Example
87
+ #### Sorting
88
+
89
+ `sort=column:direction`
90
+
91
+ Single Sort: `http://www.example.com/resources?sort=resource_name:desc`
92
+
93
+ Multiple Sorts: `http://www.example.com/resources?sort=resource_name:desc,resource_age:asc`
94
+
95
+ Lowercase Sort: `http://www.example.com/resources?sort=resource_name:desc:lowercase`
96
+
97
+ #### Filtering
98
+
99
+ `filter[column][operator_code]=value`
100
+
101
+ Single Filter: `http://www.example.com/resources?filter[resource_age][gt]=50`
102
+
103
+ Multiple Filters: `http://www.example.com/resources?filter[resource_age][gt]=50&[resource_name][eql]=banana_resource`
104
+
105
+ Operator Code | SQL Operator
106
+ --- | ---
107
+ gte | >=
108
+ lte | <=
109
+ gt | >
110
+ lt | <
111
+ eql | =
112
+ noteql | !=
113
+ like | like
114
+ in | in
115
+ notin | not in
116
+ null | is null *or* is not null
117
+
118
+ Note: For the null operator code, toggle *is null* operator with true and *is not null* operator with false
119
+
120
+ #### Associations
121
+
122
+ Include ActiveRecord associations in the payload. The association must be defined in the model.
123
+
124
+ `include=association`
125
+
126
+ Single Association: `http://www.example.com/resources?include=child_resource`
127
+
128
+ Multiple Associations: `http://www.example.com/resources?include[]=child_resource&include[]=parent_resource`
275
129
 
276
- The following is an example of a query_helpers object that can be passed into the sql and active record methods
277
130
 
278
- ```ruby
279
- query_helpers = {
280
- page: 1,
281
- per_page: 20,
282
- sort: "name:desc"
283
- include: ["child"]
284
- filter: {
285
- "id" => {
286
- "gte" => 20,
287
- "lt" => 40
288
- }
289
- }
290
- ```
291
131
 
292
132
  ## Payload Formats
293
133
 
294
- The QueryHelper gem will return results in one of three formats
134
+ The QueryHelper gem will return the following payload
295
135
 
296
136
  ### Paginated List Payload
297
137
  ```json
@@ -330,30 +170,39 @@ The QueryHelper gem will return results in one of three formats
330
170
  }
331
171
  ```
332
172
 
333
- ### List Payload
334
- ```json
335
- {
336
- "data": [
337
- {
338
- "id": 1,
339
- "attribute_1": "string_attribute",
340
- "attribute_2": 12345,
341
- "attribute_3": 0.3423212
342
- },
343
- {
344
- "id": 2,
345
- "attribute_1": "string_attribute",
346
- "attribute_2": 12345,
347
- "attribute_3": 0.3423212
348
- },
349
- {
350
- "id": 3,
351
- "attribute_1": "string_attribute",
352
- "attribute_2": 12345,
353
- "attribute_3": 0.3423212
354
- },
355
- ]
356
- }
173
+ ## Advanced Options
174
+
175
+ ### Associations
176
+
177
+ You can preload additional and include additional associations in your payload besides what's defined in the `include` url parameter.
178
+
179
+ ```ruby
180
+ @query_helper.update(
181
+ associations: ['association1']
182
+ )
183
+ ```
184
+
185
+ ### as_json options
186
+
187
+ You can pass in additional as_json options to be included in the payload.
188
+
189
+ ```ruby
190
+ @query_helper.update(
191
+ as_json_options: { methods: [:last_ran_at] }
192
+ )
193
+ ```
194
+
195
+ ### Single Record Queries
196
+ If you only want to return a single result, but still want to be able to use some of the other functionality of QueryHelper, you can set `single_record` to true in the QueryHelper object.
197
+
198
+ ```ruby
199
+ @query_helper.single_record = true
200
+ ```
201
+ or
202
+ ```ruby
203
+ @query_helper.update(
204
+ single_record: true
205
+ )
357
206
  ```
358
207
 
359
208
  ### Single Record Payload
@@ -370,7 +219,7 @@ The QueryHelper gem will return results in one of three formats
370
219
 
371
220
  ## Contributing
372
221
 
373
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/query_helper. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
222
+ Bug reports and pull requests are welcome on GitHub at https://github.com/iserve_products/query_helper. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
374
223
 
375
224
  ## License
376
225
 
@@ -70,10 +70,13 @@ class QueryHelper
70
70
 
71
71
  def mofify_criterion
72
72
  # lowercase strings for comparison
73
- @criterion.downcase! if criterion.class == String && criterion.scan(/[a-zA-Z]/).any?
73
+ @criterion.downcase! if @criterion.class == String && @criterion.scan(/[a-zA-Z]/).any?
74
74
 
75
75
  # turn the criterion into an array for in and notin comparisons
76
- @criterion = criterion.split(",") if ["in", "notin"].include?(operator_code) && criterion.class == String
76
+ @criterion = @criterion.split(",") if ["in", "notin"].include?(@operator_code) && @criterion.class == String
77
+
78
+ # Add wildcards for like comparisons
79
+ @criterion = "%#{@criterion}%" if @operator_code == "like"
77
80
  end
78
81
 
79
82
  def modify_comparate
@@ -1,5 +1,4 @@
1
1
  require 'active_support/concern'
2
- require "query_helper/sql_filter"
3
2
 
4
3
  class QueryHelper
5
4
  module QueryHelperConcern
@@ -24,7 +23,7 @@ class QueryHelper
24
23
  end
25
24
 
26
25
  def create_query_helper_associations
27
-
26
+ QueryHelper::Associations.process_association_params(params[:include])
28
27
  end
29
28
 
30
29
  def query_helper_params
@@ -10,7 +10,8 @@ class QueryHelper
10
10
  where_clauses: nil,
11
11
  having_clauses: nil,
12
12
  order_by_clauses: nil,
13
- include_limit_clause: false
13
+ include_limit_clause: false,
14
+ additional_select_clauses: []
14
15
  )
15
16
  @parser = SqlParser.new(sql)
16
17
  @sql = @parser.sql.dup
@@ -18,23 +19,23 @@ class QueryHelper
18
19
  @having_clauses = having_clauses
19
20
  @order_by_clauses = order_by_clauses
20
21
  @include_limit_clause = include_limit_clause
22
+ @additional_select_clauses = additional_select_clauses
21
23
  end
22
24
 
23
25
  def build
24
26
  insert_having_clauses()
25
27
  insert_where_clauses()
26
- insert_total_count_select_clause()
28
+ insert_select_clauses()
27
29
  insert_order_by_and_limit_clause()
28
30
  @sql.squish
29
31
  end
30
32
 
31
33
  private
32
34
 
33
- def insert_total_count_select_clause
34
- return unless @include_limit_clause
35
- total_count_clause = " ,count(*) over () as _query_full_count "
36
- @sql.insert(@parser.insert_select_index, total_count_clause)
37
- # Potentially update parser here
35
+ def insert_select_clauses
36
+ total_count_clause = "count(*) over () as _query_full_count"
37
+ @additional_select_clauses << total_count_clause if @include_limit_clause
38
+ @sql.insert(@parser.insert_select_index, " , #{@additional_select_clauses.join(", ")} ") if @additional_select_clauses.length > 0
38
39
  end
39
40
 
40
41
  def insert_where_clauses
@@ -3,11 +3,12 @@ require "query_helper/invalid_query_error"
3
3
  class QueryHelper
4
4
  class SqlSort
5
5
 
6
- attr_accessor :column_maps
6
+ attr_accessor :column_maps, :select_strings
7
7
 
8
8
  def initialize(sort_string: "", column_maps: [])
9
9
  @sort_string = sort_string
10
10
  @column_maps = column_maps
11
+ @select_strings = []
11
12
  end
12
13
 
13
14
  def parse_sort_string
@@ -38,6 +39,8 @@ class QueryHelper
38
39
  case modifier
39
40
  when "lowercase"
40
41
  sql_expression = "lower(#{sql_expression})"
42
+ # When select distincts are used, the order by clause must be included in the select clause
43
+ @select_strings << sql_expression
41
44
  end
42
45
 
43
46
  sql_strings << "#{sql_expression} #{direction}"
@@ -1,3 +1,3 @@
1
1
  class QueryHelper
2
- VERSION = "0.0.0"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/query_helper.rb CHANGED
@@ -24,7 +24,7 @@ class QueryHelper
24
24
  page: nil, # define the page you want returned
25
25
  per_page: nil, # define how many results you want per page
26
26
  single_record: false, # whether or not you expect the record to return a single result, if toggled, only the first result will be returned
27
- associations: nil, # a list of activerecord associations you'd like included in the payload
27
+ associations: [], # a list of activerecord associations you'd like included in the payload
28
28
  as_json_options: nil, # a list of as_json options you'd like run before returning the payload
29
29
  custom_mappings: {}, # custom keyword => sql_expression mappings
30
30
  api_payload: false # Return the paginated payload or simply return the result array
@@ -52,10 +52,24 @@ class QueryHelper
52
52
  end
53
53
  end
54
54
 
55
- def update_query(query: nil, model:nil, bind_variables: {})
55
+ def update(
56
+ query: nil,
57
+ model: nil,
58
+ bind_variables: {},
59
+ filters: [],
60
+ associations: [],
61
+ as_json_options: nil,
62
+ single_record: nil,
63
+ custom_mappings: nil
64
+ )
56
65
  @model = model if model
57
66
  @query = query if query
58
67
  @bind_variables.merge!(bind_variables)
68
+ filters.each{ |f| add_filter(**f) }
69
+ @associations = @associations | associations
70
+ @single_record = single_record if single_record
71
+ @as_json_options = as_json_options if as_json_options
72
+ @custom_mappings = custom_mappings if custom_mappings
59
73
  end
60
74
 
61
75
  def add_filter(operator_code:, criterion:, comparate:)
@@ -68,6 +82,7 @@ class QueryHelper
68
82
 
69
83
  # Create column maps to be used by the filter and sort objects
70
84
  column_maps = create_column_maps()
85
+
71
86
  @sql_filter.column_maps = column_maps
72
87
  @sql_sort.column_maps = column_maps
73
88
 
@@ -83,7 +98,8 @@ class QueryHelper
83
98
  where_clauses: @sql_filter.where_clauses,
84
99
  having_clauses: @sql_filter.having_clauses,
85
100
  order_by_clauses: @sql_sort.parse_sort_string,
86
- include_limit_clause: @page && @per_page ? true : false
101
+ include_limit_clause: @page && @per_page ? true : false,
102
+ additional_select_clauses: @sql_sort.select_strings
87
103
  )
88
104
  @executed_query = manipulator.build()
89
105
  @results = @model.find_by_sql([@executed_query, @bind_variables]) # Execute Sql Query
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: query_helper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan McDaniel
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-07-23 00:00:00.000000000 Z
11
+ date: 2019-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler