rokaki 0.5.1 → 0.8.1
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 +53 -44
- data/Guardfile +1 -0
- data/LICENSE.txt +2 -0
- data/README.md +200 -27
- data/lib/rokaki.rb +2 -0
- data/lib/rokaki/filter_model.rb +68 -90
- 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: 8a4312a80ff21e5c59187f2677c803626aa2d9bd2331316dea46de60461c50fa
|
|
4
|
+
data.tar.gz: f2d1b2b2aeefb8f82cf7be3ecfe5f058472701ad23064a8909fe09e6c289e4d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c4d9d19c3d0f008721cfe2cbae3364f29e46fc094edd03af7738999a5cbe3680606f9201d884d4ce1316d5ad3da54b1f572d8bec7bcc3a00cc60696e36a0db11
|
|
7
|
+
data.tar.gz: 2c83644c0338d9024422de614edb92d336ec542261f3453b07f5713e676e8c0c6c5947e8e75fb85bfde6f862185f56a9eac44664ea6769767dda2e901937b9ea
|
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.1)
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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)
|
|
62
|
+
rake (13.0.1)
|
|
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
|
|
90
|
-
|
|
98
|
+
pry-byebug
|
|
99
|
+
rake (~> 13.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,14 +1,19 @@
|
|
|
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:
|
|
11
10
|
|
|
11
|
+
You can install from Rubygems:
|
|
12
|
+
```
|
|
13
|
+
gem 'rokaki'
|
|
14
|
+
```
|
|
15
|
+
Or from github
|
|
16
|
+
|
|
12
17
|
```ruby
|
|
13
18
|
gem 'rokaki', git: 'https://github.com/tevio/rokaki.git'
|
|
14
19
|
```
|
|
@@ -17,42 +22,118 @@ And then execute:
|
|
|
17
22
|
|
|
18
23
|
$ bundle
|
|
19
24
|
|
|
20
|
-
## Usage
|
|
25
|
+
## `Rokaki::Filterable` - Usage
|
|
26
|
+
|
|
27
|
+
To use the DSL first include the `Rokaki::Filterable` module in your [por](http://blog.jayfields.com/2007/10/ruby-poro.html) class.
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
### `#define_filter_keys`
|
|
30
|
+
#### A Simple Example
|
|
23
31
|
|
|
24
32
|
A simple example might be:-
|
|
25
33
|
|
|
26
34
|
```ruby
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
class FilterArticles
|
|
36
|
+
include Rokaki::Filterable
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
def initialize(filters:)
|
|
39
|
+
@filters = filters
|
|
40
|
+
@articles = Article
|
|
41
|
+
end
|
|
34
42
|
|
|
35
|
-
|
|
43
|
+
attr_accessor :filters
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
define_filter_keys :date, author: [:first_name, :last_name]
|
|
38
46
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
end
|
|
47
|
+
def filter_results
|
|
48
|
+
@articles = @articles.where(date: date) if date
|
|
49
|
+
@articles = @articles.joins(:author).where(author: { first_name: author_first_name }) if author_first_name
|
|
43
50
|
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
article_filter = FilterArticles.new(filters: {
|
|
54
|
+
date: '10-10-10',
|
|
55
|
+
author: {
|
|
56
|
+
first_name: 'Steve',
|
|
57
|
+
last_name: 'Martin'
|
|
58
|
+
}})
|
|
59
|
+
article_filter.author_first_name == 'Steve'
|
|
60
|
+
article_filter.author_last_name == 'Martin'
|
|
61
|
+
article_filter.date == '10-10-10'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
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.
|
|
65
|
+
|
|
66
|
+
#### A More Complex Example
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
class AdvancedFilterable
|
|
70
|
+
include Rokaki::Filterable
|
|
71
|
+
|
|
72
|
+
def initialize(filters:)
|
|
73
|
+
@fyltrz = filters
|
|
74
|
+
end
|
|
75
|
+
attr_accessor :fyltrz
|
|
76
|
+
|
|
77
|
+
filterable_object_name :fyltrz
|
|
78
|
+
filter_key_infix :__
|
|
79
|
+
define_filter_keys :basic, advanced: {
|
|
80
|
+
filter_key_1: [:filter_key_2, { filter_key_3: :deep_node }],
|
|
81
|
+
filter_key_4: :deep_leaf_array
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
advanced_filterable = AdvancedFilterable.new(filters: {
|
|
87
|
+
basic: 'ABC',
|
|
88
|
+
advanced: {
|
|
89
|
+
filter_key_1: {
|
|
90
|
+
filter_key_2: '123',
|
|
91
|
+
filter_key_3: { deep_node: 'NODE' }
|
|
92
|
+
},
|
|
93
|
+
filter_key_4: { deep_leaf_array: [1,2,3,4] }
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
advanced_filterable.advanced__filter_key_4__deep_leaf_array == [1,2,3,4]
|
|
98
|
+
advanced_filterable.advanced__filter_key_1__filter_key_3__deep_node == 'NODE'
|
|
44
99
|
```
|
|
100
|
+
### `#define_filter_map`
|
|
45
101
|
|
|
46
|
-
This
|
|
102
|
+
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.
|
|
47
103
|
|
|
48
|
-
|
|
49
|
-
|
|
104
|
+
#### A Simple Example
|
|
105
|
+
```ruby
|
|
106
|
+
class FilterMap
|
|
107
|
+
include Rokaki::Filterable
|
|
108
|
+
|
|
109
|
+
def initialize(fylterz:)
|
|
110
|
+
@fylterz = fylterz
|
|
111
|
+
end
|
|
112
|
+
attr_accessor :fylterz
|
|
113
|
+
|
|
114
|
+
filterable_object_name :fylterz
|
|
115
|
+
define_filter_map :query, :mapped_a, association: :field
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
filter_map = FilterMap.new(fytlerz: { query: 'H2O' })
|
|
119
|
+
|
|
120
|
+
filter_map.mapped_a == 'H2O'
|
|
121
|
+
filter_map.association_field = 'H2O'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### Additional `Filterable` options
|
|
125
|
+
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
126
|
|
|
51
127
|
`filter_key_prefix :__` would result in key accessors like `__author_first_name`
|
|
52
128
|
|
|
53
129
|
`filter_key_infix :__` would result in key accessors like `author__first_name`
|
|
54
130
|
|
|
55
|
-
|
|
131
|
+
`filterable_object_name :fylterz` would use an internal filter state object named `@fyltrz` instead of the default `@filters`
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
## `Rokaki::FilterModel` - Usage
|
|
135
|
+
|
|
136
|
+
### ActiveRecord
|
|
56
137
|
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
138
|
|
|
58
139
|
```ruby
|
|
@@ -67,7 +148,7 @@ end
|
|
|
67
148
|
|
|
68
149
|
|
|
69
150
|
class ArticleFilter
|
|
70
|
-
include FilterModel
|
|
151
|
+
include Rokaki::FilterModel
|
|
71
152
|
|
|
72
153
|
filters :date, :title, author: [:first_name, :last_name]
|
|
73
154
|
|
|
@@ -84,16 +165,19 @@ filter = ArticleFilter.new(filters: params[:filters])
|
|
|
84
165
|
filtered_results = filter.results
|
|
85
166
|
|
|
86
167
|
```
|
|
168
|
+
### Arrays of params
|
|
169
|
+
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'] } }`.
|
|
170
|
+
|
|
87
171
|
|
|
88
172
|
### Partial matching
|
|
89
|
-
You can use `like` (or, if you use postgres, the case insensitive `ilike`) to perform a partial match on a specific
|
|
173
|
+
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
174
|
|
|
91
175
|
#### 1. The `filter` command syntax
|
|
92
176
|
|
|
93
177
|
|
|
94
178
|
```ruby
|
|
95
179
|
class ArticleFilter
|
|
96
|
-
include FilterModel
|
|
180
|
+
include Rokaki::FilterModel
|
|
97
181
|
|
|
98
182
|
filter :article,
|
|
99
183
|
like: { # you can use ilike here instead if you use postgres and want case insensitive results
|
|
@@ -112,14 +196,45 @@ end
|
|
|
112
196
|
```
|
|
113
197
|
Or
|
|
114
198
|
|
|
115
|
-
#### 2. The
|
|
199
|
+
#### 2. The `filter_map` command syntax
|
|
200
|
+
`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`)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
class AuthorFilter
|
|
205
|
+
include Rokaki::FilterModel
|
|
206
|
+
|
|
207
|
+
filter_map :author, :query,
|
|
208
|
+
like: {
|
|
209
|
+
articles: {
|
|
210
|
+
title: :circumfix,
|
|
211
|
+
reviews: {
|
|
212
|
+
title: :circumfix
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
attr_accessor :filters, :model
|
|
218
|
+
|
|
219
|
+
def initialize(filters:)
|
|
220
|
+
@filters = filters
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
filters = { query: "Jiddu" }
|
|
225
|
+
filtered_authors = AuthorFilter.new(filters: filters).results
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
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.
|
|
229
|
+
|
|
230
|
+
#### 3. The porcelain command syntax
|
|
116
231
|
|
|
117
232
|
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
233
|
|
|
119
234
|
|
|
120
235
|
```ruby
|
|
121
236
|
class ArticleFilter
|
|
122
|
-
include FilterModel
|
|
237
|
+
include Rokaki::FilterModel
|
|
123
238
|
|
|
124
239
|
filters :date, :title, author: [:first_name, :last_name]
|
|
125
240
|
like title: :circumfix
|
|
@@ -138,7 +253,7 @@ Or without the model in the initializer
|
|
|
138
253
|
|
|
139
254
|
```ruby
|
|
140
255
|
class ArticleFilter
|
|
141
|
-
include FilterModel
|
|
256
|
+
include Rokaki::FilterModel
|
|
142
257
|
|
|
143
258
|
filters :date, :title, author: [:first_name, :last_name]
|
|
144
259
|
like title: :circumfix
|
|
@@ -158,6 +273,64 @@ Would produce a query with a LIKE which circumfixes '%' around the filter term,
|
|
|
158
273
|
@model = @model.where('title LIKE :query', query: "%#{title}%")
|
|
159
274
|
```
|
|
160
275
|
|
|
276
|
+
### Deep nesting
|
|
277
|
+
You can filter joins both with basic matching and partial matching
|
|
278
|
+
```ruby
|
|
279
|
+
class ArticleFilter
|
|
280
|
+
include Rokaki::FilterModel
|
|
281
|
+
|
|
282
|
+
filter :author,
|
|
283
|
+
like: {
|
|
284
|
+
articles: {
|
|
285
|
+
reviews: {
|
|
286
|
+
title: :circumfix
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
attr_accessor :filters
|
|
292
|
+
|
|
293
|
+
def initialize(filters:)
|
|
294
|
+
@filters = filters
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Array params
|
|
300
|
+
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
|
|
301
|
+
|
|
302
|
+
```ruby
|
|
303
|
+
class ArticleFilter
|
|
304
|
+
include Rokaki::FilterModel
|
|
305
|
+
|
|
306
|
+
filter :article,
|
|
307
|
+
like: {
|
|
308
|
+
author: {
|
|
309
|
+
first_name: :circumfix,
|
|
310
|
+
last_name: :circumfix
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
match: %i[title created_at],
|
|
314
|
+
db: :postgres
|
|
315
|
+
|
|
316
|
+
attr_accessor :filters
|
|
317
|
+
|
|
318
|
+
def initialize(filters:)
|
|
319
|
+
@filters = filters
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
filterable = ArticleFilter.new(filters:
|
|
324
|
+
{
|
|
325
|
+
author: {
|
|
326
|
+
first_name: ['Match One', 'Match Two']
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
filterable.results
|
|
332
|
+
```
|
|
333
|
+
|
|
161
334
|
|
|
162
335
|
## Development
|
|
163
336
|
|
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,120 +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
|
-
key: key
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
elsif @i_like_semantics && mode = @i_like_semantics[key]
|
|
67
|
-
query = like_semantics(
|
|
68
|
-
type: 'ILIKE',
|
|
69
|
-
query: query,
|
|
70
|
-
filter: filter,
|
|
71
|
-
mode: mode,
|
|
72
|
-
key: key
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
else
|
|
76
|
-
query = "@model.where(#{key}: #{filter})"
|
|
77
|
-
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
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
end
|
|
78
|
+
class_eval basic_filter.filter_method, __FILE__, __LINE__ - 2
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
query = "@model.where(\"#{key} #{type} :query\", "
|
|
84
|
-
query += "query: \"%\#{#{filter}}%\")" if mode == :circumfix
|
|
85
|
-
query += "query: \"%\#{#{filter}}\")" if mode == :prefix
|
|
86
|
-
query += "query: \"\#{#{filter}}%\")" if mode == :suffix
|
|
87
|
-
query
|
|
80
|
+
@_chain_filters << basic_filter.filter_template
|
|
88
81
|
end
|
|
89
82
|
|
|
90
|
-
def
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
joins = ":#{key}"
|
|
104
|
-
|
|
105
|
-
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
|
|
106
96
|
end
|
|
107
97
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# chain filter here?
|
|
112
|
-
#
|
|
113
|
-
filter_method = "def #{filter_key_prefix}filter_#{name};"\
|
|
114
|
-
"@model.joins(#{joins}).where(#{where}); end;"
|
|
115
|
-
|
|
116
|
-
class_eval filter_method, __FILE__, __LINE__ - 2
|
|
117
|
-
|
|
118
|
-
@_chain_filters << "@model = #{filter_key_prefix}filter_#{name} if #{name};"
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def _chain_nested(filters_object)
|
|
122
|
-
filters_object.keys.each do |key|
|
|
123
|
-
deep_chain([key], filters_object[key])
|
|
98
|
+
nested_filter.filter_templates.each do |filter_template|
|
|
99
|
+
@_chain_filters << filter_template
|
|
124
100
|
end
|
|
125
101
|
end
|
|
126
102
|
|
|
@@ -128,9 +104,9 @@ module Rokaki
|
|
|
128
104
|
@model.reflect_on_association(association).klass.table_name
|
|
129
105
|
end
|
|
130
106
|
|
|
131
|
-
def filter_model(
|
|
132
|
-
@model = (
|
|
133
|
-
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;"
|
|
134
110
|
end
|
|
135
111
|
|
|
136
112
|
def like(args)
|
|
@@ -138,8 +114,9 @@ module Rokaki
|
|
|
138
114
|
@_like_semantics = (@_like_semantics || {}).merge(args)
|
|
139
115
|
|
|
140
116
|
key_builder = LikeKeys.new(args)
|
|
117
|
+
keys = key_builder.call
|
|
141
118
|
|
|
142
|
-
filters(*
|
|
119
|
+
filters(*keys)
|
|
143
120
|
end
|
|
144
121
|
|
|
145
122
|
def ilike(args)
|
|
@@ -147,8 +124,9 @@ module Rokaki
|
|
|
147
124
|
@i_like_semantics = (@i_like_semantics || {}).merge(args)
|
|
148
125
|
|
|
149
126
|
key_builder = LikeKeys.new(args)
|
|
127
|
+
keys = key_builder.call
|
|
150
128
|
|
|
151
|
-
filters(*
|
|
129
|
+
filters(*keys)
|
|
152
130
|
end
|
|
153
131
|
|
|
154
132
|
def deep_chain(keys, value)
|
|
@@ -176,7 +154,7 @@ module Rokaki
|
|
|
176
154
|
# filter_model method
|
|
177
155
|
#
|
|
178
156
|
def define_results
|
|
179
|
-
results_def = 'def results;model;'
|
|
157
|
+
results_def = 'def results; @model || set_model;'
|
|
180
158
|
@_chain_filters.each do |item|
|
|
181
159
|
results_def += item
|
|
182
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', '~> 13.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.1
|
|
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: '13.0'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '13.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
|