rokaki 0.13.0 → 0.15.0
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/.github/workflows/spec.yml +67 -4
- data/CHANGELOG.md +13 -0
- data/Gemfile.lock +18 -10
- data/README.legacy.md +533 -0
- data/README.md +24 -444
- data/docs/adapters.md +25 -1
- data/docs/configuration.md +12 -1
- data/docs/index.md +4 -4
- data/docs/usage.md +68 -2
- data/lib/rokaki/filter_model/basic_filter.rb +8 -0
- data/lib/rokaki/filter_model/nested_filter.rb +3 -2
- data/lib/rokaki/filter_model/nested_like_filters.rb +27 -18
- data/lib/rokaki/filter_model.rb +44 -0
- data/lib/rokaki/filterable.rb +3 -1
- data/lib/rokaki/version.rb +1 -1
- data/rokaki.gemspec +5 -2
- metadata +39 -4
|
@@ -40,6 +40,8 @@ module Rokaki
|
|
|
40
40
|
'LIKE BINARY'
|
|
41
41
|
elsif db == :sqlserver
|
|
42
42
|
'LIKE'
|
|
43
|
+
elsif db == :oracle
|
|
44
|
+
'LIKE'
|
|
43
45
|
else
|
|
44
46
|
'LIKE'
|
|
45
47
|
end
|
|
@@ -52,6 +54,9 @@ module Rokaki
|
|
|
52
54
|
'LIKE'
|
|
53
55
|
elsif db == :sqlserver
|
|
54
56
|
'LIKE'
|
|
57
|
+
elsif db == :oracle
|
|
58
|
+
# Use 'ILIKE' as a signal; oracle_like will translate to UPPER(column) LIKE UPPER(:q)
|
|
59
|
+
'ILIKE'
|
|
55
60
|
else
|
|
56
61
|
'LIKE'
|
|
57
62
|
end
|
|
@@ -105,6 +110,9 @@ module Rokaki
|
|
|
105
110
|
elsif db == :sqlserver
|
|
106
111
|
# Delegate to helper that supports arrays and escaping with ESCAPE
|
|
107
112
|
query = "sqlserver_like(@model, \"#{key}\", \"#{type}\", #{filter}, :#{mode})"
|
|
113
|
+
elsif db == :oracle
|
|
114
|
+
# Oracle helper handles case-insensitive via UPPER() when type is 'ILIKE'
|
|
115
|
+
query = "oracle_like(@model, \"#{key}\", \"#{type}\", #{filter}, :#{mode})"
|
|
108
116
|
else
|
|
109
117
|
query = "@model.where(\"#{key} #{type} :query\", "
|
|
110
118
|
query += "query: \"%\#{#{filter}}%\")" if mode == :circumfix
|
|
@@ -139,10 +139,11 @@ module Rokaki
|
|
|
139
139
|
where = where.join
|
|
140
140
|
|
|
141
141
|
if search_mode
|
|
142
|
-
if db == :sqlserver
|
|
142
|
+
if db == :sqlserver || db == :oracle
|
|
143
143
|
key_leaf = "#{keys.last.to_s.pluralize}.#{leaf}"
|
|
144
|
+
helper = db == :sqlserver ? 'sqlserver_like' : 'oracle_like'
|
|
144
145
|
@filter_methods << "def #{prefix}filter#{infix}#{name};"\
|
|
145
|
-
"
|
|
146
|
+
"#{helper}(@model.joins(#{joins}), \"#{key_leaf}\", \"#{type}\", #{prefix}#{name}, :#{search_mode}); end;"
|
|
146
147
|
|
|
147
148
|
@filter_templates << "@model = #{prefix}filter#{infix}#{name} if #{prefix}#{name};"
|
|
148
149
|
else
|
|
@@ -197,7 +197,7 @@ module Rokaki
|
|
|
197
197
|
# Compute key_leaf (qualified column) like other branches
|
|
198
198
|
key_leaf = keys.last ? "#{keys.last.to_s.pluralize}.#{leaf}" : leaf
|
|
199
199
|
|
|
200
|
-
if db == :sqlserver
|
|
200
|
+
if db == :sqlserver || db == :oracle
|
|
201
201
|
# Build relation base with joins
|
|
202
202
|
if join_map.empty?
|
|
203
203
|
rel_expr = "@model"
|
|
@@ -207,7 +207,11 @@ module Rokaki
|
|
|
207
207
|
rel_expr = "@model.joins(**#{join_map})"
|
|
208
208
|
end
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
if db == :sqlserver
|
|
211
|
+
filter_query = "sqlserver_like(#{rel_expr}, \"#{key_leaf}\", \"#{type.to_s.upcase}\", #{filter_name}, :#{search_mode})"
|
|
212
|
+
else
|
|
213
|
+
filter_query = "oracle_like(#{rel_expr}, \"#{key_leaf}\", \"#{type.to_s.upcase}\", #{filter_name}, :#{search_mode})"
|
|
214
|
+
end
|
|
211
215
|
else
|
|
212
216
|
query = build_like_query(
|
|
213
217
|
type: type,
|
|
@@ -218,12 +222,25 @@ module Rokaki
|
|
|
218
222
|
leaf: leaf
|
|
219
223
|
)
|
|
220
224
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
# Compose filter_query based on adapter; for generic adapters use generic_like to support array values
|
|
226
|
+
if db == :postgres || db == :mysql
|
|
227
|
+
if join_map.empty?
|
|
228
|
+
filter_query = "@model.#{query}"
|
|
229
|
+
elsif join_map.is_a?(Array)
|
|
230
|
+
filter_query = "@model.joins(*#{join_map}).#{query}"
|
|
231
|
+
else
|
|
232
|
+
filter_query = "@model.joins(**#{join_map}).#{query}"
|
|
233
|
+
end
|
|
225
234
|
else
|
|
226
|
-
|
|
235
|
+
# Generic (e.g., SQLite)
|
|
236
|
+
if join_map.empty?
|
|
237
|
+
rel_expr = "@model"
|
|
238
|
+
elsif join_map.is_a?(Array)
|
|
239
|
+
rel_expr = "@model.joins(*#{join_map})"
|
|
240
|
+
else
|
|
241
|
+
rel_expr = "@model.joins(**#{join_map})"
|
|
242
|
+
end
|
|
243
|
+
filter_query = "generic_like(#{rel_expr}, \"#{key_leaf}\", \"#{type.to_s.upcase}\", #{filter_name}, :#{search_mode})"
|
|
227
244
|
end
|
|
228
245
|
end
|
|
229
246
|
|
|
@@ -243,17 +260,9 @@ module Rokaki
|
|
|
243
260
|
elsif db == :mysql
|
|
244
261
|
query = "where(\"#{key_leaf} #{type.to_s.upcase} :query\", "
|
|
245
262
|
query += "query: prepare_regex_terms(#{filter}, :#{search_mode}))"
|
|
246
|
-
else # :
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
query += "query: \"%\#{#{filter}}%\")"
|
|
250
|
-
elsif search_mode == :prefix
|
|
251
|
-
query += "query: \"%\#{#{filter}}\")"
|
|
252
|
-
elsif search_mode == :suffix
|
|
253
|
-
query += "query: \"\#{#{filter}}%\")"
|
|
254
|
-
else
|
|
255
|
-
query += "query: \"%\#{#{filter}}%\")"
|
|
256
|
-
end
|
|
263
|
+
else # generic adapters (e.g., SQLite): delegate to generic_like to support array values via OR
|
|
264
|
+
# We return a marker here; the caller (build_query) will assemble the full expression with joins
|
|
265
|
+
query = nil
|
|
257
266
|
end
|
|
258
267
|
|
|
259
268
|
query
|
data/lib/rokaki/filter_model.rb
CHANGED
|
@@ -69,6 +69,45 @@ module Rokaki
|
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
# Compose an Oracle LIKE relation supporting arrays of terms and case-insensitive path via UPPER()
|
|
73
|
+
# type_signal: 'LIKE' for case-sensitive semantics; 'ILIKE' to indicate case-insensitive (we will translate)
|
|
74
|
+
def oracle_like(model, column, type_signal, value, mode)
|
|
75
|
+
terms = prepare_like_terms(value, mode)
|
|
76
|
+
ci = (type_signal.to_s.upcase == 'ILIKE')
|
|
77
|
+
col_expr = ci ? "UPPER(#{column})" : column
|
|
78
|
+
build_term = proc { |t| ci ? t.to_s.upcase : t }
|
|
79
|
+
|
|
80
|
+
if terms.is_a?(Array)
|
|
81
|
+
return model.none if terms.empty?
|
|
82
|
+
first = build_term.call(terms[0])
|
|
83
|
+
rel = model.where("#{col_expr} LIKE :q0 ESCAPE '\\'", q0: first)
|
|
84
|
+
terms[1..-1]&.each_with_index do |t, i|
|
|
85
|
+
rel = rel.or(model.where("#{col_expr} LIKE :q#{i + 1} ESCAPE '\\'", "q#{i + 1}".to_sym => build_term.call(t)))
|
|
86
|
+
end
|
|
87
|
+
rel
|
|
88
|
+
else
|
|
89
|
+
model.where("#{col_expr} LIKE :q ESCAPE '\\'", q: build_term.call(terms))
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Compose a generic LIKE relation supporting arrays of terms (OR chained)
|
|
94
|
+
# Used for adapters without special handling (e.g., SQLite)
|
|
95
|
+
def generic_like(model, column, type, value, mode)
|
|
96
|
+
terms = prepare_terms(value, mode)
|
|
97
|
+
return model.none if terms.nil?
|
|
98
|
+
if terms.is_a?(Array)
|
|
99
|
+
return model.none if terms.empty?
|
|
100
|
+
rel = model.where("#{column} #{type} :q0", q0: terms[0])
|
|
101
|
+
terms[1..-1]&.each_with_index do |t, i|
|
|
102
|
+
rel = rel.or(model.where("#{column} #{type} :q#{i + 1}", "q#{i + 1}".to_sym => t))
|
|
103
|
+
end
|
|
104
|
+
rel
|
|
105
|
+
else
|
|
106
|
+
# prepare_terms returns arrays for scalar input, so this branch is rarely used
|
|
107
|
+
model.where("#{column} #{type} :q", q: terms)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
72
111
|
def prepare_regex_terms(param, mode)
|
|
73
112
|
if Array === param
|
|
74
113
|
param_map = param.map { |term| ".*#{term}.*" } if mode == :circumfix
|
|
@@ -327,6 +366,8 @@ module Rokaki
|
|
|
327
366
|
'REGEXP'
|
|
328
367
|
elsif @_filter_db == :sqlserver
|
|
329
368
|
'LIKE'
|
|
369
|
+
elsif @_filter_db == :oracle
|
|
370
|
+
'LIKE'
|
|
330
371
|
else
|
|
331
372
|
'LIKE'
|
|
332
373
|
end
|
|
@@ -340,6 +381,9 @@ module Rokaki
|
|
|
340
381
|
'REGEXP'
|
|
341
382
|
elsif @_filter_db == :sqlserver
|
|
342
383
|
'LIKE'
|
|
384
|
+
elsif @_filter_db == :oracle
|
|
385
|
+
# Use 'ILIKE' as a signal for case-insensitive; oracle_like will translate to UPPER(column) LIKE UPPER(:q)
|
|
386
|
+
'ILIKE'
|
|
343
387
|
else
|
|
344
388
|
'LIKE'
|
|
345
389
|
end
|
data/lib/rokaki/filterable.rb
CHANGED
|
@@ -77,7 +77,7 @@ module Rokaki
|
|
|
77
77
|
@filter_key_prefix ||= prefix
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
def filter_key_infix(infix = :
|
|
80
|
+
def filter_key_infix(infix = :__)
|
|
81
81
|
@filter_key_infix ||= infix
|
|
82
82
|
end
|
|
83
83
|
|
|
@@ -170,6 +170,8 @@ module Rokaki
|
|
|
170
170
|
# Enhance `filters` to support block mode accumulation
|
|
171
171
|
def filters(*filter_keys)
|
|
172
172
|
if instance_variable_defined?(:@__in_filterable_block) && @__in_filterable_block
|
|
173
|
+
@__block_filters ||= []
|
|
174
|
+
@__ctx_stack ||= []
|
|
173
175
|
filter_keys.each do |fk|
|
|
174
176
|
@__block_filters << wrap_in_context(fk)
|
|
175
177
|
end
|
data/lib/rokaki/version.rb
CHANGED
data/rokaki.gemspec
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
lib = File.expand_path('lib', __dir__)
|
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
|
|
|
32
32
|
|
|
33
33
|
spec.add_dependency 'activesupport'
|
|
34
34
|
|
|
35
|
-
spec.add_development_dependency 'activerecord'
|
|
35
|
+
spec.add_development_dependency 'activerecord', '>= 7.1', '< 9.0'
|
|
36
36
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
37
37
|
spec.add_development_dependency 'pry'
|
|
38
38
|
spec.add_development_dependency 'pry-byebug'
|
|
@@ -50,5 +50,8 @@ Gem::Specification.new do |spec|
|
|
|
50
50
|
# For SQL Server testing
|
|
51
51
|
spec.add_development_dependency 'tiny_tds'
|
|
52
52
|
spec.add_development_dependency 'activerecord-sqlserver-adapter'
|
|
53
|
+
# For Oracle testing (optional). Enable by setting WITH_ORACLE=1 before bundling.
|
|
54
|
+
spec.add_development_dependency 'ruby-oci8'
|
|
55
|
+
spec.add_development_dependency 'activerecord-oracle_enhanced-adapter', '~> 8.0.0'
|
|
53
56
|
|
|
54
57
|
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.
|
|
4
|
+
version: 0.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Steve Martin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -30,14 +30,20 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '7.1'
|
|
34
|
+
- - "<"
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '9.0'
|
|
34
37
|
type: :development
|
|
35
38
|
prerelease: false
|
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
40
|
requirements:
|
|
38
41
|
- - ">="
|
|
39
42
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
43
|
+
version: '7.1'
|
|
44
|
+
- - "<"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '9.0'
|
|
41
47
|
- !ruby/object:Gem::Dependency
|
|
42
48
|
name: bundler
|
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -234,6 +240,34 @@ dependencies:
|
|
|
234
240
|
- - ">="
|
|
235
241
|
- !ruby/object:Gem::Version
|
|
236
242
|
version: '0'
|
|
243
|
+
- !ruby/object:Gem::Dependency
|
|
244
|
+
name: ruby-oci8
|
|
245
|
+
requirement: !ruby/object:Gem::Requirement
|
|
246
|
+
requirements:
|
|
247
|
+
- - ">="
|
|
248
|
+
- !ruby/object:Gem::Version
|
|
249
|
+
version: '0'
|
|
250
|
+
type: :development
|
|
251
|
+
prerelease: false
|
|
252
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
253
|
+
requirements:
|
|
254
|
+
- - ">="
|
|
255
|
+
- !ruby/object:Gem::Version
|
|
256
|
+
version: '0'
|
|
257
|
+
- !ruby/object:Gem::Dependency
|
|
258
|
+
name: activerecord-oracle_enhanced-adapter
|
|
259
|
+
requirement: !ruby/object:Gem::Requirement
|
|
260
|
+
requirements:
|
|
261
|
+
- - "~>"
|
|
262
|
+
- !ruby/object:Gem::Version
|
|
263
|
+
version: 8.0.0
|
|
264
|
+
type: :development
|
|
265
|
+
prerelease: false
|
|
266
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
267
|
+
requirements:
|
|
268
|
+
- - "~>"
|
|
269
|
+
- !ruby/object:Gem::Version
|
|
270
|
+
version: 8.0.0
|
|
237
271
|
description: A dsl for filtering data in web requests
|
|
238
272
|
email:
|
|
239
273
|
- steve@martian.media
|
|
@@ -253,6 +287,7 @@ files:
|
|
|
253
287
|
- Gemfile.lock
|
|
254
288
|
- Guardfile
|
|
255
289
|
- LICENSE.txt
|
|
290
|
+
- README.legacy.md
|
|
256
291
|
- README.md
|
|
257
292
|
- Rakefile
|
|
258
293
|
- bin/console
|