rokaki 0.8.0 → 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: b0654c6e63cc988e84b4df0a33b2055661bc5f7e29367923bdc1267fc4bbe64e
4
- data.tar.gz: fd7f3e44cc48096389697d82f89b720a67900bbe073ce60e62828362fd46376d
3
+ metadata.gz: 53402d2c7b2fca689234d8bed13602eef1bac7a161b09fe15da517da8939e5ac
4
+ data.tar.gz: 2a903781f7d5f2e4b90c419def1dc5a2b8289798a708a66e6be91604de02de86
5
5
  SHA512:
6
- metadata.gz: dc938b3a7c3632ed7f92a0452b87a91c5e50f0ae37b3f00cb5f9d79a65e0ca0f23f60d5b92b6cd709c287ddcf4a891bde460fd757df1e2fd8c4b0f551499fd05
7
- data.tar.gz: a350805490d53b145b68813fdf9144b37871fa9b06942df0d2befb906e7912244258ffe14d0b5174bd0ab1fe7f92b4b9fcf650b847c1a36aefba2ecb8aee344b
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.0)
4
+ rokaki (0.8.3)
5
5
  activesupport
6
6
 
7
7
  GEM
@@ -59,7 +59,7 @@ GEM
59
59
  pry-byebug (3.9.0)
60
60
  byebug (~> 11.0)
61
61
  pry (~> 0.13.0)
62
- rake (10.5.0)
62
+ rake (13.0.1)
63
63
  rb-fsevent (0.10.4)
64
64
  rb-inotify (0.10.1)
65
65
  ffi (~> 1.0)
@@ -96,7 +96,7 @@ DEPENDENCIES
96
96
  pg
97
97
  pry
98
98
  pry-byebug
99
- rake (~> 10.0)
99
+ rake (~> 13.0)
100
100
  rokaki!
101
101
  rspec (~> 3.0)
102
102
  sqlite3
data/README.md CHANGED
@@ -8,6 +8,12 @@ There are two modes of use `Filterable` and `FilterModel` that can be activated
8
8
 
9
9
  Add this line to your application's Gemfile:
10
10
 
11
+ You can install from Rubygems:
12
+ ```
13
+ gem 'rokaki'
14
+ ```
15
+ Or from github
16
+
11
17
  ```ruby
12
18
  gem 'rokaki', git: 'https://github.com/tevio/rokaki.git'
13
19
  ```
@@ -221,6 +227,36 @@ filtered_authors = AuthorFilter.new(filters: filters).results
221
227
 
222
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.
223
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
+
224
260
  #### 3. The porcelain command syntax
225
261
 
226
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
@@ -27,9 +27,10 @@ module Rokaki
27
27
 
28
28
  def filter_map(model, query_key, options)
29
29
  filter_model(model)
30
- @_query_key = query_key
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,56 @@ 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 @_query_key
49
- define_filter_map(@_query_key, *filter_keys)
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
+ )
84
+ nested_like_filter.call
85
+
86
+ _chain_nested_like_filter(nested_like_filter)
64
87
  define_results # writes out all the generated filters
65
88
  end
66
89
 
67
- def _chain_filter(key)
90
+ def _build_basic_filter(key)
68
91
  basic_filter = BasicFilter.new(
69
92
  keys: [key],
70
93
  prefix: filter_key_prefix,
@@ -74,13 +97,17 @@ module Rokaki
74
97
  db: @_filter_db
75
98
  )
76
99
  basic_filter.call
100
+ basic_filter
101
+ end
77
102
 
103
+ def _chain_filter(key)
104
+ basic_filter = _build_basic_filter(key)
78
105
  class_eval basic_filter.filter_method, __FILE__, __LINE__ - 2
79
106
 
80
107
  @_chain_filters << basic_filter.filter_template
81
108
  end
82
109
 
83
- def _chain_nested_filter(filters_object)
110
+ def _build_nested_filter(filters_object)
84
111
  nested_filter = NestedFilter.new(
85
112
  filter_key_object: filters_object,
86
113
  prefix: filter_key_prefix,
@@ -90,18 +117,29 @@ module Rokaki
90
117
  db: @_filter_db
91
118
  )
92
119
  nested_filter.call
120
+ nested_filter
121
+ end
93
122
 
94
- nested_filter.filter_methods.each do |filter_method|
123
+ def _chain_nested_like_filter(filters_object)
124
+ filters_object.filter_methods.each do |filter_method|
95
125
  class_eval filter_method, __FILE__, __LINE__ - 2
