rokaki 0.8.1 → 0.8.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +6 -1
- data/README.md +88 -2
- data/lib/rokaki.rb +2 -3
- data/lib/rokaki/filter_model.rb +55 -41
- 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 +15 -14
- data/lib/rokaki/filter_model/nested_like_filters.rb +241 -0
- data/lib/rokaki/filterable.rb +18 -4
- data/lib/rokaki/version.rb +1 -1
- data/rokaki.gemspec +8 -4
- metadata +40 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f2a62792073dd9728a44158d6f30dc4f0f8119316bda3a3846c16fc2abc57f7
|
4
|
+
data.tar.gz: 80e9bfc7b677b7e291996c5bda1fc6652ddfbb592b31083446ef992755f00404
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6570c3b380c66092b3a583b1c94a51f8364f06d936522cc72676e23c3aa1717b4b89cc65c0d3ed6e6d67291b3c7e1c3c83d8254acd7df8f9971700b314a71aa0
|
7
|
+
data.tar.gz: 913e7bf304da343a4dce4d061dbcdaa37c2f26d7a396e590e393a224a4ac7c30c439f8d085de1a865828c9931f1bcf99dc66b32c8494e8481dcc6c5539d03ea6
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
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
|
@@ -21,6 +21,10 @@ GEM
|
|
21
21
|
byebug (11.1.3)
|
22
22
|
coderay (1.1.3)
|
23
23
|
concurrent-ruby (1.1.6)
|
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)
|
@@ -90,6 +94,7 @@ PLATFORMS
|
|
90
94
|
DEPENDENCIES
|
91
95
|
activerecord
|
92
96
|
bundler (~> 2.0)
|
97
|
+
database_cleaner-active_record
|
93
98
|
factory_bot
|
94
99
|
guard
|
95
100
|
guard-rspec
|
data/README.md
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# Rokaki
|
2
|
+
## You know, for air.
|
3
|
+
|
2
4
|
[![Gem Version](https://badge.fury.io/rb/rokaki.svg)](https://badge.fury.io/rb/rokaki)
|
3
5
|
|
4
6
|
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 +48,8 @@ class FilterArticles
|
|
46
48
|
|
47
49
|
def filter_results
|
48
50
|
@articles = @articles.where(date: date) if date
|
49
|
-
@articles = @articles.joins(:author).where(
|
51
|
+
@articles = @articles.joins(:author).where(authors: { first_name: author_first_name }) if author_first_name
|
52
|
+
@articles = @articles.joins(:author).where(authors: { last_name: author_last_name }) if author_last_name
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
@@ -98,6 +101,7 @@ advanced_filterable.advanced__filter_key_4__deep_leaf_array == [1,2,3,4]
|
|
98
101
|
advanced_filterable.advanced__filter_key_1__filter_key_3__deep_node == 'NODE'
|
99
102
|
```
|
100
103
|
### `#define_filter_map`
|
104
|
+
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
105
|
|
102
106
|
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
107
|
|
@@ -115,7 +119,7 @@ class FilterMap
|
|
115
119
|
define_filter_map :query, :mapped_a, association: :field
|
116
120
|
end
|
117
121
|
|
118
|
-
filter_map = FilterMap.new(
|
122
|
+
filter_map = FilterMap.new(fylterz: { query: 'H2O' })
|
119
123
|
|
120
124
|
filter_map.mapped_a == 'H2O'
|
121
125
|
filter_map.association_field = 'H2O'
|
@@ -227,6 +231,88 @@ filtered_authors = AuthorFilter.new(filters: filters).results
|
|
227
231
|
|
228
232
|
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
233
|
|
234
|
+
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:-
|
235
|
+
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
class AuthorFilter
|
239
|
+
include Rokaki::FilterModel
|
240
|
+
|
241
|
+
filter_map :author, :query,
|
242
|
+
like: {
|
243
|
+
articles: {
|
244
|
+
title: :circumfix,
|
245
|
+
or: { # the or is aware of the join and will generate a compound join aware or query
|
246
|
+
reviews: {
|
247
|
+
title: :circumfix
|
248
|
+
}
|
249
|
+
}
|
250
|
+
},
|
251
|
+
}
|
252
|
+
|
253
|
+
attr_accessor :filters, :model
|
254
|
+
|
255
|
+
def initialize(filters:)
|
256
|
+
@filters = filters
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
filters = { query: "Lao" }
|
261
|
+
filtered_authors = AuthorFilter.new(filters: filters).results
|
262
|
+
```
|
263
|
+
|
264
|
+
## CAVEATS
|
265
|
+
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:-
|
266
|
+
|
267
|
+
### #structurally_incompatible_values_for_or
|
268
|
+
|
269
|
+
``` ruby
|
270
|
+
module ActiveRecord
|
271
|
+
module QueryMethods
|
272
|
+
def structurally_incompatible_values_for_or(other)
|
273
|
+
Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } +
|
274
|
+
(Relation::MULTI_VALUE_METHODS - [:joins, :eager_load, :references, :extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } +
|
275
|
+
(Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") }
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
```
|
280
|
+
|
281
|
+
### A has one relation to a model called Or
|
282
|
+
If you happen to have a model/table named 'Or' then you can override the `or:` key syntax by specifying a special `or_key`:-
|
283
|
+
|
284
|
+
```ruby
|
285
|
+
class AuthorFilter
|
286
|
+
include Rokaki::FilterModel
|
287
|
+
|
288
|
+
or_key :my_or
|
289
|
+
filter_map :author, :query,
|
290
|
+
like: {
|
291
|
+
articles: {
|
292
|
+
title: :circumfix,
|
293
|
+
my_or: { # the or is aware of the join and will generate a compound join aware or query
|
294
|
+
or: { # The Or model has a title field
|
295
|
+
title: :circumfix
|
296
|
+
}
|
297
|
+
}
|
298
|
+
},
|
299
|
+
}
|
300
|
+
|
301
|
+
attr_accessor :filters, :model
|
302
|
+
|
303
|
+
def initialize(filters:)
|
304
|
+
@filters = filters
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
filters = { query: "Syntaxes" }
|
309
|
+
filtered_authors = AuthorFilter.new(filters: filters).results
|
310
|
+
```
|
311
|
+
|
312
|
+
|
313
|
+
See [this issue](https://github.com/rails/rails/issues/24055) for details.
|
314
|
+
|
315
|
+
|
230
316
|
#### 3. The porcelain command syntax
|
231
317
|
|
232
318
|
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
|
@@ -27,9 +27,10 @@ module Rokaki
|
|
27
27
|
|
28
28
|
def filter_map(model, query_key, options)
|
29
29
|
filter_model(model)
|
30
|
-
@
|
30
|
+
@filter_map_query_key = query_key
|
31
31
|
|
32
32
|
@_filter_db = options[:db] || :postgres
|
33
|
+
@_filter_mode = options[:mode] || :and
|
33
34
|
like(options[:like]) if options[:like]
|
34
35
|
ilike(options[:ilike]) if options[:ilike]
|
35
36
|
filters(*options[:match]) if options[:match]
|
@@ -37,34 +38,57 @@ module Rokaki
|
|
37
38
|
|
38
39
|
def filter(model, options)
|
39
40
|
filter_model(model)
|
41
|
+
@filter_map_query_key = nil
|
40
42
|
|
41
43
|
@_filter_db = options[:db] || :postgres
|
44
|
+
@_filter_mode = options[:mode] || :and
|
42
45
|
like(options[:like]) if options[:like]
|
43
46
|
ilike(options[:ilike]) if options[:ilike]
|
44
47
|
filters(*options[:match]) if options[:match]
|
45
48
|
end
|
46
49
|
|
47
50
|
def filters(*filter_keys)
|
48
|
-
if @
|
49
|
-
define_filter_map(@
|
51
|
+
if @filter_map_query_key
|
52
|
+
define_filter_map(@filter_map_query_key, *filter_keys)
|
50
53
|
else
|
51
54
|
define_filter_keys(*filter_keys)
|
52
55
|
end
|
53
56
|
|
54
57
|
@_chain_filters ||= []
|
55
58
|
filter_keys.each do |filter_key|
|
56
|
-
|
57
59
|
# TODO: does the key need casting to an array here?
|
58
60
|
_chain_filter(filter_key) unless filter_key.is_a? Hash
|
59
|
-
|
60
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
|
61
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)
|
62
72
|
end
|
63
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)
|
64
88
|
define_results # writes out all the generated filters
|
65
89
|
end
|
66
90
|
|
67
|
-
def
|
91
|
+
def _build_basic_filter(key)
|
68
92
|
basic_filter = BasicFilter.new(
|
69
93
|
keys: [key],
|
70
94
|
prefix: filter_key_prefix,
|
@@ -74,13 +98,17 @@ module Rokaki
|
|
74
98
|
db: @_filter_db
|
75
99
|
)
|
76
100
|
basic_filter.call
|
101
|
+
basic_filter
|
102
|
+
end
|
77
103
|
|
104
|
+
def _chain_filter(key)
|
105
|
+
basic_filter = _build_basic_filter(key)
|
78
106
|
class_eval basic_filter.filter_method, __FILE__, __LINE__ - 2
|
79
107
|
|
80
108
|
@_chain_filters << basic_filter.filter_template
|
81
109
|
end
|
82
110
|
|
83
|
-
def
|
111
|
+
def _build_nested_filter(filters_object)
|
84
112
|
nested_filter = NestedFilter.new(
|
85
113
|
filter_key_object: filters_object,
|
86
114
|
prefix: filter_key_prefix,
|
@@ -90,18 +118,29 @@ module Rokaki
|
|
90
118
|
db: @_filter_db
|
91
119
|
)
|
92
120
|
nested_filter.call
|
121
|
+
nested_filter
|
122
|
+
end
|
93
123
|
|
94
|
-
|
124
|
+
def _chain_nested_like_filter(filters_object)
|
125
|
+
filters_object.filter_methods.each do |filter_method|
|
95
126
|
class_eval filter_method, __FILE__, __LINE__ - 2
|
96
127
|
end
|
97
128
|
|
98
|
-
|
129
|
+
filters_object.templates.each do |filter_template|
|
99
130
|
@_chain_filters << filter_template
|
100
131
|
end
|
101
132
|
end
|
102
133
|
|
103
|
-
def
|
104
|
-
|
134
|
+
def _chain_nested_filter(filters_object)
|
135
|
+
nested_filter = _build_nested_filter(filters_object)
|
136
|
+
|
137
|
+
nested_filter.filter_methods.each do |filter_method|
|
138
|
+
class_eval filter_method, __FILE__, __LINE__ - 2
|
139
|
+
end
|
140
|
+
|
141
|
+
nested_filter.filter_templates.each do |filter_template|
|
142
|
+
@_chain_filters << filter_template
|
143
|
+
end
|
105
144
|
end
|
106
145
|
|
107
146
|
def filter_model(model_class)
|
@@ -113,41 +152,16 @@ module Rokaki
|
|
113
152
|
raise ArgumentError, 'argument mush be a hash' unless args.is_a? Hash
|
114
153
|
@_like_semantics = (@_like_semantics || {}).merge(args)
|
115
154
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
filters(*keys)
|
155
|
+
like_keys = LikeKeys.new(args)
|
156
|
+
like_filters(like_keys, term_type: :like)
|
120
157
|
end
|
121
158
|
|
122
159
|
def ilike(args)
|
123
160
|
raise ArgumentError, 'argument mush be a hash' unless args.is_a? Hash
|
124
161
|
@i_like_semantics = (@i_like_semantics || {}).merge(args)
|
125
162
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
filters(*keys)
|
130
|
-
end
|
131
|
-
|
132
|
-
def deep_chain(keys, value)
|
133
|
-
if value.is_a? Hash
|
134
|
-
value.keys.map do |key|
|
135
|
-
_keys = keys.dup << key
|
136
|
-
deep_chain(_keys, value[key])
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
if value.is_a? Array
|
141
|
-
value.each do |av|
|
142
|
-
_keys = keys.dup << av
|
143
|
-
_build_deep_chain(_keys)
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
if value.is_a? Symbol
|
148
|
-
_keys = keys.dup << value
|
149
|
-
_build_deep_chain(_keys)
|
150
|
-
end
|
163
|
+
like_keys = LikeKeys.new(args)
|
164
|
+
like_filters(like_keys, term_type: :ilike)
|
151
165
|
end
|
152
166
|
|
153
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
|
@@ -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:, db:)
|
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
|
@@ -13,8 +13,9 @@ module Rokaki
|
|
13
13
|
@filter_methods = []
|
14
14
|
@filter_templates = []
|
15
15
|
@db = db
|
16
|
+
@mode = mode
|
16
17
|
end
|
17
|
-
attr_reader :filter_key_object, :prefix, :infix, :like_semantics, :i_like_semantics, :db
|
18
|
+
attr_reader :filter_key_object, :prefix, :infix, :like_semantics, :i_like_semantics, :db, :mode
|
18
19
|
attr_accessor :filter_methods, :filter_templates
|
19
20
|
|
20
21
|
def call # _chain_nested_filter
|
@@ -56,7 +57,7 @@ module Rokaki
|
|
56
57
|
end
|
57
58
|
|
58
59
|
def find_i_like_key(keys)
|
59
|
-
return nil unless
|
60
|
+
return nil unless i_like_semantics && i_like_semantics.keys.any?
|
60
61
|
current_like_key = i_like_semantics
|
61
62
|
keys.each do |key|
|
62
63
|
current_like_key = current_like_key[key]
|
@@ -65,7 +66,7 @@ module Rokaki
|
|
65
66
|
end
|
66
67
|
|
67
68
|
def _build_deep_chain(keys)
|
68
|
-
name = ''
|
69
|
+
name = ''
|
69
70
|
count = keys.size - 1
|
70
71
|
|
71
72
|
joins_before = []
|
@@ -74,13 +75,13 @@ module Rokaki
|
|
74
75
|
where_before = []
|
75
76
|
where_after = []
|
76
77
|
out = ''
|
77
|
-
|
78
|
+
search_mode = nil
|
78
79
|
type = nil
|
79
80
|
leaf = nil
|
80
81
|
|
81
|
-
if
|
82
|
+
if search_mode = find_like_key(keys)
|
82
83
|
type = 'LIKE'
|
83
|
-
elsif
|
84
|
+
elsif search_mode = find_i_like_key(keys)
|
84
85
|
type = 'ILIKE'
|
85
86
|
end
|
86
87
|
leaf = keys.pop
|
@@ -113,12 +114,12 @@ module Rokaki
|
|
113
114
|
joins = joins.join
|
114
115
|
where = where.join
|
115
116
|
|
116
|
-
if
|
117
|
+
if search_mode
|
117
118
|
query = build_like_query(
|
118
119
|
type: type,
|
119
120
|
query: '',
|
120
121
|
filter: "#{prefix}#{name}",
|
121
|
-
|
122
|
+
search_mode: search_mode,
|
122
123
|
key: keys.last.to_s.pluralize,
|
123
124
|
leaf: leaf
|
124
125
|
)
|
@@ -135,15 +136,15 @@ module Rokaki
|
|
135
136
|
end
|
136
137
|
end
|
137
138
|
|
138
|
-
def build_like_query(type:, query:, filter:,
|
139
|
+
def build_like_query(type:, query:, filter:, search_mode:, key:, leaf:)
|
139
140
|
if db == :postgres
|
140
141
|
query = "where(\"#{key}.#{leaf} #{type} ANY (ARRAY[?])\", "
|
141
|
-
query += "prepare_terms(#{filter}, :#{
|
142
|
+
query += "prepare_terms(#{filter}, :#{search_mode}))"
|
142
143
|
else
|
143
144
|
query = "where(\"#{key}.#{leaf} #{type} :query\", "
|
144
|
-
query += "query: \"%\#{#{filter}}%\")" if
|
145
|
-
query += "query: \"%\#{#{filter}}\")" if
|
146
|
-
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
|
147
148
|
end
|
148
149
|
|
149
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,18 +15,22 @@ 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
|
|
30
|
+
def define_query_key(key = nil)
|
31
|
+
@filter_map_query_key = key
|
32
|
+
end
|
33
|
+
|
26
34
|
def filter_key_prefix(prefix = nil)
|
27
35
|
@filter_key_prefix ||= prefix
|
28
36
|
end
|
@@ -31,11 +39,16 @@ module Rokaki
|
|
31
39
|
@filter_key_infix ||= infix
|
32
40
|
end
|
33
41
|
|
42
|
+
def or_key(or_key = :or)
|
43
|
+
@or_key ||= or_key
|
44
|
+
end
|
45
|
+
|
34
46
|
def filterable_object_name(name = 'filters')
|
35
47
|
@filterable_object_name ||= name
|
36
48
|
end
|
37
49
|
|
38
50
|
def _build_filter(keys)
|
51
|
+
keys.delete(or_key)
|
39
52
|
name = @filter_key_prefix.to_s
|
40
53
|
count = keys.size - 1
|
41
54
|
|
@@ -48,6 +61,7 @@ module Rokaki
|
|
48
61
|
end
|
49
62
|
|
50
63
|
def _map_filters(query_field, keys)
|
64
|
+
keys.delete(or_key)
|
51
65
|
name = @filter_key_prefix.to_s
|
52
66
|
count = keys.size - 1
|
53
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.1
|
4
|
+
version: 0.8.3.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: 2020-06-
|
11
|
+
date: 2020-06-28 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
|
- - ">="
|
@@ -200,9 +214,12 @@ files:
|
|
200
214
|
- lib/rokaki.rb
|
201
215
|
- lib/rokaki/filter_model.rb
|
202
216
|
- lib/rokaki/filter_model/basic_filter.rb
|
217
|
+
- lib/rokaki/filter_model/deep_assign_struct.rb
|
203
218
|
- lib/rokaki/filter_model/filter_chain.rb
|
219
|
+
- lib/rokaki/filter_model/join_map.rb
|
204
220
|
- lib/rokaki/filter_model/like_keys.rb
|
205
221
|
- lib/rokaki/filter_model/nested_filter.rb
|
222
|
+
- lib/rokaki/filter_model/nested_like_filters.rb
|
206
223
|
- lib/rokaki/filterable.rb
|
207
224
|
- lib/rokaki/version.rb
|
208
225
|
- rokaki.gemspec
|