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.
@@ -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
- "sqlserver_like(@model.joins(#{joins}), \"#{key_leaf}\", \"#{type}\", #{prefix}#{name}, :#{search_mode}); end;"
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
- filter_query = "sqlserver_like(#{rel_expr}, \"#{key_leaf}\", \"#{type.to_s.upcase}\", #{filter_name}, :#{search_mode})"
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
- if join_map.empty?
222
- filter_query = "@model.#{query}"
223
- elsif join_map.is_a?(Array)
224
- filter_query = "@model.joins(*#{join_map}).#{query}"
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
- filter_query = "@model.joins(**#{join_map}).#{query}"
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 # :sqlserver and others
247
- query = "where(\"#{key_leaf} #{type.to_s.upcase} :query\", "
248
- if search_mode == :circumfix
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
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Rokaki
2
- VERSION = "0.13.0"
2
+ VERSION = "0.15.0"
3
3
  end
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.13.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-25 00:00:00.000000000 Z
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: '0'
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: '0'
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