96
126
  end
97
127
 
98
- nested_filter.filter_templates.each do |filter_template|
128
+ filters_object.templates.each do |filter_template|
99
129
  @_chain_filters << filter_template
100
130
  end
101
131
  end
102
132
 
103
- def associated_table(association)
104
- @model.reflect_on_association(association).klass.table_name
133
+ def _chain_nested_filter(filters_object)
134
+ nested_filter = _build_nested_filter(filters_object)
135
+
136
+ nested_filter.filter_methods.each do |filter_method|
137
+ class_eval filter_method, __FILE__, __LINE__ - 2
138
+ end
139
+
140
+ nested_filter.filter_templates.each do |filter_template|
141
+ @_chain_filters << filter_template
142
+ end
105
143
  end
106
144
 
107
145
  def filter_model(model_class)
@@ -113,41 +151,16 @@ module Rokaki
113
151
  raise ArgumentError, 'argument mush be a hash' unless args.is_a? Hash
114
152
  @_like_semantics = (@_like_semantics || {}).merge(args)
115
153
 
116
- key_builder = LikeKeys.new(args)
117
- keys = key_builder.call
118
-
119
- filters(*keys)
154
+ like_keys = LikeKeys.new(args)
155
+ like_filters(like_keys, term_type: :like)
120
156
  end
121
157
 
122
158
  def ilike(args)
123
159
  raise ArgumentError, 'argument mush be a hash' unless args.is_a? Hash
124
160
  @i_like_semantics = (@i_like_semantics || {}).merge(args)
125
161
 
126
- key_builder = LikeKeys.new(args)
127
- keys = key_builder.call
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
162
+ like_keys = LikeKeys.new(args)
163
+ like_filters(like_keys, term_type: :ilike)
151
164
  end
152
165
 
153
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,43 +9,45 @@ 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
- like_keys << 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)
27
- key_result = {}
27
+ def map_keys(key:, value:, key_path: [])
28
28
 
29
- if value.is_a? Hash
30
- value.keys.each do |sub_key|
31
- sub_value = value[sub_key]
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
- else
45
- key_result = 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
46
40
  end
47
41
 
48
- key_result
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 like_semantics && i_like_semantics.keys.any?
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]
@@ -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
+
@@ -23,6 +23,10 @@ module Rokaki
23
23
  end
24
24
  end
25
25
 
26
+ def define_query_key(key = nil)
27
+ @filter_map_query_key = key
28
+ end
29
+
26
30
  def filter_key_prefix(prefix = nil)
27
31
  @filter_key_prefix ||= prefix
28
32
  end
@@ -31,11 +35,16 @@ module Rokaki
31
35
  @filter_key_infix ||= infix
32
36
  end
33
37
 
38
+ def or_key(or_key = :or)
39
+ @or_key ||= or_key
40
+ end
41
+
34
42
  def filterable_object_name(name = 'filters')
35
43
  @filterable_object_name ||= name
36
44
  end
37
45
 
38
46
  def _build_filter(keys)
47
+ keys.delete(or_key)
39
48
  name = @filter_key_prefix.to_s
40
49
  count = keys.size - 1
41
50
 
@@ -48,6 +57,7 @@ module Rokaki
48
57
  end
49
58
 
50
59
  def _map_filters(query_field, keys)
60
+ keys.delete(or_key)
51
61
  name = @filter_key_prefix.to_s
52
62
  count = keys.size - 1
53
63
 
@@ -1,3 +1,3 @@
1
1
  module Rokaki
2
- VERSION = "0.8.0"
2
+ VERSION = "0.8.3"
3
3
  end
@@ -40,7 +40,7 @@ Gem::Specification.new do |spec|
40
40
  spec.add_development_dependency 'pg'
41
41
  spec.add_development_dependency 'pry'
42
42
  spec.add_development_dependency 'pry-byebug'
43
- spec.add_development_dependency 'rake', '~> 10.0'
43
+ spec.add_development_dependency 'rake', '~> 13.0'
44
44
  spec.add_development_dependency 'rspec', '~> 3.0'
45
45
  spec.add_development_dependency 'sqlite3'
46
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.8.0
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-21 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
@@ -142,14 +142,14 @@ dependencies:
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: '10.0'
145
+ version: '13.0'
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: '10.0'
152
+ version: '13.0'
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: rspec
155
155
  requirement: !ruby/object:Gem::Requirement
@@ -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