rokaki 0.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed3b16c71be49df063699af31b64190d9aa2db724bd5f6310e458aa33f63025a
4
- data.tar.gz: c81607decccf33f1e9a71b1e68c3bd478428b22a1f375f9e5554b39ddfc0d8d6
3
+ metadata.gz: e754206bd6927db93fa0977aed409762b9bc9aa2a663c5b0d94977c2d18ba284
4
+ data.tar.gz: 9df4b1d7d067be43ac1df89f1b5962b2950b91e6bc5e915817cde2ef625f55f0
5
5
  SHA512:
6
- metadata.gz: 18d022ef801a2a978f01d1f142c34fb95171131d30f8f8cbe8837a60fc9ea44c162d48e9f672380a965f578de65b568124686399974918497141e0eca549b29a
7
- data.tar.gz: 77f547a998d588d4128f6df533ae0e555c8561b402e496b53b66c7daee87e42a7ed3b2152ef5101930abd568ee2b0b268aeb71bb75d841feabb102238d776f49
6
+ metadata.gz: bf35a2999e21604de1d4daa615042036b0f16b651e251f569b4a13706d9e0e0c17022d0856303dfa71183b2c77af03631e3cae83e3b4cb790c855d47980ccc1c
7
+ data.tar.gz: ad6b7c1e51bfee821d54923ccf6447ed91b0633bc084d0cd0adbd6a901d36200025334b3415c53814f62c0cf644bbb8e5a6e1a8418c1c8bac9cdf5add470e8f8
@@ -50,7 +50,7 @@ jobs:
50
50
  - name: Install system dependencies
51
51
  run: |
52
52
  sudo apt-get update
53
- sudo apt-get install -y build-essential libpq-dev default-libmysqlclient-dev freetds-dev netcat-openbsd unzip curl libaio1t64 libaio-dev
53
+ sudo apt-get install -y build-essential libpq-dev default-libmysqlclient-dev freetds-dev netcat-openbsd unzip curl libaio1t64 libaio-dev libsqlite3-dev
54
54
  sudo ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.1
55
55
 
56
56
  - name: Set up Ruby
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ### 0.15.0 — 2025-10-27
2
+ - Add first-class SQLite support: adapter-aware LIKE behavior with OR expansion for arrays.
3
+ - Added SQLite badge in README.
4
+ - Updated documentation: adapters, configuration, index and usage; noted SQLite default in-memory config and env override `SQLITE_DATABASE`.
5
+ - Internal: introduced `generic_like` helper for generic adapters (used by SQLite); no breaking changes for other adapters.
6
+
1
7
  ### 0.13.0 — 2025-10-25
2
8
  - Add block-form DSL parity across both FilterModel and Filterable (`filter_map do ... end` with `like`, `ilike`, `nested`, and `filters`).
3
9
  - Support circumfix affix synonyms: `:parafix`, `:confix`, `:ambifix` (treated as `:circumfix`).
@@ -15,3 +21,10 @@
15
21
 
16
22
  ### 0.10.0 and earlier
17
23
  - Core DSL: Filterable and FilterModel modes, LIKE matching with prefix/suffix/circumfix, nested filters, and adapter-aware SQL for Postgres/MySQL.
24
+
25
+ ### 0.14.1 — 2025-10-25
26
+ - Oracle enabled by default in tests and CI; added Oracle service and Instant Client install in GitHub Actions (Ubuntu 24.04: use libaio1t64, Instant Client ZIP install).
27
+ - Removed `ORACLE_ENABLED` flag from runner; Oracle suite runs in `./spec/ordered_run.sh` by default.
28
+ - ActiveRecord 8 support: relaxed dev dependency to `>= 7.1, < 9.0`.
29
+ - Added dynamic runtime listener tests (anonymous classes) across all adapters and documentation section.
30
+ - Simplified README with concise overview and links to GitHub Pages; moved legacy content to `README.legacy.md`.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rokaki (0.14.0)
4
+ rokaki (0.15.0)
5
5
  activesupport
6
6
 
