ru.Bee 1.11.1 → 2.1.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.
@@ -345,4 +345,98 @@ zip: '555555')
345
345
  end
346
346
  end
347
347
  end
348
+
349
+ describe 'pubsub' do
350
+ describe 'when Rubee::PubSub::Subscriber is included' do
351
+ before { User.include(Rubee::PubSub::Subscriber) }
352
+
353
+ it 'reveals sub method' do
354
+ _(User.respond_to?(:sub)).must_equal(true)
355
+ end
356
+
357
+ it 'reveals unsub method' do
358
+ _(User.respond_to?(:unsub)).must_equal(true)
359
+ end
360
+ end
361
+
362
+ describe 'when Rubee::PubSub::Publisher is included' do
363
+ before { User.include(Rubee::PubSub::Publisher) }
364
+
365
+ it 'reveals pub method' do
366
+ _(User.respond_to?(:pub)).must_equal(true)
367
+ end
368
+ end
369
+
370
+ describe '.sub' do
371
+ before do
372
+ User.include(Rubee::PubSub::Subscriber)
373
+ User.include(Rubee::PubSub::Publisher)
374
+ end
375
+
376
+ user = User.create(email: 'ok-test@test.com', password: '123')
377
+
378
+ describe 'when sub with channel and args' do
379
+ it 'returns true' do
380
+ _(User.sub("ok", [user.id.to_s])).must_equal(true)
381
+
382
+ User.unsub("ok", [user.id.to_s])
383
+ user.destroy
384
+ end
385
+ end
386
+ end
387
+
388
+ describe '.unsub' do
389
+ before do
390
+ User.include(Rubee::PubSub::Subscriber)
391
+ User.include(Rubee::PubSub::Publisher)
392
+ end
393
+
394
+ describe 'when unsub with channel and args' do
395
+ it 'returns true' do
396
+ _(User.unsub("ok", ["123456"])).must_equal(true)
397
+ end
398
+ end
399
+ end
400
+
401
+ describe 'pub flow' do
402
+ describe 'when pub with channel and args' do
403
+ after do
404
+ User.destroy_all(cascade: true)
405
+ end
406
+
407
+ it 'fan out ouput' do
408
+ User.include(Rubee::PubSub::Subscriber)
409
+ User.include(Rubee::PubSub::Publisher)
410
+
411
+ user_one = User.create(email: 'ok-test1@test.com', password: '123')
412
+ user_two = User.create(email: 'ok-test@2test.com', password: '123')
413
+ user_three = User.create(email: 'ok-test3@test.com', password: '123')
414
+
415
+ User.singleton_class.define_method(:on_pub) do |channel, *args, **options|
416
+ id = args.first
417
+ user = User.find(id)
418
+ if user
419
+ user.update(password: '321')
420
+ else
421
+ raise "User with id=#{id} not found"
422
+ end
423
+ end
424
+
425
+ User.sub("ok", [user_one.id.to_s])
426
+ User.sub("ok", [user_two.id.to_s])
427
+ User.sub("ok", [user_three.id.to_s])
428
+
429
+ User.pub("ok", message: "hello")
430
+
431
+ User.unsub("ok", [user_one.id.to_s])
432
+ User.unsub("ok", [user_two.id.to_s])
433
+ User.unsub("ok", [user_three.id.to_s])
434
+
435
+ _(user_one.reload.password).must_equal('321')
436
+ _(user_two.reload.password).must_equal('321')
437
+ _(user_three.reload.password).must_equal('321')
438
+ end
439
+ end
440
+ end
441
+ end
348
442
  end
data/lib/tests/test.db CHANGED
Binary file
@@ -39,3 +39,9 @@ ensure
39
39
  $stdout = old_stdout
40
40
  end
41
41
 
