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.
- checksums.yaml +4 -4
- data/lib/app/controllers/users_controller.rb +57 -0
- data/lib/app/controllers/welcome_controller.rb +2 -0
- data/lib/config/routes.rb +1 -0
- data/lib/inits/system.rb +7 -0
- data/lib/rubee/autoload.rb +10 -1
- data/lib/rubee/cli/console.rb +0 -8
- data/lib/rubee/cli/project.rb +6 -1
- data/lib/rubee/cli/server.rb +2 -2
- data/lib/rubee/configuration.rb +16 -0
- data/lib/rubee/controllers/base_controller.rb +55 -13
- data/lib/rubee/extensions/hookable.rb +53 -12
- data/lib/rubee/extensions/serializable.rb +2 -1
- data/lib/rubee/extensions/validatable.rb +130 -0
- data/lib/rubee/features.rb +22 -0
- data/lib/rubee/models/database_objectable.rb +1 -0
- data/lib/rubee/models/sequel_object.rb +14 -6
- data/lib/rubee/pubsub/container.rb +44 -0
- data/lib/rubee/pubsub/publisher.rb +18 -0
- data/lib/rubee/pubsub/redis.rb +99 -0
- data/lib/rubee/pubsub/subscriber.rb +25 -0
- data/lib/rubee/pubsub/test_one.rb +29 -0
- data/lib/rubee/websocket/websocket.rb +102 -0
- data/lib/rubee/websocket/websocket_connections.rb +35 -0
- data/lib/rubee.rb +15 -8
- data/lib/tests/controllers/base_controller_test.rb +1 -1
- data/lib/tests/controllers/users_controller_test.rb +41 -0
- data/lib/tests/models/account_model_test.rb +17 -0
- data/lib/tests/models/comment_model_test.rb +142 -5
- data/lib/tests/models/user_model_test.rb +94 -0
- data/lib/tests/test.db +0 -0
- data/lib/tests/test_helper.rb +6 -0
- data/readme.md +329 -29
- metadata +14 -2
|
@@ -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
|
data/lib/tests/test_helper.rb
CHANGED
data/readme.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
# <img src="lib/images/rubee.svg" alt="
|
|
9
|
+
# <img src="lib/images/rubee.svg" alt="ru.Bee" height="40"> ... ru.Bee
|
|
10
10
|
|
|
11
|
-
|
|
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
|
[](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
|
+
[](https://www.youtube.com/watch?v=gp8IheKBNm4)
|
|
23
|
+
|
|
20
24
|
## Production ready
|
|
21
25
|
|
|
22
|
-
Take a look on the
|
|
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:
|
|
48
|
+
- Server: ru.Bee app hosted via Nginx + HTTPS
|
|
45
49
|
|
|
46
|
-
This
|
|
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
|
|
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 | **
|
|
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
|
|
112
|
+
React Ready – React is supported as a first-class ru.Bee view engine.
|
|
109
113
|
<br>
|
|
110
|
-
Bundlable – Charge your
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
|
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
|
|
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?
|