toqua 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 +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +9 -0
- data/Gemfile +6 -0
- data/README.md +304 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/toqua/keyset_pagination.rb +63 -0
- data/lib/toqua/pagination.rb +44 -0
- data/lib/toqua/scoping.rb +46 -0
- data/lib/toqua/search.rb +45 -0
- data/lib/toqua/sorting.rb +25 -0
- data/lib/toqua/transform_params.rb +38 -0
- data/lib/toqua/version.rb +3 -0
- data/lib/toqua.rb +15 -0
- data/toqua.gemspec +33 -0
- metadata +185 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 43a5f057196fff4c8cd5b4b182eae2e1f53ea7b7
|
4
|
+
data.tar.gz: db0835906e1e11b74eec8dda63519326684be04a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a2beeb545bf067166984712a900ecbce55e4ece2828bb844b4aab65f27950990856d4f87e53737522613f0ae1ef2f6d6155ac9868a7662ee9e219071f5784e53
|
7
|
+
data.tar.gz: bd3b513750b11822c272e037be436126f9c9a209a2fd7821680fb3ce7d138c22b71aefca3253c6e8674a22d018fcdae5aec66e7a30d864c7e42a3fa22355e9a0
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,304 @@
|
|
1
|
+
# Toqua
|
2
|
+
|
3
|
+
[](https://travis-ci.org/rogercampos/toqua)
|
4
|
+
|
5
|
+
Collection of controller utilities for rails applications. Created with the intention of bringing back most of the nice things
|
6
|
+
about inherited resources, but in a more simple and explicit way.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'toqua'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install toqua
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
This library contain different tools that can be used independently, described below.
|
27
|
+
|
28
|
+
### Transform params
|
29
|
+
|
30
|
+
Use this to change the value of a `params` key, for example:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class MyController < ApplicationController
|
34
|
+
include Toqua::TransformParams
|
35
|
+
transform_params(:q) { |v| JSON.parse(v) }
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
This would transform the value of `params[:q]` from a raw string into its json representation. The new value is whatever returns your block. Also works on nested keys:
|
40
|
+
|
41
|
+
`transform_params(user: :q) {|v| DataManipulation.change(v) }`
|
42
|
+
|
43
|
+
Or more levels of nesting:
|
44
|
+
|
45
|
+
`transform_params(user: {data: :q}) {|v| DataManipulation.change(v) }`
|
46
|
+
|
47
|
+
|
48
|
+
### Scoping
|
49
|
+
|
50
|
+
This allows you to further refine an `ActiveRecord::Relation`, optionally depending on conditionals. For example:
|
51
|
+
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class MyController < ApplicationController
|
55
|
+
include Toqua::Scoping
|
56
|
+
|
57
|
+
scope {|s| s.where(parent_id: params[:parent_id])}
|
58
|
+
|
59
|
+
def index
|
60
|
+
@elements = apply_scopes(Element)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
The `scope` definitions are lambdas that receive an `ActiveRecord::Relation` as parameter and must return another
|
66
|
+
`AR::Relation`. They are chained in the order they're defined. To use them, you explicitly call `apply_scopes` with the initial
|
67
|
+
argument.
|
68
|
+
|
69
|
+
You can use `:if` or `:unless` to conditionally choose if execute the scope or not. Examples:
|
70
|
+
|
71
|
+
`scope(if: :show_all?) { |s| s.includes(:a, :b) }`
|
72
|
+
|
73
|
+
This will call the method `show_all?` defined in the controller and their return value (truthy or falsey) will indicate if the scope
|
74
|
+
applies or not. You can also use anything that responds to `:call` directly, ex:
|
75
|
+
|
76
|
+
`scope(if: -> { false }) { |s| s.all }`
|
77
|
+
|
78
|
+
Finally, you can also condition the scope execution based on the action on the controller:
|
79
|
+
|
80
|
+
`scope(only: :index) { |s| s.includes(:a, :b) }`
|
81
|
+
|
82
|
+
This is the foundation used to build searching, sorting and pagination over an `AR::Relation`.
|
83
|
+
|
84
|
+
Used as an independent tool, it provides a way to define scopes used by multiple actions in the same place (authorization, eager loading, etc.).
|
85
|
+
|
86
|
+
|
87
|
+
### Pagination
|
88
|
+
|
89
|
+
This tool can be used to paginate collections using [Kaminari](https://github.com/kaminari/kaminari), providing some additional useful things. Example of basic usage:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
class MyController < ApplicationController
|
93
|
+
include Toqua::Pagination
|
94
|
+
|
95
|
+
paginate
|
96
|
+
|
97
|
+
def index
|
98
|
+
@elements = apply_scopes(Element)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
As the `paginate` method uses scoping, you can pass the options of `:if, :unless and :action` that will get forwarded to the `scope` method, allowing
|
104
|
+
you to conditionally decide when to paginate. Ex:
|
105
|
+
|
106
|
+
`paginate(only: :index)`
|
107
|
+
|
108
|
+
Or, to paginate only on html but not xlsx format:
|
109
|
+
|
110
|
+
`paginate(unless: :xlsx?)`
|
111
|
+
|
112
|
+
The names of the url parameters used to identify the current page and the number of results per page are `page` and `per_page` by default, but can be changed using:
|
113
|
+
|
114
|
+
`paginate(page_key: "page", per_page_key: "per_page")`
|
115
|
+
|
116
|
+
The number of results in each page can be controlled with the `:per` option:
|
117
|
+
|
118
|
+
`paginate(per: 100)`
|
119
|
+
|
120
|
+
The last option available is `:headers`. If used, 3 additional headers will be attached into the response allowing you to know info about the pagination of the collection. This is useful for API clients. Ex:
|
121
|
+
|
122
|
+
`paginate(headers: true)`
|
123
|
+
|
124
|
+
The response will include the following headers:
|
125
|
+
|
126
|
+
- 'X-Pagination-Total': Total number of elements in the collection without pagination
|
127
|
+
- 'X-Pagination-Per-Page': Number of elements per page
|
128
|
+
- 'X-Pagination-Page': Number of the current page
|
129
|
+
|
130
|
+
Finally, the method `paginated?` available in both the controller and the views will tell you if the collection has been paginated or not.
|
131
|
+
|
132
|
+
|
133
|
+
### Search
|
134
|
+
|
135
|
+
Small utility to help in the implementation of searching, using [Doure](https://github.com/rogercampos/doure) as a way to filter an AR model. Given that you have a model with filters defined, ex:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
class Post < ApplicationRecord
|
139
|
+
extend Doure::Filterable
|
140
|
+
filter_class PostFilter
|
141
|
+
end
|
142
|
+
|
143
|
+
class PostFilter
|
144
|
+
cont_filter(:title)
|
145
|
+
cont_filter(:slug)
|
146
|
+
present_filter(:scheduled_at)
|
147
|
+
eq_filter(:id)
|
148
|
+
filter(:category_id_eq) { |s, value| s.joins(:post_categories).where(post_categories: {category_id: value}) }
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
You can setup searching in the controller using:
|
153
|
+
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
class PostsController < ApplicationController
|
157
|
+
include Toqua::Search
|
158
|
+
|
159
|
+
searchable
|
160
|
+
|
161
|
+
def index
|
162
|
+
@elements = apply_scopes(Post)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
The parameter used to represent the search criteria is `:q`.
|
168
|
+
|
169
|
+
The method `search_params` will give you a hash representing the contents of `:q`, which is the current search criteria, for example:
|
170
|
+
|
171
|
+
`{title_cont: "Air", category_id_eq: "12", slug_cont: "", scheduled_at_present: "", id_eq: ""}`
|
172
|
+
|
173
|
+
The method `active_search_params` will give you only the search parameters containing some value:
|
174
|
+
|
175
|
+
`{title_cont: "Air", category_id_eq: "12"}`
|
176
|
+
|
177
|
+
The method `search_object`, available in the view, gives an `ActiveModel` like object stuffed with the current `search_params`, so you can use that as the object
|
178
|
+
of the search form to automatically pre-fill all the search inputs with their current value. Ex:
|
179
|
+
|
180
|
+
```slim
|
181
|
+
= form_for search_object do |f|
|
182
|
+
= f.input :title_cont
|
183
|
+
= f.input :category_id_eq, collection: ...
|
184
|
+
```
|
185
|
+
|
186
|
+
The method `searching?` will tell you if there's a current search or not.
|
187
|
+
|
188
|
+
Finally, you can define a `default_search_params` method in the controller to setup default search criteria:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
def default_search_params
|
192
|
+
{ visible_by_role: "editor" }
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
|
197
|
+
|
198
|
+
### Sorting
|
199
|
+
|
200
|
+
The sorting utility allows you to sort the collection, using the parameter `s` in the url with a format like `title+asc` or `title+desc`. Usage example:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
class PostsController < ApplicationController
|
204
|
+
include Toqua::Sorting
|
205
|
+
|
206
|
+
sorting
|
207
|
+
|
208
|
+
def index
|
209
|
+
@elements = apply_scopes(Post)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
```
|
213
|
+
|
214
|
+
A helper to create sorting links easily is not directly provided by the gem, but can be something like this:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
def sort_link(name, label = nil, opts = {})
|
218
|
+
label ||= name.to_s.humanize
|
219
|
+
current_attr_name, current_direction = params[:s].present? && params[:s].split("+").map(&:strip)
|
220
|
+
|
221
|
+
next_direction = opts.fetch(:default_order, current_direction == "asc" ? "desc" : "asc")
|
222
|
+
|
223
|
+
parameters = request.query_parameters
|
224
|
+
parameters.merge!(opts[:link_params]) if opts[:link_params]
|
225
|
+
|
226
|
+
dest_url = url_for(parameters.merge(s: "#{name}+#{next_direction}"))
|
227
|
+
direction_icon = current_direction == "asc" ? "↑" : "↓"
|
228
|
+
anchor = current_attr_name == name.to_s ? "#{label} #{direction_icon}" : label
|
229
|
+
|
230
|
+
link_opts = opts.fetch(:link_opts, {})
|
231
|
+
|
232
|
+
link_to(anchor, dest_url, link_opts)
|
233
|
+
end
|
234
|
+
```
|
235
|
+
|
236
|
+
Then used as:
|
237
|
+
|
238
|
+
`= sort_link :title, "Title"`
|
239
|
+
|
240
|
+
### Keyset pagination
|
241
|
+
|
242
|
+
The keyset pagination is similar to the pagination utility, but working with [OrderQuery](https://github.com/glebm/order_query) to provide pagination that works with no offsets. Example usage:
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
class PostsController < ApplicationController
|
246
|
+
include Toqua::KeysetPagination
|
247
|
+
|
248
|
+
keyset_paginate :score_order
|
249
|
+
|
250
|
+
def index
|
251
|
+
@elements = apply_scopes(Post)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
```
|
255
|
+
|
256
|
+
It takes care of applying the correct scoping based on the id of the current element, as identified by the `:idx` parameter as default. With the optional `:headers` parameter some headers are also added into the response:
|
257
|
+
|
258
|
+
`keyset_paginate :score_order, headers: true`
|
259
|
+
|
260
|
+
Will generate those headers:
|
261
|
+
|
262
|
+
- `'X-KeysetPagination-Index'`: The `id` of the current element index.
|
263
|
+
- `'X-KeysetPagination-Next-Index'`: The `id` of the element to use as the next page.
|
264
|
+
- `'X-KeysetPagination-Prev-Index'`: The `id` of the element to use as the previous page.
|
265
|
+
|
266
|
+
The next and prev indexes are also available via the instance vars `@keyset_pagination_prev_index` and `@keyset_pagination_next_index`.
|
267
|
+
|
268
|
+
If the value of `@keyset_pagination_prev_index` (or via header) is `-1` it means the previous page is the initial one. If it's `nil`, there's no previous page.
|
269
|
+
|
270
|
+
To generate pagination links, you can use something like this:
|
271
|
+
|
272
|
+
```ruby
|
273
|
+
def keyset_pagination_next_link(index_key = :idx)
|
274
|
+
if @keyset_pagination_next_index
|
275
|
+
url_for(request.GET.merge(index_key => @keyset_pagination_next_index))
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def keyset_pagination_prev_link(index_key = :idx)
|
280
|
+
if @keyset_pagination_prev_index
|
281
|
+
if @keyset_pagination_prev_index == -1
|
282
|
+
url_for(request.GET.merge(index_key => nil))
|
283
|
+
else
|
284
|
+
url_for(request.GET.merge(index_key => @keyset_pagination_prev_index))
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
### Final notes
|
291
|
+
|
292
|
+
If you use multiple scope declarations either mixed with the other utilities shown here or not, be aware of the order. For example, pagination must
|
293
|
+
always go last.
|
294
|
+
|
295
|
+
|
296
|
+
## Development
|
297
|
+
|
298
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
299
|
+
|
300
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
301
|
+
|
302
|
+
## Contributing
|
303
|
+
|
304
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/toqua.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "toqua"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module Toqua
|
2
|
+
module KeysetPagination
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Scoping
|
5
|
+
|
6
|
+
DEFAULT_INDEX_KEY = :idx
|
7
|
+
DEFAULT_PER_PAGE = 30
|
8
|
+
|
9
|
+
included do
|
10
|
+
helper_method :paginated?
|
11
|
+
end
|
12
|
+
|
13
|
+
def paginated?
|
14
|
+
@paginated
|
15
|
+
end
|
16
|
+
|
17
|
+
class_methods do
|
18
|
+
def keyset_paginate(order_query_name, opts = {})
|
19
|
+
opts.assert_valid_keys(:per, :index_key, :headers)
|
20
|
+
per = opts.fetch(:per, DEFAULT_PER_PAGE)
|
21
|
+
index_key = opts.fetch(:index_key, DEFAULT_INDEX_KEY)
|
22
|
+
|
23
|
+
scope do |scope|
|
24
|
+
idx = params[index_key].presence
|
25
|
+
|
26
|
+
if idx
|
27
|
+
current_object = scope.model.find_by(id: idx)
|
28
|
+
end
|
29
|
+
|
30
|
+
current_object ||= scope.send(order_query_name).first
|
31
|
+
|
32
|
+
if current_object # Only in case there are no objects to paginate
|
33
|
+
@paginated = true
|
34
|
+
|
35
|
+
point = scope.model.send("#{order_query_name}_at", current_object)
|
36
|
+
|
37
|
+
two_previous_points = point.before.merge(scope.except(:order, :preload, :eager_load)).offset(per - 1).limit(2).to_a
|
38
|
+
|
39
|
+
if two_previous_points.empty?
|
40
|
+
@keyset_pagination_prev_index = nil
|
41
|
+
elsif two_previous_points.size == 1
|
42
|
+
@keyset_pagination_prev_index = -1
|
43
|
+
else
|
44
|
+
@keyset_pagination_prev_index = two_previous_points.first.id
|
45
|
+
end
|
46
|
+
|
47
|
+
@keyset_pagination_next_index = point.after.merge(scope.except(:order, :preload, :eager_load)).offset(per - 1).first.try(:id)
|
48
|
+
|
49
|
+
if opts[:headers]
|
50
|
+
headers['X-KeysetPagination-Index'] = current_object.id
|
51
|
+
headers['X-KeysetPagination-Next-Index'] = @keyset_pagination_next_index
|
52
|
+
headers['X-KeysetPagination-Prev-Index'] = @keyset_pagination_prev_index
|
53
|
+
end
|
54
|
+
|
55
|
+
scope.except(:order).merge(point.after(false).limit(per)).send(order_query_name)
|
56
|
+
else
|
57
|
+
scope
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Toqua
|
2
|
+
module Pagination
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Scoping
|
5
|
+
|
6
|
+
DEFAULT_PAGE = 1
|
7
|
+
DEFAULT_PAGE_KEY = :page
|
8
|
+
DEFAULT_PER_PAGE = 30
|
9
|
+
DEFAULT_PER_PAGE_KEY = :per_page
|
10
|
+
|
11
|
+
included do
|
12
|
+
helper_method :paginated?
|
13
|
+
end
|
14
|
+
|
15
|
+
def paginated?
|
16
|
+
@paginated
|
17
|
+
end
|
18
|
+
|
19
|
+
class_methods do
|
20
|
+
def paginate(opts = {})
|
21
|
+
page_key = opts.fetch(:page_key, DEFAULT_PAGE_KEY)
|
22
|
+
per_page_key = opts.fetch(:per_page_key, DEFAULT_PER_PAGE_KEY)
|
23
|
+
|
24
|
+
scope_opts = opts.slice(:if, :unless, :only)
|
25
|
+
|
26
|
+
scope(scope_opts) do |scope|
|
27
|
+
@paginated = true
|
28
|
+
|
29
|
+
@page = params.fetch(page_key, opts.fetch(:page, DEFAULT_PAGE))
|
30
|
+
per = params.fetch(per_page_key, opts.fetch(:per, DEFAULT_PER_PAGE))
|
31
|
+
|
32
|
+
if opts[:headers]
|
33
|
+
scope_count = scope.count
|
34
|
+
headers['X-Pagination-Total'] = scope_count.is_a?(Hash) ? scope_count.keys.count.to_s : scope_count.to_s
|
35
|
+
headers['X-Pagination-Per-Page'] = per.to_s
|
36
|
+
headers['X-Pagination-Page'] = @page.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
per == '0' ? scope : scope.page(@page).per(per)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Toqua
|
2
|
+
module Scoping
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def apply_scopes(start)
|
6
|
+
s = start
|
7
|
+
self.class.__scopes.each do |scope, opts|
|
8
|
+
s = __run_scope(s, scope, opts)
|
9
|
+
end
|
10
|
+
s
|
11
|
+
end
|
12
|
+
|
13
|
+
def __run_scope(relation, scope, opts)
|
14
|
+
if_condition = if opts[:if]
|
15
|
+
opts[:if].respond_to?(:call) ? !!instance_exec(&opts[:if]) : send(opts[:if])
|
16
|
+
elsif opts[:unless]
|
17
|
+
opts[:unless].respond_to?(:call) ? !instance_exec(&opts[:unless]) : !send(opts[:unless])
|
18
|
+
else
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
action_condition = if opts[:only]
|
23
|
+
[opts[:only]].flatten.include?(action_name.to_sym)
|
24
|
+
else
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
if if_condition && action_condition
|
29
|
+
self.instance_exec(relation, &scope)
|
30
|
+
else
|
31
|
+
relation
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
included do
|
36
|
+
class_attribute(:__scopes)
|
37
|
+
self.__scopes = []
|
38
|
+
end
|
39
|
+
|
40
|
+
class_methods do
|
41
|
+
def scope(opts = {}, &block)
|
42
|
+
self.__scopes += [[block, opts]]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/toqua/search.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Toqua
|
2
|
+
module Search
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Scoping
|
5
|
+
|
6
|
+
included do
|
7
|
+
helper_method :search_params, :searching?, :search_object, :active_search_params
|
8
|
+
end
|
9
|
+
|
10
|
+
def search_params
|
11
|
+
params[:q].permit!
|
12
|
+
(params[:q] || {}).to_h.reverse_merge(default_search_params)
|
13
|
+
end
|
14
|
+
|
15
|
+
def active_search_params
|
16
|
+
search_params.select { |_, b| !b.nil? && b != "" }.to_h
|
17
|
+
end
|
18
|
+
|
19
|
+
def search_object
|
20
|
+
@search_object ||= begin
|
21
|
+
Class.new(Hashie::Mash) do
|
22
|
+
extend ActiveModel::Naming
|
23
|
+
|
24
|
+
def self.model_name
|
25
|
+
ActiveModel::Name.new(Class, nil, "q")
|
26
|
+
end
|
27
|
+
end.new(search_params)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def searching?
|
32
|
+
params.key?(:q)
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_search_params
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
|
39
|
+
class_methods do
|
40
|
+
def searchable
|
41
|
+
scope { |s| s.filter(search_params) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Toqua
|
2
|
+
module Sorting
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class_methods do
|
6
|
+
def sorting(opts = {})
|
7
|
+
reorder = opts.fetch(:reorder, true)
|
8
|
+
|
9
|
+
scope do |s|
|
10
|
+
if params[:s].present?
|
11
|
+
attr_name, direction = params[:s].to_s.split("+").map(&:strip)
|
12
|
+
raise "You must provide an attribute to sort by" unless attr_name
|
13
|
+
raise "You must provide a direction" unless direction
|
14
|
+
direction.downcase!
|
15
|
+
raise "Direction must be ASC or DESC" unless ["asc", "desc"].include?(direction)
|
16
|
+
|
17
|
+
s.send(reorder ? :reorder : :order, s.arel_table[attr_name].send(direction))
|
18
|
+
else
|
19
|
+
s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Toqua
|
2
|
+
module TransformParams
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class ParameterConverter
|
6
|
+
def parse(params, hash, block)
|
7
|
+
if Hash === hash
|
8
|
+
key = hash.keys.first
|
9
|
+
|
10
|
+
if params.key?(key)
|
11
|
+
parse(params[key], hash[key], block)
|
12
|
+
end
|
13
|
+
else
|
14
|
+
if params.key?(hash)
|
15
|
+
params[hash] = block.call(params[hash])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
included do
|
22
|
+
class_attribute(:__transform_params)
|
23
|
+
self.__transform_params = []
|
24
|
+
|
25
|
+
prepend_before_action do
|
26
|
+
self.class.__transform_params.each do |hash, block|
|
27
|
+
ParameterConverter.new.parse(params, hash, block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class_methods do
|
33
|
+
def transform_params(hash, &block)
|
34
|
+
self.__transform_params += [[hash, block]]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/toqua.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "toqua/version"
|
2
|
+
|
3
|
+
require 'active_support/all'
|
4
|
+
|
5
|
+
require 'toqua/scoping'
|
6
|
+
require 'toqua/pagination'
|
7
|
+
require 'toqua/search'
|
8
|
+
require 'toqua/sorting'
|
9
|
+
require 'toqua/transform_params'
|
10
|
+
require 'toqua/keyset_pagination'
|
11
|
+
|
12
|
+
|
13
|
+
module Toqua
|
14
|
+
# Your code goes here...
|
15
|
+
end
|
data/toqua.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "toqua/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "toqua"
|
8
|
+
spec.version = Toqua::VERSION
|
9
|
+
spec.authors = ["Roger Campos"]
|
10
|
+
spec.email = ["roger@rogercampos.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Collection of small utilities for controllers in rails applications}
|
13
|
+
spec.description = %q{Collection of small utilities for controllers in rails applications}
|
14
|
+
spec.homepage = "https://github.com/rogercampos/toqua"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_dependency "activesupport", ">= 4.0.0"
|
24
|
+
spec.add_dependency "actionpack", ">= 4.0.0"
|
25
|
+
spec.add_dependency "activerecord", ">= 4.0.0"
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
30
|
+
spec.add_development_dependency "minitest-rails"
|
31
|
+
spec.add_development_dependency "sqlite3"
|
32
|
+
spec.add_development_dependency "order_query", ">= 0.3.4"
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: toqua
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Roger Campos
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: actionpack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 4.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 4.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activerecord
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 4.0.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 4.0.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.15'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.15'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '5.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '5.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: minitest-rails
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: order_query
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.3.4
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.3.4
|
139
|
+
description: Collection of small utilities for controllers in rails applications
|
140
|
+
email:
|
141
|
+
- roger@rogercampos.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- ".gitignore"
|
147
|
+
- ".travis.yml"
|
148
|
+
- Gemfile
|
149
|
+
- README.md
|
150
|
+
- Rakefile
|
151
|
+
- bin/console
|
152
|
+
- bin/setup
|
153
|
+
- lib/toqua.rb
|
154
|
+
- lib/toqua/keyset_pagination.rb
|
155
|
+
- lib/toqua/pagination.rb
|
156
|
+
- lib/toqua/scoping.rb
|
157
|
+
- lib/toqua/search.rb
|
158
|
+
- lib/toqua/sorting.rb
|
159
|
+
- lib/toqua/transform_params.rb
|
160
|
+
- lib/toqua/version.rb
|
161
|
+
- toqua.gemspec
|
162
|
+
homepage: https://github.com/rogercampos/toqua
|
163
|
+
licenses: []
|
164
|
+
metadata: {}
|
165
|
+
post_install_message:
|
166
|
+
rdoc_options: []
|
167
|
+
require_paths:
|
168
|
+
- lib
|
169
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - ">="
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '0'
|
179
|
+
requirements: []
|
180
|
+
rubyforge_project:
|
181
|
+
rubygems_version: 2.5.2
|
182
|
+
signing_key:
|
183
|
+
specification_version: 4
|
184
|
+
summary: Collection of small utilities for controllers in rails applications
|
185
|
+
test_files: []
|