rokaki 0.6.0 → 0.8.1.1

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: 7da479ed93b1e8e6aa8e91e080356e71b39061a647960175c49ef0f5a1b90855
4
- data.tar.gz: 021aaa044351b96fec1566c1e900f816908cc9a7dcd53d7d8b2a547e7d120872
3
+ metadata.gz: 44e54fda626422f9ed27d25aa30aa7f6303722a16b0bb931b6aeead9965b380f
4
+ data.tar.gz: 2b5a2ea0ce4284e84fcf006071b5831e41df8dfbd83399be23fd62f2cae7ad9c
5
5
  SHA512:
6
- metadata.gz: 6f4b5be13f85b953aced1c4269539d5abe5c2611a22bbc3fdda2c19c8254e872116b51ec235e856015e0574f8439d761d3eb433c6f94499683590084814b560d
7
- data.tar.gz: fb29f358eca0b1186afd6cb1417538bac16731a8a2c1faf06c9526afd7e00bc93bcbdfd642b1a8912b02fbe39318842e8730a738586e64ed5df5d7915e94a6b2
6
+ metadata.gz: '039a420452518968761d8eff610603a643825fa98de17d51cb038f6fcd79f4870b898688c5bbc6fc61a193ccc21a6cd86b7a50027aa6219520fa1cd3b25469d6'
7
+ data.tar.gz: 5c69385b228a28f6378b2c8a4e584e597c6e80b5781b634fd8ca977e16f67d65c64c4ca16a1f6ff2bfe6c0cd3a55410033eabe2ac8f195fc46d23cba2ac7c624
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
12
  tags
13
+ *.gem
@@ -1,30 +1,32 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rokaki (0.5.1)
4
+ rokaki (0.8.1)
5
5
  activesupport
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activemodel (6.0.0)
11
- activesupport (= 6.0.0)
12
- activerecord (6.0.0)
13
- activemodel (= 6.0.0)
14
- activesupport (= 6.0.0)
15
- 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)
16
16
  concurrent-ruby (~> 1.0, >= 1.0.2)
17
17
  i18n (>= 0.7, < 2)
18
18
  minitest (~> 5.1)
19
19
  tzinfo (~> 1.1)
20
- zeitwerk (~> 2.1, >= 2.1.8)
21
- byebug (11.0.1)
22
- coderay (1.1.2)
23
- 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)
24
24
  diff-lcs (1.3)
25
- ffi (1.11.1)
25
+ factory_bot (6.0.2)
26
+ activesupport (>= 5.0.0)
27
+ ffi (1.13.1)
26
28
  formatador (0.2.5)
27
- guard (2.15.0)
29
+ guard (2.16.2)
28
30
  formatador (>= 0.2.4)
29
31
  listen (>= 2.7, < 4.0)
30
32
  lumberjack (>= 1.0.12, < 2.0)
@@ -38,50 +40,49 @@ GEM
38
40
  guard (~> 2.1)
39
41
  guard-compat (~> 1.1)
40
42
  rspec (>= 2.99.0, < 4.0)
41
- i18n (1.6.0)
43
+ i18n (1.8.3)
42
44
  concurrent-ruby (~> 1.0)
43
- listen (3.1.5)
44
- rb-fsevent (~> 0.9, >= 0.9.4)
45
- rb-inotify (~> 0.9, >= 0.9.7)
46
- ruby_dep (~> 1.2)
47
- lumberjack (1.0.13)
48
- method_source (0.9.2)
49
- 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)
50
51
  nenv (0.3.0)
51
52
  notiffany (0.1.3)
52
53
  nenv (~> 0.1)
53
54
  shellany (~> 0.0)
54
- pry (0.12.2)
55
- coderay (~> 1.1.0)
56
- method_source (~> 0.9.0)
57
- pry-byebug (3.7.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)
58
60
  byebug (~> 11.0)
59
- pry (~> 0.10)
60
- rake (10.5.0)
61
- rb-fsevent (0.10.3)
62
- rb-inotify (0.10.0)
61
+ pry (~> 0.13.0)
62
+ rake (13.0.1)
63
+ rb-fsevent (0.10.4)
64
+ rb-inotify (0.10.1)
63
65
  ffi (~> 1.0)
