rokaki 0.8.1.2 → 0.8.4
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/.ruby-version +1 -0
- data/Gemfile.lock +31 -27
- data/README.md +87 -2
- data/lib/rokaki.rb +2 -3
- data/lib/rokaki/filter_model.rb +49 -38
- data/lib/rokaki/filter_model/basic_filter.rb +3 -2
- data/lib/rokaki/filter_model/deep_assign_struct.rb +57 -0
- data/lib/rokaki/filter_model/join_map.rb +105 -0
- data/lib/rokaki/filter_model/like_keys.rb +24 -22
- data/lib/rokaki/filter_model/nested_filter.rb +11 -11
- data/lib/rokaki/filter_model/nested_like_filters.rb +241 -0
- data/lib/rokaki/filterable.rb +14 -4
- data/lib/rokaki/version.rb +1 -1
- data/rokaki.gemspec +8 -4
- metadata +45 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7b14c1377cb6ea9d8aa3571f310ffc884f027c495067e08adfce02caf1f7d0d2
|
|
4
|
+
data.tar.gz: 5326391f9ed786eb26f9cb5573d2afc78862e72b5ab64f8b88f26b8ff18fdcfe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a5c2e4a0b0fea5a627db0136291654aa6df25e6a2f548ba3c42fc1ebc8b3e427c3fcc53e0488c1df6e99f8bb6fd44e752c3ccdabe1f4be506a5fd7d53878cf3c
|
|
7
|
+
data.tar.gz: 79934522b0dba27889946208f47a8b2d769362016ae914ce99c7796c0b6899f6de8d96e25569e9ecc5086f0b0aeae23f0a6e21b6fe410db022eb89d6c6c15dd7
|
data/.gitignore
CHANGED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.7.4
|
data/Gemfile.lock
CHANGED
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
rokaki (0.8.1
|
|
4
|
+
rokaki (0.8.3.1)
|
|
5
5
|
activesupport
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
activemodel (6.
|
|
11
|
-
activesupport (= 6.
|
|
12
|
-
activerecord (6.
|
|
13
|
-
activemodel (= 6.
|
|
14
|
-
activesupport (= 6.
|
|
15
|
-
activesupport (6.
|
|
10
|
+
activemodel (6.1.3)
|
|
11
|
+
activesupport (= 6.1.3)
|
|
12
|
+
activerecord (6.1.3)
|
|
13
|
+
activemodel (= 6.1.3)
|
|
14
|
+
activesupport (= 6.1.3)
|
|
15
|
+
activesupport (6.1.3)
|
|
16
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
17
|
-
i18n (>=
|
|
18
|
-
minitest (
|
|
19
|
-
tzinfo (~>
|
|
20
|
-
zeitwerk (~> 2.
|
|
17
|
+
i18n (>= 1.6, < 2)
|
|
18
|
+
minitest (>= 5.1)
|
|
19
|
+
tzinfo (~> 2.0)
|
|
20
|
+
zeitwerk (~> 2.3)
|
|
21
21
|
byebug (11.1.3)
|
|
22
22
|
coderay (1.1.3)
|
|
23
|
-
concurrent-ruby (1.1.
|
|
23
|
+
concurrent-ruby (1.1.8)
|
|
24
|
+
database_cleaner (1.8.5)
|
|
25
|
+
database_cleaner-active_record (1.8.0)
|
|
26
|
+
activerecord
|
|
27
|
+
database_cleaner (~> 1.8.0)
|
|
24
28
|
diff-lcs (1.3)
|
|
25
29
|
factory_bot (6.0.2)
|
|
26
30
|
activesupport (>= 5.0.0)
|
|
27
|
-
ffi (1.
|
|
28
|
-
formatador (0.
|
|
29
|
-
guard (2.
|
|
31
|
+
ffi (1.15.3)
|
|
32
|
+
formatador (0.3.0)
|
|
33
|
+
guard (2.18.0)
|
|
30
34
|
formatador (>= 0.2.4)
|
|
31
35
|
listen (>= 2.7, < 4.0)
|
|
32
36
|
lumberjack (>= 1.0.12, < 2.0)
|
|
33
37
|
nenv (~> 0.1)
|
|
34
38
|
notiffany (~> 0.0)
|
|
35
|
-
pry (>= 0.
|
|
39
|
+
pry (>= 0.13.0)
|
|
36
40
|
shellany (~> 0.0)
|
|
37
41
|
thor (>= 0.18.1)
|
|
38
42
|
guard-compat (1.2.1)
|
|
@@ -40,14 +44,14 @@ GEM
|
|
|
40
44
|
guard (~> 2.1)
|
|
41
45
|
guard-compat (~> 1.1)
|
|
42
46
|
rspec (>= 2.99.0, < 4.0)
|
|
43
|
-
i18n (1.8.
|
|
47
|
+
i18n (1.8.9)
|
|
44
48
|
concurrent-ruby (~> 1.0)
|
|
45
|
-
listen (3.
|
|
49
|
+
listen (3.5.1)
|
|
46
50
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
47
51
|
rb-inotify (~> 0.9, >= 0.9.10)
|
|
48
|
-
lumberjack (1.2.
|
|
52
|
+
lumberjack (1.2.8)
|
|
49
53
|
method_source (1.0.0)
|
|
50
|
-
minitest (5.14.
|
|
54
|
+
minitest (5.14.4)
|
|
51
55
|
nenv (0.3.0)
|
|
52
56
|
notiffany (0.1.3)
|
|
53
57
|
nenv (~> 0.1)
|
|
@@ -60,7 +64,7 @@ GEM
|
|
|
60
64
|
byebug (~> 11.0)
|
|
61
65
|
pry (~> 0.13.0)
|
|
62
66
|
rake (13.0.1)
|
|
63
|
-
rb-fsevent (0.
|
|
67
|
+
rb-fsevent (0.11.0)
|
|
64
68
|
rb-inotify (0.10.1)
|
|
65
69
|
ffi (~> 1.0)
|
|
66
70
|
rspec (3.9.0)
|
|
@@ -78,11 +82,10 @@ GEM
|
|
|
78
82
|
rspec-support (3.9.3)
|
|
79
83
|
shellany (0.0.1)
|
|
80
84
|
sqlite3 (1.4.2)
|
|
81
|
-
thor (1.0
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
zeitwerk (2.3.0)
|
|
85
|
+
thor (1.1.0)
|
|
86
|
+
tzinfo (2.0.4)
|
|
87
|
+
concurrent-ruby (~> 1.0)
|
|
88
|
+
zeitwerk (2.4.2)
|
|
86
89
|
|
|
87
90
|
PLATFORMS
|
|
88
91
|
ruby
|
|
@@ -90,6 +93,7 @@ PLATFORMS
|
|
|
90
93
|
DEPENDENCIES
|
|
91
94
|
activerecord
|
|
92
95
|
bundler (~> 2.0)
|
|
96
|
+
database_cleaner-active_record
|
|
93
97
|
factory_bot
|
|
94
98
|
guard
|
|
95
99
|
guard-rspec
|
|
@@ -102,4 +106,4 @@ DEPENDENCIES
|
|
|
102
106
|
sqlite3
|
|
103
107
|
|
|
104
108
|
BUNDLED WITH
|
|
105
|
-
2.
|
|
109
|
+
2.2.3
|
data/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Rokaki
|
|
2
|
+
|
|
2
3
|
[](https://badge.fury.io/rb/rokaki)
|
|
3
4
|
|
|
4
5
|
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".
|
|
@@ -46,7 +47,8 @@ class FilterArticles
|
|
|
46
47
|
|
|
47
48
|
def filter_results
|
|
48
49
|
@articles = @articles.where(date: date) if date
|
|
49
|
-
@articles = @articles.joins(:author).where(
|
|
50
|
+
@articles = @articles.joins(:author).where(authors: { first_name: author_first_name }) if author_first_name
|
|
51
|
+
@articles = @articles.joins(:author).where(authors: { last_name: author_last_name }) if author_last_name
|
|
50
52
|
end
|
|
51
53
|
end
|
|
52
54
|
|
|
@@ -98,6 +100,7 @@ advanced_filterable.advanced__filter_key_4__deep_leaf_array == [1,2,3,4]
|
|
|
98
100
|
advanced_filterable.advanced__filter_key_1__filter_key_3__deep_node == 'NODE'
|
|
99
101
|
```
|
|
100
102
|
### `#define_filter_map`
|
|
103
|
+
The define_filter_map method is more suited to classic "search", where you might want to search multiple fields on a model or across a graph. See the section on [filter_map](https://github.com/tevio/rokaki#2-the-filter_map-command-syntax) with OR for more on this kind of application.
|
|
101
104
|
|
|
102
105
|
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.
|
|
103
106
|
|
|
@@ -115,7 +118,7 @@ class FilterMap
|
|
|
115
118
|
define_filter_map :query, :mapped_a, association: :field
|
|
116
119
|
end
|
|
117
120
|
|
|
118
|
-
filter_map = FilterMap.new(
|
|
121
|
+
filter_map = FilterMap.new(fylterz: { query: 'H2O' })
|
|
119
122
|
|
|
120
123
|
filter_map.mapped_a == 'H2O'
|
|
121
124
|
filter_map.association_field = 'H2O'
|
|
@@ -227,6 +230,88 @@ filtered_authors = AuthorFilter.new(filters: filters).results
|
|
|
227
230
|
|
|
228
231
|
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
232
|
|
|
233
|
+
The above example performs an "ALL" like query, where all fields must satisfy the query term. Conversly you can use `or` to perform an "ANY", where any of the fields within the `or` will satisfy the query term, like so:-
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
class AuthorFilter
|
|
238
|
+
include Rokaki::FilterModel
|
|
239
|
+
|
|
240
|
+
filter_map :author, :query,
|
|
241
|
+
like: {
|
|
242
|
+
articles: {
|
|
243
|
+
title: :circumfix,
|
|
244
|
+
or: { # the or is aware of the join and will generate a compound join aware or query
|
|
245
|
+
reviews: {
|
|
246
|
+
title: :circumfix
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
attr_accessor :filters, :model
|
|
253
|
+
|
|
254
|
+
def initialize(filters:)
|
|
255
|
+
@filters = filters
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
filters = { query: "Lao" }
|
|
260
|
+
filtered_authors = AuthorFilter.new(filters: filters).results
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## CAVEATS
|
|
264
|
+
Active record OR over a join may require you to add something like the following in an initializer in order for it to function properly:-
|
|
265
|
+
|
|
266
|
+
### #structurally_incompatible_values_for_or
|
|
267
|
+
|
|
268
|
+
``` ruby
|
|
269
|
+
module ActiveRecord
|
|
270
|
+
module QueryMethods
|
|
271
|
+
def structurally_incompatible_values_for_or(other)
|
|
272
|
+
Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } +
|
|
273
|
+
(Relation::MULTI_VALUE_METHODS - [:joins, :eager_load, :references, :extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } +
|
|
274
|
+
(Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") }
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### A has one relation to a model called Or
|
|
281
|
+
If you happen to have a model/table named 'Or' then you can override the `or:` key syntax by specifying a special `or_key`:-
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
class AuthorFilter
|
|
285
|
+
include Rokaki::FilterModel
|
|
286
|
+
|
|
287
|
+
or_key :my_or
|
|
288
|
+
filter_map :author, :query,
|
|
289
|
+
like: {
|
|
290
|
+
articles: {
|
|
291
|
+
title: :circumfix,
|
|
292
|
+
my_or: { # the or is aware of the join and will generate a compound join aware or query
|
|
293
|
+
or: { # The Or model has a title field
|
|
294
|
+
title: :circumfix
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
attr_accessor :filters, :model
|
|
301
|
+
|
|
302
|
+
def initialize(filters:)
|
|
303
|
+
@filters = filters
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
filters = { query: "Syntaxes" }
|
|
308
|
+
filtered_authors = AuthorFilter.new(filters: filters).results
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
See [this issue](https://github.com/rails/rails/issues/24055) for details.
|
|
313
|
+
|
|
314
|
+
|
|
230
315
|
#### 3. The porcelain command syntax
|
|
231
316
|
|
|
232
317
|
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`
|
data/lib/rokaki.rb
CHANGED
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
require 'rokaki/version'
|
|
4
4
|
require 'rokaki/filterable'
|
|
5
5
|
require 'rokaki/filter_model'
|
|
6
|
+
require 'rokaki/filter_model/join_map'
|
|
6
7
|
require 'rokaki/filter_model/like_keys'
|
|
7
8
|
require 'rokaki/filter_model/basic_filter'
|
|
8
9
|
require 'rokaki/filter_model/nested_filter'
|
|
10
|
+
require 'rokaki/filter_model/nested_like_filters'
|
|
9
11
|
|
|
10
12
|
module Rokaki
|
|
11
13
|
class Error < StandardError; end
|
|
12
|
-
|
|
13
|
-
# include this module for filters dsl in an object
|
|
14
|
-
#
|
|
15
14
|
end
|
data/lib/rokaki/filter_model.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Rokaki
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def prepare_terms(param, mode)
|
|
11
|
-
if param
|
|
11
|
+
if Array === param
|
|
12
12
|
return param.map { |term| "%#{term}%" } if mode == :circumfix
|
|
13
13
|
return param.map { |term| "%#{term}" } if mode == :prefix
|
|
14
14
|
return param.map { |term| "#{term}%" } if mode == :suffix
|
|
@@ -56,18 +56,39 @@ module Rokaki
|
|
|
56
56
|
|
|
57
57
|
@_chain_filters ||= []
|
|
58
58
|
filter_keys.each do |filter_key|
|
|
59
|
-
|
|
60
59
|
# TODO: does the key need casting to an array here?
|
|
61
60
|
_chain_filter(filter_key) unless filter_key.is_a? Hash
|
|
62
|
-
|
|
63
61
|
_chain_nested_filter(filter_key) if filter_key.is_a? Hash
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
define_results # writes out all the generated filters
|
|
65
|
+
end
|
|
64
66
|
|
|
67
|
+
def like_filters(like_keys, term_type: :like)
|
|
68
|
+
if @filter_map_query_key
|
|
69
|
+
define_filter_map(@filter_map_query_key, *like_keys.call)
|
|
70
|
+
else
|
|
71
|
+
define_filter_keys(*like_keys.call)
|
|
65
72
|
end
|
|
66
73
|
|
|
74
|
+
@_chain_filters ||= []
|
|
75
|
+
filter_map = []
|
|
76
|
+
|
|
77
|
+
nested_like_filter = NestedLikeFilters.new(
|
|
78
|
+
filter_key_object: like_keys,
|
|
79
|
+
prefix: filter_key_prefix,
|
|
80
|
+
infix: filter_key_infix,
|
|
81
|
+
db: @_filter_db,
|
|
82
|
+
type: term_type,
|
|
83
|
+
or_key: or_key
|
|
84
|
+
)
|
|
85
|
+
nested_like_filter.call
|
|
86
|
+
|
|
87
|
+
_chain_nested_like_filter(nested_like_filter)
|
|
67
88
|
define_results # writes out all the generated filters
|
|
68
89
|
end
|
|
69
90
|
|
|
70
|
-
def
|
|
91
|
+
def _build_basic_filter(key)
|
|
71
92
|
basic_filter = BasicFilter.new(
|
|
72
93
|
keys: [key],
|
|
73
94
|
prefix: filter_key_prefix,
|
|
@@ -77,13 +98,17 @@ module Rokaki
|
|
|
77
98
|
db: @_filter_db
|
|
78
99
|
)
|
|
79
100
|
basic_filter.call
|
|
101
|
+
basic_filter
|
|
102
|
+
end
|
|
80
103
|
|
|
104
|
+
def _chain_filter(key)
|
|
105
|
+
basic_filter = _build_basic_filter(key)
|
|
81
106
|
class_eval basic_filter.filter_method, __FILE__, __LINE__ - 2
|
|
82
107
|
|
|
83
108
|
@_chain_filters << basic_filter.filter_template
|
|
84
109
|
end
|
|
85
110
|
|
|
86
|
-
def
|
|
111
|
+
def _build_nested_filter(filters_object)
|
|
87
112
|
nested_filter = NestedFilter.new(
|
|
88
113
|
filter_key_object: filters_object,
|
|
89
114
|
prefix: filter_key_prefix,
|
|
@@ -93,6 +118,21 @@ module Rokaki
|
|
|
93
118
|
db: @_filter_db
|
|
94
119
|
)
|
|
95
120
|
nested_filter.call
|
|
121
|
+
nested_filter
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def _chain_nested_like_filter(filters_object)
|
|
125
|
+
filters_object.filter_methods.each do |filter_method|
|
|
126
|
+
class_eval filter_method, __FILE__, __LINE__ - 2
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
filters_object.templates.each do |filter_template|
|
|
130
|
+
@_chain_filters << filter_template
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def _chain_nested_filter(filters_object)
|
|
135
|
+
nested_filter = _build_nested_filter(filters_object)
|
|
96
136
|
|
|
97
137
|
nested_filter.filter_methods.each do |filter_method|
|
|
98
138
|
class_eval filter_method, __FILE__, __LINE__ - 2
|
|
@@ -103,10 +143,6 @@ module Rokaki
|
|
|
103
143
|
end
|
|
104
144
|
end
|
|
105
145
|
|
|
106
|
-
# def associated_table(association)
|
|
107
|
-
# @model.reflect_on_association(association).klass.table_name
|
|
108
|
-
# end
|
|
109
|
-
|
|
110
146
|
def filter_model(model_class)
|
|
111
147
|
@model = (model_class.is_a?(Class) ? model_class : Object.const_get(model_class.capitalize))
|
|
112
148
|
class_eval "def set_model; @model ||= #{@model}; end;"
|
|
@@ -116,41 +152,16 @@ module Rokaki
|
|
|
116
152
|
raise ArgumentError, 'argument mush be a hash' unless args.is_a? Hash
|
|
117
153
|
@_like_semantics = (@_like_semantics || {}).merge(args)
|
|
118
154
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
filters(*keys)
|
|
155
|
+
like_keys = LikeKeys.new(args)
|
|
156
|
+
like_filters(like_keys, term_type: :like)
|
|
123
157
|
end
|
|
124
158
|
|
|
125
159
|
def ilike(args)
|
|
126
160
|
raise ArgumentError, 'argument mush be a hash' unless args.is_a? Hash
|
|
127
161
|
@i_like_semantics = (@i_like_semantics || {}).merge(args)
|
|
128
162
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
filters(*keys)
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def deep_chain(keys, value)
|
|
136
|
-
if value.is_a? Hash
|
|
137
|
-
value.keys.map do |key|
|
|
138
|
-
_keys = keys.dup << key
|
|
139
|
-
deep_chain(_keys, value[key])
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
if value.is_a? Array
|
|
144
|
-
value.each do |av|
|
|
145
|
-
_keys = keys.dup << av
|
|
146
|
-
_build_deep_chain(_keys)
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
if value.is_a? Symbol
|
|
151
|
-
_keys = keys.dup << value
|
|
152
|
-
_build_deep_chain(_keys)
|
|
153
|
-
end
|
|
163
|
+
like_keys = LikeKeys.new(args)
|
|
164
|
+
like_filters(like_keys, term_type: :ilike)
|
|
154
165
|
end
|
|
155
166
|
|
|
156
167
|
# the model method is called to instatiate @model from the
|
|
@@ -10,8 +10,9 @@ module Rokaki
|
|
|
10
10
|
@like_semantics = like_semantics
|
|
11
11
|
@i_like_semantics = i_like_semantics
|
|
12
12
|
@db = db
|
|
13
|
+
@filter_query = nil
|
|
13
14
|
end
|
|
14
|
-
attr_reader :keys, :prefix, :infix, :like_semantics, :i_like_semantics, :db
|
|
15
|
+
attr_reader :keys, :prefix, :infix, :like_semantics, :i_like_semantics, :db, :filter_query
|
|
15
16
|
attr_accessor :filter_method, :filter_template
|
|
16
17
|
|
|
17
18
|
def call
|
|
@@ -56,7 +57,7 @@ module Rokaki
|
|
|
56
57
|
query = "@model.where(#{key}: #{filter})"
|
|
57
58
|
end
|
|
58
59
|
|
|
59
|
-
query
|
|
60
|
+
@filter_query = query
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
def build_like_query(type:, query:, filter:, mode:, key:)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rokaki
|
|
4
|
+
module FilterModel
|
|
5
|
+
class DeepAssignStruct
|
|
6
|
+
def initialize(keys:, value:, struct: nil)
|
|
7
|
+
@keys = keys
|
|
8
|
+
@value = value
|
|
9
|
+
@struct = struct
|
|
10
|
+
end
|
|
11
|
+
attr_reader :keys, :value
|
|
12
|
+
attr_accessor :struct
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
base_keys = keys
|
|
16
|
+
i = base_keys.length - 1
|
|
17
|
+
|
|
18
|
+
base_keys.reverse_each.reduce (value) do |struc,key|
|
|
19
|
+
i -= 1
|
|
20
|
+
cur_keys = base_keys[0..i]
|
|
21
|
+
|
|
22
|
+
if struct
|
|
23
|
+
val = struct.dig(*cur_keys)
|
|
24
|
+
val[key] = struc
|
|
25
|
+
p val
|
|
26
|
+
return val
|
|
27
|
+
else
|
|
28
|
+
if key.is_a?(Integer)
|
|
29
|
+
struct = [struc]
|
|
30
|
+
else
|
|
31
|
+
{ key=>struc }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def deep_construct(keys, value)
|
|
40
|
+
|
|
41
|
+
if keys.last.is_a?(Integer)
|
|
42
|
+
rstruct = struct[keys.last] = value
|
|
43
|
+
else
|
|
44
|
+
rstruct = { keys.last => value }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
keys[0..-2].reverse_each.reduce (rstruct) do |struc,key|
|
|
48
|
+
if key.is_a?(Integer)
|
|
49
|
+
[struc]
|
|
50
|
+
else
|
|
51
|
+
{ key=>struc }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# DOUBLE SPLAT HASHES TO MAKE ARG LISTS!
|
|
4
|
+
#
|
|
5
|
+
# Array#dig could be useful
|
|
6
|
+
#
|
|
7
|
+
# Array#intersection could be useful
|
|
8
|
+
#
|
|
9
|
+
# Array#difference could be useful
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
module Rokaki
|
|
13
|
+
module FilterModel
|
|
14
|
+
class JoinMap
|
|
15
|
+
def initialize(key_paths)
|
|
16
|
+
@key_paths = key_paths
|
|
17
|
+
@result = {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :key_paths
|
|
21
|
+
attr_accessor :result
|
|
22
|
+
|
|
23
|
+
def call
|
|
24
|
+
key_paths.uniq.each do |key_path|
|
|
25
|
+
current_key_path = []
|
|
26
|
+
previous_key = nil
|
|
27
|
+
|
|
28
|
+
if Symbol === key_path
|
|
29
|
+
if key_paths.length == 1
|
|
30
|
+
@result = key_paths
|
|
31
|
+
else
|
|
32
|
+
result[key_path] = {} unless result.keys.include? key_path
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if Array === key_path
|
|
37
|
+
key_path.each do |key|
|
|
38
|
+
current_path_length = current_key_path.length
|
|
39
|
+
|
|
40
|
+
if current_path_length > 0 && result.dig(current_key_path).nil?
|
|
41
|
+
|
|
42
|
+
if current_path_length == 1
|
|
43
|
+
parent_result = result[previous_key]
|
|
44
|
+
|
|
45
|
+
if Symbol === parent_result && parent_result != key
|
|
46
|
+
result[previous_key] = [parent_result, key]
|
|
47
|
+
elsif Array === parent_result
|
|
48
|
+
|
|
49
|
+
parent_result.each_with_index do |array_item, index|
|
|
50
|
+
if array_item == key
|
|
51
|
+
current_key_path << index
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
else
|
|
56
|
+
result[previous_key] = key unless result[previous_key] == key
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
else
|
|
60
|
+
previous_key_path = current_key_path - [previous_key]
|
|
61
|
+
previous_path_length = previous_key_path.length
|
|
62
|
+
p current_key_path
|
|
63
|
+
|
|
64
|
+
if previous_path_length == 1
|
|
65
|
+
res = result.dig(*previous_key_path)
|
|
66
|
+
|
|
67
|
+
if Symbol === res
|
|
68
|
+
result[previous_key_path.first] = { previous_key => key }
|
|
69
|
+
end
|
|
70
|
+
elsif previous_path_length > 1
|
|
71
|
+
res = result.dig(*previous_key_path)
|
|
72
|
+
|
|
73
|
+
if Symbol === res
|
|
74
|
+
base = previous_key_path.pop
|
|
75
|
+
result.dig(*previous_key_path)[base] = { previous_key => key }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
else
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
previous_key = key
|
|
84
|
+
current_key_path << key
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
result
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
@@ -9,43 +9,45 @@ module Rokaki
|
|
|
9
9
|
class LikeKeys
|
|
10
10
|
def initialize(args)
|
|
11
11
|
@args = args
|
|
12
|
-
@
|
|
12
|
+
@keys = []
|
|
13
|
+
@key_paths = []
|
|
13
14
|
end
|
|
14
15
|
|
|
15
|
-
attr_reader :args, :
|
|
16
|
+
attr_reader :args, :keys, :key_paths
|
|
16
17
|
|
|
17
18
|
def call
|
|
18
19
|
args.keys.each do |key|
|
|
19
|
-
|
|
20
|
+
map_keys(key: key, value: args[key])
|
|
20
21
|
end
|
|
21
|
-
|
|
22
|
+
keys
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
private
|
|
25
26
|
|
|
26
|
-
def map_keys(value
|
|
27
|
-
key_result = {}
|
|
27
|
+
def map_keys(key:, value:, key_path: [])
|
|
28
28
|
|
|
29
|
-
if value.is_a?
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if sub_value.is_a? Symbol
|
|
34
|
-
if key_result[key].is_a? Array
|
|
35
|
-
key_result[key] << sub_key
|
|
36
|
-
else
|
|
37
|
-
key_result[key] = [ sub_key ]
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
elsif sub_value.is_a? Hash
|
|
41
|
-
key_result[key] = map_keys(sub_value, sub_key)
|
|
29
|
+
if value.is_a?(Hash)
|
|
30
|
+
key_path << key
|
|
31
|
+
value.keys.each do |key|
|
|
32
|
+
map_keys(key: key, value: value[key], key_path: key_path.dup)
|
|
42
33
|
end
|
|
43
34
|
end
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
|
|
36
|
+
if value.is_a?(Symbol)
|
|
37
|
+
keys << (key_path.empty? ? key : deep_assign(key_path, key))
|
|
38
|
+
key_path << key
|
|
39
|
+
key_paths << key_path
|
|
46
40
|
end
|
|
47
41
|
|
|
48
|
-
|
|
42
|
+
key_path
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Many thanks Cary Swoveland
|
|
47
|
+
# https://stackoverflow.com/questions/56634950/ruby-dig-set-assign-values-using-hashdig/56635124
|
|
48
|
+
#
|
|
49
|
+
def deep_assign(keys, value)
|
|
50
|
+
keys[0..-2].reverse_each.reduce ({ keys.last => value }) { |h,key| { key=>h } }
|
|
49
51
|
end
|
|
50
52
|
end
|
|
51
53
|
end
|
|
@@ -66,7 +66,7 @@ module Rokaki
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def _build_deep_chain(keys)
|
|
69
|
-
name = ''
|
|
69
|
+
name = ''
|
|
70
70
|
count = keys.size - 1
|
|
71
71
|
|
|
72
72
|
joins_before = []
|
|
@@ -75,13 +75,13 @@ module Rokaki
|
|
|
75
75
|
where_before = []
|
|
76
76
|
where_after = []
|
|
77
77
|
out = ''
|
|
78
|
-
|
|
78
|
+
search_mode = nil
|
|
79
79
|
type = nil
|
|
80
80
|
leaf = nil
|
|
81
81
|
|
|
82
|
-
if
|
|
82
|
+
if search_mode = find_like_key(keys)
|
|
83
83
|
type = 'LIKE'
|
|
84
|
-
elsif
|
|
84
|
+
elsif search_mode = find_i_like_key(keys)
|
|
85
85
|
type = 'ILIKE'
|
|
86
86
|
end
|
|
87
87
|
leaf = keys.pop
|
|
@@ -114,12 +114,12 @@ module Rokaki
|
|
|
114
114
|
joins = joins.join
|
|
115
115
|
where = where.join
|
|
116
116
|
|
|
117
|
-
if
|
|
117
|
+
if search_mode
|
|
118
118
|
query = build_like_query(
|
|
119
119
|
type: type,
|
|
120
120
|
query: '',
|
|
121
121
|
filter: "#{prefix}#{name}",
|
|
122
|
-
|
|
122
|
+
search_mode: search_mode,
|
|
123
123
|
key: keys.last.to_s.pluralize,
|
|
124
124
|
leaf: leaf
|
|
125
125
|
)
|
|
@@ -136,15 +136,15 @@ module Rokaki
|
|
|
136
136
|
end
|
|
137
137
|
end
|
|
138
138
|
|
|
139
|
-
def build_like_query(type:, query:, filter:,
|
|
139
|
+
def build_like_query(type:, query:, filter:, search_mode:, key:, leaf:)
|
|
140
140
|
if db == :postgres
|
|
141
141
|
query = "where(\"#{key}.#{leaf} #{type} ANY (ARRAY[?])\", "
|
|
142
|
-
query += "prepare_terms(#{filter}, :#{
|
|
142
|
+
query += "prepare_terms(#{filter}, :#{search_mode}))"
|
|
143
143
|
else
|
|
144
144
|
query = "where(\"#{key}.#{leaf} #{type} :query\", "
|
|
145
|
-
query += "query: \"%\#{#{filter}}%\")" if
|
|
146
|
-
query += "query: \"%\#{#{filter}}\")" if
|
|
147
|
-
query += "query: \"\#{#{filter}}%\")" if
|
|
145
|
+
query += "query: \"%\#{#{filter}}%\")" if search_mode == :circumfix
|
|
146
|
+
query += "query: \"%\#{#{filter}}\")" if search_mode == :prefix
|
|
147
|
+
query += "query: \"\#{#{filter}}%\")" if search_mode == :suffix
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
query
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'active_support/inflector'
|
|
3
|
+
|
|
4
|
+
module Rokaki
|
|
5
|
+
module FilterModel
|
|
6
|
+
class NestedLikeFilters
|
|
7
|
+
def initialize(filter_key_object:, prefix:, infix:, db:, mode: :and, or_key: :or, type: :like)
|
|
8
|
+
@filter_key_object = filter_key_object
|
|
9
|
+
@prefix = prefix
|
|
10
|
+
@infix = infix
|
|
11
|
+
@db = db
|
|
12
|
+
@mode = mode
|
|
13
|
+
@or_key = or_key
|
|
14
|
+
@type = type
|
|
15
|
+
|
|
16
|
+
@names = []
|
|
17
|
+
@filter_methods = []
|
|
18
|
+
@templates = []
|
|
19
|
+
@filter_queries = []
|
|
20
|
+
@method_names = []
|
|
21
|
+
@filter_names = []
|
|
22
|
+
@join_key_paths = []
|
|
23
|
+
@key_paths = []
|
|
24
|
+
@search_modes = []
|
|
25
|
+
@modes = []
|
|
26
|
+
end
|
|
27
|
+
attr_reader :filter_key_object, :prefix, :infix, :like_semantics, :i_like_semantics,
|
|
28
|
+
:db, :mode, :or_key, :filter_queries, :type
|
|
29
|
+
attr_accessor :filter_methods, :templates, :method_names, :filter_names, :names, :join_key_paths, :key_paths, :search_modes, :modes
|
|
30
|
+
|
|
31
|
+
def call
|
|
32
|
+
build_filters_data
|
|
33
|
+
compound_filters
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_filters_data
|
|
37
|
+
results = filter_key_object.key_paths.each do |key_path|
|
|
38
|
+
if key_path.is_a?(Symbol)
|
|
39
|
+
build_filter_data(key_path)
|
|
40
|
+
else
|
|
41
|
+
if key_path.include? or_key
|
|
42
|
+
build_filter_data(key_path.dup, mode: or_key)
|
|
43
|
+
else
|
|
44
|
+
build_filter_data(key_path.dup)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def compound_filters
|
|
51
|
+
# key_paths represents a structure like
|
|
52
|
+
# [
|
|
53
|
+
# [ # this is an or
|
|
54
|
+
# [:articles, :title],
|
|
55
|
+
# [:articles, :authors, :first_name],
|
|
56
|
+
# [:articles, :authors, :reviews, :title],
|
|
57
|
+
# [:articles, :authors, :reviews, :content]
|
|
58
|
+
# ],
|
|
59
|
+
# [:articles, :content] # this is an and
|
|
60
|
+
# ]
|
|
61
|
+
#
|
|
62
|
+
# Each item in the array represents a compounded filter
|
|
63
|
+
#
|
|
64
|
+
key_paths.each_with_index do |key_path_item, index|
|
|
65
|
+
base_names = get_name(index)
|
|
66
|
+
join_map = JoinMap.new(join_key_paths[index])
|
|
67
|
+
join_map.call
|
|
68
|
+
|
|
69
|
+
if key_path_item.first.is_a?(Array)
|
|
70
|
+
item_search_modes = search_modes[index]
|
|
71
|
+
|
|
72
|
+
base_name = base_names.shift
|
|
73
|
+
method_name = prefix.to_s + ([:filter].push base_name).compact.join(infix.to_s)
|
|
74
|
+
method_name += (infix.to_s+'or'+infix.to_s) + (base_names).join(infix.to_s+'or'+infix.to_s)
|
|
75
|
+
item_filter_names = [prefix.to_s + base_name]
|
|
76
|
+
|
|
77
|
+
base_names.each do |filter_base_name|
|
|
78
|
+
item_filter_names << (prefix.to_s + filter_base_name)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
base_modes = modes[index]
|
|
82
|
+
key_path_item.each_with_index do |key_path, kp_index|
|
|
83
|
+
|
|
84
|
+
build_query(keys: key_path.dup, join_map: join_map.result, mode: base_modes[kp_index], filter_name: item_filter_names[kp_index], search_mode: item_search_modes[kp_index])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
item_filter_queries = filter_queries[index]
|
|
88
|
+
first_query = item_filter_queries.shift
|
|
89
|
+
|
|
90
|
+
ored = item_filter_queries.map do |query|
|
|
91
|
+
".or(#{query})"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
filter_conditions = item_filter_names.join(' || ')
|
|
95
|
+
|
|
96
|
+
@filter_methods << "def #{method_name}; #{first_query + ored.join}; end;"
|
|
97
|
+
@templates << "@model = #{method_name} if #{filter_conditions};"
|
|
98
|
+
else
|
|
99
|
+
|
|
100
|
+
base_name = get_name(index)
|
|
101
|
+
filter_name = "#{prefix}#{get_filter_name(index)}"
|
|
102
|
+
|
|
103
|
+
method_name = ([prefix, :filter, base_name]).compact.join(infix.to_s)
|
|
104
|
+
|
|
105
|
+
build_query(keys: key_path_item.dup, join_map: join_map.result, filter_name: filter_name, search_mode: search_modes[index])
|
|
106
|
+
|
|
107
|
+
@filter_methods << "def #{method_name}; #{filter_queries[index]}; end;"
|
|
108
|
+
@templates << "@model = #{method_name} if #{filter_name};"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def get_name(index)
|
|
116
|
+
names[index]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def get_filter_name(index)
|
|
120
|
+
filter_names[index]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def find_mode_key(keys)
|
|
124
|
+
current_like_key = @filter_key_object.args.dup
|
|
125
|
+
keys.each do |key|
|
|
126
|
+
current_like_key = current_like_key[key]
|
|
127
|
+
end
|
|
128
|
+
current_like_key
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def build_filter_data(key_path, mode: :and)
|
|
132
|
+
# if key_path.is_a?(Symbol)
|
|
133
|
+
# search_mode = @filter_key_object.args[key_path]
|
|
134
|
+
|
|
135
|
+
# name = key_path
|
|
136
|
+
# filter_name = (prefix.to_s + key_path.to_s)
|
|
137
|
+
# @names << name
|
|
138
|
+
# @filter_names << filter_name
|
|
139
|
+
# @key_paths << key_path
|
|
140
|
+
# @search_modes << search_mode
|
|
141
|
+
# @modes << mode
|
|
142
|
+
# else
|
|
143
|
+
search_mode = find_mode_key(key_path)
|
|
144
|
+
|
|
145
|
+
key_path.delete(mode)
|
|
146
|
+
|
|
147
|
+
name = key_path.join(infix.to_s)
|
|
148
|
+
filter_name = key_path.compact.join(infix.to_s)
|
|
149
|
+
|
|
150
|
+
if mode == or_key
|
|
151
|
+
@names << [@names.pop, name].flatten
|
|
152
|
+
@filter_names << [@filter_names.pop, filter_name].flatten
|
|
153
|
+
|
|
154
|
+
or_key_paths = @key_paths.pop
|
|
155
|
+
if or_key_paths.first.is_a?(Array)
|
|
156
|
+
@key_paths << [*or_key_paths] + [key_path.dup]
|
|
157
|
+
else
|
|
158
|
+
@key_paths << [or_key_paths] + [key_path.dup]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
@search_modes << [@search_modes.pop, search_mode].flatten
|
|
162
|
+
@modes << [@modes.pop, mode].flatten
|
|
163
|
+
|
|
164
|
+
else
|
|
165
|
+
@names << name
|
|
166
|
+
@filter_names << filter_name
|
|
167
|
+
@key_paths << key_path.dup # having this wrapped in an array is messy for single items
|
|
168
|
+
@search_modes << search_mode
|
|
169
|
+
@modes << mode
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
join_key_path = key_path.dup
|
|
173
|
+
|
|
174
|
+
leaf = join_key_path.pop
|
|
175
|
+
if mode == or_key
|
|
176
|
+
or_join_key_paths = @join_key_paths.pop
|
|
177
|
+
if or_join_key_paths.first.is_a?(Array)
|
|
178
|
+
@join_key_paths << [*or_join_key_paths] + [join_key_path.dup]
|
|
179
|
+
else
|
|
180
|
+
@join_key_paths << [or_join_key_paths] + [join_key_path.dup]
|
|
181
|
+
end
|
|
182
|
+
else
|
|
183
|
+
if join_key_path.length == 1
|
|
184
|
+
@join_key_paths << join_key_path
|
|
185
|
+
else
|
|
186
|
+
@join_key_paths << [join_key_path.dup]
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
# end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# DOUBLE SPLAT HASHES TO MAKE ARG LISTS!
|
|
193
|
+
def build_query(keys: , join_map:, mode: :and, filter_name:, search_mode:)
|
|
194
|
+
leaf = nil
|
|
195
|
+
leaf = keys.pop
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
query = build_like_query(
|
|
199
|
+
type: type,
|
|
200
|
+
query: '',
|
|
201
|
+
filter: filter_name,
|
|
202
|
+
search_mode: search_mode,
|
|
203
|
+
key: keys.last,
|
|
204
|
+
leaf: leaf
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if join_map.empty?
|
|
208
|
+
filter_query = "@model.#{query}"
|
|
209
|
+
elsif join_map.is_a?(Array)
|
|
210
|
+
filter_query = "@model.joins(*#{join_map}).#{query}"
|
|
211
|
+
else
|
|
212
|
+
filter_query = "@model.joins(**#{join_map}).#{query}"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
if mode == or_key
|
|
216
|
+
@filter_queries << [@filter_queries.pop, filter_query].flatten
|
|
217
|
+
else
|
|
218
|
+
@filter_queries << filter_query
|
|
219
|
+
end
|
|
220
|
+
filter_query
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def build_like_query(type:, query:, filter:, search_mode:, key:, leaf:)
|
|
224
|
+
key_leaf = key ? "#{key.to_s.pluralize}.#{leaf}" : leaf
|
|
225
|
+
if db == :postgres
|
|
226
|
+
query = "where(\"#{key_leaf} #{type.to_s.upcase} ANY (ARRAY[?])\", "
|
|
227
|
+
query += "prepare_terms(#{filter}, :#{search_mode}))"
|
|
228
|
+
else
|
|
229
|
+
query = "where(\"#{key_leaf} #{type.to_s.upcase} :query\", "
|
|
230
|
+
query += "query: \"%\#{#{filter}}%\")" if search_mode == :circumfix
|
|
231
|
+
query += "query: \"%\#{#{filter}}\")" if search_mode == :prefix
|
|
232
|
+
query += "query: \"\#{#{filter}}%\")" if search_mode == :suffix
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
query
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
data/lib/rokaki/filterable.rb
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Rokaki
|
|
4
|
+
# include this module for rokaki's filtering dsl in any object
|
|
5
|
+
#
|
|
2
6
|
module Filterable
|
|
3
7
|
def self.included(base)
|
|
4
8
|
base.extend(ClassMethods)
|
|
@@ -11,15 +15,15 @@ module Rokaki
|
|
|
11
15
|
|
|
12
16
|
def define_filter_keys(*filter_keys)
|
|
13
17
|
filter_keys.each do |filter_key|
|
|
14
|
-
_build_filter([filter_key]) unless filter_key
|
|
15
|
-
_nested_key filter_key if filter_key
|
|
18
|
+
_build_filter([filter_key]) unless Hash === filter_key
|
|
19
|
+
_nested_key filter_key if Hash === filter_key
|
|
16
20
|
end
|
|
17
21
|
end
|
|
18
22
|
|
|
19
23
|
def define_filter_map(query_field, *filter_keys)
|
|
20
24
|
filter_keys.each do |filter_key|
|
|
21
|
-
_map_filters(query_field, [filter_key]) unless filter_key
|
|
22
|
-
_nested_map query_field, filter_key if filter_key
|
|
25
|
+
_map_filters(query_field, [filter_key]) unless Hash === filter_key
|
|
26
|
+
_nested_map query_field, filter_key if Hash === filter_key
|
|
23
27
|
end
|
|
24
28
|
end
|
|
25
29
|
|
|
@@ -35,11 +39,16 @@ module Rokaki
|
|
|
35
39
|
@filter_key_infix ||= infix
|
|
36
40
|
end
|
|
37
41
|
|
|
42
|
+
def or_key(or_key = :or)
|
|
43
|
+
@or_key ||= or_key
|
|
44
|
+
end
|
|
45
|
+
|
|
38
46
|
def filterable_object_name(name = 'filters')
|
|
39
47
|
@filterable_object_name ||= name
|
|
40
48
|
end
|
|
41
49
|
|
|
42
50
|
def _build_filter(keys)
|
|
51
|
+
keys.delete(or_key)
|
|
43
52
|
name = @filter_key_prefix.to_s
|
|
44
53
|
count = keys.size - 1
|
|
45
54
|
|
|
@@ -52,6 +61,7 @@ module Rokaki
|
|
|
52
61
|
end
|
|
53
62
|
|
|
54
63
|
def _map_filters(query_field, keys)
|
|
64
|
+
keys.delete(or_key)
|
|
55
65
|
name = @filter_key_prefix.to_s
|
|
56
66
|
count = keys.size - 1
|
|
57
67
|
|
data/lib/rokaki/version.rb
CHANGED
data/rokaki.gemspec
CHANGED
|
@@ -34,13 +34,17 @@ Gem::Specification.new do |spec|
|
|
|
34
34
|
|
|
35
35
|
spec.add_development_dependency 'activerecord'
|
|
36
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
37
|
spec.add_development_dependency 'pry'
|
|
42
38
|
spec.add_development_dependency 'pry-byebug'
|
|
43
39
|
spec.add_development_dependency 'rake', '~> 13.0'
|
|
40
|
+
|
|
41
|
+
spec.add_development_dependency 'guard'
|
|
42
|
+
spec.add_development_dependency 'guard-rspec'
|
|
44
43
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
44
|
+
spec.add_development_dependency 'factory_bot'
|
|
45
|
+
|
|
46
|
+
spec.add_development_dependency 'pg'
|
|
45
47
|
spec.add_development_dependency 'sqlite3'
|
|
48
|
+
spec.add_development_dependency 'database_cleaner-active_record'
|
|
49
|
+
|
|
46
50
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rokaki
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Steve Martin
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-07-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -53,7 +53,7 @@ dependencies:
|
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '2.0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
56
|
+
name: pry
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
59
|
- - ">="
|
|
@@ -67,7 +67,7 @@ dependencies:
|
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '0'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
70
|
+
name: pry-byebug
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
73
|
- - ">="
|
|
@@ -81,21 +81,21 @@ dependencies:
|
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '0'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
84
|
+
name: rake
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
|
-
- - "
|
|
87
|
+
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '0'
|
|
89
|
+
version: '13.0'
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
|
-
- - "
|
|
94
|
+
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '0'
|
|
96
|
+
version: '13.0'
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
98
|
+
name: guard
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
100
100
|
requirements:
|
|
101
101
|
- - ">="
|
|
@@ -109,7 +109,7 @@ dependencies:
|
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: '0'
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
112
|
+
name: guard-rspec
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
114
114
|
requirements:
|
|
115
115
|
- - ">="
|
|
@@ -123,7 +123,21 @@ dependencies:
|
|
|
123
123
|
- !ruby/object:Gem::Version
|
|
124
124
|
version: '0'
|
|
125
125
|
- !ruby/object:Gem::Dependency
|
|
126
|
-
name:
|
|
126
|
+
name: rspec
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '3.0'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '3.0'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: factory_bot
|
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
|
128
142
|
requirements:
|
|
129
143
|
- - ">="
|
|
@@ -137,35 +151,35 @@ dependencies:
|
|
|
137
151
|
- !ruby/object:Gem::Version
|
|
138
152
|
version: '0'
|
|
139
153
|
- !ruby/object:Gem::Dependency
|
|
140
|
-
name:
|
|
154
|
+
name: pg
|
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
|
142
156
|
requirements:
|
|
143
|
-
- - "
|
|
157
|
+
- - ">="
|
|
144
158
|
- !ruby/object:Gem::Version
|
|
145
|
-
version: '
|
|
159
|
+
version: '0'
|
|
146
160
|
type: :development
|
|
147
161
|
prerelease: false
|
|
148
162
|
version_requirements: !ruby/object:Gem::Requirement
|
|
149
163
|
requirements:
|
|
150
|
-
- - "
|
|
164
|
+
- - ">="
|
|
151
165
|
- !ruby/object:Gem::Version
|
|
152
|
-
version: '
|
|
166
|
+
version: '0'
|
|
153
167
|
- !ruby/object:Gem::Dependency
|
|
154
|
-
name:
|
|
168
|
+
name: sqlite3
|
|
155
169
|
requirement: !ruby/object:Gem::Requirement
|
|
156
170
|
requirements:
|
|
157
|
-
- - "
|
|
171
|
+
- - ">="
|
|
158
172
|
- !ruby/object:Gem::Version
|
|
159
|
-
version: '
|
|
173
|
+
version: '0'
|
|
160
174
|
type: :development
|
|
161
175
|
prerelease: false
|
|
162
176
|
version_requirements: !ruby/object:Gem::Requirement
|
|
163
177
|
requirements:
|
|
164
|
-
- - "
|
|
178
|
+
- - ">="
|
|
165
179
|
- !ruby/object:Gem::Version
|
|
166
|
-
version: '
|
|
180
|
+
version: '0'
|
|
167
181
|
- !ruby/object:Gem::Dependency
|
|
168
|
-
name:
|
|
182
|
+
name: database_cleaner-active_record
|
|
169
183
|
requirement: !ruby/object:Gem::Requirement
|
|
170
184
|
requirements:
|
|
171
185
|
- - ">="
|
|
@@ -187,6 +201,7 @@ extra_rdoc_files: []
|
|
|
187
201
|
files:
|
|
188
202
|
- ".gitignore"
|
|
189
203
|
- ".rspec"
|
|
204
|
+
- ".ruby-version"
|
|
190
205
|
- ".travis.yml"
|
|
191
206
|
- CODE_OF_CONDUCT.md
|
|
192
207
|
- Gemfile
|
|
@@ -200,9 +215,12 @@ files:
|
|
|
200
215
|
- lib/rokaki.rb
|
|
201
216
|
- lib/rokaki/filter_model.rb
|
|
202
217
|
- lib/rokaki/filter_model/basic_filter.rb
|
|
218
|
+
- lib/rokaki/filter_model/deep_assign_struct.rb
|
|
203
219
|
- lib/rokaki/filter_model/filter_chain.rb
|
|
220
|
+
- lib/rokaki/filter_model/join_map.rb
|
|
204
221
|
- lib/rokaki/filter_model/like_keys.rb
|
|
205
222
|
- lib/rokaki/filter_model/nested_filter.rb
|
|
223
|
+
- lib/rokaki/filter_model/nested_like_filters.rb
|
|
206
224
|
- lib/rokaki/filterable.rb
|
|
207
225
|
- lib/rokaki/version.rb
|
|
208
226
|
- rokaki.gemspec
|
|
@@ -211,7 +229,7 @@ licenses:
|
|
|
211
229
|
- MIT
|
|
212
230
|
metadata:
|
|
213
231
|
homepage_uri: https://github.com/tevio/rokaki
|
|
214
|
-
post_install_message:
|
|
232
|
+
post_install_message:
|
|
215
233
|
rdoc_options: []
|
|
216
234
|
require_paths:
|
|
217
235
|
- lib
|
|
@@ -226,8 +244,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
226
244
|
- !ruby/object:Gem::Version
|
|
227
245
|
version: '0'
|
|
228
246
|
requirements: []
|
|
229
|
-
rubygems_version: 3.1.
|
|
230
|
-
signing_key:
|
|
247
|
+
rubygems_version: 3.1.6
|
|
248
|
+
signing_key:
|
|
231
249
|
specification_version: 4
|
|
232
250
|
summary: A web request filtering library
|
|
233
251
|
test_files: []
|