rokaki 0.8.2 → 0.8.3

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: b5484d6a4935bbc4f908e9081c82fcfd757f2e49770b7cbde2c055ec9ab169b0
4
- data.tar.gz: 44a41e6c4ba814bc98934ddb2029a222841fd18425847c73ab3a9a3d86acc66a
3
+ metadata.gz: 53402d2c7b2fca689234d8bed13602eef1bac7a161b09fe15da517da8939e5ac
4
+ data.tar.gz: 2a903781f7d5f2e4b90c419def1dc5a2b8289798a708a66e6be91604de02de86
5
5
  SHA512:
6
- metadata.gz: e8c29246d64a0fc0c5ca63173c419cee77cf07cd06d99a1fc14bcc53285222d980e8ace9e624bb75d4566814bfc8ce38613305676d2152591da83f8ea04002b1
7
- data.tar.gz: 82ac3f4b16473b8adf6aeeb93907a0e71ab811c439d5071e95b063238042828c39818f33763a953fa0898c40c0d0f912cc6b3781b9ff1bb75d6c3227c8efd282
6
+ metadata.gz: 7cd1957fe50f484e465a7c592a6e770e6a0e46975479ea4863deab66434031936d477b852ae8b72095943bc0db47d1379cd089b280485f5d4a7aa90d50cc6a12
7
+ data.tar.gz: 9b966c7cafd777655fcb6f20b70b3c331d2f8175c8bf22c49ef4295188f25ae15a7b8581738f4fbac5eb5e605b129088936c8588097015d366080b8866687891
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rokaki (0.8.2)
4
+ rokaki (0.8.3)
5
5
  activesupport
6
6
 
7
7
  GEM
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`
@@ -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
@@ -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 _chain_filter(key)
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 _chain_nested_filter(filters_object)
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
- key_builder = LikeKeys.new(args)
118
- keys = key_builder.call
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
- key_builder = LikeKeys.new(args)
128
- keys = key_builder.call
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
- @like_keys = []
12
+ @keys = []
13
+ @key_paths = []
13
14
  end
14
15
 
15
- attr_reader :args, :like_keys
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], key)
20
+ map_keys(key: key, value: args[key])
20
21
  end
21
- like_keys
22
+ keys
22
23
  end
23
24
 
24
25
  private
25
26
 
26
- def map_keys(value, key, key_path = [])
27
- key_result = {}
28
- key_path << key
27
+ def map_keys(key:, value:, key_path: [])
29
28
 
30
- if value.is_a? Hash
31
- value.keys.each do |sub_key|
32
- sub_value = value[sub_key]
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
- else
47
- @like_keys = [key]
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
- key_result
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
- mode = nil
78
+ search_mode = nil
78
79
  type = nil
79
80
  leaf = nil
80
81
 
81
- if mode = find_like_key(keys)
82
+ if search_mode = find_like_key(keys)
82
83
  type = 'LIKE'
83
- elsif mode = find_i_like_key(keys)
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 mode
117
+ if search_mode
117
118
  query = build_like_query(
118
119
  type: type,
119
120
  query: '',
120
121
  filter: "#{prefix}#{name}",
121
- mode: mode,
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:, mode:, key:, leaf:)
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}, :#{mode}))"
142
+ query += "prepare_terms(#{filter}, :#{search_mode}))"
142
143
  else
143
144
  query = "where(\"#{key}.#{leaf} #{type} :query\", "
144
- query += "query: \"%\#{#{filter}}%\")" if mode == :circumfix
145
- query += "query: \"%\#{#{filter}}\")" if mode == :prefix
146
- query += "query: \"\#{#{filter}}%\")" if mode == :suffix
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
+
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module Rokaki
2
- VERSION = "0.8.2"
2
+ VERSION = "0.8.3"
3
3
  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.2
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-22 00:00:00.000000000 Z
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