64
- rspec (3.8.0)
65
- rspec-core (~> 3.8.0)
66
- rspec-expectations (~> 3.8.0)
67
- rspec-mocks (~> 3.8.0)
68
- rspec-core (3.8.2)
69
- rspec-support (~> 3.8.0)
70
- 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)
71
73
  diff-lcs (>= 1.2.0, < 2.0)
72
- rspec-support (~> 3.8.0)
73
- rspec-mocks (3.8.1)
74
+ rspec-support (~> 3.9.0)
75
+ rspec-mocks (3.9.1)
74
76
  diff-lcs (>= 1.2.0, < 2.0)
75
- rspec-support (~> 3.8.0)
76
- rspec-support (3.8.2)
77
- ruby_dep (1.5.0)
77
+ rspec-support (~> 3.9.0)
78
+ rspec-support (3.9.3)
78
79
  shellany (0.0.1)
79
- sqlite3 (1.4.1)
80
- thor (0.20.3)
80
+ sqlite3 (1.4.2)
81
+ thor (1.0.1)
81
82
  thread_safe (0.3.6)
82
- tzinfo (1.2.5)
83
+ tzinfo (1.2.7)
83
84
  thread_safe (~> 0.1)
84
- zeitwerk (2.1.9)
85
+ zeitwerk (2.3.0)
85
86
 
86
87
  PLATFORMS
87
88
  ruby
@@ -89,14 +90,16 @@ PLATFORMS
89
90
  DEPENDENCIES
90
91
  activerecord
91
92
  bundler (~> 2.0)
93
+ factory_bot
92
94
  guard
93
95
  guard-rspec
96
+ pg
94
97
  pry
95
98
  pry-byebug
96
- rake (~> 10.0)
99
+ rake (~> 13.0)
97
100
  rokaki!
98
101
  rspec (~> 3.0)
99
102
  sqlite3
100
103
 
101
104
  BUNDLED WITH
102
- 2.0.2
105
+ 2.1.4
data/README.md CHANGED
@@ -1,14 +1,19 @@
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:
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
- To use the basic DSL include the `Rokaki::Filterable` module
29
+ ### `#define_filter_keys`
30
+ #### A Simple Example
23
31
 
24
32
  A simple example might be:-
25
33
 
26
34
  ```ruby
27
- class FilterArticles
28
- include Rokaki::Filterable
35
+ class FilterArticles
36
+ include Rokaki::Filterable
29
37
 
30
- def initialize(filters:)
31
- @filters = filters
32
- @articles = Article
33
- end
38
+ def initialize(filters:)
39
+ @filters = filters
40
+ @articles = Article
41
+ end
34
42
 
35
- attr_accessor :filters
43
+ attr_accessor :filters
36
44
 
37
- define_filter_keys :date, author: [:first_name, :last_name]
45
+ define_filter_keys :date, author: [:first_name, :last_name]
38
46
 
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
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 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' } }`.
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
- ## Additional options
49
- You can specify a `filter_key_prefix` and a `filter_key_infix` to change the structure of the accessors.
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
- ## ActiveRecord
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
 
@@ -96,7 +177,7 @@ You can use `like` (or, if you use postgres, the case insensitive `ilike`) to pe
96
177
 
97
178
  ```ruby
98
179
  class ArticleFilter
99
- include FilterModel
180
+ include Rokaki::FilterModel
100
181
 
101
182
  filter :article,
102
183
  like: { # you can use ilike here instead if you use postgres and want case insensitive results
@@ -115,14 +196,45 @@ end
115
196
  ```
116
197
  Or
117
198
 
118
- #### 2. The porcelain command syntax
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
119
231
 
120
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`
121
233
 
122
234
 
123
235
  ```ruby
124
236
  class ArticleFilter
125
- include FilterModel
237
+ include Rokaki::FilterModel
126
238
 
127
239
  filters :date, :title, author: [:first_name, :last_name]
128
240
  like title: :circumfix
@@ -141,7 +253,7 @@ Or without the model in the initializer
141
253
 
142
254
  ```ruby
143
255
  class ArticleFilter
144
- include FilterModel
256
+ include Rokaki::FilterModel
145
257
 
146
258
  filters :date, :title, author: [:first_name, :last_name]
147
259
  like title: :circumfix
@@ -161,6 +273,64 @@ Would produce a query with a LIKE which circumfixes '%' around the filter term,
161
273
  @model = @model.where('title LIKE :query', query: "%#{title}%")
162
274
  ```
