rokaki 0.5.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebc3f6bb8aab2597c36dd7876792e22395a56a8d099b18cb0d48157e178e81bf
4
- data.tar.gz: df20e88198f10346531fa5e5ffde6c8627ef5d19192119b07362865175e7357a
3
+ metadata.gz: b0654c6e63cc988e84b4df0a33b2055661bc5f7e29367923bdc1267fc4bbe64e
4
+ data.tar.gz: fd7f3e44cc48096389697d82f89b720a67900bbe073ce60e62828362fd46376d
5
5
  SHA512:
6
- metadata.gz: 9f419fea3a5e3e292d8f44c6006df9ee799d49b4a1aa14820441c2e76c21cb514b668079ea9b727bc64cfb65d107efb162d49a1176bfadc5037b588db2323352
7
- data.tar.gz: c683abe335a7d84718da478711c10704830470771e92420cd3ad3540a1062dbb065865e58fdcea4e0d5478cf4ab43293413ed7e0c6337e6f8d8a19e760a67555
6
+ metadata.gz: dc938b3a7c3632ed7f92a0452b87a91c5e50f0ae37b3f00cb5f9d79a65e0ca0f23f60d5b92b6cd709c287ddcf4a891bde460fd757df1e2fd8c4b0f551499fd05
7
+ data.tar.gz: a350805490d53b145b68813fdf9144b37871fa9b06942df0d2befb906e7912244258ffe14d0b5174bd0ab1fe7f92b4b9fcf650b847c1a36aefba2ecb8aee344b
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
12
  tags
13
+ *.gem
@@ -1,28 +1,32 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rokaki (0.4.1)
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.0)
10
- activesupport (= 6.0.0)
11
- activerecord (6.0.0)
12
- activemodel (= 6.0.0)
13
- activesupport (= 6.0.0)
14
- activesupport (6.0.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.1, >= 2.1.8)
20
- coderay (1.1.2)
21
- concurrent-ruby (1.1.5)
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
- ffi (1.11.1)
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.15.0)
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.6.0)
43
+ i18n (1.8.3)
40
44
  concurrent-ruby (~> 1.0)
41
- listen (3.1.5)
42
- rb-fsevent (~> 0.9, >= 0.9.4)
43
- rb-inotify (~> 0.9, >= 0.9.7)
44
- ruby_dep (~> 1.2)
45
- lumberjack (1.0.13)
46
- method_source (0.9.2)
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
- pry (0.12.2)
53
- coderay (~> 1.1.0)
54
- method_source (~> 0.9.0)
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.3)
57
- rb-inotify (0.10.0)
63
+ rb-fsevent (0.10.4)
64
+ rb-inotify (0.10.1)
58
65
  ffi (~> 1.0)
59
- rspec (3.8.0)
60
- rspec-core (~> 3.8.0)
61
- rspec-expectations (~> 3.8.0)
62
- rspec-mocks (~> 3.8.0)
63
- rspec-core (3.8.2)
64
- rspec-support (~> 3.8.0)
65
- rspec-expectations (3.8.4)
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.8.0)
68
- rspec-mocks (3.8.1)
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.8.0)
71
- rspec-support (3.8.2)
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.1)
75
- thor (0.20.3)
80
+ sqlite3 (1.4.2)
81
+ thor (1.0.1)
76
82
  thread_safe (0.3.6)
77
- tzinfo (1.2.5)
83
+ tzinfo (1.2.7)
78
84
  thread_safe (~> 0.1)
79
- zeitwerk (2.1.9)
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.0.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)
@@ -19,3 +19,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  THE SOFTWARE.
22
+
23
+ f007539360
data/README.md CHANGED
@@ -1,10 +1,9 @@
1
1
  # Rokaki
