rokaki 0.5.0 → 0.8.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 +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +51 -42
- data/Guardfile +1 -0
- data/LICENSE.txt +2 -0
- data/README.md +194 -27
- data/lib/rokaki.rb +2 -0
- data/lib/rokaki/filter_model.rb +68 -76
- data/lib/rokaki/filter_model/basic_filter.rb +78 -0
- data/lib/rokaki/filter_model/filter_chain.rb +13 -0
- data/lib/rokaki/filter_model/like_keys.rb +4 -0
- data/lib/rokaki/filter_model/nested_filter.rb +155 -0
- data/lib/rokaki/filterable.rb +38 -6
- data/lib/rokaki/version.rb +1 -1
- data/rokaki.gemspec +29 -22
- metadata +76 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0654c6e63cc988e84b4df0a33b2055661bc5f7e29367923bdc1267fc4bbe64e
|
|
4
|
+
data.tar.gz: fd7f3e44cc48096389697d82f89b720a67900bbe073ce60e62828362fd46376d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc938b3a7c3632ed7f92a0452b87a91c5e50f0ae37b3f00cb5f9d79a65e0ca0f23f60d5b92b6cd709c287ddcf4a891bde460fd757df1e2fd8c4b0f551499fd05
|
|
7
|
+
data.tar.gz: a350805490d53b145b68813fdf9144b37871fa9b06942df0d2befb906e7912244258ffe14d0b5174bd0ab1fe7f92b4b9fcf650b847c1a36aefba2ecb8aee344b
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
rokaki (0.
|
|
4
|
+
rokaki (0.8.0)
|
|
5
|
+
activesupport
|
|
5
6
|
|
|
6
7
|
GEM
|
|
7
8
|
remote: https://rubygems.org/
|
|
8
9
|
specs:
|
|
9
|
-
activemodel (6.0.
|
|
10
|
-
activesupport (= 6.0.
|
|
11
|
-
activerecord (6.0.
|
|
12
|
-
activemodel (= 6.0.
|
|
13
|
-
activesupport (= 6.0.
|
|
14
|
-
activesupport (6.0.
|
|
10
|
+
activemodel (6.0.3.2)
|
|
11
|
+
activesupport (= 6.0.3.2)
|
|
12
|
+
activerecord (6.0.3.2)
|
|
13
|
+
activemodel (= 6.0.3.2)
|
|
14
|
+
activesupport (= 6.0.3.2)
|
|
15
|
+
activesupport (6.0.3.2)
|
|
15
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
16
17
|
i18n (>= 0.7, < 2)
|
|
17
18
|
minitest (~> 5.1)
|
|
18
19
|
tzinfo (~> 1.1)
|
|
19
|
-
zeitwerk (~> 2.
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
|
21
|
+
byebug (11.1.3)
|
|
22
|
+
coderay (1.1.3)
|
|
23
|
+
concurrent-ruby (1.1.6)
|
|
22
24
|
diff-lcs (1.3)
|
|
23
|
-
|
|
25
|
+
factory_bot (6.0.2)
|
|
26
|
+
activesupport (>= 5.0.0)
|
|
27
|
+
ffi (1.13.1)
|
|
24
28
|
formatador (0.2.5)
|
|
25
|
-
guard (2.
|
|
29
|
+
guard (2.16.2)
|
|
26
30
|
formatador (>= 0.2.4)
|
|
27
31
|
listen (>= 2.7, < 4.0)
|
|
28
32
|
lumberjack (>= 1.0.12, < 2.0)
|
|
@@ -36,47 +40,49 @@ GEM
|
|
|
36
40
|
guard (~> 2.1)
|
|
37
41
|
guard-compat (~> 1.1)
|
|
38
42
|
rspec (>= 2.99.0, < 4.0)
|
|
39
|
-
i18n (1.
|
|
43
|
+
i18n (1.8.3)
|
|
40
44
|
concurrent-ruby (~> 1.0)
|
|
41
|
-
listen (3.1
|
|
42
|
-
rb-fsevent (~> 0.
|
|
43
|
-
rb-inotify (~> 0.9, >= 0.9.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
minitest (5.11.3)
|
|
45
|
+
listen (3.2.1)
|
|
46
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
47
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
|
48
|
+
lumberjack (1.2.6)
|
|
49
|
+
method_source (1.0.0)
|
|
50
|
+
minitest (5.14.1)
|
|
48
51
|
nenv (0.3.0)
|
|
49
52
|
notiffany (0.1.3)
|
|
50
53
|
nenv (~> 0.1)
|
|
51
54
|
shellany (~> 0.0)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
pg (1.2.3)
|
|
56
|
+
pry (0.13.1)
|
|
57
|
+
coderay (~> 1.1)
|
|
58
|
+
method_source (~> 1.0)
|
|
59
|
+
pry-byebug (3.9.0)
|
|
60
|
+
byebug (~> 11.0)
|
|
61
|
+
pry (~> 0.13.0)
|
|
55
62
|
rake (10.5.0)
|
|
56
|
-
rb-fsevent (0.10.
|
|
57
|
-
rb-inotify (0.10.
|
|
63
|
+
rb-fsevent (0.10.4)
|
|
64
|
+
rb-inotify (0.10.1)
|
|
58
65
|
ffi (~> 1.0)
|
|
59
|
-
rspec (3.
|
|
60
|
-
rspec-core (~> 3.
|
|
61
|
-
rspec-expectations (~> 3.
|
|
62
|
-
rspec-mocks (~> 3.
|
|
63
|
-
rspec-core (3.
|
|
64
|
-
rspec-support (~> 3.
|
|
65
|
-
rspec-expectations (3.
|
|
66
|
+
rspec (3.9.0)
|
|
67
|
+
rspec-core (~> 3.9.0)
|
|
68
|
+
rspec-expectations (~> 3.9.0)
|
|
69
|
+
rspec-mocks (~> 3.9.0)
|
|
70
|
+
rspec-core (3.9.2)
|
|
71
|
+
rspec-support (~> 3.9.3)
|
|
72
|
+
rspec-expectations (3.9.2)
|
|
66
73
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
67
|
-
rspec-support (~> 3.
|
|
68
|
-
rspec-mocks (3.
|
|
74
|
+
rspec-support (~> 3.9.0)
|
|
75
|
+
rspec-mocks (3.9.1)
|
|
69
76
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
70
|
-
rspec-support (~> 3.
|
|
71
|
-
rspec-support (3.
|
|
72
|
-
ruby_dep (1.5.0)
|
|
77
|
+
rspec-support (~> 3.9.0)
|
|
78
|
+
rspec-support (3.9.3)
|
|
73
79
|
shellany (0.0.1)
|
|
74
|
-
sqlite3 (1.4.
|
|
75
|
-
thor (0.
|
|
80
|
+
sqlite3 (1.4.2)
|
|
81
|
+
thor (1.0.1)
|
|
76
82
|
thread_safe (0.3.6)
|
|
77
|
-
tzinfo (1.2.
|
|
83
|
+
tzinfo (1.2.7)
|
|
78
84
|
thread_safe (~> 0.1)
|
|
79
|
-
zeitwerk (2.
|
|
85
|
+
zeitwerk (2.3.0)
|
|
80
86
|
|
|
81
87
|
PLATFORMS
|
|
82
88
|
ruby
|
|
@@ -84,13 +90,16 @@ PLATFORMS
|
|
|
84
90
|
DEPENDENCIES
|
|
85
91
|
activerecord
|
|
86
92
|
bundler (~> 2.0)
|
|
93
|
+
factory_bot
|
|
87
94
|
guard
|
|
88
95
|
guard-rspec
|
|
96
|
+
pg
|
|
89
97
|
pry
|
|
98
|
+
pry-byebug
|
|
90
99
|
rake (~> 10.0)
|
|
91
100
|
rokaki!
|
|
92
101
|
rspec (~> 3.0)
|
|
93
102
|
sqlite3
|
|
94
103
|
|
|
95
104
|
BUNDLED WITH
|
|
96
|
-
2.
|
|
105
|
+
2.1.4
|
data/Guardfile
CHANGED
|
@@ -36,6 +36,7 @@ guard :rspec, cmd: "bundle exec rspec" do
|
|
|
36
36
|
watch(rspec.spec_support) { rspec.spec_dir }
|
|
37
37
|
watch(rspec.spec_files)
|
|
38
38
|
|
|
39
|
+
watch(%r{^lib/rokaki/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
|
39
40
|
# Ruby files
|
|
40
41
|
ruby = dsl.ruby
|
|
41
42
|
dsl.watch_spec_files_for(ruby.lib_files)
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# Rokaki
|
|
2
2
|
[](https://badge.fury.io/rb/rokaki)
|
|
3
3
|
|
|
4
|
-
This gem was born out of a desire to dry up filtering services in Rails or any
|
|
5
|
-
|
|
6
|
-
It's a simple gem that just provides you with a basic dsl based on the filter params that you might pass through from a web request.
|
|
4
|
+
This gem was born out of a desire to dry up filtering services in Rails apps or any Ruby app that uses the concept of "filters" or "facets".
|
|
7
5
|
|
|
6
|
+
There are two modes of use `Filterable` and `FilterModel` that can be activated through the use of two mixins respectively, `include Rokaki::Filterable` or `include Rokaki::FilterModel`.
|
|
8
7
|
## Installation
|
|
9
8
|
|
|
10
9
|
Add this line to your application's Gemfile:
|
|
@@ -17,42 +16,118 @@ And then execute:
|
|
|
17
16
|
|
|
18
17
|
$ bundle
|
|
19
18
|
|
|
20
|
-
## Usage
|
|
19
|
+
## `Rokaki::Filterable` - Usage
|
|
20
|
+
|
|
21
|
+
To use the DSL first include the `Rokaki::Filterable` module in your [por](http://blog.jayfields.com/2007/10/ruby-poro.html) class.
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
### `#define_filter_keys`
|
|
24
|
+
#### A Simple Example
|
|
23
25
|
|
|
24
26
|
A simple example might be:-
|
|
25
27
|
|
|
26
28
|
```ruby
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
class FilterArticles
|
|
30
|
+
include Rokaki::Filterable
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
def initialize(filters:)
|
|
33
|
+
@filters = filters
|
|
34
|
+
@articles = Article
|
|
35
|
+
end
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
attr_accessor :filters
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
define_filter_keys :date, author: [:first_name, :last_name]
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
end
|
|
41
|
+
def filter_results
|
|
42
|
+
@articles = @articles.where(date: date) if date
|
|
43
|
+
@articles = @articles.joins(:author).where(author: { first_name: author_first_name }) if author_first_name
|
|
43
44
|
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
article_filter = FilterArticles.new(filters: {
|
|
48
|
+
date: '10-10-10',
|
|
49
|
+
author: {
|
|
50
|
+
first_name: 'Steve',
|
|
51
|
+
last_name: 'Martin'
|
|
52
|
+
}})
|
|
53
|
+
article_filter.author_first_name == 'Steve'
|
|
54
|
+
article_filter.author_last_name == 'Martin'
|
|
55
|
+
article_filter.date == '10-10-10'
|
|
44
56
|
```
|
|
45
57
|
|
|
46
|
-
|
|
58
|
+
In this example Rokaki maps the "flat" attribute "keys" `date`, `author_first_name` and `author_last_name` to a `@filters` object with the expected deep structure `{ date: '10-10-10', author: { first_name: 'Steve' } }`, to make it simple to use them in filter queries.
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
#### A More Complex Example
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
class AdvancedFilterable
|
|
64
|
+
include Rokaki::Filterable
|
|
65
|
+
|
|
66
|
+
def initialize(filters:)
|
|
67
|
+
@fyltrz = filters
|
|
68
|
+
end
|
|
69
|
+
attr_accessor :fyltrz
|
|
70
|
+
|
|
71
|
+
filterable_object_name :fyltrz
|
|
72
|
+
filter_key_infix :__
|
|
73
|
+
define_filter_keys :basic, advanced: {
|
|
74
|
+
filter_key_1: [:filter_key_2, { filter_key_3: :deep_node }],
|
|
75
|
+
filter_key_4: :deep_leaf_array
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
advanced_filterable = AdvancedFilterable.new(filters: {
|
|
81
|
+
basic: 'ABC',
|
|
82
|
+
advanced: {
|
|
83
|
+
filter_key_1: {
|
|
84
|
+
filter_key_2: '123',
|
|
85
|
+
filter_key_3: { deep_node: 'NODE' }
|
|
86
|
+
},
|
|
87
|
+
filter_key_4: { deep_leaf_array: [1,2,3,4] }
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
advanced_filterable.advanced__filter_key_4__deep_leaf_array == [1,2,3,4]
|
|
92
|
+
advanced_filterable.advanced__filter_key_1__filter_key_3__deep_node == 'NODE'
|
|
93
|
+
```
|
|
94
|
+
### `#define_filter_map`
|
|
95
|
+
|
|
96
|
+
This method takes a single field in the passed in filters hash and maps it to fields named in the second param, this is useful if you want to search for a single value across many different fields or associated tables simultaneously.
|
|
97
|
+
|
|
98
|
+
#### A Simple Example
|
|
99
|
+
```ruby
|
|
100
|
+
class FilterMap
|
|
101
|
+
include Rokaki::Filterable
|
|
102
|
+
|
|
103
|
+
def initialize(fylterz:)
|
|
104
|
+
@fylterz = fylterz
|
|
105
|
+
end
|
|
106
|
+
attr_accessor :fylterz
|
|
107
|
+
|
|
108
|
+
filterable_object_name :fylterz
|
|
109
|
+
define_filter_map :query, :mapped_a, association: :field
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
filter_map = FilterMap.new(fytlerz: { query: 'H2O' })
|
|
113
|
+
|
|
114
|
+
filter_map.mapped_a == 'H2O'
|
|
115
|
+
filter_map.association_field = 'H2O'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Additional `Filterable` options
|
|
119
|
+
You can specify several configuration options, for example a `filter_key_prefix` and a `filter_key_infix` to change the structure of the generated filter accessors.
|
|
50
120
|
|
|
51
121
|
`filter_key_prefix :__` would result in key accessors like `__author_first_name`
|
|
52
122
|
|
|
53
123
|
`filter_key_infix :__` would result in key accessors like `author__first_name`
|
|
54
124
|
|
|
55
|
-
|
|
125
|
+
`filterable_object_name :fylterz` would use an internal filter state object named `@fyltrz` instead of the default `@filters`
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
## `Rokaki::FilterModel` - Usage
|
|
129
|
+
|
|
130
|
+
### ActiveRecord
|
|
56
131
|
Include `Rokaki::FilterModel` in any ActiveRecord model (only AR >= 6.0.0 tested so far) you can generate the filter keys and the actual filter lookup code using the `filters` keyword on a model like so:-
|
|
57
132
|
|
|
58
133
|
```ruby
|
|
@@ -67,7 +142,7 @@ end
|
|
|
67
142
|
|
|
68
143
|
|
|
69
144
|
class ArticleFilter
|
|
70
|
-
include FilterModel
|
|
145
|
+
include Rokaki::FilterModel
|
|
71
146
|
|
|
72
147
|
filters :date, :title, author: [:first_name, :last_name]
|
|
73
148
|
|
|
@@ -84,16 +159,19 @@ filter = ArticleFilter.new(filters: params[:filters])
|
|
|
84
159
|
filtered_results = filter.results
|
|
85
160
|
|
|
86
161
|
```
|
|
162
|
+
### Arrays of params
|
|
163
|
+
You can also filter collections of fields, simply pass an array of filter values instead of a single value, eg:- `{ date: '10-10-10', author: { first_name: ['Author1', 'Author2'] } }`.
|
|
164
|
+
|
|
87
165
|
|
|
88
166
|
### Partial matching
|
|
89
|
-
You can use `like` (or, if you use postgres, the case insensitive `ilike`) to perform a partial match on a specific
|
|
167
|
+
You can use `like` (or, if you use postgres, the case insensitive `ilike`) to perform a partial match on a specific field, there are 3 options:- `:prefix`, `:circumfix` and `:suffix`. There are two syntaxes you can use for this:-
|
|
90
168
|
|
|
91
169
|
#### 1. The `filter` command syntax
|
|
92
170
|
|
|
93
171
|
|
|
94
172
|
```ruby
|
|
95
173
|
class ArticleFilter
|
|
96
|
-
include FilterModel
|
|
174
|
+
include Rokaki::FilterModel
|
|
97
175
|
|
|
98
176
|
filter :article,
|
|
99
177
|
like: { # you can use ilike here instead if you use postgres and want case insensitive results
|
|
@@ -112,14 +190,45 @@ end
|
|
|
112
190
|
```
|
|
113
191
|
Or
|
|
114
192
|
|
|
115
|
-
#### 2. The
|
|
193
|
+
#### 2. The `filter_map` command syntax
|
|
194
|
+
`filter_map` takes the model name, then a single 'query' field and maps it to fields named in the options, this is useful if you want to search for a single value across many different fields or associated tables simultaneously. (builds on `define_filter_map`)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
class AuthorFilter
|
|
199
|
+
include Rokaki::FilterModel
|
|
200
|
+
|
|
201
|
+
filter_map :author, :query,
|
|
202
|
+
like: {
|
|
203
|
+
articles: {
|
|
204
|
+
title: :circumfix,
|
|
205
|
+
reviews: {
|
|
206
|
+
title: :circumfix
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
attr_accessor :filters, :model
|
|
212
|
+
|
|
213
|
+
def initialize(filters:)
|
|
214
|
+
@filters = filters
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
filters = { query: "Jiddu" }
|
|
219
|
+
filtered_authors = AuthorFilter.new(filters: filters).results
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
In the above example we search for authors who have written articles containing the word "Jiddu" in the title that also have reviews containing the sames word in their titles.
|
|
223
|
+
|
|
224
|
+
#### 3. The porcelain command syntax
|
|
116
225
|
|
|
117
226
|
In this syntax you will need to provide three keywords:- `filters`, `like` and `filter_model` if you are not passing in the model type and assigning it to `@model`
|
|
118
227
|
|
|
119
228
|
|
|
120
229
|
```ruby
|
|
121
230
|
class ArticleFilter
|
|
122
|
-
include FilterModel
|
|
231
|
+
include Rokaki::FilterModel
|
|
123
232
|
|
|
124
233
|
filters :date, :title, author: [:first_name, :last_name]
|
|
125
234
|
like title: :circumfix
|
|
@@ -138,7 +247,7 @@ Or without the model in the initializer
|
|
|
138
247
|
|
|
139
248
|
```ruby
|
|
140
249
|
class ArticleFilter
|
|
141
|
-
include FilterModel
|
|
250
|
+
include Rokaki::FilterModel
|
|
142
251
|
|
|
143
252
|
filters :date, :title, author: [:first_name, :last_name]
|
|
144
253
|
like title: :circumfix
|
|
@@ -158,6 +267,64 @@ Would produce a query with a LIKE which circumfixes '%' around the filter term,
|
|
|
158
267
|
@model = @model.where('title LIKE :query', query: "%#{title}%")
|
|
159
268
|
```
|
|
160
269
|
|
|
270
|
+
### Deep nesting
|
|
271
|
+
You can filter joins both with basic matching and partial matching
|
|
272
|
+
```ruby
|
|
273
|
+
class ArticleFilter
|
|
274
|
+
include Rokaki::FilterModel
|
|
275
|
+
|
|
276
|
+
filter :author,
|
|
277
|
+
like: {
|
|
278
|
+
articles: {
|
|
279
|
+
reviews: {
|
|
280
|
+
title: :circumfix
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
attr_accessor :filters
|
|
286
|
+
|
|
287
|
+
def initialize(filters:)
|
|
288
|
+
@filters = filters
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Array params
|
|
294
|
+
You can pass array params (and partially match them), to filters (search multiple matches) in databases that support it (postgres) by passing the `db` param to the filter keyword, and passing an array of search terms at runtine
|
|
295
|
+
|
|
296
|
+
```ruby
|
|
297
|
+
class ArticleFilter
|
|
298
|
+
include Rokaki::FilterModel
|
|
299
|
+
|
|
300
|
+
filter :article,
|
|
301
|
+
like: {
|
|
302
|
+
author: {
|
|
303
|
+
first_name: :circumfix,
|
|
304
|
+
last_name: :circumfix
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
match: %i[title created_at],
|
|
308
|
+
db: :postgres
|
|
309
|
+
|
|
310
|
+
attr_accessor :filters
|
|
311
|
+
|
|
312
|
+
def initialize(filters:)
|
|
313
|
+
@filters = filters
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
filterable = ArticleFilter.new(filters:
|
|
318
|
+
{
|
|
319
|
+
author: {
|
|
320
|
+
first_name: ['Match One', 'Match Two']
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
filterable.results
|
|
326
|
+
```
|
|
327
|
+
|
|
161
328
|
|
|
162
329
|
## Development
|
|
163
330
|
|
data/lib/rokaki.rb
CHANGED
|
@@ -4,6 +4,8 @@ require 'rokaki/version'
|
|
|
4
4
|
require 'rokaki/filterable'
|
|
5
5
|
require 'rokaki/filter_model'
|
|
6
6
|
require 'rokaki/filter_model/like_keys'
|
|
7
|
+
require 'rokaki/filter_model/basic_filter'
|
|
8
|
+
require 'rokaki/filter_model/nested_filter'
|
|
7
9
|
|
|
8
10
|
module Rokaki
|
|
9
11
|
class Error < StandardError; end
|
data/lib/rokaki/filter_model.rb
CHANGED
|
@@ -7,106 +7,96 @@ module Rokaki
|
|
|
7
7
|
base.extend(ClassMethods)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
def prepare_terms(param, mode)
|
|
11
|
+
if param.is_a? Array
|
|
12
|
+
return param.map { |term| "%#{term}%" } if mode == :circumfix
|
|
13
|
+
return param.map { |term| "%#{term}" } if mode == :prefix
|
|
14
|
+
return param.map { |term| "#{term}%" } if mode == :suffix
|
|
15
|
+
else
|
|
16
|
+
return ["%#{param}%"] if mode == :circumfix
|
|
17
|
+
return ["%#{param}"] if mode == :prefix
|
|
18
|
+
return ["#{param}%"] if mode == :suffix
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
10
23
|
module ClassMethods
|
|
11
24
|
include Filterable::ClassMethods
|
|
12
25
|
|
|
13
26
|
private
|
|
14
27
|
|
|
28
|
+
def filter_map(model, query_key, options)
|
|
29
|
+
filter_model(model)
|
|
30
|
+
@_query_key = query_key
|
|
31
|
+
|
|
32
|
+
@_filter_db = options[:db] || :postgres
|
|
33
|
+
like(options[:like]) if options[:like]
|
|
34
|
+
ilike(options[:ilike]) if options[:ilike]
|
|
35
|
+
filters(*options[:match]) if options[:match]
|
|
36
|
+
end
|
|
37
|
+
|
|
15
38
|
def filter(model, options)
|
|
16
39
|
filter_model(model)
|
|
17
40
|
|
|
41
|
+
@_filter_db = options[:db] || :postgres
|
|
18
42
|
like(options[:like]) if options[:like]
|
|
19
43
|
ilike(options[:ilike]) if options[:ilike]
|
|
20
44
|
filters(*options[:match]) if options[:match]
|
|
21
45
|
end
|
|
22
46
|
|
|
23
47
|
def filters(*filter_keys)
|
|
24
|
-
|
|
48
|
+
if @_query_key
|
|
49
|
+
define_filter_map(@_query_key, *filter_keys)
|
|
50
|
+
else
|
|
51
|
+
define_filter_keys(*filter_keys)
|
|
52
|
+
end
|
|
25
53
|
|
|
26
54
|
@_chain_filters ||= []
|
|
27
55
|
filter_keys.each do |filter_key|
|
|
28
|
-
_chain_filter([filter_key]) unless filter_key.is_a? Hash
|
|
29
|
-
_chain_nested(filter_key) if filter_key.is_a? Hash
|
|
30
|
-
end
|
|
31
56
|
|
|
32
|
-
|
|
33
|
-
|
|
57
|
+
# TODO: does the key need casting to an array here?
|
|
58
|
+
_chain_filter(filter_key) unless filter_key.is_a? Hash
|
|
34
59
|
|
|
35
|
-
|
|
36
|
-
first_key = keys.shift
|
|
37
|
-
filter = "#{filter_key_prefix}#{first_key}"
|
|
38
|
-
name = first_key
|
|
60
|
+
_chain_nested_filter(filter_key) if filter_key.is_a? Hash
|
|
39
61
|
|
|
40
|
-
keys.each do |key|
|
|
41
|
-
filter += "#{filter_key_infix}#{key}"
|
|
42
|
-
name += "#{filter_key_infix}#{key}"
|
|
43
62
|
end
|
|
44
63
|
|
|
45
|
-
|
|
46
|
-
"#{_chain_filter_type(name)} end;"
|
|
47
|
-
|
|
48
|
-
class_eval filter_method, __FILE__, __LINE__ - 2
|
|
49
|
-
|
|
50
|
-
@_chain_filters << "@model = #{filter_key_prefix}filter_#{name} if #{filter};"
|
|
64
|
+
define_results # writes out all the generated filters
|
|
51
65
|
end
|
|
52
66
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
end
|
|
67
|
+
def _chain_filter(key)
|
|
68
|
+
basic_filter = BasicFilter.new(
|
|
69
|
+
keys: [key],
|
|
70
|
+
prefix: filter_key_prefix,
|
|
71
|
+
infix: filter_key_infix,
|
|
72
|
+
like_semantics: @_like_semantics,
|
|
73
|
+
i_like_semantics: @i_like_semantics,
|
|
74
|
+
db: @_filter_db
|
|
75
|
+
)
|
|
76
|
+
basic_filter.call
|
|
64
77
|
|
|
65
|
-
|
|
66
|
-
end
|
|
78
|
+
class_eval basic_filter.filter_method, __FILE__, __LINE__ - 2
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
query = "@model.where(\"#{key} #{type} :query\", "
|
|
70
|
-
query += "query: \"%\#{#{filter}}%\")" if mode == :circumfix
|
|
71
|
-
query += "query: \"%\#{#{filter}}\")" if mode == :prefix
|
|
72
|
-
query += "query: \"\#{#{filter}}%\")" if mode == :suffix
|
|
73
|
-
query
|
|
80
|
+
@_chain_filters << basic_filter.filter_template
|
|
74
81
|
end
|
|
75
82
|
|
|
76
|
-
def
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
joins = ":#{key}"
|
|
90
|
-
|
|
91
|
-
where = "{ #{key.to_s.pluralize}: { #{leaf}: #{name} } }"
|
|
83
|
+
def _chain_nested_filter(filters_object)
|
|
84
|
+
nested_filter = NestedFilter.new(
|
|
85
|
+
filter_key_object: filters_object,
|
|
86
|
+
prefix: filter_key_prefix,
|
|
87
|
+
infix: filter_key_infix,
|
|
88
|
+
like_semantics: @_like_semantics,
|
|
89
|
+
i_like_semantics: @i_like_semantics,
|
|
90
|
+
db: @_filter_db
|
|
91
|
+
)
|
|
92
|
+
nested_filter.call
|
|
93
|
+
|
|
94
|
+
nested_filter.filter_methods.each do |filter_method|
|
|
95
|
+
class_eval filter_method, __FILE__, __LINE__ - 2
|
|
92
96
|
end
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
# chain filter here?
|
|
98
|
-
#
|
|
99
|
-
filter_method = "def #{filter_key_prefix}filter_#{name};"\
|
|
100
|
-
"@model.joins(#{joins}).where(#{where}); end;"
|
|
101
|
-
|
|
102
|
-
class_eval filter_method, __FILE__, __LINE__ - 2
|
|
103
|
-
|
|
104
|
-
@_chain_filters << "@model = #{filter_key_prefix}filter_#{name} if #{name};"
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def _chain_nested(filters_object)
|
|
108
|
-
filters_object.keys.each do |key|
|
|
109
|
-
deep_chain([key], filters_object[key])
|
|
98
|
+
nested_filter.filter_templates.each do |filter_template|
|
|
99
|
+
@_chain_filters << filter_template
|
|
110
100
|
end
|
|
111
101
|
end
|
|
112
102
|
|
|
@@ -114,9 +104,9 @@ module Rokaki
|
|
|
114
104
|
@model.reflect_on_association(association).klass.table_name
|
|
115
105
|
end
|
|
116
106
|
|
|
117
|
-
def filter_model(
|
|
118
|
-
@model = (
|
|
119
|
-
class_eval "def
|
|
107
|
+
def filter_model(model_class)
|
|
108
|
+
@model = (model_class.is_a?(Class) ? model_class : Object.const_get(model_class.capitalize))
|
|
109
|
+
class_eval "def set_model; @model ||= #{@model}; end;"
|
|
120
110
|
end
|
|
121
111
|
|
|
122
112
|
def like(args)
|
|
@@ -124,8 +114,9 @@ module Rokaki
|
|
|
124
114
|
@_like_semantics = (@_like_semantics || {}).merge(args)
|
|
125
115
|
|
|
126
116
|
key_builder = LikeKeys.new(args)
|
|
117
|
+
keys = key_builder.call
|
|
127
118
|
|
|
128
|
-
filters(*
|
|
119
|
+
filters(*keys)
|
|
129
120
|
end
|
|
130
121
|
|
|
131
122
|
def ilike(args)
|
|
@@ -133,8 +124,9 @@ module Rokaki
|
|
|
133
124
|
@i_like_semantics = (@i_like_semantics || {}).merge(args)
|
|
134
125
|
|
|
135
126
|
key_builder = LikeKeys.new(args)
|
|
127
|
+
keys = key_builder.call
|
|
136
128
|
|
|
137
|
-
filters(*
|
|
129
|
+
filters(*keys)
|
|
138
130
|
end
|
|
139
131
|
|
|
140
132
|
def deep_chain(keys, value)
|
|
@@ -162,7 +154,7 @@ module Rokaki
|
|
|
162
154
|
# filter_model method
|
|
163
155
|
#
|
|
164
156
|
def define_results
|
|
165
|
-
results_def = 'def results;model;'
|
|
157
|
+
results_def = 'def results; @model || set_model;'
|
|
166
158
|
@_chain_filters.each do |item|
|
|
167
159
|
results_def += item
|
|
168
160
|
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rokaki
|
|
4
|
+
module FilterModel
|
|
5
|
+
class BasicFilter
|
|
6
|
+
def initialize(keys:, prefix:, infix:, like_semantics:, i_like_semantics:, db:)
|
|
7
|
+
@keys = keys
|
|
8
|
+
@prefix = prefix
|
|
9
|
+
@infix = infix
|
|
10
|
+
@like_semantics = like_semantics
|
|
11
|
+
@i_like_semantics = i_like_semantics
|
|
12
|
+
@db = db
|
|
13
|
+
end
|
|
14
|
+
attr_reader :keys, :prefix, :infix, :like_semantics, :i_like_semantics, :db
|
|
15
|
+
attr_accessor :filter_method, :filter_template
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
first_key = keys.shift
|
|
19
|
+
filter = "#{prefix}#{first_key}"
|
|
20
|
+
name = first_key
|
|
21
|
+
|
|
22
|
+
keys.each do |key|
|
|
23
|
+
filter += "#{infix}#{key}"
|
|
24
|
+
name += "#{infix}#{key}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@filter_method = "def #{prefix}filter_#{name};" \
|
|
28
|
+
"#{_chain_filter_type(name)} end;"
|
|
29
|
+
|
|
30
|
+
# class_eval filter_method, __FILE__, __LINE__ - 2
|
|
31
|
+
|
|
32
|
+
@filter_template = "@model = #{prefix}filter_#{name} if #{filter};"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def _chain_filter_type(key)
|
|
36
|
+
filter = "#{prefix}#{key}"
|
|
37
|
+
query = ''
|
|
38
|
+
|
|
39
|
+
if like_semantics && mode = like_semantics[key]
|
|
40
|
+
query = build_like_query(
|
|
41
|
+
type: 'LIKE',
|
|
42
|
+
query: query,
|
|
43
|
+
filter: filter,
|
|
44
|
+
mode: mode,
|
|
45
|
+
key: key
|
|
46
|
+
)
|
|
47
|
+
elsif i_like_semantics && mode = i_like_semantics[key]
|
|
48
|
+
query = build_like_query(
|
|
49
|
+
type: 'ILIKE',
|
|
50
|
+
query: query,
|
|
51
|
+
filter: filter,
|
|
52
|
+
mode: mode,
|
|
53
|
+
key: key
|
|
54
|
+
)
|
|
55
|
+
else
|
|
56
|
+
query = "@model.where(#{key}: #{filter})"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
query
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build_like_query(type:, query:, filter:, mode:, key:)
|
|
63
|
+
if db == :postgres
|
|
64
|
+
query = "@model.where(\"#{key} #{type} ANY (ARRAY[?])\", "
|
|
65
|
+
query += "prepare_terms(#{filter}, :#{mode}))"
|
|
66
|
+
else
|
|
67
|
+
query = "@model.where(\"#{key} #{type} :query\", "
|
|
68
|
+
query += "query: \"%\#{#{filter}}%\")" if mode == :circumfix
|
|
69
|
+
query += "query: \"%\#{#{filter}}\")" if mode == :prefix
|
|
70
|
+
query += "query: \"\#{#{filter}}%\")" if mode == :suffix
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
query
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'active_support/inflector'
|
|
3
|
+
|
|
4
|
+
module Rokaki
|
|
5
|
+
module FilterModel
|
|
6
|
+
class NestedFilter
|
|
7
|
+
def initialize(filter_key_object:, prefix:, infix:, like_semantics:, i_like_semantics:, db:)
|
|
8
|
+
@filter_key_object = filter_key_object
|
|
9
|
+
@prefix = prefix
|
|
10
|
+
@infix = infix
|
|
11
|
+
@like_semantics = like_semantics
|
|
12
|
+
@i_like_semantics = i_like_semantics
|
|
13
|
+
@filter_methods = []
|
|
14
|
+
@filter_templates = []
|
|
15
|
+
@db = db
|
|
16
|
+
end
|
|
17
|
+
attr_reader :filter_key_object, :prefix, :infix, :like_semantics, :i_like_semantics, :db
|
|
18
|
+
attr_accessor :filter_methods, :filter_templates
|
|
19
|
+
|
|
20
|
+
def call # _chain_nested_filter
|
|
21
|
+
filter_key_object.keys.each do |key|
|
|
22
|
+
deep_chain([key], filter_key_object[key])
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def deep_chain(keys, value)
|
|
29
|
+
if value.is_a? Hash
|
|
30
|
+
value.keys.map do |key|
|
|
31
|
+
_keys = keys.dup << key
|
|
32
|
+
deep_chain(_keys, value[key])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if value.is_a? Array
|
|
37
|
+
value.each do |av|
|
|
38
|
+
_keys = keys.dup << av
|
|
39
|
+
_build_deep_chain(_keys)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if value.is_a? Symbol
|
|
44
|
+
_keys = keys.dup << value
|
|
45
|
+
_build_deep_chain(_keys)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def find_like_key(keys)
|
|
50
|
+
return nil unless like_semantics && like_semantics.keys.any?
|
|
51
|
+
current_like_key = like_semantics
|
|
52
|
+
keys.each do |key|
|
|
53
|
+
current_like_key = current_like_key[key]
|
|
54
|
+
end
|
|
55
|
+
current_like_key
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def find_i_like_key(keys)
|
|
59
|
+
return nil unless like_semantics && i_like_semantics.keys.any?
|
|
60
|
+
current_like_key = i_like_semantics
|
|
61
|
+
keys.each do |key|
|
|
62
|
+
current_like_key = current_like_key[key]
|
|
63
|
+
end
|
|
64
|
+
current_like_key
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def _build_deep_chain(keys)
|
|
68
|
+
name = '' # prefix.to_s
|
|
69
|
+
count = keys.size - 1
|
|
70
|
+
|
|
71
|
+
joins_before = []
|
|
72
|
+
joins_after = []
|
|
73
|
+
joins = ''
|
|
74
|
+
where_before = []
|
|
75
|
+
where_after = []
|
|
76
|
+
out = ''
|
|
77
|
+
mode = nil
|
|
78
|
+
type = nil
|
|
79
|
+
leaf = nil
|
|
80
|
+
|
|
81
|
+
if mode = find_like_key(keys)
|
|
82
|
+
type = 'LIKE'
|
|
83
|
+
elsif mode = find_i_like_key(keys)
|
|
84
|
+
type = 'ILIKE'
|
|
85
|
+
end
|
|
86
|
+
leaf = keys.pop
|
|
87
|
+
|
|
88
|
+
keys.each_with_index do |key, i|
|
|
89
|
+
if keys.length == 1
|
|
90
|
+
joins_before << ":#{key}"
|
|
91
|
+
else
|
|
92
|
+
if i == 0
|
|
93
|
+
joins_before << "#{key}: "
|
|
94
|
+
elsif (keys.length-1) == i
|
|
95
|
+
joins_before << " :#{key}"
|
|
96
|
+
else
|
|
97
|
+
joins_before << "{ #{key}:"
|
|
98
|
+
joins_after << " }"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
name += "#{key}#{infix}"
|
|
103
|
+
where_before.push("{ #{key.to_s.pluralize}: ")
|
|
104
|
+
where_after.push(" }")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
joins = joins_before + joins_after
|
|
108
|
+
|
|
109
|
+
name += "#{leaf}"
|
|
110
|
+
where_middle = ["{ #{leaf}: #{prefix}#{name} }"]
|
|
111
|
+
|
|
112
|
+
where = where_before + where_middle + where_after
|
|
113
|
+
joins = joins.join
|
|
114
|
+
where = where.join
|
|
115
|
+
|
|
116
|
+
if mode
|
|
117
|
+
query = build_like_query(
|
|
118
|
+
type: type,
|
|
119
|
+
query: '',
|
|
120
|
+
filter: "#{prefix}#{name}",
|
|
121
|
+
mode: mode,
|
|
122
|
+
key: keys.last.to_s.pluralize,
|
|
123
|
+
leaf: leaf
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
@filter_methods << "def #{prefix}filter#{infix}#{name};"\
|
|
127
|
+
"@model.joins(#{joins}).#{query}; end;"
|
|
128
|
+
|
|
129
|
+
@filter_templates << "@model = #{prefix}filter#{infix}#{name} if #{prefix}#{name};"
|
|
130
|
+
else
|
|
131
|
+
@filter_methods << "def #{prefix}filter#{infix}#{name};"\
|
|
132
|
+
"@model.joins(#{joins}).where(#{where}); end;"
|
|
133
|
+
|
|
134
|
+
@filter_templates << "@model = #{prefix}filter#{infix}#{name} if #{prefix}#{name};"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def build_like_query(type:, query:, filter:, mode:, key:, leaf:)
|
|
139
|
+
if db == :postgres
|
|
140
|
+
query = "where(\"#{key}.#{leaf} #{type} ANY (ARRAY[?])\", "
|
|
141
|
+
query += "prepare_terms(#{filter}, :#{mode}))"
|
|
142
|
+
else
|
|
143
|
+
query = "where(\"#{key}.#{leaf} #{type} :query\", "
|
|
144
|
+
query += "query: \"%\#{#{filter}}%\")" if mode == :circumfix
|
|
145
|
+
query += "query: \"%\#{#{filter}}\")" if mode == :prefix
|
|
146
|
+
query += "query: \"\#{#{filter}}%\")" if mode == :suffix
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
query
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
data/lib/rokaki/filterable.rb
CHANGED
|
@@ -16,6 +16,13 @@ module Rokaki
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
def define_filter_map(query_field, *filter_keys)
|
|
20
|
+
filter_keys.each do |filter_key|
|
|
21
|
+
_map_filters(query_field, [filter_key]) unless filter_key.is_a? Hash
|
|
22
|
+
_nested_map query_field, filter_key if filter_key.is_a? Hash
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
19
26
|
def filter_key_prefix(prefix = nil)
|
|
20
27
|
@filter_key_prefix ||= prefix
|
|
21
28
|
end
|
|
@@ -24,6 +31,10 @@ module Rokaki
|
|
|
24
31
|
@filter_key_infix ||= infix
|
|
25
32
|
end
|
|
26
33
|
|
|
34
|
+
def filterable_object_name(name = 'filters')
|
|
35
|
+
@filterable_object_name ||= name
|
|
36
|
+
end
|
|
37
|
+
|
|
27
38
|
def _build_filter(keys)
|
|
28
39
|
name = @filter_key_prefix.to_s
|
|
29
40
|
count = keys.size - 1
|
|
@@ -33,12 +44,30 @@ module Rokaki
|
|
|
33
44
|
name += filter_key_infix.to_s unless count == i
|
|
34
45
|
end
|
|
35
46
|
|
|
36
|
-
class_eval "def #{name};
|
|
47
|
+
class_eval "def #{name}; #{filterable_object_name}.dig(*#{keys}); end;", __FILE__, __LINE__
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def _map_filters(query_field, keys)
|
|
51
|
+
name = @filter_key_prefix.to_s
|
|
52
|
+
count = keys.size - 1
|
|
53
|
+
|
|
54
|
+
keys.each_with_index do |key, i|
|
|
55
|
+
name += key.to_s
|
|
56
|
+
name += filter_key_infix.to_s unless count == i
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class_eval "def #{name}; #{filterable_object_name}.dig(:#{query_field}); end;", __FILE__, __LINE__
|
|
37
60
|
end
|
|
38
61
|
|
|
39
62
|
def _nested_key(filters_object)
|
|
40
63
|
filters_object.keys.each do |key|
|
|
41
|
-
deep_map([key], filters_object[key])
|
|
64
|
+
deep_map([key], filters_object[key]) { |keys| _build_filter(keys) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def _nested_map(query_field, filters_object)
|
|
69
|
+
filters_object.keys.each do |key|
|
|
70
|
+
deep_map([key], filters_object[key]) { |keys| _map_filters(query_field, keys) }
|
|
42
71
|
end
|
|
43
72
|
end
|
|
44
73
|
|
|
@@ -46,21 +75,24 @@ module Rokaki
|
|
|
46
75
|
if value.is_a? Hash
|
|
47
76
|
value.keys.map do |key|
|
|
48
77
|
_keys = keys.dup << key
|
|
49
|
-
deep_map(_keys, value[key])
|
|
78
|
+
deep_map(_keys, value[key], &Proc.new)
|
|
50
79
|
end
|
|
51
80
|
end
|
|
52
81
|
|
|
53
|
-
|
|
54
82
|
if value.is_a? Array
|
|
55
83
|
value.each do |av|
|
|
84
|
+
if av.is_a? Symbol
|
|
56
85
|
_keys = keys.dup << av
|
|
57
|
-
|
|
86
|
+
yield _keys
|
|
87
|
+
else
|
|
88
|
+
deep_map(keys, av, &Proc.new)
|
|
89
|
+
end
|
|
58
90
|
end
|
|
59
91
|
end
|
|
60
92
|
|
|
61
93
|
if value.is_a? Symbol
|
|
62
94
|
_keys = keys.dup << value
|
|
63
|
-
|
|
95
|
+
yield _keys
|
|
64
96
|
end
|
|
65
97
|
end
|
|
66
98
|
|
data/lib/rokaki/version.rb
CHANGED
data/rokaki.gemspec
CHANGED
|
@@ -1,39 +1,46 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
-
require
|
|
5
|
+
require 'rokaki/version'
|
|
4
6
|
|
|
5
7
|
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name =
|
|
8
|
+
spec.name = 'rokaki'
|
|
7
9
|
spec.version = Rokaki::VERSION
|
|
8
|
-
spec.authors = [
|
|
9
|
-
spec.email = [
|
|
10
|
+
spec.authors = ['Steve Martin']
|
|
11
|
+
spec.email = ['steve@martian.media']
|
|
10
12
|
|
|
11
|
-
spec.summary =
|
|
12
|
-
spec.description =
|
|
13
|
-
spec.homepage =
|
|
14
|
-
spec.license =
|
|
13
|
+
spec.summary = 'A web request filtering library'
|
|
14
|
+
spec.description = 'A dsl for filtering data in web requests'
|
|
15
|
+
spec.homepage = 'https://github.com/tevio/rokaki'
|
|
16
|
+
spec.license = 'MIT'
|
|
15
17
|
|
|
16
18
|
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
|
17
19
|
|
|
18
|
-
spec.metadata[
|
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
19
21
|
# spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
|
20
22
|
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
|
21
23
|
|
|
22
24
|
# Specify which files should be added to the gem when it is released.
|
|
23
25
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
24
|
-
spec.files
|
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
25
27
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
26
28
|
end
|
|
27
|
-
spec.bindir =
|
|
29
|
+
spec.bindir = 'exe'
|
|
28
30
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
29
|
-
spec.require_paths = [
|
|
30
|
-
|
|
31
|
-
spec.
|
|
32
|
-
|
|
33
|
-
spec.add_development_dependency
|
|
34
|
-
spec.add_development_dependency
|
|
35
|
-
spec.add_development_dependency
|
|
36
|
-
spec.add_development_dependency
|
|
37
|
-
spec.add_development_dependency
|
|
38
|
-
spec.add_development_dependency
|
|
31
|
+
spec.require_paths = ['lib']
|
|
32
|
+
|
|
33
|
+
spec.add_dependency 'activesupport'
|
|
34
|
+
|
|
35
|
+
spec.add_development_dependency 'activerecord'
|
|
36
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
37
|
+
spec.add_development_dependency 'factory_bot'
|
|
38
|
+
spec.add_development_dependency 'guard'
|
|
39
|
+
spec.add_development_dependency 'guard-rspec'
|
|
40
|
+
spec.add_development_dependency 'pg'
|
|
41
|
+
spec.add_development_dependency 'pry'
|
|
42
|
+
spec.add_development_dependency 'pry-byebug'
|
|
43
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
|
44
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
45
|
+
spec.add_development_dependency 'sqlite3'
|
|
39
46
|
end
|
metadata
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rokaki
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Steve Martin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2020-06-21 00:00:00.000000000 Z
|
|
12
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: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activerecord
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
13
41
|
- !ruby/object:Gem::Dependency
|
|
14
42
|
name: bundler
|
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -25,35 +53,35 @@ dependencies:
|
|
|
25
53
|
- !ruby/object:Gem::Version
|
|
26
54
|
version: '2.0'
|
|
27
55
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
56
|
+
name: factory_bot
|
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
|
30
58
|
requirements:
|
|
31
|
-
- - "
|
|
59
|
+
- - ">="
|
|
32
60
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
61
|
+
version: '0'
|
|
34
62
|
type: :development
|
|
35
63
|
prerelease: false
|
|
36
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
65
|
requirements:
|
|
38
|
-
- - "
|
|
66
|
+
- - ">="
|
|
39
67
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
68
|
+
version: '0'
|
|
41
69
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
70
|
+
name: guard
|
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
|
44
72
|
requirements:
|
|
45
|
-
- - "
|
|
73
|
+
- - ">="
|
|
46
74
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
75
|
+
version: '0'
|
|
48
76
|
type: :development
|
|
49
77
|
prerelease: false
|
|
50
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
79
|
requirements:
|
|
52
|
-
- - "
|
|
80
|
+
- - ">="
|
|
53
81
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
82
|
+
version: '0'
|
|
55
83
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: guard
|
|
84
|
+
name: guard-rspec
|
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
|
58
86
|
requirements:
|
|
59
87
|
- - ">="
|
|
@@ -67,7 +95,7 @@ dependencies:
|
|
|
67
95
|
- !ruby/object:Gem::Version
|
|
68
96
|
version: '0'
|
|
69
97
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
98
|
+
name: pg
|
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
|
72
100
|
requirements:
|
|
73
101
|
- - ">="
|
|
@@ -81,7 +109,7 @@ dependencies:
|
|
|
81
109
|
- !ruby/object:Gem::Version
|
|
82
110
|
version: '0'
|
|
83
111
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
112
|
+
name: pry
|
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
|
86
114
|
requirements:
|
|
87
115
|
- - ">="
|
|
@@ -95,7 +123,7 @@ dependencies:
|
|
|
95
123
|
- !ruby/object:Gem::Version
|
|
96
124
|
version: '0'
|
|
97
125
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
126
|
+
name: pry-byebug
|
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
|
100
128
|
requirements:
|
|
101
129
|
- - ">="
|
|
@@ -108,6 +136,34 @@ dependencies:
|
|
|
108
136
|
- - ">="
|
|
109
137
|
- !ruby/object:Gem::Version
|
|
110
138
|
version: '0'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: rake
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '10.0'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '10.0'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: rspec
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - "~>"
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '3.0'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - "~>"
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '3.0'
|
|
111
167
|
- !ruby/object:Gem::Dependency
|
|
112
168
|
name: sqlite3
|
|
113
169
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -143,7 +199,10 @@ files:
|
|
|
143
199
|
- bin/setup
|
|
144
200
|
- lib/rokaki.rb
|
|
145
201
|
- lib/rokaki/filter_model.rb
|
|
202
|
+
- lib/rokaki/filter_model/basic_filter.rb
|
|
203
|
+
- lib/rokaki/filter_model/filter_chain.rb
|
|
146
204
|
- lib/rokaki/filter_model/like_keys.rb
|
|
205
|
+
- lib/rokaki/filter_model/nested_filter.rb
|
|
147
206
|
- lib/rokaki/filterable.rb
|
|
148
207
|
- lib/rokaki/version.rb
|
|
149
208
|
- rokaki.gemspec
|
|
@@ -167,7 +226,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
167
226
|
- !ruby/object:Gem::Version
|
|
168
227
|
version: '0'
|
|
169
228
|
requirements: []
|
|
170
|
-
rubygems_version: 3.
|
|
229
|
+
rubygems_version: 3.1.2
|
|
171
230
|
signing_key:
|
|
172
231
|
specification_version: 4
|
|
173
232
|
summary: A web request filtering library
|