ru.Bee 2.7.3 → 2.7.4

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: 6dae50d53499025c3bb722a77239ffaf3c7ef566dbe58e665986b62148bcbf60
4
- data.tar.gz: 012e74d3c4c3cb488462689bcd0ba0b5c56488cf77da19e3c98caf3682847b26
3
+ metadata.gz: 2661a78ea4d5fe99234d85375b48b2020bee244951103f0514f51ad727804824
4
+ data.tar.gz: 581395ecfbce635f11b6647ee1c25eadea0a68ae737b9bb1b4f88584af33d66b
5
5
  SHA512:
6
- metadata.gz: 7d22033e1be9a7583e872582bff2455715964d934905789b99065a702284b8bd8831f6bf4d2fc20c354c8a828e188cfa130b5396d443056bda1179cca9634e92
7
- data.tar.gz: f7941cc128ac31cea3c98da487fdff20da3f01acfb14bfe3d1dcba4fa2a0a7fb6ade7404af5d5bca98746689cdb42f53b9ce7fec59540e74a745de35e27d2b7e
6
+ metadata.gz: 5175ca33f398ea99103fae92e84f83dd23926ce3ab1074b95cd00a98ef1515a1c0c56635f71898a732cfc55cd3bf70630a47150cd915af04a7b31ae174b22d25
7
+ data.tar.gz: 215401fd826cc1e003a6cfec1619ca8704370d319eae717d874f27a48c13506465aeffb8572b4f3c0ff8decbc1fc19e5b727891ac8b6598d6330ce35714379ba
@@ -1,15 +1,48 @@
1
1
  module Rubee
2
2
  class SidekiqAsync
3
3
  def perform_async(**args)
4
- options = if args[:options].is_a?(Hash)
5
- [JSON.generate(args[:options])]
6
- elsif args[:options].is_a?(Array)
7
- args[:options]
8
- else
9
- [args[:options]]
4
+ options = serialize_options(args[:options])
5
+ args[:_class].perform_async(*options)
6
+ end
7
+
8
+ def perform_at(interval, **args)
9
+ options = serialize_options(args[:options])
10
+ args[:_class].perform_at(interval, *options)
11
+ end
12
+
13
+ def perform_in(interval, **args)
14
+ options = serialize_options(args[:options])
15
+ args[:_class].perform_in(interval, *options)
16
+ end
17
+
18
+ def perform_later(interval, **args)
19
+ perform_in(interval, **args)
20
+ end
21
+
22
+ def perform_bulk(jobs_args)
23
+ jobs_args.map! do |args|
24
+ options = serialize_options(args[:options])
25
+ { args: options }
10
26
  end
11
27
 
12
- args[:_class].perform_async(*options)
28
+ args[:_class].perform_bulk(jobs_args)
29
+ end
30
+
31
+ def set(options, **args)
32
+ serialized_options = serialize_options(args[:options])
33
+ args[:_class].set(options).perform_async(*serialized_options)
34
+ end
35
+
36
+ private
37
+
38
+ def serialize_options(options)
39
+ if options.is_a?(Hash)
40
+ [JSON.generate(options)]
41
+ elsif options.is_a?(Array)
42
+ options
43
+ else
44
+ [options]
45
+ end
13
46
  end
14
47
  end
15
48
  end
data/lib/rubee.rb CHANGED
@@ -20,7 +20,7 @@ module Rubee
20
20
  RUBEE_SUPPORT = { "Rubee::Support::Hash" => Hash, "Rubee::Support::String" => String }
21
21
  end
22
22
 
23
- VERSION = '2.7.3'
23
+ VERSION = '2.7.4'
24
24
 
25
25
  require_relative 'rubee/router'
26
26
  require_relative 'rubee/logger'