2
2
  [![Gem Version](https://badge.fury.io/rb/rokaki.svg)](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 ruby app that uses the concept of `filters`.
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
- To use the basic DSL include the `Rokaki::Filterable` module
23
+ ### `#define_filter_keys`
24
+ #### A Simple Example
23
25
 
24
26
  A simple example might be:-
25
27
 
26
28
  ```ruby
27
- class FilterArticles
28
- include Rokaki::Filterable
29
+ class FilterArticles
30
+ include Rokaki::Filterable
29
31
 
30
- def initialize(filters:)
31
- @filters = filters
32
- @articles = Article
33
- end
32
+ def initialize(filters:)
33
+ @filters = filters
34
+ @articles = Article
35
+ end
34
36
 
35
- attr_accessor :filters
37
+ attr_accessor :filters
36
38
 
37
- define_filter_keys :date, author: [:first_name, :last_name]
39
+ define_filter_keys :date, author: [:first_name, :last_name]
38
40
 
39
- def filter_results
40
- @articles = @articles.where(date: date) if date
41
- @articles = @articles.joins(:author).where(author: { first_name: author_first_name }) if author_first_name
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
- This maps attributes `date`, `author_first_name` and `author_last_name` to a filters object with the structure `{ date: '10-10-10', author: { first_name: 'Shteeve' } }`.
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
- ## Additional options
49
- You can specify a `filter_key_prefix` and a `filter_key_infix` to change the structure of the accessors.
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
- ## ActiveRecord
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 key, there are 3 options:- `:prefix`, `:circumfix` and `:suffix`. There are two syntaxes you can use for this:-
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 porcelain command syntax
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
 
@@ -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
@@ -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
- define_filter_keys *filter_keys
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
- define_results
33
- end
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
- def _chain_filter(keys)
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
- filter_method = "def #{filter_key_prefix}filter_#{name};" \
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 _chain_filter_type(key)
54
- filter = "#{filter_key_prefix}#{key}"
55
-
56
- query = ''
57
- if @_like_semantics && mode = @_like_semantics[key]
58
- query = like_semantics(type: 'LIKE', query: query, filter: filter, mode: mode, key: key)
59
- elsif @i_like_semantics && mode = @i_like_semantics[key]
60
- query = like_semantics(type: 'ILIKE', query: query, filter: filter, mode: mode, key: key)
61
- else
62
- query = "@model.where(#{filter}: #{key})"
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
- query
66
- end
78
+ class_eval basic_filter.filter_method, __FILE__, __LINE__ - 2
67
79
 
68
- def like_semantics(type:, query:, filter:, mode:, key:)
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 _build_deep_chain(keys)
77
- name = filter_key_prefix.to_s
78
- count = keys.size - 1
79
-
80
- joins = ''
81
- where = ''
82
- out = ''
83
-
84
- leaf = keys.pop
85
-
86
- keys.each_with_index do |key, _i|
87
- next unless keys.length == 1
88
- name = "#{filter_key_prefix}#{key}#{filter_key_infix}#{leaf}"
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
- joins = joins += out
95
- where = where += out
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(model)
118
- @model = (model.is_a?(Class) ? model : Object.const_get(model.capitalize))
119
- class_eval "def model; @model ||= #{@model}; end;"
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(*key_builder.call)
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(*key_builder.call)
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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rokaki
4
+ module FilterModel
5
+ class FilterChain
6
+ def initialize(keys)
7
+ @keys = keys
8
+ @filter_chain = []
9
+ end
10
+ end
11
+ end
12
+ end
13
+
@@ -2,6 +2,10 @@
2
2
 
3
3
  module Rokaki
4
4
  module FilterModel
5
+ # Converts deep hashes into keys
6
+ # effectively drops the leaf values and make's their
7
+ # keys the leaves
8
+ #
5
9
  class LikeKeys
6
10
  def initialize(args)
7
11
  @args = args
@@ -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
+
@@ -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}; filters.dig(*#{keys}); end;", __FILE__, __LINE__
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
- _build_filter(_keys)
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
- _build_filter(_keys)
95
+ yield _keys
64
96
  end
65
97
  end
66
98
 
@@ -1,3 +1,3 @@
1
1
  module Rokaki
2
- VERSION = "0.5.0"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -1,39 +1,46 @@
1
- lib = File.expand_path("lib", __dir__)
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 "rokaki/version"
5
+ require 'rokaki/version'
4
6
 
5
7
  Gem::Specification.new do |spec|
6
- spec.name = "rokaki"
8
+ spec.name = 'rokaki'
7
9
  spec.version = Rokaki::VERSION
8
- spec.authors = ["Steve Martin"]
9
- spec.email = ["steve@martian.media"]
10
+ spec.authors = ['Steve Martin']
11
+ spec.email = ['steve@martian.media']
10
12
 
11
- spec.summary = %q{A web request filtering library}
12
- spec.description = %q{A dsl for filtering data in web requests}
13
- spec.homepage = "https://github.com/tevio/rokaki"
14
- spec.license = "MIT"
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["homepage_uri"] = spec.homepage
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 = Dir.chdir(File.expand_path('..', __FILE__)) do
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 = "exe"
29
+ spec.bindir = 'exe'
28
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
- spec.require_paths = ["lib"]
30
-
31
- spec.add_development_dependency "bundler", "~> 2.0"
32
- spec.add_development_dependency "rake", "~> 10.0"
33
- spec.add_development_dependency "rspec", "~> 3.0"
34
- spec.add_development_dependency "guard"
35
- spec.add_development_dependency "pry"
36
- spec.add_development_dependency "guard-rspec"
37
- spec.add_development_dependency "activerecord"
38
- spec.add_development_dependency "sqlite3"
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.5.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: 2019-10-17 00:00:00.000000000 Z
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: rake
56
+ name: factory_bot
29
57
  requirement: !ruby/object:Gem::Requirement
30
58
  requirements:
31
- - - "~>"
59
+ - - ">="
32
60
  - !ruby/object:Gem::Version
33
- version: '10.0'
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: '10.0'
68
+ version: '0'
41
69
  - !ruby/object:Gem::Dependency
42
- name: rspec
70
+ name: guard
43
71
  requirement: !ruby/object:Gem::Requirement
44
72
  requirements:
45
- - - "~>"
73
+ - - ">="
46
74
  - !ruby/object:Gem::Version
47
- version: '3.0'
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: '3.0'
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: pry
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: guard-rspec
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: activerecord
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.0.4
229
+ rubygems_version: 3.1.2
171
230
  signing_key:
172
231
  specification_version: 4
173
232
  summary: A web request filtering library