163
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
+
164
334
 
165
335
  ## Development
166
336
 
@@ -7,21 +7,52 @@ 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
+ @filter_map_query_key = query_key
31
+
32
+ @_filter_db = options[:db] || :postgres
33
+ @_filter_mode = options[:mode] || :and
34
+ like(options[:like]) if options[:like]
35
+ ilike(options[:ilike]) if options[:ilike]
36
+ filters(*options[:match]) if options[:match]
37
+ end
38
+
15
39
  def filter(model, options)
16
40
  filter_model(model)
41
+ @filter_map_query_key = nil
17
42
 
43
+ @_filter_db = options[:db] || :postgres
44
+ @_filter_mode = options[:mode] || :and
18
45
  like(options[:like]) if options[:like]
19
46
  ilike(options[:ilike]) if options[:ilike]
20
47
  filters(*options[:match]) if options[:match]
21
48
  end
22
49
 
23
50
  def filters(*filter_keys)
24
- define_filter_keys(*filter_keys)
51
+ if @filter_map_query_key
52
+ define_filter_map(@filter_map_query_key, *filter_keys)
53
+ else
54
+ define_filter_keys(*filter_keys)
55
+ end
25
56
 
26
57
  @_chain_filters ||= []
27
58
  filter_keys.each do |filter_key|
@@ -42,7 +73,8 @@ module Rokaki
42
73
  prefix: filter_key_prefix,
43
74
  infix: filter_key_infix,
44
75
  like_semantics: @_like_semantics,
45
- i_like_semantics: @i_like_semantics
76
+ i_like_semantics: @i_like_semantics,
77
+ db: @_filter_db
46
78
  )
47
79
  basic_filter.call
48
80
 
@@ -51,21 +83,14 @@ module Rokaki
51
83
  @_chain_filters << basic_filter.filter_template
52
84
  end
53
85
 
54
- def like_semantics(type:, query:, filter:, mode:, key:)
55
- query = "@model.where(\"#{key} #{type} :query\", "
56
- query += "query: \"%\#{#{filter}}%\")" if mode == :circumfix
57
- query += "query: \"%\#{#{filter}}\")" if mode == :prefix
58
- query += "query: \"\#{#{filter}}%\")" if mode == :suffix
59
- query
60
- end
61
-
62
86
  def _chain_nested_filter(filters_object)
63
87
  nested_filter = NestedFilter.new(
64
88
  filter_key_object: filters_object,
65
89
  prefix: filter_key_prefix,
66
90
  infix: filter_key_infix,
67
91
  like_semantics: @_like_semantics,
68
- i_like_semantics: @i_like_semantics
92
+ i_like_semantics: @i_like_semantics,
93
+ db: @_filter_db
69
94
  )
70
95
  nested_filter.call
71
96
 
@@ -78,13 +103,13 @@ module Rokaki
78
103
  end
79
104
  end
80
105
 
81
- def associated_table(association)
82
- @model.reflect_on_association(association).klass.table_name
83
- end
106
+ # def associated_table(association)
107
+ # @model.reflect_on_association(association).klass.table_name
108
+ # end
84
109
 
85
- def filter_model(model)
86
- @model = (model.is_a?(Class) ? model : Object.const_get(model.capitalize))
87
- class_eval "def model; @model ||= #{@model}; end;"
110
+ def filter_model(model_class)
111
+ @model = (model_class.is_a?(Class) ? model_class : Object.const_get(model_class.capitalize))
112
+ class_eval "def set_model; @model ||= #{@model}; end;"
88
113
  end
89
114
 
90
115
  def like(args)
@@ -132,7 +157,7 @@ module Rokaki
132
157
  # filter_model method
133
158
  #
134
159
  def define_results
135
- results_def = 'def results; model;'
160
+ results_def = 'def results; @model || set_model;'
136
161
  @_chain_filters.each do |item|
137
162
  results_def += item
138
163
  end
@@ -3,14 +3,15 @@
3
3
  module Rokaki
4
4
  module FilterModel
5
5
  class BasicFilter
6
- def initialize(keys:, prefix:, infix:, like_semantics:, i_like_semantics:)
6
+ def initialize(keys:, prefix:, infix:, like_semantics:, i_like_semantics:, db:)
7
7
  @keys = keys