data/readme.md CHANGED
@@ -91,6 +91,8 @@ The comparison is based on generic and subjective information available on the i
91
91
  - [Rubee::Support](#rubee-support)
92
92
  - [Testing](#testing)
93
93
  - [Background jobs](#background-jobs)
94
+ - [Sidekiq engine](#sidekiq-engine)
95
+ - [ThreadAsync engine](#threadasync-engine)
94
96
  - [Modular application](#modular-application)
95
97
  - [Logger](#logger)
96
98
  - [WebSocket](#websocket)
@@ -1240,14 +1242,24 @@ rubee test models/user_model_test.rb --line=12 # run a specific line
1240
1242
 
1241
1243
  ## Background jobs
1242
1244
 
1243
- ### Sidekiq engine
1245
+ There are currently two ways to integrate background jobs into your application:
1244
1246
 
1245
- 1. Add Sidekiq to your Gemfile
1246
- ```bash
1247
+ - [Sidekiq](#sidekiq-engine)
1248
+ - [ThreadAsync](#threadasync-engine)
1249
+
1250
+ ## Sidekiq Engine
1251
+
1252
+ ## Installation & Setup
1253
+
1254
+ ### 1. Add Sidekiq to your Gemfile
1255
+
1256
+ ```ruby
1247
1257
  gem 'sidekiq'
1258
+ gem 'rack-session' # Required for Sidekiq Web UI
1248
1259
  ```
1249
1260
 
1250
- 2. Configure the adapter for the desired environment
1261
+ ### 2. Configure the adapter for the desired environment
1262
+
1251
1263
  ```ruby
1252
1264
  # config/base_configuration.rb
1253
1265
  Rubee::Configuration.setup(env = :development) do |config|
@@ -1256,20 +1268,32 @@ Rubee::Configuration.setup(env = :development) do |config|
1256
1268
  end
1257
1269
  ```
1258
1270
 
1259
- 3. Install dependencies
1271
+ ### 3. Install dependencies
1272
+
1260
1273
  ```bash
1261
1274
  bundle install
1262
1275
  ```
1263
1276
 
1264
- 4. Start Redis
1277
+ ### 4. Start Redis
1278
+
1279
+ Redis must be running before starting Sidekiq.
1280
+
1265
1281
  ```bash
1282
+ # Start Redis server
1266
1283
  redis-server
1284
+
1285
+ # Or in background (macOS with Homebrew)
1286
+ brew services start redis
1287
+
1288
+ # Verify Redis is running
1289
+ redis-cli ping
1290
+ # Should respond: PONG
1267
1291
  ```
1268
1292
 
1269
- 5. Add a Sidekiq configuration file
1293
+ ### 5. Add Sidekiq configuration file
1294
+
1270
1295
  ```yaml
1271
1296
  # config/sidekiq.yml
1272
-
1273
1297
  development:
1274
1298
  redis: redis://localhost:6379/0
1275
1299
  concurrency: 5
@@ -1279,29 +1303,302 @@ development:
1279
1303
  high:
1280
1304
  ```
1281
1305
 
1282
- 6. Create a Sidekiq worker
1306
+ ### 6. Create Sidekiq boot file
1307
+
1283
1308
  ```ruby
1284
- # app/async/test_async_runner.rb
1285
- require_relative 'extensions/asyncable' unless defined? Asyncable
1309
+ # inits/sidekiq.rb
1286
1310
 
1311
+ # Configure Redis connection
1312
+ Sidekiq.configure_server do |config|
1313
+ config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0') }
1314
+ end
1315
+
1316
+ Sidekiq.configure_client do |config|
1317
+ config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0') }
1318
+ end
1319
+
1320
+ # Load Rubee application context
1321
+ unless Object.const_defined?('Rubee')
1322
+ require 'rubee'
1323
+
1324
+ # Load environment variables from the Ruby file.
1325
+ require_relative 'dev.rb' if File.exist?(File.join(__dir__, 'dev.rb'))
1326
+
1327
+ # Trigger Rubee autoload
1328
+ Rubee::Autoload.call
1329
+ end
1330
+ ```
1331
+
1332
+ ### 7. Create a Sidekiq worker
1333
+
1334
+ ```ruby
1335
+ # app/workers/test_async_runner.rb
1287
1336
  class TestAsyncRunner
1288
1337
  include Rubee::Asyncable
1289
1338
  include Sidekiq::Worker
1290
1339
 
1291
- sidekiq_options queue: :default
1340
+ sidekiq_options queue: :default, retry: 3
1292
1341
 
1293
1342
  def perform(options)
1294
- User.create(email: options['email'], password: options['password'])
1343
+ options = parse_options(options)
1344
+
1345
+ User.create(
1346
+ email: options['email'],
1347
+ password: options['password']
1348
+ )
1349
+ end
1350
+
1351
+ private
1352
+
1353
+ def parse_options(options)
1354
+ return options unless options.is_a?(String)
1355
+
1356
+ begin
1357
+ JSON.parse(options)
1358
+ rescue JSON::ParserError
1359
+ options
1360
+ end
1295
1361
  end
1296
1362
  end
1297
1363
  ```
1298
1364
 
1299
- 7. Use it in your codebase
1365
+ ### 8. Use it in your codebase
1366
+
1300
1367
  ```ruby
1301
- TestAsyncRunner.new.perform_async(options: { "email" => "new@new.com", "password" => "123" })
1368
+
1369
+ TestAsyncRunner.new.perform_async(
1370
+ "email" => "new@new.com",
1371
+ "password" => "123"
1372
+ )
1302
1373
  ```
1303
1374
 
1304
- ### Default engine — ThreadAsync
1375
+ ---
1376
+
1377
+ ## Running Sidekiq
1378
+
1379
+ ### Start Sidekiq (Foreground)
1380
+
1381
+ ```bash
1382
+ bundle exec sidekiq -C config/sidekiq.yml -r ./inits/sidekiq.rb
1383
+ ```
1384
+
1385
+ ### Start Sidekiq (Background/Daemon)
1386
+
1387
+ ```bash
1388
+ # Start as daemon
1389
+ bundle exec sidekiq -d -C config/sidekiq.yml -r ./inits/sidekiq.rb
1390
+
1391
+ # Stop daemon
1392
+ kill -TERM $(cat tmp/pids/sidekiq.pid)
1393
+
1394
+ # View logs
1395
+ tail -f log/sidekiq.log
1396
+ ```
1397
+
1398
+ ### Helper Scripts
1399
+
1400
+ Create convenient management scripts:
1401
+
1402
+ ```bash
1403
+ # bin/sidekiq_start
1404
+ #!/bin/bash
1405
+ bundle exec sidekiq -d \
1406
+ -C config/sidekiq.yml \
1407
+ -r ./inits/sidekiq.rb \
1408
+ ```
1409
+
1410
+ ```bash
1411
+ # bin/sidekiq_stop
1412
+ #!/bin/bash
1413
+ if [ -f tmp/pids/sidekiq.pid ]; then
1414
+ kill -TERM $(cat tmp/pids/sidekiq.pid)
1415
+ rm tmp/pids/sidekiq.pid
1416
+ echo "✓ Sidekiq stopped"
1417
+ else
1418
+ echo "✗ Sidekiq is not running"
1419
+ fi
1420
+ ```
1421
+
1422
+ Make them executable:
1423
+ ```bash
1424
+ chmod +x bin/sidekiq_start bin/sidekiq_stop
1425
+ ```
1426
+
1427
+ ---
1428
+
1429
+ ## Enable Sidekiq Web Dashboard
1430
+
1431
+ ### 1. Create Sidekiq middleware
1432
+
1433
+ ```ruby
1434
+ # inits/middlewares/sidekiq_middleware.rb
1435
+ require 'sidekiq/web'
1436
+ require 'rack/session'
1437
+
1438
+ class SidekiqMiddleware
1439
+ def initialize(app)
1440
+ @app = app
1441
+
1442
+ # Get or generate session secret
1443
+ session_secret = ENV.fetch('SESSION_SECRET') { generate_secret }
1444
+
1445
+ # Build Sidekiq Web app with authentication
1446
+ @sidekiq_app = Rack::Builder.new do
1447
+ # Session support (required for CSRF protection)
1448
+ use Rack::Session::Cookie,
1449
+ secret: session_secret,
1450
+ same_site: true,
1451
+ max_age: 86400
1452
+
1453
+ # Basic authentication
1454
+ use Rack::Auth::Basic, "Sidekiq Dashboard" do |username, password|
1455
+ username == ENV.fetch('SIDEKIQ_USERNAME', 'admin') &&
1456
+ password == ENV.fetch('SIDEKIQ_PASSWORD', 'password')
1457
+ end
1458
+
1459
+ run Sidekiq::Web
1460
+ end
1461
+ end
1462
+
1463
+ def call(env)
1464
+ if env['PATH_INFO'].start_with?('/sidekiq')
1465
+ # Route to Sidekiq Web UI
1466
+ env['SCRIPT_NAME'] = '/sidekiq'
1467
+ env['PATH_INFO'] = env['PATH_INFO'].sub(%r{^/sidekiq}, '') || '/'
1468
+ @sidekiq_app.call(env)
1469
+ else
1470
+ # Pass through to main app
1471
+ @app.call(env)
1472
+ end
1473
+ end
1474
+
1475
+ private
1476
+
1477
+ def generate_secret
1478
+ secret_file = '.session.key'
1479
+
1480
+ if File.exist?(secret_file)
1481
+ File.read(secret_file).strip
1482
+ else
1483
+ require 'securerandom'
1484
+ secret = SecureRandom.hex(64)
1485
+ File.write(secret_file, secret)
1486
+ puts "Generated new session secret in #{secret_file}"
1487
+ secret
1488
+ end
1489
+ end
1490
+ end
1491
+ ```
1492
+
1493
+ ### 2. Access the dashboard
1494
+
1495
+ Start your Rubee application and visit /sidekiq:
1496
+ ```
1497
+ http://localhost:7000/sidekiq
1498
+ ```
1499
+
1500
+ Login with credentials from your `/inits/dev.rb` file for developmet purposes.
1501
+
1502
+ ---
1503
+
1504
+ ## Worker Examples
1505
+
1506
+ ### Worker with Database Records
1507
+
1508
+ ```ruby
1509
+ # app/workers/booking_confirmation_worker.rb
1510
+ class BookingConfirmationWorker
1511
+ include Rubee::Asyncable
1512
+ include Sidekiq::Worker
1513
+
1514
+ sidekiq_options queue: :mailers, retry: 3
1515
+
1516
+ def perform(options)
1517
+ options = parse_options(options)
1518
+
1519
+ # Fetch records from database
1520
+ service = Service.find(options['service_id'])
1521
+ time_slot = TimeSlot.find(options['time_slot_id'])
1522
+
1523
+ Mailer.booking_confirmation(
1524
+ to: options['to'],
1525
+ client_name: options['client_name'],
1526
+ service: service,
1527
+ time_slot: time_slot
1528
+ )
1529
+ end
1530
+
1531
+ private
1532
+
1533
+ def parse_options(options)
1534
+ return options unless options.is_a?(String)
1535
+ JSON.parse(options) rescue options
1536
+ end
1537
+ end
1538
+
1539
+ # Usage
1540
+ BookingConfirmationWorker.new.perform_async(options: {
1541
+ "to" => "client@example.com",
1542
+ "client_name" => "John Doe",
1543
+ "service_id" => 15,
1544
+ "time_slot_id" => 91
1545
+ })
1546
+ ```
1547
+
1548
+ ---
1549
+ ## Monitoring & Troubleshooting
1550
+
1551
+ ### Check Sidekiq Status
1552
+
1553
+ ```bash
1554
+ # View running processes
1555
+ ps aux | grep sidekiq
1556
+
1557
+ # Check Redis connection
1558
+ redis-cli ping
1559
+
1560
+ # View queue sizes
1561
+ redis-cli LLEN queue:default
1562
+ ```
1563
+
1564
+ ### Common Issues
1565
+
1566
+ **Workers not processing:**
1567
+ - Ensure Redis is running: `redis-cli ping`
1568
+ - Check Sidekiq is started: `ps aux | grep sidekiq`
1569
+ - Verify queue names match in worker and config
1570
+
1571
+ **Authentication errors on Web UI:**
1572
+ - Ensure `rack-session` gem is installed
1573
+ - Check SESSION_SECRET is at least 64 bytes
1574
+ - Verify SIDEKIQ_USERNAME and SIDEKIQ_PASSWORD are set
1575
+
1576
+ **Jobs failing:**
1577
+ - Check `log/sidekiq.log` for errors
1578
+ - View failed jobs in Web UI at `/sidekiq/retries`
1579
+ - Verify environment variables are loaded in `inits/sidekiq.rb`
1580
+
1581
+ ---
1582
+
1583
+ ## Best Practices
1584
+
1585
+ 1. **Pass IDs, not objects** - Use `booking.id`, not `booking` itself
1586
+ 2. **Keep jobs small** - One job should do one thing
1587
+ 3. **Make jobs idempotent** - Safe to run multiple times
1588
+ 4. **Set appropriate retries** - Critical: more retries, notifications: fewer
1589
+ 5. **Use different queues** - Separate critical from low-priority jobs
1590
+ 6. **Handle JSON properly** - Always parse options in `perform` method
1591
+ 7. **Monitor your queues** - Use Web UI to watch for backlogs
1592
+
1593
+ ---
1594
+
1595
+ ## Additional Resources
1596
+
1597
+ - [Sidekiq Official Docs](https://github.com/sidekiq/sidekiq/wiki)
1598
+ - [Best Practices](https://github.com/sidekiq/sidekiq/wiki/Best-Practices)
1599
+ - [Error Handling](https://github.com/sidekiq/sidekiq/wiki/Error-Handling)
1600
+
1601
+ ### ThreadAsync engine
1305
1602
 
1306
1603
  The default adapter is `ThreadAsync`. It is not yet recommended for production — use with caution.
1307
1604
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ru.Bee
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.3
4
+ version: 2.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oleg Saltykov