7
7
  GEM
data/README.legacy.md ADDED
@@ -0,0 +1,533 @@
1
+ Rokaki is a small DSL for building safe, composable filters for ActiveRecord queries — without writing SQL. It maps incoming params to predicates on models and associations and works across PostgreSQL, MySQL, SQL Server, and Oracle.
2
+
3
+ - Works with ActiveRecord 7.1 and 8.x
4
+ - LIKE modes: `:prefix`, `:suffix`, `:circumfix` (+ synonyms) and array‑of‑terms
5
+ - Nested filters with auto‑joins and qualified columns
6
+ - Block‑form DSL (`filter_map do ... end`) and classic argument form
7
+ - Runtime usage: build an anonymous filter class from a payload (no predeclared class needed)
8
+
9
+ Install
10
+ ```ruby
11
+ gem "rokaki"
12
+ ```
13
+
14
+ Docs
15
+ - Usage and examples: https://tevio.github.io/rokaki/usage
16
+ - Adapters: https://tevio.github.io/rokaki/adapters
17
+ - Configuration: https://tevio.github.io/rokaki/configuration
18
+
19
+ Tip: For a dynamic runtime listener (build a filter class from a JSON/hash payload at runtime), see “Dynamic runtime listener” in the Usage docs.
20
+
21
+ ---
22
+
23
+ # Rokaki
24
+
25
+ [![Gem Version](https://badge.fury.io/rb/rokaki.svg)](https://badge.fury.io/rb/rokaki)
26
+ [![Run RSpec tests](https://github.com/tevio/rokaki/actions/workflows/spec.yml/badge.svg)](https://github.com/tevio/rokaki/actions/workflows/spec.yml)
27
+
28
+ Rokaki is a small DSL for building safe, composable filters for ActiveRecord queries — without writing SQL. It maps incoming params to predicates on models and associations and works across PostgreSQL, MySQL, SQL Server, and Oracle.
29
+
30
+ - Works with ActiveRecord 7.1 and 8.x
31
+ - LIKE modes: `:prefix`, `:suffix`, `:circumfix` (+ synonyms) and array‑of‑terms
32
+ - Nested filters with auto‑joins and qualified columns
33
+ - Block‑form DSL (`filter_map do ... end`) and classic argument form
34
+ - Runtime usage: build an anonymous filter class from a payload (no predeclared class needed)
35
+
36
+ Install
37
+ ```ruby
38
+ gem "rokaki"
39
+ ```
40
+
41
+ Minimal example
42
+ ```ruby
43
+ class ArticleQuery
44
+ include Rokaki::FilterModel
45
+ filter_model :article, db: :postgres
46
+ define_query_key :q
47
+ filter_map do
48
+ like title: :circumfix
49
+ nested :author do
50
+ like first_name: :prefix
51
+ end
52
+ end
53
+ attr_accessor :filters
54
+ def initialize(filters: {}) ; @filters = filters ; end
55
+ end
56
+
57
+ ArticleQuery.new(filters: { q: ["Kavya", "Mateo"] }).results
58
+ ```
59
+
60
+ Docs
61
+ - Usage and examples: https://tevio.github.io/rokaki/usage
62
+ - Adapters: https://tevio.github.io/rokaki/adapters
63
+ - Configuration: https://tevio.github.io/rokaki/configuration
64
+
65
+ Tip: For a dynamic runtime listener (build a filter class from a JSON/hash payload at runtime), see “Dynamic runtime listener” in the Usage docs.
66
+
67
+ ---
68
+
69
+ ## Further reading
70
+
71
+ - Usage and examples: https://tevio.github.io/rokaki/usage
72
+ - Database adapters: https://tevio.github.io/rokaki/adapters
73
+ - Configuration and environment variables: https://tevio.github.io/rokaki/configuration
74
+
75
+ Changelog: see `CHANGELOG.md`.
76
+
77
+ The overall vision is to abstract away all of the lower level repetitive SQL and relational code to allow you to write model filters in a simple, relatively intuitive way, using ruby hashes and arrays mostly.
78
+
79
+ The DSL allows you to construct complex search models to filter results through without writing any SQL. I would recommend the reader to consult the specs in order to understand the features and syntax in detail, an intermediate understanding of Ruby and rspec TDD, and basic relational logic are recommended.
80
+
81
+ There are two modes of use, `Filterable` (designed for plain Ruby) and `FilterModel` (designed for Rails) that can be activated through the use of two mixins respectively, `include Rokaki::Filterable` or `include Rokaki::FilterModel`.
82
+ ## Installation
83
+
84
+ Add this line to your application's Gemfile:
85
+
86
+ You can install from Rubygems:
87
+ ```
88
+ gem 'rokaki'
89
+ ```
90
+ Or from github
91
+
92
+ ```ruby
93
+ gem 'rokaki', git: 'https://github.com/tevio/rokaki.git'
94
+ ```
95
+
96
+ And then execute:
97
+
98
+ $ bundle
99
+
100
+ ## `Rokaki::Filterable` - Usage
101
+
102
+ To use the DSL first include the `Rokaki::Filterable` module in your [por](http://blog.jayfields.com/2007/10/ruby-poro.html) class.
103
+
104
+ ### `#define_filter_keys`
105
+ #### A Simple Example
106
+
107
+ A simple example might be:-
108
+
109
+ ```ruby
110
+ class FilterArticles
111
+ include Rokaki::Filterable
112
+
113
+ def initialize(filters:)
114
+ @filters = filters
115
+ @articles = Article
116
+ end
117
+
118
+ attr_accessor :filters
119
+
120
+ define_filter_keys :date, author: [:first_name, :last_name]
121
+
122
+ def filter_results
123
+ @articles = @articles.where(date: date) if date
124
+ @articles = @articles.joins(:author).where(authors: { first_name: author_first_name }) if author_first_name
125
+ @articles = @articles.joins(:author).where(authors: { last_name: author_last_name }) if author_last_name
126
+ end
127
+ end
128
+
129
+ article_filter = FilterArticles.new(filters: {
130
+ date: '10-10-10',
131
+ author: {
132
+ first_name: 'Steve',
133
+ last_name: 'Martin'
134
+ }})
135
+ article_filter.author_first_name == 'Steve'
136
+ article_filter.author_last_name == 'Martin'
137
+ article_filter.date == '10-10-10'
138
+ ```
139
+
140
+ In this example Rokaki maps the "flat" attribute "keys" `date`, `author_first_name` and `author_last_name` to a `@filters` object with the expected deep structure `{ date: '10-10-10', author: { first_name: 'Steve' } }`, to make it simple to use them in filter queries.
141
+
142
+ #### A More Complex Example
143
+
144
+ ```ruby
145
+ class AdvancedFilterable
146
+ include Rokaki::Filterable
147
+
148
+ def initialize(filters:)
149
+ @fyltrz = filters
150
+ end
151
+ attr_accessor :fyltrz
152
+
153
+ filterable_object_name :fyltrz
154
+ filter_key_infix :__
155
+ define_filter_keys :basic, advanced: {
156
+ filter_key_1: [:filter_key_2, { filter_key_3: :deep_node }],
157
+ filter_key_4: :deep_leaf_array
158
+ }
159
+ end
160
+
161
+
162
+ advanced_filterable = AdvancedFilterable.new(filters: {
163
+ basic: 'ABC',
164
+ advanced: {
165
+ filter_key_1: {
166
+ filter_key_2: '123',
167
+ filter_key_3: { deep_node: 'NODE' }
168
+ },
169
+ filter_key_4: { deep_leaf_array: [1,2,3,4] }
170
+ }
171
+ })
172
+
173
+ advanced_filterable.advanced__filter_key_4__deep_leaf_array == [1,2,3,4]
174
+ advanced_filterable.advanced__filter_key_1__filter_key_3__deep_node == 'NODE'
175
+ ```
176
+ ### `#define_filter_map`
177
+ The define_filter_map method is more suited to classic "search", where you might want to search multiple fields on a model or across a graph. See the section on [filter_map](https://github.com/tevio/rokaki#2-the-filter_map-command-syntax) with OR for more on this kind of application.
178
+
179
+ This method takes a single field in the passed in filters hash and maps it to fields named in the second param, this is useful if you want to search for a single value across many different fields or associated tables simultaneously.
180
+
181
+ #### A Simple Example
182
+ ```ruby
183
+ class FilterMap
184
+ include Rokaki::Filterable
185
+
186
+ def initialize(fylterz:)
187
+ @fylterz = fylterz
188
+ end
189
+ attr_accessor :fylterz
190
+
191
+ filterable_object_name :fylterz
192
+ define_filter_map :query, :mapped_a, association: :field
193
+ end
194
+
195
+ filter_map = FilterMap.new(fylterz: { query: 'H2O' })
196
+
197
+ filter_map.mapped_a == 'H2O'
198
+ filter_map.association_field = 'H2O'
199
+ ```
200
+
201
+ #### Additional `Filterable` options
202
+ You can specify several configuration options, for example a `filter_key_prefix` and a `filter_key_infix` to change the structure of the generated filter accessors.
203
+
204
+ `filter_key_prefix :__` would result in key accessors like `__author_first_name`
205
+
206
+ `filter_key_infix :__` would result in key accessors like `author__first_name`
207
+
208
+ `filterable_object_name :fyltrz` would use an internal filter state object named `@fyltrz` instead of the default `@filters`
209
+
210
+
211
+ ## `Rokaki::FilterModel` - Usage
212
+
213
+ ### ActiveRecord
214
+ Include `Rokaki::FilterModel` in any ActiveRecord model (only AR >= 8.0.3 tested so far) you can generate the filter keys and the actual filter lookup code using the `filters` keyword on a model like so:-
215
+
216
+ ```ruby
217
+ # Given the models
218
+ class Author < ActiveRecord::Base
219
+ has_many :articles, inverse_of: :author
220
+ end
221
+
222
+ class Article < ActiveRecord::Base
223
+ belongs_to :author, inverse_of: :articles, required: true
224
+ end
225
+
226
+
227
+ class ArticleFilter
228
+ include Rokaki::FilterModel
229
+
230
+ filters :date, :title, author: [:first_name, :last_name]
231
+
232
+ attr_accessor :filters
233
+
234
+ def initialize(filters:, model: Article)
235
+ @filters = filters
236
+ @model = model
237
+ end
238
+ end
239
+
240
+ filter = ArticleFilter.new(filters: params[:filters])
241
+
242
+ filtered_results = filter.results
243
+
244
+ ```
245
+ ### Arrays of params
246
+ You can also filter collections of fields, simply pass an array of filter values instead of a single value, eg:- `{ date: '10-10-10', author: { first_name: ['Author1', 'Author2'] } }`.
247
+
248
+
249
+ ### Partial matching
250
+ You can use `like` or the case insensitive `ilike` to perform a partial match on a specific field, there are 3 options:- `:prefix`, `:circumfix` and `:suffix`. There are two syntaxes you can use for this:-
251
+
252
+ #### 1. The `filter` command syntax
253
+
254
+
255
+ ```ruby
256
+ class ArticleFilter
257
+ include Rokaki::FilterModel
258
+
259
+ filter :article,
260
+ like: { # you can use ilike here instead if you want case insensitive results
261
+ author: {
262
+ first_name: :circumfix,
263
+ last_name: :circumfix
264
+ }
265
+ }
266
+
267
+ attr_accessor :filters
268
+
269
+ def initialize(filters:)
270
+ @filters = filters
271
+ end
272
+ end
273
+ ```
274
+ Or
275
+
276
+ #### 2. The `filter_map` command syntax
277
+ `filter_map` takes the model name, then a single 'query' field and maps it to fields named in the options, this is useful if you want to search for a single value across many different fields or associated tables simultaneously. (builds on `define_filter_map`)
278
+
279
+
280
+ ```ruby
281
+ class AuthorFilter
282
+ include Rokaki::FilterModel
283
+
284
+ filter_map :author, :query,
285
+ like: {
286
+ articles: {
287
+ title: :circumfix,
288
+ reviews: {
289
+ title: :circumfix
290
+ }
291
+ },
292
+ }
293
+
294
+ attr_accessor :filters, :model
295
+
296
+ def initialize(filters:)
297
+ @filters = filters
298
+ end
299
+ end
300
+
301
+ filters = { query: "Jiddu" }
302
+ filtered_authors = AuthorFilter.new(filters: filters).results
303
+ ```
304
+
305
+ 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.
306
+
307
+ 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:-
308
+
309
+
310
+ ```ruby
311
+ class AuthorFilter
312
+ include Rokaki::FilterModel
313
+
314
+ filter_map :author, :query,
315
+ like: {
316
+ articles: {
317
+ title: :circumfix,
318
+ or: { # the or is aware of the join and will generate a compound join aware or query
319
+ reviews: {
320
+ title: :circumfix
321
+ }
322
+ }
323
+ },
324
+ }
325
+
326
+ attr_accessor :filters, :model
327
+
328
+ def initialize(filters:)
329
+ @filters = filters
330
+ end
331
+ end
332
+
333
+ filters = { query: "Lao" }
334
+ filtered_authors = AuthorFilter.new(filters: filters).results
335
+ ```
336
+
337
+ ## CAVEATS
338
+ Active record OR over a join may require you to add something like the following in an initializer in order for it to function properly:-
339
+
340
+ ### #structurally_incompatible_values_for_or
341
+
342
+ ``` ruby
343
+ module ActiveRecord
344
+ module QueryMethods
345
+ def structurally_incompatible_values_for_or(other)
346
+ Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other send("#{m}_value") } +
347
+ (Relation::MULTI_VALUE_METHODS - [:joins, :eager_load, :references, :extending]).reject { |m| send("#{m}_values") == other send("#{m}_values") } +
348
+ (Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other send("#{m}_clause") }
349
+ end
350
+ end
351
+ end
352
+ ```
353
+
354
+ ### A has one relation to a model called Or
355
+ If you happen to have a model/table named 'Or' then you can override the `or:` key syntax by specifying a special `or_key`:-
356
+
357
+ ```ruby
358
+ class AuthorFilter
359
+ include Rokaki::FilterModel
360
+
361
+ or_key :my_or
362
+ filter_map :author, :query,
363
+ like: {
364
+ articles: {
365
+ title: :circumfix,
366
+ my_or: { # the or is aware of the join and will generate a compound join aware or query
367
+ or: { # The Or model has a title field
368
+ title: :circumfix
369
+ }
370
+ }
371
+ },
372
+ }
373
+
374
+ attr_accessor :filters, :model
375
+
376
+ def initialize(filters:)
377
+ @filters = filters
378
+ end
379
+ end
380
+
381
+ filters = { query: "Syntaxes" }
382
+ filtered_authors = AuthorFilter.new(filters: filters).results
383
+ ```
384
+
385
+
386
+ See [this issue](https://github.com/rails/rails/issues/24055) for details.
387
+
388
+
389
+ #### 3. The porcelain command syntax
390
+
391
+ 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`
392
+
393
+
394
+ ```ruby
395
+ class ArticleFilter
396
+ include Rokaki::FilterModel
397
+
398
+ filters :date, :title, author: [:first_name, :last_name]
399
+ like title: :circumfix
400
+ # ilike title: :circumfix # case insensitive mode
401
+
402
+ attr_accessor :filters
403
+
404
+ def initialize(filters:, model: Article)
405
+ @filters = filters
406
+ @model = model
407
+ end
408
+ end
409
+ ```
410
+
411
+ Or without the model in the initializer
412
+
413
+ ```ruby
414
+ class ArticleFilter
415
+ include Rokaki::FilterModel
416
+
417
+ filters :date, :title, author: [:first_name, :last_name]
418
+ like title: :circumfix
419
+ filter_model :article
420
+
421
+ attr_accessor :filters
422
+
423
+ def initialize(filters:)
424
+ @filters = filters
425
+ end
426
+ end
427
+ ```
428
+
429
+ Would produce a query with a LIKE which circumfixes '%' around the filter term, like:-
430
+
431
+ ```ruby
432
+ @model = @model.where('title LIKE :query', query: "%#{title}%")
433
+ ```
434
+
435
+ ### Deep nesting
436
+ You can filter joins both with basic matching and partial matching
437
+ ```ruby
438
+ class ArticleFilter
439
+ include Rokaki::FilterModel
440
+
441
+ filter :author,
442
+ like: {
443
+ articles: {
444
+ reviews: {
445
+ title: :circumfix
446
+ }
447
+ },
448
+ }
449
+
450
+ attr_accessor :filters
451
+
452
+ def initialize(filters:)
453
+ @filters = filters
454
+ end
455
+ end
456
+ ```
457
+
458
+ ### Array params
459
+ You can pass array params (and partially match them), to filters (search multiple matches) in databases that support it (postgres) by passing the `db` param to the filter keyword, and passing an array of search terms at runtine
460
+
461
+ ```ruby
462
+ class ArticleFilter
463
+ include Rokaki::FilterModel
464
+
465
+ filter :article,
466
+ like: {
467
+ author: {
468
+ first_name: :circumfix,
469
+ last_name: :circumfix
470
+ }
471
+ },
472
+ match: %i[title created_at],
473
+ db: :postgres
474
+
475
+ attr_accessor :filters
476
+
477
+ def initialize(filters:)
478
+ @filters = filters
479
+ end
480
+ end
481
+
482
+ filterable = ArticleFilter.new(filters:
483
+ {
484
+ author: {
485
+ first_name: ['Match One', 'Match Two']
486
+ }
487
+ }
488
+ )
489
+
490
+ filterable.results
491
+ ```
492
+
493
+
494
+ ## Development
495
+
496
+ ### Ruby setup
497
+ After checking out the repo, run `bin/setup` to install dependencies.
498
+
499
+ ### Setting up the test databases
500
+
501
+ #### Postgres
502
+ ```
503
+ docker pull postgres
504
+ docker run --name rokaki-postgres -e POSTGRES_USER=rokaki -e POSTGRES_PASSWORD=rokaki -d -p 5432:5432 postgres
505
+ ```
506
+
507
+ #### Mysql
508
+ ```
509
+ docker pull mysql
510
+ docker run --name rokaki-mysql -e MYSQL_ROOT_PASSWORD=rokaki -e MYSQL_PASSWORD=rokaki -e MYSQL_DATABASE=rokaki -e MYSQL_USER=rokaki -d -p 3306:3306 mysql:latest mysqld
511
+ ```
512
+
513
+ ### Specialised test runner
514
+ The test suite is designed to run against all supported database backends, since this can cause problems with timing and connection pools, the recommended way to run the tests is via a shell script.
515
+
516
+ From the rokaki root directory run: `./spec/ordered_run.sh`. This is the same script that runs on the Github CI here: [![Run RSpec tests](https://github.com/tevio/rokaki/actions/workflows/spec.yml/badge.svg)](https://github.com/tevio/rokaki/actions/workflows/spec.yml)
517
+
518
+ ### Standard test runner (only recommended for development cycles)
519
+ You can still run `rake spec` to run the tests, there's no guarantee they will all pass due to race conditions from using multiple db backends (see above), but this mode is recommended for focusing on specific backends or tests during development (comment out what you don't want). You can also run `bin/console` for an interactive prompt that will allow you to experiment.
520
+
521
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
522
+
523
+ ## Contributing
524
+
525
+ Bug reports and pull requests are welcome on GitHub at https://github.com/tevio/rokaki. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
526
+
527
+ ## License
528
+
529
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
530
+
531
+ ## Code of Conduct
532
+
533
+ Everyone interacting in the Rokaki project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/tevio/rokaki/blob/master/CODE_OF_CONDUCT.md).