toqua 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/rogercampos/toqua.svg?branch=master)](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: []
|