42
+ def raise_error
43
+ yield
44
+ rescue => e
45
+ e
46
+ end
47
+
data/readme.md CHANGED
@@ -6,9 +6,9 @@
6
6
  ![GitHub Repo stars](https://img.shields.io/github/stars/nucleom42/rubee?style=social)
7
7
 
8
8
 
9
- # <img src="lib/images/rubee.svg" alt="RUBEE" height="40"> ... RUBEE
9
+ # <img src="lib/images/rubee.svg" alt="ru.Bee" height="40"> ... ru.Bee
10
10
 
11
- Rubee is a Ruby-based web framework designed to streamline the development of modular monolith web applications. \
11
+ ru.Bee is a Ruby-based web framework designed to streamline the development of modular monolith web applications. \
12
12
  Under the hood, it leverages the power of Ruby and Rack backed by Puma, offering a clean, efficient, and flexible architecture. \
13
13
  It offers a structured approach to building scalable, maintainable, and React-ready projects, \
14
14
  making it an ideal choice for developers seeking a balance between monolithic simplicity and modular flexibility.
@@ -17,9 +17,13 @@ Want to get a quick API server up and runing? You can do it for real quick!
17
17
  <br />
18
18
  [![Watch the demo](https://img.youtube.com/vi/ko7H70s7qq0/hqdefault.jpg)](https://www.youtube.com/watch?v=ko7H70s7qq0)
19
19
 
20
+ 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. \
21
+ <br />
22
+ [![Watch the demo](https://img.youtube.com/vi/gp8IheKBNm4/hqdefault.jpg)](https://www.youtube.com/watch?v=gp8IheKBNm4)
23
+
20
24
  ## Production ready
21
25
 
22
- Take a look on the rubee demo site with all documentation stored in there: https://rubee.dedyn.io/
26
+ Take a look on the ru.Bee demo site with all documentation stored in there: https://rubee.dedyn.io/
23
27
  Want to explore how it built? https://github.com/nucleom42/rubee-site
24
28
 
25
29
  ## Stress tested
@@ -41,17 +45,17 @@ Transfer/sec: 140.07KB
41
45
  - Average latency: ~305 ms
42
46
  - Total requests handled: 9,721
43
47
  - Hardware: Raspberry Pi 5(8 Gb) (single board computer)
44
- - Server: Rubee app hosted via Nginx + HTTPS
48
+ - Server: ru.Bee app hosted via Nginx + HTTPS
45
49
 
46
- This demonstrates RUBEE’s efficient architecture and suitability for lightweight deployments — even on low-power hardware.
50
+ This demonstrate ru.Bee’s efficient architecture and suitability for lightweight deployments — even on low-power hardware.
47
51
 
48
52
  ## Comparison
49
- Here below is a **short web frameworks comparison** built with Ruby, so you can evaluate your choice with RUBEE.
53
+ Here below is a **short web frameworks comparison** built with Ruby, so you can evaluate your choice with ru.Bee.
50
54
 
51
55
  **Disclaimer:**
52
56
  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.
53
57
 
54
- | Feature / Framework | **Rubee** | Rails | Sinatra | Hanami | Padrino | Grape |
58
+ | Feature / Framework | **ru.Bee** | Rails | Sinatra | Hanami | Padrino | Grape |
55
59
  |---------------------|-----------|-------|---------|--------|---------|-------|
56
60
  | **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 |
57
61
  | **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 |
@@ -73,7 +77,9 @@ The comparison is based on a very generic and subjective information open in the
73
77
  - [Database](#database)
74
78
  - [Views](#views)
75
79
  - [Hooks](#hooks)
80
+ - [Validations](#validations)
76
81
  - [JWT based authentification](#jwt-based-authentification)
82
+ - [OAuth2 based authentification](#oauth-authentification)
77
83
  - [Rubee commands](#rubee-commands)
78
84
  - [Generate commands](#generate-commands)
79
85
  - [Migration commands](#migration-commands)
@@ -82,12 +88,10 @@ The comparison is based on a very generic and subjective information open in the
82
88
  - [Background jobs](#background-jobs)
83
89
  - [Modular](#modualar-application)
84
90
  - [Logger](#logger)
91
+ - [Websocket](#websocket)
85
92
 
86
93
  You can read it on the demo: [site](https://rubee.dedyn.io/)
87
94
 
88
- 🚧 The doc site is on update mode now. We are working on it.
89
- Please refer to the documentation shown below.
90
-
91
95
  ## Features
92
96
 
93
97
  Lightweight – A minimal footprint focused on serving Ruby applications efficiently.
@@ -105,9 +109,9 @@ Databases – Supports SQLite3, PostgreSQL, MySQL, and more via the Sequel gem.
105
109
  <br>
106
110
  Views – JSON, ERB, and plain HTML out of the box.
107
111
  <br>
108
- React Ready – React is supported as a first-class Rubee view engine.
112
+ React Ready – React is supported as a first-class ru.Bee view engine.
109
113
  <br>
110
- Bundlable – Charge your Rubee app with any gem you need. Update effortlessly via Bundler.
114
+ Bundlable – Charge your ru.Bee app with any gem you need. Update effortlessly via Bundler.
111
115
  <br>
112
116
  ORM-agnostic – Models are native ORM objects, but you can use them as blueprints for any data source.
113
117
  <br>
@@ -122,10 +126,14 @@ Asyncable – Plug in async adapters and use any popular background job engine.
122
126
  Console – Start an interactive console and reload on the fly.
123
127
  <br>
124
128
  Background Jobs – Schedule and process background jobs using your preferred async stack.
129
+ <br>
130
+ Websocket – Serve and handle WebSocket connections.
131
+ <br>
132
+ Logger – Use any logger you want.
125
133
 
126
134
  ## Installation
127
135
 
128
- 1. Install RUBEE
136
+ 1. Install ru.Bee
129
137
  ```bash
130
138
  gem install ru.Bee
131
139
  ```
@@ -150,7 +158,7 @@ Make sure:
150
158
  bundle install
151
159
  ```
152
160
 
153
- 4. Run RUBEE server. Default port is 7000
161
+ 4. Run RUBER server. Default port is 7000
154
162
  ```bash
155
163
  rubee start # or rubee start_dev for development
156
164
 
@@ -215,7 +223,7 @@ This will generate the following files
215
223
  [Back to content](#content)
216
224
 
217
225
  ## Model
218
- Model in RUBEE is just simple ruby object that can be serilalized in the view
226
+ Model in ru.Bee is just simple ruby object that can be serilalized in the view
219
227
  in the way it required (ie json).
220
228
  Here below is a simple example on how it can be used by rendering json from in memory object
221
229
 
@@ -354,7 +362,7 @@ irb(main):023> User.all
354
362
  => []
355
363
  ```
356
364
 
357
- Use complex queries chains and when ready serialize it back to Rubee object.
365
+ Use complex queries chains and when ready serialize it back to ru.Bee object.
358
366
  ```Ruby
359
367
  # user model
360
368
  class User < Rubee::SequelObject
@@ -391,7 +399,7 @@ irb(main):009> .where(comment_id: Comment.where(text: "test").last.id)
391
399
  irb(main):010> .then { |dataset| Comment.serialize(dataset) }
392
400
  => [#<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>]
393
401
  ```
394
- This is recommended when you want to run one query and serialize it back to Rubee object only once.
402
+ This is recommended when you want to run one query and serialize it back to ru.Bee object only once.
395
403
  So it may safe some resources.
396
404
 
397
405
  [Back to content](#content)
@@ -407,7 +415,7 @@ If you feel comfortable you can play with retry configuration parameters:
407
415
  config.db_busy_timeout = { env:, value: 1000 } # this is busy timeout in ms, before raising bussy error
408
416
  ```
409
417
 
410
- For Rubee model class persist methods create and update retry will be added automatically. However, \
418
+ For ru.Bee model class persist methods create and update retry will be added automatically. However, \
411
419
  if you want to do it with Sequel dataset you need to do it yourself:
412
420
 
413
421
  ```ruby
@@ -416,7 +424,7 @@ if you want to do it with Sequel dataset you need to do it yourself:
416
424
  [Back to content](#content)
417
425
 
418
426
  ## Routing
419
- Rubee uses explicit routes. In the routes.rb yout can define routes for any of the main HTTP methods. \
427
+ ru.Bee uses explicit routes. In the routes.rb yout can define routes for any of the main HTTP methods. \
420
428
  You can also add any matched parameter denoted by a pair of `{ }` in the path of the route. \
421
429
  Eg. `/path/to/{a_key}/somewhere`
422
430
 
@@ -442,7 +450,7 @@ route.{http_method} {path}, to: "{controller}#{action}",
442
450
  ```
443
451
 
444
452
  ### Defining Model attributes in routes
445
- One of Rubee's unique traits is where we can define our models for generation. \
453
+ One of ru.Bee's unique traits is where we can define our models for generation. \
446
454
  You've seen above one possible way you can set up.
447
455
 
448
456
  ```ruby
@@ -571,13 +579,13 @@ Will generate:
571
579
 
572
580
  ### Modualar application
573
581
 
574
- You can also use RUBEE to create modular applications.\
582
+ You can also use ru.Bee to create modular applications.\
575
583
  And attach as many subprojects you need.
576
584
  Main philosophy of attach functinality is to keep the main project clean and easy to maintain. It will still\
577
585
  share data with the main app. So where to define a border between the main app and subprojects is up to developer.
578
586
  Howerver by attching new subproject you will get a new folder and files configured and namespaced respectively.
579
587
 
580
- So if you need to extend your main app with a separate project, you can do it easily in RUBEE.
588
+ So if you need to extend your main app with a separate project, you can do it easily in ruBEE.
581
589
  1. Attach new subrpoject
582
590
 
583
591
  ```bash
@@ -648,7 +656,7 @@ rubee start # or rubee start_dev for development
648
656
  [Back to content](#content)
649
657
 
650
658
  ## Views
651
- View in RUBEE is just a plain html/erb/react file that can be rendered from the controller.
659
+ View in ru.Bee is just a plain html/erb/react file that can be rendered from the controller.
652
660
 
653
661
  ## Templates over erb
654
662
 
@@ -793,7 +801,7 @@ function Users() {
793
801
 
794
802
  ## Object hooks
795
803
 
796
- In RUBEE by extending Hookable module any Ruby object can be charged with hooks (logic),
804
+ In ru.Bee by extending Hookable module any Ruby object can be charged with hooks (logic),
797
805
  that can be executed before, after and around a specific method execution.
798
806
 
799
807
  Here below a controller example. However it can be used in any Ruby object, like Model etc.
@@ -858,6 +866,88 @@ world!
858
866
 
859
867
  [Back to content](#content)
860
868
 
869
+ ## Validations
870
+
871
+ In ru.Bee any class can be charged with validations. This is done by including Validatable module.
872
+ Please note, ru.Bee model is validatable by default. No need to include it explicitly.
873
+ ```ruby
874
+ class Foo
875
+ include Rubee::Validatable
876
+
877
+ attr_accessor :name, :age
878
+
879
+ def initialize(name, age)
880
+ @name = name
881
+ @age = age
882
+ end
883
+
884
+ validate do |foo|
885
+ foo
886
+ .required(:name, required: 'Name is required')
887
+ .type(String, type: 'must be a string')
888
+ .condition(->{ foo.name.length > 2 }, length: 'Name must be at least 3 characters long')
889
+
890
+ foo
891
+ .required(:age, required: 'Age is required')
892
+ .type(Integer, type: 'must be an integer')
893
+ .condition(->{ foo.age > 18 }, age: 'You must be at least 18 years old')
894
+ end
895
+ end
896
+ ```
897
+ ```bash
898
+ irb(main):068> Foo.new("Joe", "20")
899
+ =>
900
+ #<Foo:0x0000000120d7f778
901
+ @__validation_state=#<Rubee::Validatable::State:0x0000000120d7f700 @errors={age: {type: "must be an integer"}}, @valid=false>,
902
+ @age="20",
903
+ @name="Joe">
904
+ irb(main):069> foo = Foo.new("Joe", 11)
905
+ =>
906
+ #<Foo:0x0000000105f2b0b0
907
+ ...
908
+ irb(main):070> foo.valid?
909
+ => false
910
+ irb(main):071> foo.errors
911
+ => {age: {age: "You must be at least 18 years old"}}
912
+ irb(main):072> foo.age=20
913
+ => 20
914
+ irb(main):073> foo.valid?
915
+ => true
916
+ ```
917
+ Model example
918
+ ```ruby
919
+ class User < Rubee::SequelObject
920
+ attr_accessor :id, :email, :password, :created, :updated
921
+
922
+ validate do |user|
923
+ user
924
+ .required(:email, required: 'Email is required')
925
+ .condition(
926
+ ->{ user.email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i) }, email: 'Wrong email format'
927
+ )
928
+ end
929
+ validate_before_persist! # This will validate and raise error in case invalid before saving to DB
930
+ end
931
+ ```
932
+ ```bash
933
+ irb(main):089> user = User.new(email: "wrong", password: 123)
934
+ => #<User:0x000000010cda23b8 @email="wrong", @password=123>
935
+ irb(main):090> user.valid?
936
+ => false
937
+ irb(main):091> user.errors
938
+ => {email: {email: "Wrong email format"}}
939
+ irb(main):091> user.save
940
+ ...sequel_object.rb:229:in 'block in Rubee::SequelObject.validate_before_persist!': {email: {email: "Wrong email format"}} (Rubee::Validatable::Error)
941
+ irb(main):092> user.email = "ok@ok.com"
942
+ => "ok@ok.com"
943
+ irb(main):094> user.valid?
944
+ => true
945
+ irb(main):095> user.save
946
+ => true
947
+
948
+ ```
949
+ [Back to content](#content)
950
+
861
951
  ## JWT based authentification
862
952
 
863
953
  Charge you rpoject with token based authentification system and customize it for your needs.
@@ -874,7 +964,7 @@ Feel free to customize it in the /db/create_users.rb file before running migrati
874
964
  Then in the controller you can include the AuthTokenable module and use its methods:
875
965
  ```ruby
876
966
  class UsersController < Rubee::BaseController
877
- include AuthTokenable
967
+ include Rubee::AuthTokenable
878
968
  # List methods you want to restrict
879
969
  auth_methods :index # unless the user is authentificated it will return unauthentificated
880
970
 
@@ -907,10 +997,103 @@ class UsersController < Rubee::BaseController
907
997
  end
908
998
  end
909
999
  ```
1000
+ ## OAuth authentification
1001
+ If you want to plug in the OAuth 2.0 authentication, you can use the following code using OAuth2 gem:
1002
+ First thing you need to do is to add the gem to your Gemfile
1003
+ ```bash
1004
+ gem 'oauth2'
1005
+ ```
1006
+ Then use down below code as an example and add yours to your controller
1007
+ ```ruby
1008
+ class UsersController < Rubee::BaseController
1009
+ include Rubee::AuthTokenable
1010
+
1011
+ REDIRECT_URI = 'https://mysite.com/users/outh_callback'
1012
+ CLIENT_ID = ENV['GOOGLE_CLIENT_ID']
1013
+ CLIENT_SECRET = ENV['GOOGLE_CLIENT_SECRET']
1014
+
1015
+ # GET /login (login form page)
1016
+ def edit
1017
+ response_with
1018
+ end
1019
+
1020
+ # POST /users/login (login logic)
1021
+ def login
1022
+ Rubee::Logger.info(message: "Login attempt for user #{params[:email]}")
1023
+ if authentificate! # AuthTokenable method that init @token_header
1024
+ Rubee::Logger.info(message: "Successful login for user #{@authentificated_user.email}")
1025
+ response_with(type: :redirect, to: "/sections", headers: @token_header)
1026
+ else
1027
+ @error = "Wrong email or password"
1028
+ response_with(render_view: "users_edit")
1029
+ end
1030
+ end
1031
+
1032
+ # GET /users/outh_login
1033
+ def outh_login
1034
+ response_with(
1035
+ type: :redirect,
1036
+ to: auth_client.auth_code.authorize_url(
1037
+ redirect_uri: REDIRECT_URI,
1038
+ scope: 'email profile openid'
1039
+ )
1040
+ )
1041
+ end
1042
+
1043
+ # GET /users/outh_callback
1044
+ def outh_callback
1045
+ code = params[:code]
1046
+ token = auth_client.auth_code.get_token(code, redirect_uri: REDIRECT_URI)
1047
+ user_info = JSON.parse(token.get('https://www.googleapis.com/oauth2/v1/userinfo?alt=json').body)
1048
+ Rubee::Logger.debug(object: user_info, method: "outh_callback", class: "UsersController")
1049
+
1050
+ user = User.where(email: user_info['email'])&.last
1051
+ unless user
1052
+ raise "User with email #{user_info['email']} not found"
1053
+ end
1054
+
1055
+ params[:email] = user_info['email']
1056
+ params[:password] = user.password
1057
+
1058
+ if authentificate! # AuthTokenable method that init @token_header
1059
+ Rubee::Logger.info(message: "Successful Outh login for user #{@authentificated_user.email}")
1060
+ response_with(type: :redirect, to: "/sections", headers: @token_header)
1061
+ else
1062
+ @error = "Something went wrong"
1063
+ response_with(render_view: "users_edit")
1064
+ end
1065
+ rescue OAuth2::Error => e
1066
+ @error = "OAuth login failed"
1067
+ response_with(render_view: "users_edit")
1068
+ rescue StandardError => e
1069
+ @error = "Something went wrong"
1070
+ response_with(render_view: "users_edit")
1071
+ end
1072
+
1073
+ # POST /users/logout (logout logic)
1074
+ def logout
1075
+ unauthentificate! # AuthTokenable method aimed to handle logout action.
1076
+ # Make sure @zeroed_token_header is paRssed within headers options
1077
+ response_with(type: :redirect, to: "/login", headers: @zeroed_token_header)
1078
+ end
1079
+
1080
+ private
1081
+
1082
+ def auth_client
1083
+ @client ||= OAuth2::Client.new(
1084
+ CLIENT_ID,
1085
+ CLIENT_SECRET,
1086
+ site: 'https://accounts.google.com',
1087
+ authorize_url: '/o/oauth2/auth',
1088
+ token_url: 'https://oauth2.googleapis.com/token'
1089
+ )
1090
+ end
1091
+ end
1092
+ ```
910
1093
 
911
1094
  [Back to content](#content)
912
1095
 
913
- ## Rubee commands
1096
+ ## ru.Bee commands
914
1097
  ```bash
915
1098
  rubee start # start the server
916
1099
  rubee start_dev # start the server in dev mode, which restart server on changes
@@ -932,7 +1115,7 @@ rubee db run:create_apples # where create_apples is the name of the migration fi
932
1115
  rubee db structure # generate migration file for the database structure
933
1116
  ```
934
1117
 
935
- ## Rubee console
1118
+ ## ru.Bee console
936
1119
  ```bash
937
1120
  rubee console # start the console
938
1121
  # you can reload the console by typing reload, so it will pick up latest changes
@@ -947,7 +1130,7 @@ rubee test auth_tokenable_test.rb # run specific tests
947
1130
  [Back to content](#content)
948
1131
 
949
1132
 
950
- If you want to run any RUBEE command within a specific ENV make sure you added it before a command.
1133
+ If you want to run any ru.Bee command within a specific ENV make sure you added it before a command.
951
1134
  For instance if you want to run console in test environment you need to run the following command
952
1135
 
953
1136
  ```bash
@@ -1079,9 +1262,126 @@ When you trigger the controller action, the logs will look like this:
1079
1262
 
1080
1263
  [Back to content](#content)
1081
1264
 
1265
+ ## Websocket
1266
+
1267
+ With ru.Bee 2.0.0 you can use Websocket with ease!
1268
+
1269
+ Here are steps to get started:
1270
+ 1. Make sure redis server is installed and running
1271
+ ````bash
1272
+ sudo apt-get install -y redis # linux
1273
+ brew install redis # osx
1274
+ ````
1275
+ 2. Enable websocket and redis to your Gemfile
1276
+ ```bash
1277
+ gem 'ru.Bee'
1278
+ gem 'redis'
1279
+ gem 'websocket'
1280
+ ```
1281
+ 3. Add the redis url to your configuration file, unless it connects to 127.0.0.1:6379
1282
+ ```ruby
1283
+ # config/base_configuration.rb
1284
+ Rubee::Configuration.setup(env=:development) do |config|
1285
+ #...
1286
+ config.redis_url = { url: "redis://localhost:6378/0", env: }
1287
+ end
1288
+ ```
1289
+ 3. Add webscoket entry connection route
1290
+ ```ruby
1291
+ # config/routes.rb
1292
+ Rubee::Router.draw do |router|
1293
+ #...
1294
+ router.get('/ws', to: 'users#websocket') # entry point to start websocket session
1295
+ # So, const ws = new WebSocket("ws://website/ws"); on the client side, should establish the connection
1296
+ end
1297
+ ```
1298
+ 4. Make model pubsubable
1299
+ ```ruby
1300
+ # app/models/user.rb
1301
+ class User < Rubee::BaseModel
1302
+ include Rubee::PubSub::Publisher
1303
+ include Rubee::PubSub::Subscriber
1304
+ #...
1305
+ end
1306
+ ```
1307
+ 5. Enable websocket in your controller and implement required methods
1308
+ ```ruby
1309
+ # app/controllers/users_controller.rb
1310
+ class UsersController < Rubee::BaseController
1311
+ attach_websocket! # this will handle websocket connections and direct them to the controller methods: publish, subscribe, unsubscribe
1312
+
1313
+
1314
+ # Subscribe is expected to get next params from the client:
1315
+ # { action: 'subscribe', 'channel': 'default', 'id': '123', 'subscriber': 'User' }
1316
+ # where
1317
+ # - action corresponds to the method name
1318
+ # - channel is the name of the channel
1319
+ # - id is the id of the user
1320
+ # - subscriber is the name of the model
1321
+ def subscribe
1322
+ channel = params[:channel]
1323
+ sender_id = params[:options][:id] # id moved to options
1324
+ io = params[:options][:io] # io is a websocket connection
1325
+
1326
+ User.sub(channel, sender_id, io) do |channel, args| # subscribe the user for the channel updates
1327
+ websocket_connections.register(channel, args[:io]) # register the websocket connection
1328
+ end
1329
+ # return websocket response
1330
+ response_with(object: { type: 'system', channel: params[:channel], status: :subscribed }, type: :websocket)
1331
+ rescue StandardError => e
1332
+ response_with(object: { type: 'system', error: e.message }, type: :websocket)
1333
+ end
1334
+
1335
+ # Unsubscribe is expected to get next params from the client:
1336
+ # { action: 'unsubscribe', 'channel': 'default', 'id': '123', 'subscriber': 'User' }
1337
+ # where
1338
+ # - action corresponds to the method name
1339
+ # - channel is the name of the channel
1340
+ # - id is the id of the user
1341
+ # - subscriber is the name of the model
1342
+ def unsubscribe
1343
+ channel = params[:channel]
1344
+ sender_id = params[:options][:id]
1345
+ io = params[:options][:io]
1346
+
1347
+ User.unsub(channel, sender_id, io) do |channel, args|
1348
+ websocket_connections.remove(channel, args[:io])
1349
+ end
1350
+
1351
+ response_with(object: params.merge(type: 'system', status: :unsubscribed), type: :websocket)
1352
+ rescue StandardError => e
1353
+ response_with(object: { type: 'system', error: e.message }, type: :websocket)
1354
+ end
1355
+ # Publish is expected to get next params from the client:
1356
+ # { action: 'publish', 'channel': 'default', 'message': 'Hello world', 'id': '123', 'subscriber': 'User' }
1357
+ # where
1358
+ # - action corresponds to the method name
1359
+ # - channel is the name of the channel
1360
+ # - id is the id of the user
1361
+ # - subscriber is the name of the model
1362
+ def publish
1363
+ args = {}
1364
+ User.pub(params[:channel], message: params[:message]) do |channel|
1365
+ # Here we pack args with any additional data client might need
1366
+ user = User.find(params[:options][:id])
1367
+ args[:message] = params[:message]
1368
+ args[:sender] = params[:options][:id]
1369
+ args[:sender_name] = user.email
1370
+ websocket_connections.stream(channel, args)
1371
+ end
1372
+
1373
+ response_with(object: { type: 'system', message: params[:message], status: :published }, type: :websocket)
1374
+ rescue StandardError => e
1375
+ response_with(object: { type: 'system', error: e.message }, type: :websocket)
1376
+ end
1377
+ end
1378
+ ```
1379
+ If you are interested to see chat app example, please check [chat](https://github.com/nucleom42/rubee-chat)
1380
+
1381
+ [Back to content](#content)
1082
1382
  ### Contributing
1083
1383
 
1084
- If you are interested in contributing to RUBEE,
1384
+ If you are interested in contributing to ru.Bee,
1085
1385
  please read the [Contributing]()https://github.com/nucleom42/rubee/blob/main/contribution.md) guide.
1086
1386
  Also feel free to open an [issue](https://github.com/nucleom42/rubee/issues) if you apot one.
1087
1387
  Have an idea or you wnat to discuss something?