ru.Bee 2.6.5 → 2.7.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.
data/readme.md CHANGED
@@ -5,25 +5,25 @@
5
5
  ![Gem](https://img.shields.io/gem/v/ru.Bee.svg)
6
6
  ![GitHub Repo stars](https://img.shields.io/github/stars/nucleom42/rubee?style=social)
7
7
 
8
- <img width="363" height="131" alt="Screen Shot 2026-01-27 at 11 19 15 PM" src="https://github.com/user-attachments/assets/f6f07b88-f58d-4f36-a857-a293e613269e" />
8
+ <img width="363" height="131" alt="ru.Bee logo" src="https://github.com/user-attachments/assets/f6f07b88-f58d-4f36-a857-a293e613269e" />
9
9
 
10
- ru.Bee is a Ruby-based web framework designed to streamline the development of modular monolith web applications. \
11
- Under the hood, it leverages the power of Ruby and Rack backed by Puma, offering a clean, efficient, and flexible architecture. \
12
- It offers a structured approach to building scalable, maintainable, and React-ready projects, \
10
+ ru.Bee is a Ruby-based web framework designed to streamline the development of modular monolith web applications.
11
+ Under the hood, it leverages the power of Ruby and Rack backed by Puma, offering a clean, efficient, and flexible architecture.
12
+ It offers a structured approach to building scalable, maintainable, and React-ready projects,
13
13
  making it an ideal choice for developers seeking a balance between monolithic simplicity and modular flexibility.
14
14
 
15
- Want to get a quick API server up and runing? You can do it for real quick!
15
+ Want to get a quick API server up and running? You can do it in no time!
16
16
  <br />
17
17
  [![Watch the demo](https://img.youtube.com/vi/ko7H70s7qq0/hqdefault.jpg)](https://www.youtube.com/watch?v=ko7H70s7qq0)
18
18
 
19
- Starting from ru.Bee 2.0.0, ru.Bee supports Websocket, which is a feature that allows you to build real-time applications with ease. \
19
+ Starting from ru.Bee 2.0.0, ru.Bee supports WebSocket, which allows you to build real-time applications with ease.
20
20
  <br />
21
21
  [![Watch the demo](https://img.youtube.com/vi/gp8IheKBNm4/hqdefault.jpg)](https://www.youtube.com/watch?v=gp8IheKBNm4)
22
22
 
23
23
  ## Production ready
24
24
 
25
- Take a look on the ru.Bee demo site with all documentation stored in there: https://rubee.dedyn.io/
26
- Want to explore how it built? https://github.com/nucleom42/rubee-site
25
+ Take a look at the ru.Bee demo site with full documentation: https://rubee.dedyn.io/
26
+ Want to explore how it was built? https://github.com/nucleom42/rubee-site
27
27
 
28
28
  ## Stress tested
29
29
 
@@ -39,31 +39,33 @@ Requests/sec: 323.78
39
39
  Transfer/sec: 140.07KB
40
40
  ```
41
41
 
42
- - Short output explanation:
42
+ Short output explanation:
43
+
43
44
  - Requests/sec: ~324
44
45
  - Average latency: ~305 ms
45
46
  - Total requests handled: 9,721
46
- - Hardware: Raspberry Pi 5(8 Gb) (single board computer)
47
+ - Hardware: Raspberry Pi 5 (8 GB) single board computer
47
48
  - Server: ru.Bee app hosted via Nginx + HTTPS
48
49
 
49
- This demonstrate ru.Bees efficient architecture and suitability for lightweight deployments — even on low-power hardware.
50
+ This demonstrates ru.Bee's efficient architecture and suitability for lightweight deployments — even on low-power hardware.
50
51
 
51
52
  ## Comparison
52
- Here below is a **short web frameworks comparison** built with Ruby, so you can evaluate your choice with ru.Bee.
53
+
54
+ Here is a short web frameworks comparison built with Ruby, so you can evaluate your choice with ru.Bee.
53
55
 
54
56
  **Disclaimer:**
55
- The comparison is based on a very generic and subjective information open in the Internet and is not a real benchmark. The comparison is aimed to give you a general idea of the differences between the frameworks and Rubee and not to compare the frameworks directly.
57
+ The comparison is based on generic and subjective information available on the internet and is not a real benchmark. It is aimed at giving you a general idea of the differences between the frameworks and is not intended as a direct comparison.
56
58
 
57
59
  | Feature / Framework | **ru.Bee** | Rails | Sinatra | Hanami | Padrino | Grape |
58
60
  |---------------------|-----------|-------|---------|--------|---------|-------|
59
61
  | **React readiness** | Built-in React integration (route generator can scaffold React components that fetch data via controllers) | React via webpacker/importmap, but indirect | No direct React support | Can integrate React | Can integrate via JS pipelines | API-focused, no React support |
60
62
  | **Routing style** | Explicit, file-based routes with clear JSON/HTML handling | DSL, routes often implicit inside controllers | Explicit DSL, inline in code | Declarative DSL | Rails-like DSL | API-oriented DSL |
61
- | **Modularity** | Lightweight core, pluggable projects | One project by default, but can be extended with repsecrive gem | Very modular (small DSL) | Designed for modularity | Semi-modular, still Rails-like | Modular (mount APIs) |
63
+ | **Modularity** | Lightweight core, pluggable projects | One project by default, but can be extended with respective gem | Very modular (small DSL) | Designed for modularity | Semi-modular, still Rails-like | Modular (mount APIs) |
62
64
  | **Startup / Load speed** | Very fast (minimal boot time, designed for modern Ruby) | Not very fast, especially on large apps | Very fast | Medium (slower than Sinatra, faster than Rails) | Similar to Rails (heavier) | Fast |
63
- | **Ecosystem** | 🌱 Early-stage, focused on modern simplicity, but easily expandable over bundler | Huge ecosystem, gems, community | Large ecosystem, many gems work | Small, growing | Small, less active | Small, niche |
65
+ | **Ecosystem** | Early-stage, focused on modern simplicity, but easily expandable via Bundler | Huge ecosystem, gems, community | Large ecosystem, many gems work | Small, growing | Small, less active | Small, niche |
64
66
  | **Learning curve** | Simple, explicit, minimal DSL | Steep (lots of conventions & magic) | Very low (DSL fits in one file) | Medium, more concepts (repositories, entities) | Similar to Rails, easier in parts | Low (API-only) |
65
67
  | **Customizability** | High (explicit over implicit, hooks & generators) | Limited without monkey-patching | Very high (you control flow) | High, modular architecture | Medium | High (designed for APIs) |
66
- | **Target use case** | Modern full-stack apps with React frontends or APIs, may be well suite if you prefer modular monolith over microservices | Large, full-stack, mature apps | Small apps, microservices | Modular apps, DDD | Rails-like but modular | APIs & microservices |
68
+ | **Target use case** | Modern full-stack apps with React frontends or APIs; well-suited if you prefer modular monolith over microservices | Large, full-stack, mature apps | Small apps, microservices | Modular apps, DDD | Rails-like but modular | APIs & microservices |
67
69
  | **Early adopters support** | Personal early adopters support via fast extending and fixing | Not available | Not known | Not known | Not known | Not known |
68
70
 
69
71
  ## Content
@@ -75,33 +77,33 @@ The comparison is based on a very generic and subjective information open in the
75
77
  - [Routing](#routing)
76
78
  - [Database](#database)
77
79
  - [Views](#views)
78
- - [Hooks](#hooks)
80
+ - [Object hooks](#object-hooks)
79
81
  - [Validations](#validations)
80
- - [JWT based authentification](#jwt-based-authentification)
81
- - [OAuth2 based authentification](#oauth-authentification)
82
- - [Rubee commands](#rubee-commands)
82
+ - [JWT based authentication](#jwt-based-authentication)
83
+ - [OAuth authentication](#oauth-authentication)
84
+ - [ru.Bee commands](#rubee-commands)
83
85
  - [Generate commands](#generate-commands)
84
86
  - [Migration commands](#migration-commands)
85
- - [Rubee console](#rubee-console)
87
+ - [ru.Bee console](#rubee-console)
86
88
  - [Rubee::Support](#rubee-support)
87
89
  - [Testing](#testing)
88
90
  - [Background jobs](#background-jobs)
89
- - [Modular](#modualar-application)
91
+ - [Modular application](#modular-application)
90
92
  - [Logger](#logger)
91
- - [Websocket](#websocket)
93
+ - [WebSocket](#websocket)
94
+ - [Bee assistant](#bee-assistant)
92
95
 
93
- You can read it on the demo: [site](https://rubee.dedyn.io/)
96
+ You can read the full docs on the demo site: [rubee.dedyn.io](https://rubee.dedyn.io/)
94
97
 
95
98
  ## Features
96
99
 
97
100
  Lightweight – A minimal footprint focused on serving Ruby applications efficiently.
98
101
  <br>
99
- Modular – A modular approach to application development. Build modular monolith app with ease by attaching
100
- as many subprojects as you need.
102
+ Modular – A modular approach to application development. Build a modular monolith app with ease by attaching as many subprojects as you need.
101
103
  <br>
102
104
  Contract-driven – Define your API contracts in a simple, declarative way, then generate all the boilerplate you need.
103
105
  <br>
104
- Fast – Optimized for speed, providing quick responses. (Everything is relative, we know! 😄)
106
+ Fast – Optimized for speed, providing quick responses.
105
107
  <br>
106
108
  Rack-powered – Built on Rack. The full Rack API is available for easy integration.
107
109
  <br>
@@ -127,7 +129,7 @@ Console – Start an interactive console and reload on the fly.
127
129
  <br>
128
130
  Background Jobs – Schedule and process background jobs using your preferred async stack.
129
131
  <br>
130
- Websocket – Serve and handle WebSocket connections.
132
+ WebSocket – Serve and handle WebSocket connections.
131
133
  <br>
132
134
  Logger – Use any logger you want.
133
135
 
@@ -145,44 +147,39 @@ rubee project my_project
145
147
  cd my_project
146
148
  ```
147
149
 
148
- [Back to content](#content)
149
-
150
150
  3. Install dependencies
151
151
 
152
- ***Prerequisites***<br />
153
- Make sure:
154
- **Ruby** language (3.1 or higher, 3.4.1 recommended) is installed
155
- **Bundler** is installed
152
+ Prerequisites: make sure **Ruby** (3.1 or higher, 3.4.1 recommended) and **Bundler** are installed.
156
153
 
157
154
  ```bash
158
155
  bundle install
159
156
  ```
160
157
 
161
- 4. Run ru.Bee server. Default port is 7000
158
+ 4. Run the ru.Bee server. Default port is 7000.
162
159
  ```bash
163
160
  rubee start # or rubee start_dev for development
164
161
 
165
- # Sarting from veriosn 1.8.0, you can also start you rubee server with yjit compiler and enjoy speed boost.
162
+ # Starting from version 1.8.0, you can also start the server with the yjit compiler for a speed boost.
166
163
  rubee start --jit=yjit
167
- # Option is available for dev environment too
164
+ # This option is available for the dev environment too.
168
165
  rubee start_dev --jit=yjit
169
166
  ```
170
167
 
171
168
  5. Open your browser and go to http://localhost:7000
172
169
 
173
170
  ## Run tests
171
+
174
172
  ```bash
175
173
  rubee test
176
- # or you can specify specific test file
174
+ # or specify a specific test file
177
175
  rubee test models/user_model_test.rb
178
- # you can run specicfc line in the test file
176
+ # or run a specific line in the test file
179
177
  rubee test models/user_model_test.rb --line=12
180
178
  ```
181
- [Back to content](#content)
182
179
 
183
180
  ## Draw contract
184
181
 
185
- 1. Add the routes to the routes.rb
182
+ 1. Add the routes to `routes.rb`
186
183
  ```ruby
187
184
  Rubee::Router.draw do |router|
188
185
  ...
@@ -201,28 +198,28 @@ rubee test models/user_model_test.rb --line=12
201
198
  end
202
199
  ```
203
200
 
204
- 2. generate the files
201
+ 2. Generate the files
205
202
  ```bash
206
- rubee generate get /apples
203
+ rubee generate get /apples
207
204
  ```
208
- This will generate the following files
205
+ This will generate the following files:
209
206
  ```bash
210
- ./app/controllers/apples_controller.rb # Controller with respective action
211
- ./app/views/apples_index.erb # ERB view that is rendered by the controller right away
212
- ./app/models/apple.rb # Model that acts as ORM
213
- ./db/create_apples.rb # Database migration file needed for creating repsective table
207
+ ./app/controllers/apples_controller.rb # Controller with respective action
208
+ ./app/views/apples_index.erb # ERB view rendered by the controller
209
+ ./app/models/apple.rb # Model that acts as ORM
210
+ ./db/create_apples.rb # Database migration file for the respective table
214
211
  ```
215
212
 
216
- 3. Run the initial db migration
213
+ 3. Run the initial database migration
217
214
  ```bash
218
- rubee db run:all
215
+ rubee db run:all
219
216
  ```
220
217
 
221
- 4. Fill the generated files with the logic you need and run the server again!
218
+ 4. Fill the generated files with the logic you need and run the server again.
222
219
 
223
- 5. You can find full snapshot of the schema in the STRUCTURE constant or in the db/structur.rb file.
220
+ 5. You can find a full snapshot of the schema in the `STRUCTURE` constant or in the `db/structure.rb` file.
224
221
 
225
- 6. You can use rubee cli for printing out lates schema, out of STRUCUTRE constant
222
+ 6. Print the latest schema from the `STRUCTURE` constant via the CLI
226
223
  ```bash
227
224
  -> rubee db schema
228
225
  --- users
@@ -232,7 +229,7 @@ This will generate the following files
232
229
 
233
230
  --- accounts
234
231
  - id, (PK), type (INTEGER)
235
- - addres, type (varchar(255))
232
+ - address, type (varchar(255))
236
233
  - user_id, type (INTEGER)
237
234
 
238
235
  --- posts
@@ -245,7 +242,8 @@ This will generate the following files
245
242
  - text, type (varchar(255))
246
243
  - user_id, type (INTEGER)
247
244
  ```
248
- 7. You can also rpint out the schema for the table you need
245
+
246
+ 7. Print the schema for a specific table
249
247
  ```bash
250
248
  -> rubee db schema posts
251
249
  --- posts
@@ -259,149 +257,145 @@ This will generate the following files
259
257
  - comment_id → comments() on delete no_action on update no_action
260
258
  - user_id → users() on delete no_action on update no_action
261
259
  ```
262
- 8. Drop all tables can be handy for development process. But be careful and make sure you pass desired environment
260
+
261
+ 8. Dropping all tables can be handy during development. Be careful and make sure you pass the desired environment.
263
262
  ```bash
264
263
  RACK_ENV=test rubee db drop_tables
265
- These tables has been dropped for test env
264
+ These tables have been dropped for the test env:
266
265
  [:companies, :company_clients, :services]
267
266
  ```
268
267
 
269
268
  [Back to content](#content)
270
269
 
271
270
  ## Model
272
- Model in ru.Bee is just simple ruby object that can be serilalized in the view
273
- in the way it required (ie json).
274
- Here below is a simple example on how it can be used by rendering json from in memory object
271
+
272
+ A model in ru.Bee is a simple Ruby object that can be serialized in the view in whatever form is required (e.g. JSON).
273
+ Here is a simple example of rendering JSON from an in-memory object:
275
274
 
276
275
  ```ruby
277
- #ApplesController
276
+ # ApplesController
278
277
 
279
- def show
280
- # In memory example
281
- apples = [Apple.new(colour: 'red', weight: '1lb'), Apple.new(colour: 'green', weight: '1lb')]
282
- apple = apples.find { |apple| apple.colour = params[:colour] }
278
+ def show
279
+ # In-memory example
280
+ apples = [Apple.new(colour: 'red', weight: '1lb'), Apple.new(colour: 'green', weight: '1lb')]
281
+ apple = apples.find { |apple| apple.colour = params[:colour] }
283
282
 
284
- response_with object: apple, type: :json
285
- end
283
+ response_with object: apple, type: :json
284
+ end
286
285
  ```
287
286
 
288
- Just make sure Serializable module included in the target class.
287
+ Make sure the `Serializable` module is included in the target class:
289
288
  ```ruby
290
- class Apple
291
- include Serializable
292
- attr_accessor :id, :colour, :weight
293
- end
289
+ class Apple
290
+ include Serializable
291
+ attr_accessor :id, :colour, :weight
292
+ end
294
293
  ```
295
294
 
296
- However, you can simply turn it to ORM object by extending database class Rubee::SequelObject.
297
- This one is already serializable and charged with hooks.
298
- ```Ruby
299
- class Apple < Rubee::SequelObject
300
- attr_accessor :id, :colour, :weight
301
- end
295
+ You can also turn it into an ORM object by extending `Rubee::SequelObject`, which is already serializable and charged with hooks:
296
+ ```ruby
297
+ class Apple < Rubee::SequelObject
298
+ attr_accessor :id, :colour, :weight
299
+ end
302
300
  ```
303
301
 
304
- So in the controller you would need to query your target object now.
302
+ In the controller, query your target object directly:
305
303
  ```ruby
306
- #ApplesController
304
+ # ApplesController
307
305
 
308
- def show
309
- apple = Apple.where(colour: params[:colour])&.last
306
+ def show
307
+ apple = Apple.where(colour: params[:colour])&.last
310
308
 
311
- if apple
312
- response_with object: apple, type: :json
313
- else
314
- response_with object: { error: "apple with colour #{params[:colour]} not found" }, status: 422, type: :json
315
- end
309
+ if apple
310
+ response_with object: apple, type: :json
311
+ else
312
+ response_with object: { error: "apple with colour #{params[:colour]} not found" }, status: 422, type: :json
316
313
  end
314
+ end
317
315
  ```
318
316
 
319
317
  [Back to content](#content)
320
318
 
321
- #### Rubee::SequelObject base methods
319
+ ### Rubee::SequelObject base methods
322
320
 
323
- Initiate new record in memory
324
- ```Ruby
321
+ Initiate a new record in memory
322
+ ```ruby
325
323
  irb(main):015> user = User.new(email: "llo@ok.com", password: 543)
326
324
  => #<User:0x000000010cda23b8 @email="llo@ok.com", @password=543>
327
325
  ```
328
326
 
329
- Save record in db
330
- ```Ruby
331
- => #<User:0x000000010cda23b8 @email="llo@ok.com", @password=543, @created="2025-09-28 22:03:07.011332 -0400", @updated="2025-09-28 22:03:07.011332 -0400">
327
+ Save a record to the database
328
+ ```ruby
332
329
  irb(main):018> user.save
333
330
  => true
334
331
  ```
335
332
 
336
- Update record with new value
337
- ```Ruby
333
+ Update a record with a new value
334
+ ```ruby
338
335
  irb(main):019> user.update(email: "update@email.com")
339
- => #<User:0x000000010c39b298 @email="update@email.com", @id=3, @password="543" @created="2025-09-28 22:03:07.011332 -0400", @updated="2025-09-28 22:03:07.011332 -0400">
336
+ => #<User:0x000000010c39b298 @email="update@email.com", @id=3, @password="543", @created="2025-09-28 22:03:07.011332 -0400", @updated="2025-09-28 22:03:07.011332 -0400">
340
337
  ```
341
338
 
342
- Check whether it includes id
343
- ```Ruby
344
- irb(main):015> user = User.new(email: "llo@ok.com", password: 543)
345
- => #<User:0x000000010cda23b8 @email="llo@ok.com", @password=543>
339
+ Check whether a record has been persisted
340
+ ```ruby
346
341
  irb(main):016> user.persisted?
347
342
  => false
348
343
  ```
349
344
 
350
- Get the record from the database
351
- ```Ruby
345
+ Get a record from the database and reload it
346
+ ```ruby
352
347
  irb(main):011> user = User.last
353
- => #<User:0x000000010ccea178 @email="ok23@ok.com", @id=2, @password="123", @created="2025-09-28 22:03:07.011332 -0400", @updated="2025-09-28 22:03:07.011332 -0400">
348
+ => #<User:0x000000010ccea178 @email="ok23@ok.com", @id=2, @password="123", ...>
354
349
  irb(main):012> user.email = "new@ok.com"
355
350
  => "new@ok.com"
356
- irb(main):013> user
357
- => #<User:0x000000010ccea178 @email="new@ok.com", @id=2, @password="123", @created="2025-09-28 22:03:07.011332 -0400", @updated="2025-09-28 22:03:07.011332 -0400">
358
351
  irb(main):014> user.reload
359
- => #<User:0x000000010c488548 @email="ok23@ok.com", @id=2, @password="123", @created="2025-09-28 22:03:07.011332 -0400", @updated="2025-09-28 22:03:07.011332 -0400"> # not persited data was updated from db
352
+ => #<User:0x000000010c488548 @email="ok23@ok.com", @id=2, @password="123", ...> # unpersisted data refreshed from db
360
353
  ```
361
354
 
362
- Assign attributes without persisiting it to db
363
- ```Ruby
355
+ Assign attributes without persisting to the database
356
+ ```ruby
364
357
  irb(main):008> User.last.assign_attributes(email: "bb@ok.com")
365
- => {"id" => 2, "email" => "ok23@ok.com", "password" => "123"
358
+ => {"id" => 2, "email" => "ok23@ok.com", "password" => "123"}
366
359
  ```
367
360
 
368
- Get all records scoped by field
369
- ```Ruby
361
+ Get all records scoped by a field
362
+ ```ruby
370
363
  irb(main):005> User.where(email: "ok23@ok.com")
371
364
  => [#<User:0x000000010cfaa5c0 @email="ok23@ok.com", @id=2, @password="123">]
372
365
  ```
373
366
 
374
- Get all record
375
- ```Ruby
367
+ Get all records
368
+ ```ruby
376
369
  irb(main):001> User.all
377
- => [#<User:0x000000010c239a30 @email="ok@ok.com", @id=1, @password="password", @created="2025-09-28 22:03:07.011332 -0400", @updated="2025-09-28 22:03:07.011332 -0400">]
370
+ => [#<User:0x000000010c239a30 @email="ok@ok.com", @id=1, @password="password", ...>]
378
371
  ```
372
+
379
373
  Find by id
380
- ```Ruby
374
+ ```ruby
381
375
  irb(main):002> user = User.find 1
382
- => #<User:0x000000010c2f7cd8 @email="ok@ok.com", @id=1, @password="password", @created="2025-09-28 22:03:07.011332 -0400", @updated="2025-09-28 22:03:07.011332 -0400">
376
+ => #<User:0x000000010c2f7cd8 @email="ok@ok.com", @id=1, @password="password", ...>
383
377
  ```
384
378
 
385
- Get last record
386
- ```Ruby
379
+ Get the last record
380
+ ```ruby
387
381
  irb(main):003> User.last
388
- => #<User:0x000000010c2f7cd8 @email="ok@ok.com", @id=1, @password="password", @created="2025-09-28 22:03:07.011332 -0400", @updated="2025-09-28 22:03:07.011332 -0400">
382
+ => #<User:0x000000010c2f7cd8 @email="ok@ok.com", @id=1, @password="password", ...>
389
383
  ```
390
384
 
391
- Create new persited record
392
- ```Ruby
385
+ Create a new persisted record
386
+ ```ruby
393
387
  irb(main):004> User.create(email: "ok23@ok.com", password: 123)
394
- => #<User:0x000000010c393818 @email="ok23@ok.com", @id=2, @password=123, @created="2025-09-28 22:03:07.011332 -0400", @updated="2025-09-28 22:03:07.011332 -0400">
388
+ => #<User:0x000000010c393818 @email="ok23@ok.com", @id=2, @password=123, ...>
395
389
  ```
396
390
 
397
- Destroy record and all related records
398
- ```Ruby
391
+ Destroy a record and all related records
392
+ ```ruby
399
393
  irb(main):021> user.destroy(cascade: true)
400
394
  => 1
401
395
  ```
402
396
 
403
- Find record in the databse or initialize the instance for following persistence
404
- ```Ruby
397
+ Find a record in the database or initialize a new instance for subsequent persistence
398
+ ```ruby
405
399
  irb(main):020> user = User.find_or_new(email: "ok23@ok.com")
406
400
  => #<User:0x000000010cfaa5c0 @email="ok23@ok.com", @id=2, @password="123">
407
401
  irb(main):021> user.persisted?
@@ -413,15 +407,15 @@ irb(main):023> user.persisted?
413
407
  ```
414
408
 
415
409
  Destroy all records one by one
416
- ```Ruby
410
+ ```ruby
417
411
  irb(main):022> User.destroy_all
418
- => [#<User:0x000000010d42df98 @email="ok@ok.com", @id=1, @password="password", @created="2025-09-28 22:03:07.011332 -0400", @updated="2025-09-28 22:03:07.011332 -0400">, #<User:0x000000010d42de80 @email="ok23@ok.com", @id=2, @password="123", @created="2025-09-28 22:03:07.011332 -0400", @updated="2025-09-28 22:03:07.011332 -0400">>
412
+ => [#<User ...>, #<User ...>]
419
413
  irb(main):023> User.all
420
414
  => []
421
415
  ```
422
416
 
423
- Use complex queries chains and when ready serialize it back to ru.Bee object.
424
- ```Ruby
417
+ Use complex query chains and serialize results back to ru.Bee objects in a single query:
418
+ ```ruby
425
419
  # user model
426
420
  class User < Rubee::SequelObject
427
421
  attr_accessor :id, :email, :password, :created, :updated
@@ -442,54 +436,40 @@ class Post < Rubee::SequelObject
442
436
  end
443
437
  ```
444
438
 
445
- ```Ruby
446
- irb(main):001> comment = Comment.new(text: "test")
447
- irb(main):002> comment.save
448
- irb(main):003> user = User.new(email: "ok-test@test.com", password: "123")
449
- irb(main):004> user.save
450
- irb(main):005> post = Post.new(user_id: user.id, comment_id: comment.id)
451
- irb(main):006> post.save
452
- => true
453
- irb(main):007> comment
454
- => #<Comment:0x000000012281a650 @id=21, @text="test", @created=2025-09-28 22:03:07.011332 -0400, @updated=2025-09-28 22:03:07.011332 -0400>
439
+ ```ruby
455
440
  irb(main):008> result = Comment.dataset.join(:posts, comment_id: :id)
456
441
  irb(main):009> .where(comment_id: Comment.where(text: "test").last.id)
457
442
  irb(main):010> .then { |dataset| Comment.serialize(dataset) }
458
- => [#<Comment:0x0000000121889998 @id=30, @text="test", @user_id=702, @created=2025-09-28 22:03:07.011332 -0400, @updated=2025-09-28 22:03:07.011332 -0400>]
443
+ => [#<Comment:0x0000000121889998 @id=30, @text="test", @user_id=702, ...>]
459
444
  ```
460
- This is recommended when you want to run one query and serialize it back to ru.Bee object only once.
461
- So it may safe some resources.
462
445
 
463
- Since version 2.6.0 Rubee::SequelObject supports chaining queries:
446
+ Since version 2.6.0, `Rubee::SequelObject` supports chained queries. Supported methods: `where`, `order`, `limit`, `offset`, `all`, `owns_many`, `owns_one`, `join`, `paginate`.
447
+
464
448
  ```ruby
465
449
  irb(main):001> Comment.where(text: "test").where(user_id: 1)
466
- => [#<Comment:0x0000000121889998 @id=30, @text="test", @user_id=702, @created=2025-09-28 22:03:07.011332 -0400, @updated=2025-09-28 22:03:07.011332 -0400>]
450
+ => [#<Comment:0x0000000121889998 @id=30, @text="test", @user_id=702, ...>]
467
451
  ```
468
- It will run only one query tho.
469
- Supported methods: where, order, limit, offset, all, owns_many, owns_one, join, paginate
470
452
 
471
- Also new paginate method is available:
453
+ A `paginate` method is also available:
472
454
  ```ruby
473
455
  irb(main):001> comments = Comment.all.paginate(page: 1, per_page: 3)
474
- [#<Comment:0x0000000121889998 @id=30, @text="test", @user_id=702, @created=2025-09-28 22:03:07.011332 -0400, @updated=2025-09-28 22:03:07.011332 -0400>,
475
- #<Comment:0x0000000121889998 @id=30, @text="test", @user_id=702, @created=2025-09-28 22:03:07.011332 -0400, @updated=2025-09-28 22:03:07.011332 -0400>,
476
- #<Comment:0x0000000121889998 @id=30, @text="test", @user_id=702, @created=2025-09-28 22:03:07.011332 -0400, @updated=2025-09-28 22:03:07.011332 -0400>]
477
456
 
478
457
  irb(main):001> comments.pagination_meta
479
458
  => {:current_page=>1, :per_page=>3, :total_count=>10, :first_page=>true, :last_page=>false, :prev=>nil, :next=>2}
480
459
  ```
460
+
481
461
  [Back to content](#content)
482
462
 
483
463
  ## Database
484
464
 
485
- ru.Bee supports Postgres and Mysqlite databases fully and can potentially be used with any
486
- database supported by Sequel gem.
465
+ ru.Bee supports Postgres and SQLite databases fully and can potentially be used with any database supported by the Sequel gem.
487
466
 
488
- When it comes to sqlite make sure you have sqlite3 included in your Gemfile.
467
+ When using SQLite, include `sqlite3` in your Gemfile:
489
468
  ```ruby
490
469
  gem 'sqlite3'
491
470
  ```
492
- And define your database urls for each environment in config/base_configuration.rb file:
471
+
472
+ Define your database URLs for each environment in `config/base_configuration.rb`:
493
473
  ```ruby
494
474
  Rubee::Configuration.setup(env = :development) do |config|
495
475
  config.database_url = { url: 'sqlite://db/development.db', env: }
@@ -504,11 +484,12 @@ Rubee::Configuration.setup(env = :production) do |config|
504
484
  ...
505
485
  end
506
486
  ```
507
- For the PostgreSQL you need to include pg gem in your Gemfile
487
+
488
+ For PostgreSQL, include the `pg` gem and configure the URLs:
508
489
  ```ruby
509
490
  gem 'pg'
510
491
  ```
511
- And define your database urls for each environment in config/base_configuration.rb file:
492
+
512
493
  ```ruby
513
494
  Rubee::Configuration.setup(env = :development) do |config|
514
495
  config.database_url = { url: "postgres://postgres@localhost:5432/development", env: }
@@ -523,68 +504,70 @@ Rubee::Configuration.setup(env = :production) do |config|
523
504
  ...
524
505
  end
525
506
  ```
526
- Before you start the server or runninng test suite you need to ensure your database is initated.
507
+
508
+ Before starting the server or running the test suite, ensure your database is initialized:
527
509
  ```bash
528
- rubee db init # this will ensure your database is created for each environment
529
- RACK_ENV=test rubee db run:all # this will run all migrations for test environment
530
- RACK_ENV=development rubee db run:all # this will run all migrations for development environment
510
+ rubee db init # ensures your database is created for each environment
511
+ RACK_ENV=test rubee db run:all # runs all migrations for the test environment
512
+ RACK_ENV=development rubee db run:all # runs all migrations for the development environment
531
513
  ```
532
514
 
533
515
  [Back to content](#content)
534
516
 
535
- ## Mysqlite production ready
536
- Starting from verison 1.9.0 main issue for using sqlite - write db lock is resolved!
537
- If you feel comfortable you can play with retry configuration parameters:
517
+ ### SQLite production ready
518
+
519
+ Starting from version 1.9.0, the main issue with SQLite write database locking — is resolved.
520
+ You can tune the retry configuration parameters as needed:
538
521
 
539
522
  ```ruby
540
- ## configure db write retries
541
- config.db_max_retries = { env:, value: 3 } # set it to 0 to disable or increase if needed
542
- config.db_retry_delay = { env:, value: 0.1 }
543
- config.db_busy_timeout = { env:, value: 1000 } # this is busy timeout in ms, before raising bussy error
523
+ ## configure database write retries
524
+ config.db_max_retries = { env:, value: 3 } # set to 0 to disable, or increase if needed
525
+ config.db_retry_delay = { env:, value: 0.1 }
526
+ config.db_busy_timeout = { env:, value: 1000 } # busy timeout in milliseconds before raising an error
544
527
  ```
545
528
 
546
- For ru.Bee model class persist methods create and update retry will be added automatically. However, \
547
- if you want to do it with Sequel dataset you need to do it yourself:
529
+ For ru.Bee model `create` and `update` methods, retries are added automatically. To use retries with a Sequel dataset directly:
548
530
 
549
531
  ```ruby
550
- Rubee::DBTools.with_retry { User.dataset.insert(email: "test@ok.com", password: "123") }
532
+ Rubee::DBTools.with_retry { User.dataset.insert(email: "test@ok.com", password: "123") }
551
533
  ```
534
+
552
535
  [Back to content](#content)
553
536
 
554
537
  ## Routing
555
- ru.Bee uses explicit routes. In the routes.rb yout can define routes for any of the main HTTP methods. \
556
- You can also add any matched parameter denoted by a pair of `{ }` in the path of the route. \
557
- Eg. `/path/to/{a_key}/somewhere`
538
+
539
+ ru.Bee uses explicit routes. In `routes.rb` you can define routes for any of the main HTTP methods.
540
+ You can also include matched parameters denoted by `{ }` in the route path, e.g. `/path/to/{a_key}/somewhere`.
558
541
 
559
542
  ### Routing methods
560
- ``` ruby
543
+
544
+ ```ruby
561
545
  Rubee::Router.draw do |router|
562
- router.get '/posts', to: 'posts#index'
563
- router.post '/posts', to: 'posts#create'
564
- router.patch '/posts/{id}', to: 'posts#update'
565
- router.put '/posts/{id}', to: 'posts#update'
566
- router.delete '/posts/{id}', to: 'posts#delete'
567
- router.head '/posts', to: 'posts#index'
568
- router.connect '/posts', to: 'posts#index'
569
- router.options '/posts', to: 'posts#index'
570
- router.trace '/posts', to: 'posts#index'
546
+ router.get '/posts', to: 'posts#index'
547
+ router.post '/posts', to: 'posts#create'
548
+ router.patch '/posts/{id}', to: 'posts#update'
549
+ router.put '/posts/{id}', to: 'posts#update'
550
+ router.delete '/posts/{id}', to: 'posts#delete'
551
+ router.head '/posts', to: 'posts#index'
552
+ router.connect '/posts', to: 'posts#index'
553
+ router.options '/posts', to: 'posts#index'
554
+ router.trace '/posts', to: 'posts#index'
571
555
  end
572
556
  ```
573
557
 
574
- As you see above every route is set up as:\
558
+ Every route follows this structure:
575
559
  ```ruby
576
560
  route.{http_method} {path}, to: "{controller}#{action}",
577
- model { ...optional }, namespace { ...optional }, react { ...optional }
561
+ model: { ...optional }, namespace: { ...optional }, react: { ...optional }
578
562
  ```
579
563
 
580
- ### Defining Model attributes in routes
581
- One of ru.Bee's unique traits is where we can define our models for generation. \
582
- You've seen above one possible way you can set up.
564
+ ### Defining model attributes in routes
565
+
566
+ One of ru.Bee's unique traits is defining models for generation directly in the routes:
583
567
 
584
568
  ```ruby
585
569
  Rubee::Router.draw do |router|
586
570
  ...
587
- # draw the contract
588
571
  router.get "/apples", to: "apples#index",
589
572
  model: {
590
573
  name: "apple",
@@ -599,139 +582,105 @@ Rubee::Router.draw do |router|
599
582
  end
600
583
  ```
601
584
 
602
- There are many other types supported by us and Sequel to help generate your initial db files. \
603
- Other supported attribute key types are:
604
- ``` ruby
585
+ Other supported attribute types via Sequel:
586
+ ```ruby
605
587
  [
606
- { name: 'id', type: :primary},
607
- { name: 'name', type: :string },
608
- { name: 'description', type: :text },
609
- { name: 'quntity', type: :integer },
610
- { name: 'created', type: :date },
611
- { name: 'modified', type: :datetime },
612
- { name: 'exists', type: :time },
613
- { name: 'active', type: :boolean },
614
- { name: 'hash', type: :bigint },
615
- { name: 'price', type: :decimal },
616
- { name: 'item_id', type: :foreign_key },
617
- { name: 'item_id_index', type: :index },
618
- { name: 'item_id_unique', type: :unique }
588
+ { name: 'id', type: :primary },
589
+ { name: 'name', type: :string },
590
+ { name: 'description', type: :text },
591
+ { name: 'quantity', type: :integer },
592
+ { name: 'created', type: :date },
593
+ { name: 'modified', type: :datetime },
594
+ { name: 'exists', type: :time },
595
+ { name: 'active', type: :boolean },
596
+ { name: 'hash', type: :bigint },
597
+ { name: 'price', type: :decimal },
598
+ { name: 'item_id', type: :foreign_key },
599
+ { name: 'item_id_index', type: :index },
600
+ { name: 'item_id_unique', type: :unique }
619
601
  ]
620
602
  ```
621
- Every attribute can have a set of options passed based on their related \
622
- [Sequel schema definition](https://github.com/jeremyevans/sequel/blob/master/doc/schema_modification.rdoc).
623
603
 
624
- An example of this would be for the type string: \
604
+ Every attribute can carry options based on the [Sequel schema definition](https://github.com/jeremyevans/sequel/blob/master/doc/schema_modification.rdoc). For example:
605
+
625
606
  ```ruby
626
- {name: 'key', type: :string, options: { size: 50, fixed: true } }
607
+ { name: 'key', type: :string, options: { size: 50, fixed: true } }
627
608
  ```
628
609
 
629
- Gets translated to:\
630
- ```
631
- rubyString :key, size: 50, fixed: true
610
+ Gets translated to:
611
+ ```ruby
612
+ String :key, size: 50, fixed: true
632
613
  ```
633
614
 
634
615
  ### Generation from routes
635
- As long as you have a `{ model: 'something' }` passed to your given route, \
636
- you can use it to generate your initial model files. If only a `path` and a `to:` are defined will only generate \
637
- a controller and a corresponding view.
638
616
 
639
- To generate based on a get route for the path /apples:\
640
- ```ruby
641
- rubee generate get /apples # or rubee gen get /apples
642
- ```
617
+ As long as a route has a `model:` key, you can use it to generate initial model files. If only `path` and `to:` are defined, only a controller and view will be generated.
643
618
 
644
- To generate base on a patch request for the path /apples/{id}:\
645
- ```ruby
646
- rubee generate patch /apples/{id} # or rubee gen patch /apples/{id}
619
+ ```bash
620
+ rubee generate get /apples # or: rubee gen get /apples
621
+ rubee generate patch /apples/{id} # or: rubee gen patch /apples/{id}
647
622
  ```
648
623
 
649
- Example:
624
+ **Example 1** — route without a model:
650
625
  ```ruby
651
- Rubee::Router.draw do |router|
652
- ...
653
- # draw the contract
654
- router.get "/apples", to: "apples#index"
655
- end
626
+ router.get "/apples", to: "apples#index"
656
627
  ```
657
- Will Generate:
628
+ Generates:
658
629
  ```bash
659
- ./app/controllers/apples_controller.rb # Controller with respective action
660
- ./app/views/apples_index.erb # ERB view that is rendered by the controller right away
630
+ ./app/controllers/apples_controller.rb
631
+ ./app/views/apples_index.erb
661
632
  ```
662
633
 
663
- Example 2:
634
+ **Example 2** — route with a model name only:
664
635
  ```ruby
665
- Rubee::Router.draw do |router|
666
- ...
667
- # draw the contract
668
- router.get "/apples", to: "apples#index", model: { name: 'apple' }
669
- end
636
+ router.get "/apples", to: "apples#index", model: { name: 'apple' }
670
637
  ```
671
-
672
- Will generate:
638
+ Generates:
673
639
  ```bash
674
- ./app/controllers/apples_controller.rb # Controller with respective action
675
- ./app/views/apples_index.erb # ERB view that is rendered by the controller right away
676
- ./app/models/apple.rb # Model that acts as ORM
677
- ./db/create_apples.rb # Database migration file needed for creating repsective table
640
+ ./app/controllers/apples_controller.rb
641
+ ./app/views/apples_index.erb
642
+ ./app/models/apple.rb
643
+ ./db/create_apples.rb
678
644
  ```
679
645
 
680
- Example 3:
646
+ **Example 3** — route with full model attributes:
681
647
  ```ruby
682
- Rubee::Router.draw do |router|
683
- ...
684
- # draw the contract
685
- router.get "/apples", to: "apples#index",
686
- model: {
687
- name: 'apple',
688
- attributes: [
689
- { name: 'id', type: :primary },
690
- { name: 'colour', type: :string },
691
- { name: 'weight', type: :integer },
692
- { name: 'created', type: :datetime },
693
- { name: 'updated', type: :datetime },
694
- ]
695
- }
696
- end
697
- ```
698
-
699
- Will generate:
648
+ router.get "/apples", to: "apples#index",
649
+ model: {
650
+ name: 'apple',
651
+ attributes: [
652
+ { name: 'id', type: :primary },
653
+ { name: 'colour', type: :string },
654
+ { name: 'weight', type: :integer },
655
+ { name: 'created', type: :datetime },
656
+ { name: 'updated', type: :datetime },
657
+ ]
658
+ }
659
+ ```
660
+ Generates:
700
661
  ```bash
701
- ./app/controllers/apples_controller.rb # Controller with respective action
702
- ./app/models/apple.rb # Model that acts as ORM
703
- ./app/views/apples_index.erb # ERB view that is rendered by the controller right away
704
- ./db/create_apples.rb # Database migration file needed for creating repsective table
662
+ ./app/controllers/apples_controller.rb
663
+ ./app/models/apple.rb
664
+ ./app/views/apples_index.erb
665
+ ./db/create_apples.rb
705
666
  ```
706
667
 
668
+ ### Modular application
707
669
 
708
- ### Modualar application
709
-
710
- You can also use ru.Bee to create modular applications.\
711
- And attach as many subprojects you need.
712
- Main philosophy of attach functinality is to keep the main project clean and easy to maintain. It will still\
713
- share data with the main app. So where to define a border between the main app and subprojects is up to developer.
714
- Howerver by attching new subproject you will get a new folder and files configured and namespaced respectively.
715
-
716
- So if you need to extend your main app with a separate project, you can do it easily in ruBEE.
717
- 1. Attach new subrpoject
670
+ ru.Bee supports modular applications — attach as many subprojects as you need. Each subproject gets its own folder, MVC setup, routes, and namespacing, while still sharing data with the main app.
718
671
 
672
+ 1. Attach a new subproject
719
673
  ```bash
720
674
  rubee attach admin
721
675
  ```
722
- This will create a dedicated folder in the project root called admin and all the MVC setup, route and configuraion \
723
- files will be created there.
724
676
 
725
677
  2. Add routes
726
-
727
678
  ```ruby
728
679
  # admin_routes.rb
729
680
  Rubee::Router.draw do |router|
730
- ...
731
- # draw the contract
732
- router.get '/admin/cabages', to: 'cabages#index',
681
+ router.get '/admin/cabbages', to: 'cabbages#index',
733
682
  model: {
734
- name: 'cabage',
683
+ name: 'cabbage',
735
684
  attributes: [
736
685
  { name: 'id', type: :primary },
737
686
  { name: 'name', type: :string },
@@ -739,59 +688,51 @@ Rubee::Router.draw do |router|
739
688
  { name: 'updated', type: :datetime },
740
689
  ]
741
690
  },
742
- namespace: :admin # mandatory option for supporting namespacing
691
+ namespace: :admin # mandatory for namespacing support
743
692
  end
744
693
  ```
745
694
 
746
- 3. Run gen command
747
-
695
+ 3. Run the generate command
748
696
  ```bash
749
- rubee gen get /admin/cabages app:admin
697
+ rubee gen get /admin/cabbages app:admin
750
698
  ```
751
-
752
- This will generate the bolierplate files:
753
-
699
+ Generates:
754
700
  ```bash
755
- ./admin/controllers/cabages_controller.rb
756
- ./admin/views/cabages_index.erb
757
- ./admin/models/cabage.rb
758
- ./db/create_cabages.rb
701
+ ./admin/controllers/cabbages_controller.rb
702
+ ./admin/views/cabbages_index.erb
703
+ ./admin/models/cabbage.rb
704
+ ./db/create_cabbages.rb
759
705
  ```
760
706
 
761
- 4. Perform migrations
762
-
707
+ 4. Run the migration
763
708
  ```bash
764
- rubee db run:create_cabages
709
+ rubee db run:create_cabbages
765
710
  ```
766
711
 
767
- 5. Fill the views and controller with the content
768
-
712
+ 5. Fill the controller with content
769
713
  ```ruby
770
- # ./admin/controllers/cabages_controller.rb
771
- class Admin::CabagesController < Rubee::BaseController
714
+ # ./admin/controllers/cabbages_controller.rb
715
+ class Admin::CabbagesController < Rubee::BaseController
772
716
  def index
773
- response_with object: Cabage.all, type: :json
717
+ response_with object: Cabbage.all, type: :json
774
718
  end
775
719
  end
776
720
  ```
777
721
 
778
- 6. Run the rubee server
779
-
722
+ 6. Run the server
780
723
  ```bash
781
- rubee start # or rubee start_dev for development
724
+ rubee start # or rubee start_dev for development
782
725
  ```
783
726
 
784
727
  [Back to content](#content)
785
728
 
786
729
  ## Views
787
- View in ru.Bee is just a plain html/erb/react file that can be rendered from the controller.
788
730
 
789
- ## Templates over erb
731
+ A view in ru.Bee is a plain HTML, ERB, or React file rendered from the controller.
790
732
 
791
- You can use erb as a template engine in the views.
733
+ ### Templates with ERB
792
734
 
793
- layout.erb is the parent template that is rendered first and then the child templates are rendered inside it.
794
- Feel free to include you custom css and js files in the this file.
735
+ `layout.erb` is the parent template rendered first; child templates are rendered inside it. Feel free to include custom CSS and JS files there.
795
736
 
796
737
  ```ruby
797
738
  # app/controllers/welcome_controller.rb
@@ -804,80 +745,67 @@ end
804
745
  ```
805
746
 
806
747
  ```erb
807
- # app/views/welcome_header.erb
748
+ <%# app/views/welcome_header.erb %>
808
749
 
809
750
  <h1>All set up and running!</h1>
810
751
  ```
811
752
 
812
753
  ```erb
813
- # app/views/welcome_show.erb
754
+ <%# app/views/welcome_show.erb %>
814
755
 
815
756
  <div class="container">
816
- <%= render_template :welcome_header %> # you can easily attach erb temlate using render_template method
817
- <p><%= locals[:object][:message] %></p> # displaying, passed in the controller object
757
+ <%= render_template :welcome_header %> <%# attach an ERB partial with render_template %>
758
+ <p><%= locals[:object][:message] %></p> <%# display the object passed from the controller %>
818
759
  </div>
819
760
  ```
820
761
 
821
- ## React as a view
822
-
823
- React is supported out of the box in the rubee view.
824
- Make react as a view representation layer is easy.
762
+ ### React as a view
825
763
 
826
- Prerequisites: Node and NPM are required
764
+ React is supported out of the box as a view layer in ru.Bee.
827
765
 
828
- 1. Make sure after creating project and bundling you have installed react dependencies by
766
+ Prerequisites: Node and NPM are required.
829
767
 
768
+ 1. After creating your project and bundling, install React dependencies:
830
769
  ```bash
831
- rubee react prepare # this will install react related node modules
770
+ rubee react prepare
832
771
  ```
833
772
 
834
- 2. Make sure you have configured react in the configuration file
835
-
773
+ 2. Configure React in `config/base_configuration.rb`:
836
774
  ```ruby
837
- # config/base_configuration/rb
838
775
  Rubee::Configuration.setup(env = :development) do |config|
839
776
  config.database_url = { url: 'sqlite://db/development.db', env: }
840
777
 
841
- # this line registers react as a view
778
+ # register React as a view
842
779
  config.react = { on: true, env: }
843
780
  end
844
781
  ```
845
782
 
846
- 3. Start server by
847
-
783
+ 3. Start the server:
848
784
  ```bash
849
785
  rubee start
850
- # It will start to server on port 7000 (default)
851
- # You can change it by
852
-
786
+ # Default port is 7000. To change it:
853
787
  rubee start --port=3000
854
788
  ```
855
789
 
856
- 3. Open your browser and navigate to http://localhost:3000/home
857
-
858
- 4. You will see the react app running in the browser.
790
+ 4. Open your browser and navigate to http://localhost:3000/home.
859
791
 
860
- 5. For development purposes make sure you run `rubee start_dev` and in other terminal window run `rubee react watch`.
861
- So that will ensure all cahnges applying instantly.
792
+ 5. For development, run `rubee start_dev` in one terminal and `rubee react watch` in another. Changes apply instantly.
862
793
 
863
- 6. You can generate react view from the route by indicating the view name explicitly
864
-
865
- 7. Do not forget to rebuild react app in production by running `rubee react build`. This is unnecessary in development,
866
- when you use `rubee react watch` tho. So it does rebuild automatically.
794
+ 6. In production, rebuild the React app with `rubee react build`. Not needed in development when using `rubee react watch`.
867
795
 
796
+ 7. Generate a React view from a route by specifying the view name:
868
797
  ```ruby
869
798
  # config/routes.rb
870
799
  Rubee::Router.draw do |router|
871
- router.get('/', to: 'welcome#show') # override it for your app
800
+ router.get('/', to: 'welcome#show')
872
801
 
873
802
  router.get('/api/users', to: 'user#index', react: { view_name: 'users.tsx' })
874
- # Please note /api/users here is the backend endpoint
875
- # For rendering generated /app/views/users.tsx file, you need to update react routes
803
+ # Note: /api/users is the backend endpoint.
804
+ # To render /app/views/users.tsx, update the React routes as shown below.
876
805
  end
877
806
  ```
878
807
 
879
- 7. Add logic to generated api controller
880
-
808
+ 8. Add logic to the generated API controller:
881
809
  ```ruby
882
810
  # app/controllers/api/user_controller.rb
883
811
  class Api::UserController < Rubee::BaseController
@@ -886,8 +814,8 @@ class Api::UserController < Rubee::BaseController
886
814
  end
887
815
  end
888
816
  ```
889
- 8. Register path in react routes
890
817
 
818
+ 9. Register the path in React routes:
891
819
  ```javascript
892
820
  // app/views/app.tsx
893
821
  <Router>
@@ -897,10 +825,10 @@ end
897
825
  </Routes>
898
826
  </Router>
899
827
  ```
900
- 9. Fetch data from the backend in the users.tsx react component and display it in the browser http://localhost:3000/users
901
828
 
829
+ 10. Fetch data from the backend in the component:
902
830
  ```javascript
903
- # app/views/users.tsx
831
+ // app/views/users.tsx
904
832
  import { useState, useEffect } from 'react';
905
833
 
906
834
  function Users() {
@@ -923,22 +851,20 @@ function Users() {
923
851
  </div>
924
852
  );
925
853
  }
926
-
927
854
  ```
855
+
928
856
  [Back to content](#content)
929
857
 
930
858
  ## Object hooks
931
859
 
932
- In ru.Bee by extending Hookable module any Ruby object can be charged with hooks (logic),
933
- that can be executed before, after and around a specific method execution.
860
+ By including the `Hookable` module, any Ruby object can be charged with hooks logic that executes before, after, or around a specific method call.
934
861
 
935
- Here below a controller example. However it can be used in any Ruby object, like Model etc.
862
+ `BaseController` is Hookable by default:
936
863
  ```ruby
937
- # base conrteoller is hopokable by Default
938
864
  class ApplesController < Rubee::BaseController
939
- before :index, :print_hello # you can useinstance method as a handler
940
- after :index, -> { puts "after index" }, if: -> { true } # or you can use lambda
941
- after :index, -> { puts "after index2" }, unless: -> { false } # if, unless guards may accept method or lambda
865
+ before :index, :print_hello # use an instance method as a handler
866
+ after :index, -> { puts "after index" }, if: -> { true } # or use a lambda
867
+ after :index, -> { puts "after index2" }, unless: -> { false } # if/unless guards accept a method or lambda
942
868
  around :index, :log
943
869
 
944
870
  def index
@@ -950,18 +876,17 @@ class ApplesController < Rubee::BaseController
950
876
  end
951
877
 
952
878
  def log
953
- puts "before log aroud"
879
+ puts "before log around"
954
880
  res = yield
955
881
  puts "after log around"
956
882
  res
957
883
  end
958
- ...
959
884
  end
960
885
  ```
961
- Then, in the server logs we could see next execution stack
962
886
 
887
+ The server logs will show the following execution stack:
963
888
  ```bash
964
- before log aroud
889
+ before log around
965
890
  hello!
966
891
  after index
967
892
  after index2
@@ -969,12 +894,11 @@ after log around
969
894
  127.0.0.1 - - [17/Feb/2025:11:42:14 -0500] "GET /apples HTTP/1.1" 401 - 0.0359
970
895
  ```
971
896
 
972
- Starting from ver 1.11 hooks are able to be pinned to class methods.
973
-
897
+ Starting from version 1.11, hooks can also be pinned to class methods:
974
898
  ```ruby
975
899
  class AnyClass
976
900
  include Rubee::Hookable
977
- before :print_world, :print_hello, class_methods: true # you can use class method as a handler
901
+ before :print_world, :print_hello, class_methods: true
978
902
 
979
903
  class << self
980
904
  def print_world
@@ -987,6 +911,8 @@ class AnyClass
987
911
  end
988
912
  end
989
913
  ```
914
+
915
+ Output:
990
916
  ```bash
991
917
  hello!
992
918
  world!
@@ -996,8 +922,9 @@ world!
996
922
 
997
923
  ## Validations
998
924
 
999
- In ru.Bee any class can be charged with validations. This is done by including Validatable module.
1000
- Please note, ru.Bee model is validatable by default. No need to include it explicitly.
925
+ Any class can be charged with validations by including the `Validatable` module.
926
+ ru.Bee models are validatable by default no need to include it explicitly.
927
+
1001
928
  ```ruby
1002
929
  class Foo
1003
930
  include Rubee::Validatable
@@ -1013,51 +940,32 @@ class Foo
1013
940
  attribute(:name).required.type(String).condition(->{ name.length > 2 })
1014
941
 
1015
942
  attribute(:age)
1016
- .required('Age is a manadatory field')
1017
- .type(Integer, error_message: 'Must be an integerRRRRRRrrr!')
1018
- .condition(->{ age > 18 }, fancy_error: 'You must be at least 18 years old, dude!')
943
+ .required('Age is a mandatory field')
944
+ .type(Integer, error_message: 'Must be an integer!')
945
+ .condition(->{ age > 18 }, fancy_error: 'You must be at least 18 years old!')
1019
946
  end
1020
947
  end
1021
948
  ```
1022
- Then we can evaluate it in the ru.Bee console
949
+
1023
950
  ```bash
1024
- => #<Proc:0x000000010d389d80 (irb):32>
1025
- irb(main):041> Foo.new("Test", 20)
1026
- => #<Foo:0x000000010d383fc0 @__validation_state=#<Rubee::Validatable::State:0x000000010d383de0 @errors={}, @valid=true>, @age=20, @name="Test">
1027
- irb(main):042> Foo.new("Test", 1)
1028
- =>
1029
- #<Foo:0x000000010ce61c40
1030
- @__validation_state=#<Rubee::Validatable::State:0x000000010ce61bc8 @errors={age: {fancy_error: "You must be at least 18 years old, dude!"}}, @valid=false>,
1031
- @age=1,
1032
- @name="Test">
1033
- irb(main):043> Foo.new("Test", nil)
1034
- =>
1035
- #<Foo:0x000000010c46f200
1036
- @__validation_state=#<Rubee::Validatable::State:0x000000010c46f070 @errors={age: {message: "Age is a manadatory field"}}, @valid=false>,
1037
- @age=nil,
1038
- @name="Test">
1039
- irb(main):044> Foo.new("Te", 20)
1040
- =>
1041
- #<Foo:0x000000010cfe9270
1042
- @__validation_state=#<Rubee::Validatable::State:0x000000010cfe91f8 @errors={name: {message: "condition is not met"}}, @valid=false>,
1043
- @age=20,
1044
- @name="Te">
1045
- irb(main):045> foo = Foo.new("Joe", "wrong")
1046
- =>
1047
- #<Foo:0x000000010d32eb38
1048
- ...
1049
- irb(main):046> foo.valid?
951
+ irb(main):041> Foo.new("Test", 20).valid?
952
+ => true
953
+ irb(main):042> Foo.new("Test", 1).errors
954
+ => {age: {fancy_error: "You must be at least 18 years old!"}}
955
+ irb(main):046> Foo.new("Joe", "wrong").valid?
1050
956
  => false
1051
- irb(main):047> foo.errors
1052
- => {age: {error_message: "Must be an integerRRRRRRrrr!"}}
957
+ irb(main):047> Foo.new("Joe", "wrong").errors
958
+ => {age: {error_message: "Must be an integer!"}}
1053
959
  ```
1054
- Model example
960
+
961
+ Model example with persistence guards:
1055
962
  ```ruby
1056
963
  class User < Rubee::SequelObject
1057
964
  attr_accessor :id, :email, :password, :created
1058
965
 
1059
- validate_after_setters # This will run validation after each setter.
1060
- validate_before_persist! # This will validate and raise error in case invalid before saving to DB
966
+ validate_after_setters # runs validation after each setter
967
+ validate_before_persist! # validates and raises an error if invalid before saving
968
+
1061
969
  validate do
1062
970
  attribute(:email).required
1063
971
  .condition(
@@ -1066,117 +974,86 @@ class User < Rubee::SequelObject
1066
974
  end
1067
975
  end
1068
976
  ```
977
+
1069
978
  ```bash
1070
- irb(main):074> user = User.new(email: "wrong", password: 123)
1071
- =>
1072
- #<User:0x000000010d2c3e78
1073
- ...
1074
- irb(main):075> user.valid?
1075
- => false
1076
- irb(main):076> user.errors
1077
- => {email: {error: "Wrong email format"}}
1078
979
  irb(main):077> user.save
1079
- =>{email: {error: "Wrong email format"}} (Rubee::Validatable::Error) ..
980
+ => {email: {error: "Wrong email format"}} (Rubee::Validatable::Error)
1080
981
  irb(main):078> user.email = "ok@ok.com"
1081
- => "ok@ok.com"
1082
- irb(main):079> user.valid?
1083
- => true
1084
982
  irb(main):080> user.save
1085
983
  => true
1086
- irb(main):081> user
1087
- =>
1088
- #<User:0x000000010d2c3e78
1089
- @__validation_state=#<Rubee::Validatable::State:0x000000010cb28628 @errors={}, @valid=true>,
1090
- @created=2025-11-30 17:18:52.254197 -0500,
1091
- @email="ok@ok.com",
1092
- @id=2260,
1093
- @password=123,
1094
- @updated=2025-11-30 17:18:52.254206 -0500>
1095
- ```
1096
- If you want to apply validation_before_persist! and validation_after_setters globally,
1097
- add init/sequle_object_preloader.rb(you can chose any name)
1098
- file withing set up those methods
1099
- for Rubee::SequelObject parent class by adding:
1100
- ```
1101
- Rubee::SequelObject.validate_befor_persist!
984
+ ```
985
+
986
+ To apply `validate_before_persist!` and `validate_after_setters` globally, add an initializer such as `init/sequel_object_preloader.rb`:
987
+ ```ruby
988
+ Rubee::SequelObject.validate_before_persist!
1102
989
  Rubee::SequelObject.validate_after_setters
1103
990
  ```
1104
- So you shouldn't add it to each model again and again.
991
+
1105
992
  [Back to content](#content)
1106
993
 
1107
994
  ## Rubee support
1108
995
 
1109
- In Rubee you have an optional way to charge your code with usefull methods that added to base Ruby classes.
1110
- You can add them to your project globally by setting up it in configuration.
996
+ An optional set of useful methods can be added to base Ruby classes globally via configuration:
1111
997
 
1112
998
  ```ruby
1113
- # To include all support methods
999
+ # Include all support methods
1114
1000
  Rubee::Configuration.setup do |config|
1115
1001
  config.rubee_support = { all: true }
1116
1002
  end
1117
1003
 
1118
- # To include only those who belong to a indicated class
1004
+ # Include only methods for a specific class
1119
1005
  Rubee::Configuration.setup do |config|
1120
1006
  config.rubee_support = { classes: [Rubee::Support::String] }
1121
- # So only String will be charged with support methods
1122
1007
  end
1123
1008
  ```
1124
- Here are list of additionl APIs extended for:
1125
1009
 
1126
- ```ruby
1127
- # By adding to configuration Rubee::Suport::Hash class or just applying all, Hash will tollerate string or symbol keys.
1128
- {one: 1}[:one] # => 1
1129
- {one: 1}["one"] # => 1
1130
- ```
1010
+ Available extensions:
1131
1011
 
1132
- # By adding to configuration Rubee::Support::String or just applying all, String will be enriched with handy helper methods.
1133
1012
  ```ruby
1134
- "test".pluralize # => "tests"
1135
- "test".singularize # => "test"
1136
- "test".camelize # => "Test"
1137
- "TestMe".snakeize # => "test_mei"
1138
- "test".singular? # => true
1139
- "test".plural? # => false
1013
+ # Hash — tolerates string or symbol keys interchangeably
1014
+ {one: 1}[:one] # => 1
1015
+ {one: 1}["one"] # => 1
1016
+
1017
+ # Hash — deep digging
1018
+ {one: {two: 2}}.deep_dig(:two) # => 2
1140
1019
  ```
1141
1020
 
1142
1021
  ```ruby
1143
- {one: {two: 2}}.deep_dig(:two) # => 2
1022
+ # String enriched with helper methods
1023
+ "test".pluralize # => "tests"
1024
+ "test".singularize # => "test"
1025
+ "test".camelize # => "Test"
1026
+ "TestMe".snakeize # => "test_me"
1027
+ "test".singular? # => true
1028
+ "test".plural? # => false
1144
1029
  ```
1145
1030
 
1146
1031
  [Back to content](#content)
1147
1032
 
1033
+ ## JWT based authentication
1148
1034
 
1035
+ Include the `AuthTokenable` module in your controller and authenticate any action you need.
1149
1036
 
1150
- ## JWT based authentification
1151
-
1152
- Charge you rpoject with token based authentification system and customize it for your needs.
1153
- include AuthTokenable module to your controller and authentificate any action you need.
1154
-
1155
- Make sure you have initiated User model which is a part of the logic.
1037
+ First, initialize the User model:
1156
1038
  ```bash
1157
1039
  rubee db run:create_users
1158
1040
  ```
1159
- This will create table users and initiate first user with demo credentials.
1160
- email: "ok@ok.com", password: "password"
1161
- Feel free to customize it in the /db/create_users.rb file before running migration.
1162
- Please note user model is default but are free to use any model you need. See more examples below.
1163
1041
 
1164
- Then in the controller you can include the AuthTokenable module and use its methods:
1042
+ This creates the `users` table and seeds it with demo credentials email `ok@ok.com`, password `password`. Customize `/db/create_users.rb` before running the migration if needed.
1043
+
1165
1044
  ```ruby
1166
1045
  class UsersController < Rubee::BaseController
1167
1046
  include Rubee::AuthTokenable
1168
- # List methods you want to restrict
1169
- auth_methods :index # unless the user is authentificated it will return unauthentificated
1047
+ auth_methods :index # unauthenticated requests to these actions will be rejected
1170
1048
 
1171
- # GET /users/login (login form page)
1049
+ # GET /users/login
1172
1050
  def edit
1173
1051
  response_with
1174
1052
  end
1175
1053
 
1176
- # POST /users/login (login logic)
1054
+ # POST /users/login
1177
1055
  def login
1178
- if authentificate! # AuthTokenable method that init @token_header
1179
- # Redirect to restricted area, make sure headers: @token_header is passed
1056
+ if authenticate! # initializes @token_header
1180
1057
  response_with type: :redirect, to: "/users", headers: @token_header
1181
1058
  else
1182
1059
  @error = "Wrong email or password"
@@ -1184,92 +1061,57 @@ class UsersController < Rubee::BaseController
1184
1061
  end
1185
1062
  end
1186
1063
 
1187
- # POST /usres/logout (logout logic)
1064
+ # POST /users/logout
1188
1065
  def logout
1189
- unauthentificate! # AuthTokenable method aimed to handle logout action.
1190
- # Make sure @zeroed_token_header is paRssed within headers options
1066
+ unauthenticate!
1191
1067
  response_with type: :redirect, to: "/users/login", headers: @zeroed_token_header
1192
1068
  end
1193
1069
 
1194
- # GET /users (restricted endpoint)
1070
+ # GET /users (restricted)
1195
1071
  def index
1196
1072
  response_with object: User.all, type: :json
1197
1073
  end
1198
1074
  end
1199
1075
  ```
1200
- For security reason it is recommended to initialize JWT_KEY while starting ru.Bee application.
1076
+
1077
+ Set a `JWT_KEY` at startup for security:
1201
1078
  ```bash
1202
1079
  JWT_KEY=SDJwer0wer23j rubee start
1203
1080
  ```
1204
- User is a default model for validation but using it is not a mandatory. You can use any model you need by
1205
- passing arguments to authentificate! and unauthentificate! methods.
1206
1081
 
1082
+ To use a custom model instead of the default `User`, pass arguments to `authenticate!` and `unauthenticate!`:
1207
1083
  ```ruby
1208
- class Client < Sequel::Model
1209
- attr_accessor :id, :name, :digest_password, :created, :updated
1084
+ if authenticate! user_model: Client, login: :name, password: :digest_password
1085
+ response_with type: :redirect, to: "/clients", headers: @token_header
1210
1086
  end
1211
-
1212
- class ClientController < Rubee::BaseController
1213
- include Rubee::AuthTokenable
1214
- # List methods you want to restrict
1215
- auth_methods :index
1216
-
1217
- # GET /clinets/login (login form page)
1218
- def edit
1219
- response_with
1220
- end
1221
-
1222
- # POST /clients/login (login logic)
1223
- def login
1224
- if authentificate! user_model: Client, login: :name, password: :digest_password
1225
- response_with type: :redirect, to: "/clinets", headers: @token_header
1226
- else
1227
- @error = "Wrong login or password"
1228
- response_with render_view: "clinets_edit"
1229
- end
1230
- end
1231
-
1232
- # POST /clinets/logout
1233
- def logout
1234
- unauthentificate! user_model: Client, login: :name, password: :digest_password
1235
- response_with type: :redirect, to: "/clients/login", headers: @zeroed_token_header
1236
- end
1237
-
1238
- # GET /clinets (restricted endpoint)
1239
- def index
1240
- response_with object: Client.all, type: :json
1241
- end
1242
- end
1243
-
1244
1087
  ```
1245
1088
 
1246
1089
  [Back to content](#content)
1247
1090
 
1248
- ## OAuth authentification
1249
- If you want to plug in the OAuth 2.0 authentication, you can use the following code using OAuth2 gem:
1250
- First thing you need to do is to add the gem to your Gemfile
1091
+ ## OAuth authentication
1092
+
1093
+ To plug in OAuth 2.0 authentication, add the `oauth2` gem to your Gemfile:
1251
1094
  ```bash
1252
1095
  gem 'oauth2'
1253
1096
  ```
1254
- Then use down below code as an example and add yours to your controller
1097
+
1098
+ Use the following as a starting point:
1255
1099
  ```ruby
1256
1100
  class UsersController < Rubee::BaseController
1257
1101
  include Rubee::AuthTokenable
1258
1102
 
1259
- REDIRECT_URI = 'https://mysite.com/users/outh_callback'
1260
- CLIENT_ID = ENV['GOOGLE_CLIENT_ID']
1103
+ REDIRECT_URI = 'https://mysite.com/users/oauth_callback'
1104
+ CLIENT_ID = ENV['GOOGLE_CLIENT_ID']
1261
1105
  CLIENT_SECRET = ENV['GOOGLE_CLIENT_SECRET']
1262
1106
 
1263
- # GET /login (login form page)
1107
+ # GET /login
1264
1108
  def edit
1265
1109
  response_with
1266
1110
  end
1267
1111
 
1268
- # POST /users/login (login logic)
1112
+ # POST /users/login
1269
1113
  def login
1270
- Rubee::Logger.info(message: "Login attempt for user #{params[:email]}")
1271
- if authentificate! # AuthTokenable method that init @token_header
1272
- Rubee::Logger.info(message: "Successful login for user #{@authentificated_user.email}")
1114
+ if authenticate!
1273
1115
  response_with(type: :redirect, to: "/sections", headers: @token_header)
1274
1116
  else
1275
1117
  @error = "Wrong email or password"
@@ -1277,8 +1119,8 @@ class UsersController < Rubee::BaseController
1277
1119
  end
1278
1120
  end
1279
1121
 
1280
- # GET /users/outh_login
1281
- def outh_login
1122
+ # GET /users/oauth_login
1123
+ def oauth_login
1282
1124
  response_with(
1283
1125
  type: :redirect,
1284
1126
  to: auth_client.auth_code.authorize_url(
@@ -1288,40 +1130,35 @@ class UsersController < Rubee::BaseController
1288
1130
  )
1289
1131
  end
1290
1132
 
1291
- # GET /users/outh_callback
1292
- def outh_callback
1293
- code = params[:code]
1294
- token = auth_client.auth_code.get_token(code, redirect_uri: REDIRECT_URI)
1133
+ # GET /users/oauth_callback
1134
+ def oauth_callback
1135
+ code = params[:code]
1136
+ token = auth_client.auth_code.get_token(code, redirect_uri: REDIRECT_URI)
1295
1137
  user_info = JSON.parse(token.get('https://www.googleapis.com/oauth2/v1/userinfo?alt=json').body)
1296
- Rubee::Logger.debug(object: user_info, method: "outh_callback", class: "UsersController")
1297
1138
 
1298
1139
  user = User.where(email: user_info['email'])&.last
1299
- unless user
1300
- raise "User with email #{user_info['email']} not found"
1301
- end
1140
+ raise "User with email #{user_info['email']} not found" unless user
1302
1141
 
1303
- params[:email] = user_info['email']
1142
+ params[:email] = user_info['email']
1304
1143
  params[:password] = user.password
1305
1144
 
1306
- if authentificate! # AuthTokenable method that init @token_header
1307
- Rubee::Logger.info(message: "Successful Outh login for user #{@authentificated_user.email}")
1145
+ if authenticate!
1308
1146
  response_with(type: :redirect, to: "/sections", headers: @token_header)
1309
1147
  else
1310
1148
  @error = "Something went wrong"
1311
1149
  response_with(render_view: "users_edit")
1312
1150
  end
1313
- rescue OAuth2::Error => e
1151
+ rescue OAuth2::Error
1314
1152
  @error = "OAuth login failed"
1315
1153
  response_with(render_view: "users_edit")
1316
- rescue StandardError => e
1154
+ rescue StandardError
1317
1155
  @error = "Something went wrong"
1318
1156
  response_with(render_view: "users_edit")
1319
1157
  end
1320
1158
 
1321
- # POST /users/logout (logout logic)
1159
+ # POST /users/logout
1322
1160
  def logout
1323
- unauthentificate! # AuthTokenable method aimed to handle logout action.
1324
- # Make sure @zeroed_token_header is paRssed within headers options
1161
+ unauthenticate!
1325
1162
  response_with(type: :redirect, to: "/login", headers: @zeroed_token_header)
1326
1163
  end
1327
1164
 
@@ -1331,9 +1168,9 @@ class UsersController < Rubee::BaseController
1331
1168
  @client ||= OAuth2::Client.new(
1332
1169
  CLIENT_ID,
1333
1170
  CLIENT_SECRET,
1334
- site: 'https://accounts.google.com',
1171
+ site: 'https://accounts.google.com',
1335
1172
  authorize_url: '/o/oauth2/auth',
1336
- token_url: 'https://oauth2.googleapis.com/token'
1173
+ token_url: 'https://oauth2.googleapis.com/token'
1337
1174
  )
1338
1175
  end
1339
1176
  end
@@ -1342,76 +1179,83 @@ end
1342
1179
  [Back to content](#content)
1343
1180
 
1344
1181
  ## ru.Bee commands
1182
+
1345
1183
  ```bash
1346
- rubee start # start the server
1347
- rubee start_dev # start the server in dev mode, which restart server on changes
1348
- rubee react prepare # install react dependencies
1349
- rubee react watch # dev mode for react, works together with start_dev
1350
- rubee stop # stop the server
1351
- rubee restart # restart the server
1184
+ rubee start # start the server
1185
+ rubee start_dev # start the server in dev mode, restarting on file changes
1186
+ rubee react prepare # install React dependencies
1187
+ rubee react watch # React dev mode, use together with start_dev
1188
+ rubee stop # stop the server
1189
+ rubee restart # restart the server
1352
1190
  ```
1353
1191
 
1354
1192
  ## Generate commands
1193
+
1355
1194
  ```bash
1356
- rubee generate get /apples # generate controller view, model and migration if set in the routes
1195
+ rubee generate get /apples # generate controller, view, model, and migration if set in routes
1196
+ rubee gen get /apples # shorthand alias
1357
1197
  ```
1358
1198
 
1359
- ## Migraiton commands
1199
+ ## Migration commands
1200
+
1360
1201
  ```bash
1361
- rubee db run:all # run all migrations
1362
- rubee db run:create_apples # where create_apples is the name of the migration file, located in /db folder
1363
- rubee db structure # generate migration file for the database structure
1202
+ rubee db run:all # run all migrations
1203
+ rubee db run:create_apples # run a specific migration file from /db
1204
+ rubee db structure # generate a migration file for the database structure
1364
1205
  ```
1365
1206
 
1366
1207
  ## ru.Bee console
1208
+
1367
1209
  ```bash
1368
- rubee console # start the console
1369
- # you can reload the console by typing reload, so it will pick up latest changes
1210
+ rubee console # start the interactive console
1211
+ # type 'reload' inside the console to pick up the latest changes
1370
1212
  ```
1371
1213
 
1372
- ## Testing
1214
+ To run any ru.Bee command in a specific environment, prefix with the env variable:
1373
1215
  ```bash
1374
- rubee test # run all tests
1375
- rubee test auth_tokenable_test.rb # run specific tests
1216
+ RACK_ENV=test rubee console
1376
1217
  ```
1377
1218
 
1378
- [Back to content](#content)
1379
-
1380
-
1381
- If you want to run any ru.Bee command within a specific ENV make sure you added it before a command.
1382
- For instance if you want to run console in test environment you need to run the following command
1219
+ ## Testing
1383
1220
 
1384
1221
  ```bash
1385
- RACK_ENV=test rubee console
1222
+ rubee test # run all tests
1223
+ rubee test auth_tokenable_test.rb # run a specific test file
1224
+ rubee test models/user_model_test.rb --line=12 # run a specific line
1386
1225
  ```
1387
1226
 
1227
+ [Back to content](#content)
1228
+
1388
1229
  ## Background jobs
1389
- Set your background job engine with ease!
1390
1230
 
1391
1231
  ### Sidekiq engine
1392
- 1. Add sidekiq to your Gemfile
1232
+
1233
+ 1. Add Sidekiq to your Gemfile
1393
1234
  ```bash
1394
1235
  gem 'sidekiq'
1395
1236
  ```
1396
- 2. Configure adapter for desired env
1237
+
1238
+ 2. Configure the adapter for the desired environment
1397
1239
  ```ruby
1398
1240
  # config/base_configuration.rb
1399
-
1400
- Rubee::Configuration.setup(env=:development) do |config|
1401
- config.database_url = { url: "sqlite://db/development.db", env: }
1241
+ Rubee::Configuration.setup(env = :development) do |config|
1242
+ config.database_url = { url: "sqlite://db/development.db", env: }
1402
1243
  config.async_adapter = { async_adapter: SidekiqAsync, env: }
1403
1244
  end
1404
1245
  ```
1405
- 3. Bundle up
1246
+
1247
+ 3. Install dependencies
1406
1248
  ```bash
1407
1249
  bundle install
1408
1250
  ```
1409
- 4. Make sure redis is installed and running
1251
+
1252
+ 4. Start Redis
1410
1253
  ```bash
1411
1254
  redis-server
1412
1255
  ```
1413
- 5. Add sidekiq configuration file
1414
- ```bash
1256
+
1257
+ 5. Add a Sidekiq configuration file
1258
+ ```yaml
1415
1259
  # config/sidekiq.yml
1416
1260
 
1417
1261
  development:
@@ -1422,12 +1266,13 @@ development:
1422
1266
  low:
1423
1267
  high:
1424
1268
  ```
1425
- 6. Create sidekiq worker
1269
+
1270
+ 6. Create a Sidekiq worker
1426
1271
  ```ruby
1427
1272
  # app/async/test_async_runner.rb
1428
1273
  require_relative 'extensions/asyncable' unless defined? Asyncable
1429
1274
 
1430
- class TestAsyncRunnner
1275
+ class TestAsyncRunner
1431
1276
  include Rubee::Asyncable
1432
1277
  include Sidekiq::Worker
1433
1278
 
@@ -1438,18 +1283,21 @@ class TestAsyncRunnner
1438
1283
  end
1439
1284
  end
1440
1285
  ```
1441
- 7. Use it in the code base
1286
+
1287
+ 7. Use it in your codebase
1442
1288
  ```ruby
1443
- TestAsyncRunnner.new.perform_async(options: {"email"=> "new@new.com", "password"=> "123"})
1289
+ TestAsyncRunner.new.perform_async(options: { "email" => "new@new.com", "password" => "123" })
1444
1290
  ```
1445
1291
 
1446
- ### Default engine is ThreadAsync
1447
- However it is not yet recommended for production. Use it with cautions!
1448
- 1. Do not define any adapter in the /config/base_configuration.rb file, so default ThreadAsync will be taken.
1449
- 2. Just create a worker and process it.
1292
+ ### Default engine ThreadAsync
1293
+
1294
+ The default adapter is `ThreadAsync`. It is not yet recommended for production use with caution.
1295
+
1296
+ 1. Do not define any adapter in `config/base_configuration.rb`; the default `ThreadAsync` will be used.
1297
+ 2. Create a worker and process it:
1450
1298
  ```ruby
1451
1299
  # test_async_runner.rb
1452
- class TestAsyncRunnner
1300
+ class TestAsyncRunner
1453
1301
  include Rubee::Asyncable
1454
1302
 
1455
1303
  def perform(options)
@@ -1457,32 +1305,29 @@ class TestAsyncRunnner
1457
1305
  end
1458
1306
  end
1459
1307
 
1460
- TestAsyncRunnner.new.perform_async(options: {"email"=> "new@new.com", "password"=> "123"})
1308
+ TestAsyncRunner.new.perform_async(options: { "email" => "new@new.com", "password" => "123" })
1461
1309
  ```
1462
1310
 
1463
1311
  [Back to content](#content)
1464
1312
 
1465
- ### Logger
1466
-
1467
- You can use your own logger by setting it in the /config/base_configuration.rb.
1313
+ ## Logger
1468
1314
 
1315
+ Use your own logger by setting it in `config/base_configuration.rb`:
1469
1316
  ```ruby
1470
- # config/base_configuration.rb
1471
- Rubee::Configuration.setup(env=:development) do |config|
1317
+ Rubee::Configuration.setup(env = :development) do |config|
1472
1318
  config.database_url = { url: "sqlite://db/development.db", env: }
1473
- config.logger = { logger: MyLogger, env: }
1319
+ config.logger = { logger: MyLogger, env: }
1474
1320
  end
1475
1321
  ```
1476
1322
 
1477
- Or you can use the default logger.
1478
- Let's consider example with welcome controller and around hook:
1323
+ Or use the built-in logger with its full set of levels:
1479
1324
  ```ruby
1480
1325
  # app/controllers/welcome_controller.rb
1481
1326
  class WelcomeController < Rubee::BaseController
1482
1327
  around :show, ->(&target_method) do
1483
1328
  start = Time.now
1484
1329
  Rubee::Logger.warn(message: 'This is a warning message', method: :show, class_name: 'WelcomeController')
1485
- Rubee::Logger.error(message: 'This is a warning message', class_name: 'WelcomeController')
1330
+ Rubee::Logger.error(message: 'This is an error message', class_name: 'WelcomeController')
1486
1331
  Rubee::Logger.critical(message: 'We are on fire!')
1487
1332
  target_method.call
1488
1333
  Rubee::Logger.info(
@@ -1498,145 +1343,213 @@ class WelcomeController < Rubee::BaseController
1498
1343
  end
1499
1344
  end
1500
1345
  ```
1501
- When you trigger the controller action, the logs will look like this:
1502
1346
 
1347
+ Output:
1503
1348
  ```bash
1504
- [2025-04-26 12:32:33] WARN [method: show][class_name: WelcomeController] This is a warning message
1505
- [2025-04-26 12:32:33] ERROR [class_name: WelcomeController] This is a warning message
1349
+ [2025-04-26 12:32:33] WARN [method: show][class_name: WelcomeController] This is a warning message
1350
+ [2025-04-26 12:32:33] ERROR [class_name: WelcomeController] This is an error message
1506
1351
  [2025-04-26 12:32:33] CRITICAL We are on fire!
1507
- [2025-04-26 12:32:33] INFO [method: show][class_name: WelcomeController] Execution Time: 0.000655 seconds
1508
- [2025-04-26 12:32:33] DEBUG [method: show][class_name: WelcomeController] #<User:0x000000012c5c63e0 @id=4545, @email="ok@op.com", @password="123">
1352
+ [2025-04-26 12:32:33] INFO [method: show][class_name: WelcomeController] Execution Time: 0.000655 seconds
1353
+ [2025-04-26 12:32:33] DEBUG [method: show][class_name: WelcomeController] #<User:0x000000012c5c63e0 ...>
1509
1354
  ```
1510
1355
 
1511
1356
  [Back to content](#content)
1512
1357
 
1513
- ## Websocket
1358
+ ## WebSocket
1514
1359
 
1515
- With ru.Bee 2.0.0 you can use Websocket with ease!
1360
+ With ru.Bee 2.0.0 you can use WebSocket with ease.
1516
1361
 
1517
- Here are steps to get started:
1518
- 1. Make sure redis server is installed and running
1519
- ````bash
1520
- sudo apt-get install -y redis # linux
1521
- brew install redis # osx
1522
- ````
1523
- 2. Enable websocket and redis to your Gemfile
1362
+ 1. Install and start Redis
1524
1363
  ```bash
1364
+ sudo apt-get install -y redis # Linux
1365
+ brew install redis # macOS
1366
+ ```
1367
+
1368
+ 2. Add the required gems to your Gemfile
1369
+ ```ruby
1525
1370
  gem 'ru.Bee'
1526
1371
  gem 'redis'
1527
1372
  gem 'websocket'
1528
1373
  ```
1529
- 3. Add the redis url to your configuration file, unless it connects to 127.0.0.1:6379
1374
+
1375
+ 3. Add the Redis URL to your configuration, unless it defaults to `127.0.0.1:6379`
1530
1376
  ```ruby
1531
1377
  # config/base_configuration.rb
1532
- Rubee::Configuration.setup(env=:development) do |config|
1533
- #...
1378
+ Rubee::Configuration.setup(env = :development) do |config|
1379
+ ...
1534
1380
  config.redis_url = { url: "redis://localhost:6378/0", env: }
1535
1381
  end
1536
1382
  ```
1537
- 3. Add webscoket entry connection route
1383
+
1384
+ 4. Add a WebSocket entry route
1538
1385
  ```ruby
1539
1386
  # config/routes.rb
1540
1387
  Rubee::Router.draw do |router|
1541
- #...
1542
- router.get('/ws', to: 'users#websocket') # entry point to start websocket session
1543
- # So, const ws = new WebSocket("ws://website/ws"); on the client side, should establish the connection
1388
+ ...
1389
+ router.get('/ws', to: 'users#websocket')
1390
+ # On the client: const ws = new WebSocket("ws://website/ws");
1544
1391
  end
1545
1392
  ```
1546
- 4. Make model pubsubable
1393
+
1394
+ 5. Make the model pub/sub capable
1547
1395
  ```ruby
1548
1396
  # app/models/user.rb
1549
1397
  class User < Rubee::BaseModel
1550
1398
  include Rubee::PubSub::Publisher
1551
1399
  include Rubee::PubSub::Subscriber
1552
- #...
1400
+ ...
1553
1401
  end
1554
1402
  ```
1555
- 5. Enable websocket in your controller and implement required methods
1403
+
1404
+ 6. Enable WebSocket in your controller and implement the required methods
1556
1405
  ```ruby
1557
1406
  # app/controllers/users_controller.rb
1558
1407
  class UsersController < Rubee::BaseController
1559
- attach_websocket! # this will handle websocket connections and direct them to the controller methods: publish, subscribe, unsubscribe
1560
-
1408
+ attach_websocket! # handles WebSocket connections and routes them to publish, subscribe, unsubscribe
1561
1409
 
1562
- # Subscribe is expected to get next params from the client:
1563
- # { action: 'subscribe', 'channel': 'default', 'id': '123', 'subscriber': 'User' }
1564
- # where
1565
- # - action corresponds to the method name
1566
- # - channel is the name of the channel
1567
- # - id is the id of the user
1568
- # - subscriber is the name of the model
1410
+ # Expected client params: { action: 'subscribe', channel: 'default', id: '123', subscriber: 'User' }
1569
1411
  def subscribe
1570
- channel = params[:channel]
1571
- sender_id = params[:options][:id] # id moved to options
1572
- io = params[:options][:io] # io is a websocket connection
1412
+ channel = params[:channel]
1413
+ sender_id = params[:options][:id]
1414
+ io = params[:options][:io]
1573
1415
 
1574
- User.sub(channel, sender_id, io) do |channel, args| # subscribe the user for the channel updates
1575
- websocket_connections.register(channel, args[:io]) # register the websocket connection
1416
+ User.sub(channel, sender_id, io) do |channel, args|
1417
+ websocket_connections.register(channel, args[:io])
1576
1418
  end
1577
- # return websocket response
1578
1419
  response_with(object: { type: 'system', channel: params[:channel], status: :subscribed }, type: :websocket)
1579
1420
  rescue StandardError => e
1580
1421
  response_with(object: { type: 'system', error: e.message }, type: :websocket)
1581
1422
  end
1582
1423
 
1583
- # Unsubscribe is expected to get next params from the client:
1584
- # { action: 'unsubscribe', 'channel': 'default', 'id': '123', 'subscriber': 'User' }
1585
- # where
1586
- # - action corresponds to the method name
1587
- # - channel is the name of the channel
1588
- # - id is the id of the user
1589
- # - subscriber is the name of the model
1424
+ # Expected client params: { action: 'unsubscribe', channel: 'default', id: '123', subscriber: 'User' }
1590
1425
  def unsubscribe
1591
- channel = params[:channel]
1426
+ channel = params[:channel]
1592
1427
  sender_id = params[:options][:id]
1593
- io = params[:options][:io]
1428
+ io = params[:options][:io]
1594
1429
 
1595
1430
  User.unsub(channel, sender_id, io) do |channel, args|
1596
1431
  websocket_connections.remove(channel, args[:io])
1597
1432
  end
1598
-
1599
1433
  response_with(object: params.merge(type: 'system', status: :unsubscribed), type: :websocket)
1600
1434
  rescue StandardError => e
1601
1435
  response_with(object: { type: 'system', error: e.message }, type: :websocket)
1602
1436
  end
1603
- # Publish is expected to get next params from the client:
1604
- # { action: 'publish', 'channel': 'default', 'message': 'Hello world', 'id': '123', 'subscriber': 'User' }
1605
- # where
1606
- # - action corresponds to the method name
1607
- # - channel is the name of the channel
1608
- # - id is the id of the user
1609
- # - subscriber is the name of the model
1437
+
1438
+ # Expected client params: { action: 'publish', channel: 'default', message: 'Hello', id: '123', subscriber: 'User' }
1610
1439
  def publish
1611
1440
  args = {}
1612
1441
  User.pub(params[:channel], message: params[:message]) do |channel|
1613
- # Here we pack args with any additional data client might need
1614
- user = User.find(params[:options][:id])
1615
- args[:message] = params[:message]
1616
- args[:sender] = params[:options][:id]
1442
+ user = User.find(params[:options][:id])
1443
+ args[:message] = params[:message]
1444
+ args[:sender] = params[:options][:id]
1617
1445
  args[:sender_name] = user.email
1618
1446
  websocket_connections.stream(channel, args)
1619
1447
  end
1620
-
1621
1448
  response_with(object: { type: 'system', message: params[:message], status: :published }, type: :websocket)
1622
1449
  rescue StandardError => e
1623
1450
  response_with(object: { type: 'system', error: e.message }, type: :websocket)
1624
1451
  end
1625
1452
  end
1626
1453
  ```
1627
- If you are interested to see chat app example, please check [chat](https://github.com/nucleom42/rubee-chat)
1454
+
1455
+ For a full chat application example, see [rubee-chat](https://github.com/nucleom42/rubee-chat).
1628
1456
 
1629
1457
  [Back to content](#content)
1458
+
1459
+ ## Bee assistant
1460
+
1461
+ ru.Bee ships with a built-in CLI assistant called `bee`. It answers questions about the framework directly in your terminal, using a local TF-IDF knowledge base built from the project documentation. Optionally, it routes answers through a local Ollama language model for richer, more conversational responses.
1462
+
1463
+ No external API keys or internet connection are required in the default mode.
1464
+
1465
+ ### Building the knowledge base
1466
+
1467
+ Before using the assistant for the first time, generate the knowledge base from the README:
1468
+
1469
+ ```bash
1470
+ rubee bee generate # or: rubee bee gen
1471
+ ```
1472
+
1473
+ This parses the documentation, computes TF-IDF vectors, and writes a `bee_knowledge.json` file to `lib/rubee/cli/`. Re-run this command any time the documentation is updated.
1474
+
1475
+ ### Interactive mode
1476
+
1477
+ Start an interactive session and ask questions conversationally:
1478
+
1479
+ ```bash
1480
+ rubee bee
1481
+ ```
1482
+
1483
+ ```
1484
+ ⬡ ⬢ ⬢ ru.Bee — domestic AI assistant
1485
+ ──────────────────────────────────────────────
1486
+ Ask me anything about the ru.Bee framework.
1487
+ Type exit to leave • rubee bee generate to retrain.
1488
+ You: How do I run the server?
1489
+ ```
1490
+
1491
+ Type `exit`, `quit`, `bye`, or `q` to leave the session.
1492
+
1493
+ ### Single-shot mode
1494
+
1495
+ Pass a question directly as a command-line argument to get one answer and exit:
1496
+
1497
+ ```bash
1498
+ rubee bee how do hooks work
1499
+ rubee bee what databases are supported
1500
+ rubee bee how do I set up JWT authentication
1501
+ ```
1502
+
1503
+ ### LLM mode
1504
+
1505
+ If you have [Ollama](https://ollama.com) installed and running locally, enable LLM mode for more detailed answers. The assistant retrieves the most relevant documentation and passes it as context to the model.
1506
+
1507
+ ```bash
1508
+ rubee bee --llm # interactive mode, default model (qwen2.5:1.5b)
1509
+ rubee bee --llm=llama3.2 # interactive mode, specific model
1510
+ rubee bee --llm how do hooks work # single-shot LLM answer
1511
+ rubee bee --llm=qwen2.5:0.5b how do I configure WebSocket # single-shot with specific model
1512
+ ```
1513
+
1514
+ If the specified model is not available locally, the assistant automatically pulls it from Ollama before answering, displaying a live download progress bar.
1515
+
1516
+ ### Environment options
1517
+
1518
+ ```bash
1519
+ OLLAMA_URL=http://remote-host:11434 rubee bee --llm # use a custom Ollama endpoint
1520
+ BEE_KNOWLEDGE=/path/to/custom.json rubee bee # use a custom knowledge base file
1521
+ BEE_DEBUG=1 rubee bee --llm # write LLM debug output to /tmp/bee_ollama_debug.txt
1522
+ ```
1523
+
1524
+ ### Suggestions
1525
+
1526
+ After every answer, the assistant suggests up to five related topics you might want to explore next, along with a link to the full documentation at https://rubee.dedyn.io/.
1527
+
1528
+ ### Command reference
1529
+
1530
+ ```bash
1531
+ rubee bee generate # build the knowledge base from the README
1532
+ rubee bee gen # alias for generate
1533
+ rubee bee # start interactive mode
1534
+ rubee bee <question> # single-shot answer
1535
+ rubee bee --llm # interactive LLM mode (default model: qwen2.5:1.5b)
1536
+ rubee bee --llm=<model> # interactive LLM mode with a specific Ollama model
1537
+ rubee bee --llm <question> # single-shot LLM answer
1538
+ rubee bee --llm=<model> <question> # single-shot with a specific model
1539
+ ```
1540
+
1541
+ [Back to content](#content)
1542
+
1630
1543
  ### Contributing
1631
1544
 
1632
- If you are interested in contributing to ru.Bee,
1633
- please read the [Contributing]()https://github.com/nucleom42/rubee/blob/main/contribution.md) guide.
1634
- Also feel free to open an [issue](https://github.com/nucleom42/rubee/issues) if you apot one.
1635
- Have an idea or you wnat to discuss something?
1636
- Please open a [discussion](https://github.com/nucleom42/rubee/discussions)
1545
+ If you are interested in contributing to ru.Bee, please read the [Contributing](https://github.com/nucleom42/rubee/blob/main/contribution.md) guide.
1546
+ Feel free to open an [issue](https://github.com/nucleom42/rubee/issues) if you spot one.
1547
+ Have an idea or want to discuss something? Open a [discussion](https://github.com/nucleom42/rubee/discussions).
1637
1548
 
1638
1549
  ## Roadmap
1639
- Please refer the [Roadmap](https://github.com/nucleom42/rubee/blob/main/roadmap.md)
1550
+
1551
+ Please refer to the [Roadmap](https://github.com/nucleom42/rubee/blob/main/roadmap.md).
1640
1552
 
1641
1553
  ## License
1554
+
1642
1555
  This project is released under the [MIT License](https://github.com/nucleom42/rubee/blob/main/LICENSE).