8
8
  @prefix = prefix
9
9
  @infix = infix
10
10
  @like_semantics = like_semantics
11
11
  @i_like_semantics = i_like_semantics
12
+ @db = db
12
13
  end
13
- attr_reader :keys, :prefix, :infix, :like_semantics, :i_like_semantics
14
+ attr_reader :keys, :prefix, :infix, :like_semantics, :i_like_semantics, :db
14
15
  attr_accessor :filter_method, :filter_template
15
16
 
16
17
  def call
@@ -59,10 +60,16 @@ module Rokaki
59
60
  end
60
61
 
61
62
  def build_like_query(type:, query:, filter:, mode:, key:)
62
- query = "@model.where(\"#{key} #{type} :query\", "
63
- query += "query: \"%\#{#{filter}}%\")" if mode == :circumfix
64
- query += "query: \"%\#{#{filter}}\")" if mode == :prefix
65
- query += "query: \"\#{#{filter}}%\")" if mode == :suffix
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
+
66
73
  query
67
74
  end
68
75
  end
@@ -4,7 +4,7 @@ require 'active_support/inflector'
4
4
  module Rokaki
5
5
  module FilterModel
6
6
  class NestedFilter
7
- def initialize(filter_key_object:, prefix:, infix:, like_semantics:, i_like_semantics:)
7
+ def initialize(filter_key_object:, prefix:, infix:, like_semantics:, i_like_semantics:, db:, mode: :and)
8
8
  @filter_key_object = filter_key_object
9
9
  @prefix = prefix
10
10
  @infix = infix
@@ -12,8 +12,10 @@ module Rokaki
12
12
  @i_like_semantics = i_like_semantics
13
13
  @filter_methods = []
14
14
  @filter_templates = []
15
+ @db = db
16
+ @mode = mode
15
17
  end
16
- attr_reader :filter_key_object, :prefix, :infix, :like_semantics, :i_like_semantics
18
+ attr_reader :filter_key_object, :prefix, :infix, :like_semantics, :i_like_semantics, :db, :mode
17
19
  attr_accessor :filter_methods, :filter_templates
18
20
 
19
21
  def call # _chain_nested_filter
@@ -55,7 +57,7 @@ module Rokaki
55
57
  end
56
58
 
57
59
  def find_i_like_key(keys)
58
- return nil unless like_semantics && i_like_semantics.keys.any?
60
+ return nil unless i_like_semantics && i_like_semantics.keys.any?
59
61
  current_like_key = i_like_semantics
60
62
  keys.each do |key|
61
63
  current_like_key = current_like_key[key]
@@ -135,10 +137,16 @@ module Rokaki
135
137
  end
136
138
 
137
139
  def build_like_query(type:, query:, filter:, mode:, key:, leaf:)
138
- query = "where(\"#{key}.#{leaf} #{type} :query\", "
139
- query += "query: \"%\#{#{filter}}%\")" if mode == :circumfix
140
- query += "query: \"%\#{#{filter}}\")" if mode == :prefix
141
- query += "query: \"\#{#{filter}}%\")" if mode == :suffix
140
+ if db == :postgres
141
+ query = "where(\"#{key}.#{leaf} #{type} ANY (ARRAY[?])\", "
142
+ query += "prepare_terms(#{filter}, :#{mode}))"
143
+ else
144
+ query = "where(\"#{key}.#{leaf} #{type} :query\", "
145
+ query += "query: \"%\#{#{filter}}%\")" if mode == :circumfix
146
+ query += "query: \"%\#{#{filter}}\")" if mode == :prefix
147
+ query += "query: \"\#{#{filter}}%\")" if mode == :suffix
148
+ end
149
+
142
150
  query
143
151
  end
144
152
 
@@ -16,6 +16,17 @@ 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
+
26
+ def define_query_key(key = nil)
27
+ @filter_map_query_key = key
28
+ end
29
+
19
30
  def filter_key_prefix(prefix = nil)
20
31
  @filter_key_prefix ||= prefix
21
32
  end
@@ -24,6 +35,10 @@ module Rokaki
24
35
  @filter_key_infix ||= infix
25
36
  end
26
37
 
38
+ def filterable_object_name(name = 'filters')
39
+ @filterable_object_name ||= name
40
+ end
41
+
27
42
  def _build_filter(keys)
28
43
  name = @filter_key_prefix.to_s
