rokaki 0.11.0 → 0.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8fb8c022307112af51183df513130d6ad467142320a4db9be178870583c2394
4
- data.tar.gz: 611539f7d9500f30c5a847a89a7548119c14d4eac47761e9c08b5e706ae9f77c
3
+ metadata.gz: dcbf82920cbfeff8466f6ccd583f1a66652082e087c2d6641806be8720dd8d6c
4
+ data.tar.gz: 4165d387e1fba3820fc302ef414f0019077067a1dca7d307943030f0aec99207
5
5
  SHA512:
6
- metadata.gz: 3648caca4052f03440da996d1aed083c3ba9a29a5f14f06e9e9b2757d4d95f3356e793e22f1b1ebe0a149536a001367e6006d26d8521ce518885e32c36502130
7
- data.tar.gz: ca064a797447597677bd5b2d2e00f6a2836bc5e69341990ac7da0742ccdc41383834f7a8c9bb6c4089a37e0293786c2536727b5c79d1dabf05a4c9df20f02b77
6
+ metadata.gz: 8e5d866b175e0799ad9bff2cbb8660e8ebca48785e508223e7583452eb7e95e56c1c35f15ff8625fc456365651b82efff40636579f90469d9909f650f6ee2b1d
7
+ data.tar.gz: e974f32a7626a94fb618295ea9600e9747af73bd35986f9e843a81b4bc6168c98180ef85d150fc1ad96ff4fa4700daae5d16d75596bc2d3dd9279ba8d6a98c64
@@ -0,0 +1,31 @@
1
+ name: Build documentation (no deploy)
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, master ]
6
+ pull_request:
7
+
8
+ jobs:
9
+ build-docs:
10
+ runs-on: ubuntu-latest
11
+ env:
12
+ BUNDLE_GEMFILE: docs/Gemfile
13
+ steps:
14
+ - name: Checkout
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Setup Ruby
18
+ uses: ruby/setup-ruby@v1
19
+ with:
20
+ ruby-version: '3.3.0'
21
+ bundler-cache: true
22
+
23
+ - name: Build site
24
+ run: |
25
+ bundle exec jekyll build -s docs -d _site
26
+
27
+ - name: Upload built site artifact
28
+ uses: actions/upload-artifact@v4
29
+ with:
30
+ name: site
31
+ path: _site
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rokaki (0.11.0)
4
+ rokaki (0.12.0)
5
5
  activesupport
6
6
 
7
7
  GEM
