rokaki 0.8.2 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +30 -0
- data/lib/rokaki.rb +2 -0
- data/lib/rokaki/filter_model.rb +49 -37
- 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 +104 -0
- data/lib/rokaki/filter_model/like_keys.rb +17 -24
- data/lib/rokaki/filter_model/nested_filter.rb +13 -12
- data/lib/rokaki/filter_model/nested_like_filters.rb +241 -0
- data/lib/rokaki/filterable.rb +6 -0
- data/lib/rokaki/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53402d2c7b2fca689234d8bed13602eef1bac7a161b09fe15da517da8939e5ac
|
4
|
+
data.tar.gz: 2a903781f7d5f2e4b90c419def1dc5a2b8289798a708a66e6be91604de02de86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7cd1957fe50f484e465a7c592a6e770e6a0e46975479ea4863deab66434031936d477b852ae8b72095943bc0db47d1379cd089b280485f5d4a7aa90d50cc6a12
|
7
|
+
data.tar.gz: 9b966c7cafd777655fcb6f20b70b3c331d2f8175c8bf22c49ef4295188f25ae15a7b8581738f4fbac5eb5e605b129088936c8588097015d366080b8866687891
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -227,6 +227,36 @@ filtered_authors = AuthorFilter.new(filters: filters).results
|
|
227
227
|
|
228
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
229
|
|
230
|
+
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:-
|
231
|
+
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
class AuthorFilter
|
235
|
+
include Rokaki::FilterModel
|
236
|
+
|
237
|
+
filter_map :author, :query,
|
238
|
+
like: {
|
239
|
+
articles: {
|
240
|
+
title: :circumfix,
|
241
|
+
or: { # the or is aware of the join and will generate a compound join aware or query
|
242
|
+
reviews: {
|
243
|
+
title: :circumfix
|
244
|
+
}
|
245
|
+
}
|
246
|
+
},
|
247
|
+
}
|
248
|
+
|
249
|
+
attr_accessor :filters, :model
|
250
|
+
|
251
|
+
def initialize(filters:)
|
252
|
+
@filters = filters
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
filters = { query: "Lao" }
|
257
|
+
filtered_authors = AuthorFilter.new(filters: filters).results
|
258
|
+
```
|
259
|
+
|
230
260
|
#### 3. The porcelain command syntax
|
231
261
|
|
232
262
|
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,9 +3,11 @@
|
|
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
|
data/lib/rokaki/filter_model.rb
CHANGED
@@ -30,6 +30,7 @@ module Rokaki
|
|
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]
|
@@ -40,6 +41,7 @@ module Rokaki
|
|
40
41
|
@filter_map_query_key = nil
|
41
42
|
|
42
43
|
@_filter_db = options[:db] || :postgres
|
44
|
+
@_filter_mode = options[:mode] || :and
|
43
45
|
like(options[:like]) if options[:like]
|
44
46
|
ilike(options[:ilike]) if options[:ilike]
|
45
47
|
filters(*options[:match]) if options[:match]
|
@@ -54,18 +56,38 @@ module Rokaki
|
|
54
56
|
|
55
57
|
@_chain_filters ||= []
|
56
58
|
filter_keys.each do |filter_key|
|
57
|
-
|
58
59
|
# TODO: does the key need casting to an array here?
|
59
60
|
_chain_filter(filter_key) unless filter_key.is_a? Hash
|
60
|
-
|
61
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
|
62
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)
|
63
72
|
end
|
64
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
|
+
)
|
84
|
+
nested_like_filter.call
|
85
|
+
|
86
|
+
_chain_nested_like_filter(nested_like_filter)
|
65
87
|
define_results # writes out all the generated filters
|
66
88
|
end
|
67
89
|
|
68
|
-
def
|
90
|
+
def _build_basic_filter(key)
|
69
91
|
basic_filter = BasicFilter.new(
|
70
92
|
keys: [key],
|
71
93
|
prefix: filter_key_prefix,
|
@@ -75,13 +97,17 @@ module Rokaki
|
|
75
97
|
db: @_filter_db
|
76
98
|
)
|
77
99
|
basic_filter.call
|
100
|
+
basic_filter
|
101
|
+
end
|
78
102
|
|
103
|
+
def _chain_filter(key)
|
104
|
+
basic_filter = _build_basic_filter(key)
|
79
105
|
class_eval basic_filter.filter_method, __FILE__, __LINE__ - 2
|
80
106
|
|
81
107
|
@_chain_filters << basic_filter.filter_template
|
82
108
|
end
|
83
109
|
|
84
|
-
def
|
110
|
+
def _build_nested_filter(filters_object)
|
85
111
|
nested_filter = NestedFilter.new(
|
86
112
|
filter_key_object: filters_object,
|
87
113
|
prefix: filter_key_prefix,
|
@@ -91,6 +117,21 @@ module Rokaki
|
|
91
117
|
db: @_filter_db
|
92
118
|
)
|
93
119
|
nested_filter.call
|
120
|
+
nested_filter
|
121
|
+
end
|
122
|
+
|
123
|
+
def _chain_nested_like_filter(filters_object)
|
124
|
+
filters_object.filter_methods.each do |filter_method|
|
125
|
+
class_eval filter_method, __FILE__, __LINE__ - 2
|
126
|
+
end
|
127
|
+
|
128
|
+
filters_object.templates.each do |filter_template|
|
129
|
+
@_chain_filters << filter_template
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def _chain_nested_filter(filters_object)
|
134
|
+
nested_filter = _build_nested_filter(filters_object)
|
94
135
|
|
95
136
|
nested_filter.filter_methods.each do |filter_method|
|
96
137
|
class_eval filter_method, __FILE__, __LINE__ - 2
|
@@ -101,10 +142,6 @@ module Rokaki
|
|
101
142
|
end
|
102
143
|
end
|
103
144
|
|
104
|
-
# def associated_table(association)
|
105
|
-
# @model.reflect_on_association(association).klass.table_name
|
106
|
-
# end
|
107
|
-
|
108
145
|
def filter_model(model_class)
|
109
146
|
@model = (model_class.is_a?(Class) ? model_class : Object.const_get(model_class.capitalize))
|
110
147
|
class_eval "def set_model; @model ||= #{@model}; end;"
|
@@ -114,41 +151,16 @@ module Rokaki
|
|
114
151
|
raise ArgumentError, 'argument mush be a hash' unless args.is_a? Hash
|
115
152
|
@_like_semantics = (@_like_semantics || {}).merge(args)
|
116
153
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
filters(*keys)
|
154
|
+
like_keys = LikeKeys.new(args)
|
155
|
+
like_filters(like_keys, term_type: :like)
|
121
156
|
end
|
122
157
|
|
123
158
|
def ilike(args)
|
124
159
|
raise ArgumentError, 'argument mush be a hash' unless args.is_a? Hash
|
125
160
|
@i_like_semantics = (@i_like_semantics || {}).merge(args)
|
126
161
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
filters(*keys)
|
131
|
-
end
|
132
|
-
|
133
|
-
def deep_chain(keys, value)
|
134
|
-
if value.is_a? Hash
|
135
|
-
value.keys.map do |key|
|
136
|
-
_keys = keys.dup << key
|
137
|
-
deep_chain(_keys, value[key])
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
if value.is_a? Array
|
142
|
-
value.each do |av|
|
143
|
-
_keys = keys.dup << av
|
144
|
-
_build_deep_chain(_keys)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
if value.is_a? Symbol
|
149
|
-
_keys = keys.dup << value
|
150
|
-
_build_deep_chain(_keys)
|
151
|
-
end
|
162
|
+
like_keys = LikeKeys.new(args)
|
163
|
+
like_filters(like_keys, term_type: :ilike)
|
152
164
|
end
|
153
165
|
|
154
166
|
# 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,104 @@
|
|
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 key_path.is_a?(Symbol)
|
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 key_path.is_a?(Array)
|
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 parent_result.is_a?(Symbol) && parent_result != key
|
46
|
+
result[previous_key] = [parent_result, key]
|
47
|
+
elsif parent_result.is_a?(Array)
|
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
|
+
if res.is_a? Symbol
|
67
|
+
result[previous_key_path.first] = { previous_key => key }
|
68
|
+
elsif res.is_a?(Hash)
|
69
|
+
end
|
70
|
+
elsif previous_path_length > 1
|
71
|
+
res = result.dig(*previous_key_path)
|
72
|
+
if res.is_a? Symbol
|
73
|
+
base = previous_key_path.pop
|
74
|
+
result.dig(*previous_key_path)[base] = { previous_key => key }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
else
|
80
|
+
end
|
81
|
+
|
82
|
+
previous_key = key
|
83
|
+
current_key_path << key
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
result
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
|
@@ -9,45 +9,38 @@ 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
|
-
map_keys(args[key]
|
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 = {}
|
28
|
-
key_path << key
|
27
|
+
def map_keys(key:, value:, key_path: [])
|
29
28
|
|
30
|
-
if value.is_a?
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
if sub_value.is_a? Symbol
|
35
|
-
if key_result[key].is_a? Array
|
36
|
-
key_result[key] << sub_key
|
37
|
-
else
|
38
|
-
key_result[key] = [ sub_key ]
|
39
|
-
@like_keys << deep_assign(key_path, key_result[key])
|
40
|
-
end
|
41
|
-
|
42
|
-
elsif sub_value.is_a? Hash
|
43
|
-
map_keys(sub_value, sub_key, key_path)
|
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)
|
44
33
|
end
|
45
34
|
end
|
46
|
-
|
47
|
-
|
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
|
48
40
|
end
|
49
41
|
|
50
|
-
|
42
|
+
key_path
|
43
|
+
|
51
44
|
end
|
52
45
|
|
53
46
|
# Many thanks Cary Swoveland
|
@@ -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
|
@@ -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_filter(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_filter(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_filter(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
|
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
@@ -35,11 +35,16 @@ module Rokaki
|
|
35
35
|
@filter_key_infix ||= infix
|
36
36
|
end
|
37
37
|
|
38
|
+
def or_key(or_key = :or)
|
39
|
+
@or_key ||= or_key
|
40
|
+
end
|
41
|
+
|
38
42
|
def filterable_object_name(name = 'filters')
|
39
43
|
@filterable_object_name ||= name
|
40
44
|
end
|
41
45
|
|
42
46
|
def _build_filter(keys)
|
47
|
+
keys.delete(or_key)
|
43
48
|
name = @filter_key_prefix.to_s
|
44
49
|
count = keys.size - 1
|
45
50
|
|
@@ -52,6 +57,7 @@ module Rokaki
|
|
52
57
|
end
|
53
58
|
|
54
59
|
def _map_filters(query_field, keys)
|
60
|
+
keys.delete(or_key)
|
55
61
|
name = @filter_key_prefix.to_s
|
56
62
|
count = keys.size - 1
|
57
63
|
|
data/lib/rokaki/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rokaki
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.3
|
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-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -200,9 +200,12 @@ files:
|
|
200
200
|
- lib/rokaki.rb
|
201
201
|
- lib/rokaki/filter_model.rb
|
202
202
|
- lib/rokaki/filter_model/basic_filter.rb
|
203
|
+
- lib/rokaki/filter_model/deep_assign_struct.rb
|
203
204
|
- lib/rokaki/filter_model/filter_chain.rb
|
205
|
+
- lib/rokaki/filter_model/join_map.rb
|
204
206
|
- lib/rokaki/filter_model/like_keys.rb
|
205
207
|
- lib/rokaki/filter_model/nested_filter.rb
|
208
|
+
- lib/rokaki/filter_model/nested_like_filters.rb
|
206
209
|
- lib/rokaki/filterable.rb
|
207
210
|
- lib/rokaki/version.rb
|
208
211
|
- rokaki.gemspec
|