29
44
  count = keys.size - 1
@@ -33,12 +48,30 @@ module Rokaki
33
48
  name += filter_key_infix.to_s unless count == i
34
49
  end
35
50
 
36
- class_eval "def #{name}; filters.dig(*#{keys}); end;", __FILE__, __LINE__
51
+ class_eval "def #{name}; #{filterable_object_name}.dig(*#{keys}); end;", __FILE__, __LINE__
52
+ end
53
+
54
+ def _map_filters(query_field, keys)
55
+ name = @filter_key_prefix.to_s
56
+ count = keys.size - 1
57
+
58
+ keys.each_with_index do |key, i|
59
+ name += key.to_s
60
+ name += filter_key_infix.to_s unless count == i
61
+ end
62
+
63
+ class_eval "def #{name}; #{filterable_object_name}.dig(:#{query_field}); end;", __FILE__, __LINE__
37
64
  end
38
65
 
39
66
  def _nested_key(filters_object)
40
67
  filters_object.keys.each do |key|
41
- deep_map([key], filters_object[key])
68
+ deep_map([key], filters_object[key]) { |keys| _build_filter(keys) }
69
+ end
70
+ end
71
+
72
+ def _nested_map(query_field, filters_object)
73
+ filters_object.keys.each do |key|
74
+ deep_map([key], filters_object[key]) { |keys| _map_filters(query_field, keys) }
42
75
  end
43
76
  end
44
77
 
@@ -46,20 +79,24 @@ module Rokaki
46
79
  if value.is_a? Hash
47
80
  value.keys.map do |key|
48
81
  _keys = keys.dup << key
49
- deep_map(_keys, value[key])
82
+ deep_map(_keys, value[key], &Proc.new)
50
83
  end
51
84
  end
52
85
 
53
86
  if value.is_a? Array
54
87
  value.each do |av|
88
+ if av.is_a? Symbol
55
89
  _keys = keys.dup << av
56
- _build_filter(_keys)
90
+ yield _keys
91
+ else
92
+ deep_map(keys, av, &Proc.new)
93
+ end
57
94
  end
58
95
  end
59
96
 
60
97
  if value.is_a? Symbol
61
98
  _keys = keys.dup << value
62
- _build_filter(_keys)
99
+ yield _keys
63
100
  end
64
101
  end
65
102
 
@@ -1,3 +1,3 @@
1
1
  module Rokaki
2
- VERSION = "0.6.0"
2
+ VERSION = "0.8.1.1"
3
3
  end
@@ -34,11 +34,13 @@ 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'
37
38
  spec.add_development_dependency 'guard'
38
39
  spec.add_development_dependency 'guard-rspec'
40
+ spec.add_development_dependency 'pg'
39
41
  spec.add_development_dependency 'pry'
40
42
  spec.add_development_dependency 'pry-byebug'
41
- spec.add_development_dependency 'rake', '~> 10.0'
43
+ spec.add_development_dependency 'rake', '~> 13.0'
42
44
  spec.add_development_dependency 'rspec', '~> 3.0'
43
45
  spec.add_development_dependency 'sqlite3'
44
46
  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.6.0
4
+ version: 0.8.1.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: 2019-11-07 00:00:00.000000000 Z
11
+ date: 2020-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: factory_bot
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: guard
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,20 @@ dependencies:
80
94
  - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pg
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: pry
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -114,14 +142,14 @@ dependencies:
114
142
  requirements:
115
143
  - - "~>"
116
144
  - !ruby/object:Gem::Version
117
- version: '10.0'
145
+ version: '13.0'
118
146
  type: :development
119
147
  prerelease: false
120
148
  version_requirements: !ruby/object:Gem::Requirement
121
149
  requirements:
122
150
  - - "~>"
123
151
  - !ruby/object:Gem::Version
124
- version: '10.0'
152
+ version: '13.0'
125
153
  - !ruby/object:Gem::Dependency
126
154
  name: rspec
127
155
  requirement: !ruby/object:Gem::Requirement
@@ -198,7 +226,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
226
  - !ruby/object:Gem::Version
199
227
  version: '0'
200
228
  requirements: []
201
- rubygems_version: 3.0.4
229
+ rubygems_version: 3.1.2
202
230
  signing_key:
203
231
  specification_version: 4
204
232
  summary: A web request filtering library