data/docs/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ # docs/Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ # Static site generator
5
+ gem "jekyll", "~> 4.4"
6
+
7
+ # Theme used by docs/_config.yml
8
+ gem "minima", "~> 2.5"
9
+
10
+ # GitHub-flavored Markdown support
11
+ gem "kramdown-parser-gfm", "~> 1.1"
12
+
13
+ # Ruby 3+ local serving requires webrick
14
+ gem "webrick", "~> 1.8"
15
+
16
+ # Ensure compatibility with workflow expectation
17
+ gem "rake", "~> 13.3"
data/docs/_config.yml ADDED
@@ -0,0 +1,26 @@
1
+ title: Rokaki
2
+ description: A DSL for filtering data in web requests (ActiveRecord)
3
+ # For project pages (https://<user>.github.io/<repo>), set:
4
+ # url: "https://tevio.github.io"
5
+ # baseurl: "/rokaki"
6
+ # This ensures theme assets like /assets/main.css resolve under /rokaki/ on GitHub Pages.
7
+ baseurl: "/rokaki"
8
+ url: "https://tevio.github.io"
9
+ theme: minima
10
+
11
+ # Build settings
12
+ markdown: kramdown
13
+ kramdown:
14
+ input: GFM
15
+
16
+ # Exclude existing generated RDoc folder from site
17
+ exclude:
18
+ - doc/
19
+ - pkg/
20
+ - spec/
21
+ - Gemfile
22
+ - Gemfile.lock
23
+ - Rakefile
24
+ - rokaki.gemspec
25
+ - Guardfile
26
+ - README.md
data/docs/adapters.md ADDED
@@ -0,0 +1,46 @@
1
+ ---
2
+ layout: page
3
+ title: Database adapters
4
+ permalink: /adapters
5
+ ---
6
+
7
+ Rokaki generates adapter‑aware SQL for PostgreSQL, MySQL, and SQL Server.
8
+
9
+ ## Overview
10
+
11
+ - PostgreSQL
12
+ - Case‑insensitive: `ILIKE`
13
+ - Case‑sensitive: `LIKE`
14
+ - Multi‑term: `ANY (ARRAY[...])`
15
+ - MySQL
16
+ - Case‑insensitive: `LIKE`
17
+ - Case‑sensitive: `LIKE BINARY`
18
+ - Nested‑like filters may use `REGEXP` where designed in the library
19
+ - SQL Server
20
+ - Uses `LIKE` with safe escaping
21
+ - Multi‑term input expands to OR‑chained predicates (e.g., `(col LIKE :q0 OR col LIKE :q1 ...)`) with `ESCAPE '\\'`
22
+ - Case sensitivity follows DB collation by default; future versions may add inline `COLLATE` options
23
+
24
+ ## LIKE modes
25
+
26
+ All adapters support the same modes, which you declare via the values in your `like` mapping (there is no `modes:` option):
27
+
28
+ - `prefix` → `%term`
29
+ - `suffix` → `term%`
30
+ - `circumfix` → `%term%` (synonyms supported: `:parafix`, `:confix`, `:ambifix`)
31
+
32
+ Example:
33
+
34
+ ```ruby
35
+ # Declare modes via like-mapping values (no block DSL)
36
+ like title: :circumfix
37
+ like author: { first_name: :prefix }
38
+ ```
39
+
40
+ When you pass an array of terms, Rokaki composes adapter‑appropriate SQL that matches any of the terms.
41
+
42
+ ## Notes on case sensitivity
43
+
44
+ - PostgreSQL: `ILIKE` is case‑insensitive; `LIKE` is case‑sensitive depending on collation/LC settings but generally treated as case‑sensitive for ASCII.
45
+ - MySQL: `LIKE` case sensitivity depends on column collation; `LIKE BINARY` forces byte comparison (case‑sensitive for ASCII).
46
+ - SQL Server: The server/database/column collation determines sensitivity. Rokaki currently defers to your DB’s default. If you need deterministic behavior regardless of DB defaults, consider using a case‑sensitive collation on the column or open an issue to discuss inline `COLLATE` options.
@@ -0,0 +1,63 @@
1
+ ---
2
+ layout: page
3
+ title: Configuration
4
+ permalink: /configuration
5
+ ---
6
+
7
+ This page covers configuration, environment overrides, and tips for running the test suite across adapters.
8
+
9
+ ## Environment variables
10
+
11
+ Rokaki's test helpers (used in the specs) support environment variable overrides for all adapters. These are useful when your local databases run on non‑default ports or hosts.
12
+
13
+ ### SQL Server
14
+ - `SQLSERVER_HOST` (default: `localhost`)
15
+ - `SQLSERVER_PORT` (default: `1433`)
16
+ - `SQLSERVER_USERNAME` (default: `sa`)
17
+ - `SQLSERVER_PASSWORD`
18
+ - `SQLSERVER_DATABASE` (default: `rokaki`)
19
+
20
+ ### MySQL
21
+ - `MYSQL_HOST` (default: `127.0.0.1`)
22
+ - `MYSQL_PORT` (default: `3306`)
23
+ - `MYSQL_USERNAME` (default: `rokaki`)
24
+ - `MYSQL_PASSWORD` (default: `rokaki`)
25
+ - `MYSQL_DATABASE` (default: `rokaki`)
26
+
27
+ ### PostgreSQL
28
+ - `POSTGRES_HOST` (default: `127.0.0.1`)
29
+ - `POSTGRES_PORT` (default: `5432`)
30
+ - `POSTGRES_USERNAME` (default: `postgres`)
31
+ - `POSTGRES_PASSWORD` (default: `postgres`)
32
+ - `POSTGRES_DATABASE` (default: `rokaki`)
33
+
34
+ ## SQL Server notes
35
+
36
+ - Rokaki uses `LIKE` with proper escaping and OR expansion for arrays of terms.
37
+ - Case sensitivity follows your database/column collation. Future versions may allow inline `COLLATE` options.
38
+
39
+ ## Running tests locally
40
+
41
+ Ensure you have Ruby (see `.ruby-version`), then install dependencies and run specs.
42
+
43
+ ```bash
44
+ bundle install
45
+ ./spec/ordered_run.sh
46
+ ```
47
+
48
+ Or run a single adapter suite, for example SQL Server:
49
+
50
+ ```bash
51
+ bundle exec rspec spec/lib/03_sqlserver_aware_spec.rb
52
+ ```
53
+
54
+ If your SQL Server listens on a different port (e.g., 1434), set an override:
55
+
56
+ ```bash
57
+ export SQLSERVER_PORT=1434
58
+ bundle exec rspec spec/lib/03_sqlserver_aware_spec.rb
59
+ ```
60
+
61
+ ## GitHub Actions
62
+
63
+ The repository includes CI that starts MySQL (9.4), PostgreSQL (13), and SQL Server (2022) services and runs the ordered spec suite. See `.github/workflows/spec.yml`.
data/docs/index.md ADDED
@@ -0,0 +1,75 @@
1
+ ---
2
+ layout: home
3
+ title: Rokaki
4
+ permalink: /
5
+ ---
6
+
7
+ Rokaki is a small Ruby library that helps you build safe, composable filters for ActiveRecord queries in web requests.
8
+
9
+ - Works with PostgreSQL, MySQL, and SQL Server
10
+ - Supports simple and nested filters
11
+ - LIKE-based matching with prefix/suffix/circumfix modes (circumfix also accepts synonyms: parafix, confix, ambifix)
12
+ - Array-of-terms matching (adapter-aware)
13
+
14
+ Get started below or jump to:
15
+ - [Usage](./usage)
16
+ - [Database adapters](./adapters)
17
+ - [Configuration](./configuration)
18
+
19
+ ## Installation
20
+
21
+ Add to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem "rokaki", "~> 0.11"
25
+ ```
26
+
27
+ Then:
28
+
29
+ ```bash
30
+ bundle install
31
+ ```
32
+
33
+ ## Quick start
34
+
35
+ Use `Rokaki::FilterModel` and declare mappings with method arguments (no block DSL).
36
+
37
+ ```ruby
38
+ class ArticleQuery
39
+ include Rokaki::FilterModel
40
+
41
+ # Tell Rokaki which model to query and which DB adapter semantics to use
42
+ filter_model :article, db: :postgres # or :mysql, :sqlserver
43
+
44
+ # Map a single query key (:q) to multiple LIKE targets on Article
45
+ define_query_key :q
46
+ like title: :circumfix, content: :circumfix
47
+
48
+ # Nested LIKEs on associated models are expressed with hashes
49
+ like author: { first_name: :prefix, last_name: :suffix }
50
+
51
+ attr_accessor :filters
52
+ def initialize(filters: {})
53
+ @filters = filters
54
+ end
55
+ end
56
+
57
+ # In a controller/service:
58
+ filtered = ArticleQuery.new(filters: params).results
59
+ ```
60
+
61
+ Where `params` can include keys like `q`, `author_first_name`, `author_last_name`, etc. The LIKE mode for each key is defined in your `like` mapping (e.g., `title: :circumfix`), and Rokaki builds the appropriate `WHERE` clauses safely and adapter‑aware.
62
+
63
+ ## Matching modes
64
+
65
+ - prefix: matches values that start with given term(s)
66
+ - suffix: matches values that end with given term(s)
67
+ - circumfix: matches values that contain given term(s)
68
+
69
+ All modes accept either a single string or an array of terms.
70
+
71
+ ## Next steps
72
+
73
+ - Learn the full DSL and examples in [Usage](./usage)
74
+ - See adapter specifics (PostgreSQL/MySQL/SQL Server) in [Database adapters](./adapters)
75
+ - Configure connections and environment variables in [Configuration](./configuration)
data/docs/usage.md ADDED
@@ -0,0 +1,100 @@
1
+ ---
2
+ layout: page
3
+ title: Usage
4
+ permalink: /usage
5
+ ---
6
+
7
+ This page shows how to use Rokaki to define filters and apply them to ActiveRecord relations.
8
+
9
+ ## Installation
10
+
11
+ Add the gem to your Gemfile and bundle:
12
+
13
+ ```ruby
14
+ gem "rokaki", "~> 0.11"
15
+ ```
16
+
17
+ ```bash
18
+ bundle install
19
+ ```
20
+
21
+ ## Basic setup
22
+
23
+ Include `Rokaki::Filterable` in models you want to filter, and define a `filter_map` with fields and nested associations.
24
+
25
+ ```ruby
26
+ class Author < ActiveRecord::Base
27
+ has_many :articles
28
+ end
29
+
30
+ class ArticleQuery
31
+ include Rokaki::FilterModel
32
+ belongs_to :author
33
+
34
+ # Choose model and adapter
35
+ filter_model :article, db: :postgres # or :mysql, :sqlserver
36
+
37
+ # Map a single query key (:q) to multiple LIKE targets
38
+ define_query_key :q
39
+ like title: :circumfix, content: :circumfix
40
+
41
+ # Nested LIKEs via hash mapping (no block DSL)
42
+ like author: { first_name: :prefix, last_name: :suffix }
43
+ end
44
+ ```
45
+
46
+ ## Applying filters
47
+
48
+ Call `Model.filter(params)` to build a relation based on supported keys.
49
+
50
+ ```ruby
51
+ params = {
52
+ title_prefix: "Intro",
53
+ q: ["ruby", "rails"],
54
+ author_last_name: "martin"
55
+ }
56
+
57
+ filtered = Article.filter(params)
58
+ # => ActiveRecord::Relation (chainable)
59
+ ```
60
+
61
+ You can keep chaining other scopes/clauses:
62
+
63
+ ```ruby
64
+ Article.filter(params).order(published: :desc).limit(20)
65
+ ```
66
+
67
+ ## LIKE modes and affix options
68
+
69
+ Declare the LIKE mode via the value in your `like` mapping (there is no `modes:` option). For example: `like title: :prefix`.
70
+
71
+ - `prefix` → matches strings that start with a term (pattern: `%term`)
72
+ - `suffix` → matches strings that end with a term (pattern: `term%`)
73
+ - `circumfix` → matches strings that contain a term (pattern: `%term%`)
74
+ - Synonyms supported: `:parafix`, `:confix`, `:ambifix` (all behave the same as `:circumfix`)
75
+
76
+ Each accepts a single string or an array of strings. Rokaki generates adapter‑aware SQL:
77
+
78
+ - PostgreSQL: `LIKE`/`ILIKE` with `ANY (ARRAY[...])`
79
+ - MySQL: `LIKE`/`LIKE BINARY` and, in nested-like contexts, `REGEXP` where designed
80
+ - SQL Server: `LIKE` with safe escaping; arrays expand into OR chains of parameterized `LIKE` predicates
81
+
82
+ ## Nested filters
83
+
84
+ Use `nested :association` to scope filters to joined tables. Rokaki handles the necessary joins and qualified columns.
85
+
86
+ ```ruby
87
+ filter_map do
88
+ nested :author do
89
+ like :first_name, key: :author_first
90
+ end
91
+ end
92
+ ```
93
+
94
+ Params would include `author_first`, `author_first_prefix`, etc.
95
+
96
+ ## Customization tips
97
+
98
+ - Use `key:` to map a filter to a different params key.
99
+ - Combine multiple filters; Rokaki composes them with `AND` by default.
100
+ - For advanced cases, write custom filters in your app by extending the DSL (see source for `BasicFilter`/`NestedFilter`).
@@ -103,6 +103,23 @@ module Rokaki
103
103
 
104
104
  private
105
105
 
106
+ def normalize_like_modes(obj)
107
+ case obj
108
+ when Hash
109
+ obj.each_with_object({}) do |(k, v), h|
110
+ h[k] = normalize_like_modes(v)
111
+ end
112
+ when Array
113
+ obj.map { |e| normalize_like_modes(e) }
114
+ when Symbol
115
+ # Treat alternative affixes as circumfix
116
+ return :circumfix if [:parafix, :confix, :ambifix].include?(obj)
117
+ obj
118
+ else
119
+ obj
120
+ end
121
+ end
122
+
106
123
  def filter_map(model, query_key, options)
107
124
  filter_model(model)
108
125
  @filter_map_query_key = query_key
@@ -259,17 +276,19 @@ module Rokaki
259
276
 
260
277
  def like(args)
261
278
  raise ArgumentError, 'argument mush be a hash' unless args.is_a? Hash
262
- @_like_semantics = (@_like_semantics || {}).merge(args)
279
+ normalized = normalize_like_modes(args)
280
+ @_like_semantics = (@_like_semantics || {}).merge(normalized)
263
281
 
264
- like_keys = LikeKeys.new(args)
282
+ like_keys = LikeKeys.new(normalized)
265
283
  like_filters(like_keys, term_type: case_sensitive)
266
284
  end
267
285
 
268
286
  def ilike(args)
269
287
  raise ArgumentError, 'argument mush be a hash' unless args.is_a? Hash
270
- @i_like_semantics = (@i_like_semantics || {}).merge(args)
288
+ normalized = normalize_like_modes(args)
289
+ @i_like_semantics = (@i_like_semantics || {}).merge(normalized)
271
290
 
272
- like_keys = LikeKeys.new(args)
291
+ like_keys = LikeKeys.new(normalized)
273
292
  like_filters(like_keys, term_type: case_insensitive)
274
293
  end
275
294
 
@@ -1,3 +1,3 @@
1
1
  module Rokaki
2
- VERSION = "0.11.0"
2
+ VERSION = "0.12.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rokaki
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Martin
@@ -242,6 +242,7 @@ extensions: []
242
242
  extra_rdoc_files: []
243
243
  files:
244
244
  - ".github/workflows/codeql-analysis.yml"
245
+ - ".github/workflows/pages.yml"
245
246
  - ".github/workflows/spec.yml"
246
247
  - ".gitignore"
247
248
  - ".rspec"
@@ -255,6 +256,12 @@ files:
255
256
  - Rakefile
256
257
  - bin/console
257
258
  - bin/setup
259
+ - docs/Gemfile
260
+ - docs/_config.yml
261
+ - docs/adapters.md
262
+ - docs/configuration.md
263
+ - docs/index.md
264
+ - docs/usage.md
258
265
  - lib/rokaki.rb
259
266
  - lib/rokaki/filter_model.rb
260
267
  - lib/rokaki/filter_model/basic_filter.rb