ru.Bee 1.11 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b13412e9ba012f30b62abd7a994f6b4ed927be61a5e8d24503b63d5fe0d2b1d1
4
- data.tar.gz: adea3313785a63c6bbd9c502d51885c8a8325344a2b0976bfcc0e0aa3e5d5532
3
+ metadata.gz: fd4247baa0c82fd81b4be82e8e63446b8b0d7934cee6243dda47ea3b350d4213
4
+ data.tar.gz: 1ba2c534bcebc7e898408fcf9d4e594c9535ca6fc218a5de8d51895edadc9d7f
5
5
  SHA512:
6
- metadata.gz: d13cb1aabdd86f70109a496fecd67b135b5f364d6bb3ec7d0e1c5423b1bc5a8f73fcb127a7e3fda6a3b84b83d11d2fdc5acd19ec37a6665aae0dc260a29ac1a4
7
- data.tar.gz: ebee546824550e812aea3e15fe19ef43d7538be4b8c6fd560d555e667e3477e55bc85d52d1bf57602d15e1439981f20f16a2c80a0a6abe6f748f0b59dbe1f568
6
+ metadata.gz: cff63aa019e6f5895908a7b1fd7122cefd3986c9a4c1aede556aee667cb750ca82ec95727babda0bd4a234a54b3b159fac5668e9a07de7f7974913015b7dcca5
7
+ data.tar.gz: b4ae2fb9d36dc6c06d87da67d479029fb7c4399f18b26dd57e10ad176a4135a81e2c7840f92393441287e29d470859ef9ec15043dad06b414cf13b680980e35c
@@ -0,0 +1,57 @@
1
+ class UsersController < Rubee::BaseController
2
+ attach_websocket! # Method required to turn controller to been able to handle websocket requests
3
+ using ChargedHash
4
+
5
+ # Endpoint to find or create user
6
+ def create
7
+ user = User.where(**params).last
8
+ user ||= User.create(**params)
9
+
10
+ response_with(object: user, type: :json)
11
+ rescue StandardError => e
12
+ response_with(object: { error: e.message }, type: :json)
13
+ end
14
+
15
+ def subscribe
16
+ channel = params[:channel]
17
+ sender_id = params[:options][:id]
18
+ io = params[:options][:io]
19
+
20
+ User.sub(channel, sender_id, io) do |channel, args|
21
+ websocket_connections.register(channel, args[:io])
22
+ end
23
+
24
+ response_with(object: { type: 'system', channel: params[:channel], status: :subscribed }, type: :websocket)
25
+ rescue StandardError => e
26
+ response_with(object: { type: 'system', error: e.message }, type: :websocket)
27
+ end
28
+
29
+ def unsubscribe
30
+ channel = params[:channel]
31
+ sender_id = params[:options][:id]
32
+ io = params[:options][:io]
33
+
34
+ User.unsub(channel, sender_id, io) do |channel, args|
35
+ websocket_connections.remove(channel, args[:io])
36
+ end
37
+
38
+ response_with(object: params.merge(type: 'system', status: :unsubscribed), type: :websocket)
39
+ rescue StandardError => e
40
+ response_with(object: { type: 'system', error: e.message }, type: :websocket)
41
+ end
42
+
43
+ def publish
44
+ args = {}
45
+ User.pub(params[:channel], message: params[:message]) do |channel|
46
+ user = User.find(params[:options][:id])
47
+ args[:message] = params[:message]
48
+ args[:sender] = params[:options][:id]
49
+ args[:sender_name] = user.email
50
+ websocket_connections.stream(channel, args)
51
+ end
52
+
53
+ response_with(object: { type: 'system', message: params[:message], status: :published }, type: :websocket)
54
+ rescue StandardError => e
55
+ response_with(object: { type: 'system', error: e.message }, type: :websocket)
56
+ end
57
+ end
@@ -1,4 +1,6 @@
1
1
  class WelcomeController < Rubee::BaseController
2
+ using ChargedHash
3
+
2
4
  def show
3
5
  response_with
4
6
  end
@@ -2,5 +2,7 @@
2
2
  # If you remove or modify it, make sure all changes are inlined
3
3
  # with AuthTokenMiddleware and AuthTokenable modules
4
4
  class User < Rubee::SequelObject
5
+ include Rubee::PubSub::Publisher
6
+ include Rubee::PubSub::Subscriber
5
7
  attr_accessor :id, :email, :password, :created, :updated
6
8
  end
data/lib/config/routes.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  Rubee::Router.draw do |router|
2
2
  router.get('/', to: 'welcome#show') # override it for your app
3
+ router.get('/ws', to: 'users#websocket')
3
4
  end
@@ -0,0 +1,7 @@
1
+ def reload
2
+ app_files = Dir["./#{Rubee::APP_ROOT}/**/*.rb"]
3
+ app_files.each { |file| load(file) }
4
+ puts "\e[32mReloaded..\e[0m"
5
+ end
6
+
7
+
@@ -1,5 +1,6 @@
1
1
  module Rubee
2
2
  class Autoload
3
+ BLACKLIST = ['rubee.rb', 'test_helper.rb']
3
4
  class << self
4
5
  def call(black_list = [], **options)
5
6
  load_whitelisted(options[:white_list_dirs]) && return if options[:white_list_dirs]
@@ -12,7 +13,7 @@ module Rubee
12
13
  Dir.glob(File.join(Rubee::APP_ROOT, '**', '*.rb')).sort.each do |file|
13
14
  base_name = File.basename(file)
14
15
 
15
- unless base_name.end_with?('_test.rb') || (black_list + ['rubee.rb', 'test_helper.rb']).include?(base_name)
16
+ unless base_name.end_with?('_test.rb') || (black_list + BLACKLIST).include?(base_name)
16
17
  require_relative file
17
18
  end
18
19
  end
@@ -35,6 +36,14 @@ module Rubee
35
36
  Dir[File.join(Rubee::APP_ROOT, 'inits/**', '*.rb')].each do |file|
36
37
  require_relative file unless black_list.include?("#{file}.rb")
37
38
  end
39
+ # rubee pub sub
40
+ Dir[File.join(root_directory, 'rubee/pubsub/**', '*.rb')].each do |file|
41
+ require_relative file unless black_list.include?("#{file}.rb")
42
+ end
43
+ # rubee websocket
44
+ Dir[File.join(root_directory, 'rubee/websocket/**', '*.rb')].each do |file|
45
+ require_relative file unless black_list.include?("#{file}.rb")
46
+ end
38
47
  # rubee async
39
48
  Dir[File.join(root_directory, 'rubee/async/**', '*.rb')].each do |file|
40
49
  require_relative file unless black_list.include?("#{file}.rb")
@@ -14,14 +14,6 @@ module Rubee
14
14
  Rubee::Configuration.setup(env = :test) do |config|
15
15
  config.database_url = { url: 'sqlite://lib/tests/test.db', env: }
16
16
  end
17
- # Rubee::Autoload.call
18
- # Rubee::SequelObject.reconnect!
19
- end
20
-
21
- def reload
22
- app_files = Dir["./#{Rubee::APP_ROOT}/**/*.rb"]
23
- app_files.each { |file| load(file) }
24
- color_puts('Reloaded ..', color: :green)
25
17
  end
26
18
 
27
19
  begin
@@ -29,7 +29,7 @@ module Rubee
29
29
  FileUtils.mkdir_p(target_dir)
30
30
  # Define blacklist
31
31
  blacklist_files = %w[rubee.rb print_colors.rb version.rb config.ru test_helper.rb Gemfile.lock test.yml test.db
32
- development.db production.db]
32
+ development.db production.db users_controller.rb users_controller.rb]
33
33
  blacklist_dirs = %w[rubee tests .git .github .idea node_modules db inits]
34
34
  # Copy files, excluding blacklisted ones
35
35
  copy_project_files(source_dir, target_dir, blacklist_files, blacklist_dirs)
@@ -108,6 +108,11 @@ module Rubee
108
108
  gem 'json'
109
109
  gem 'jwt'
110
110
 
111
+ # Websocket is required to use integrated websocket feature
112
+ gem 'websocket'
113
+ # Redis is required for pubsub and websocket
114
+ gem 'redis'
115
+
111
116
  group :development do
112
117
  gem 'rerun'
113
118
  gem 'minitest'
@@ -7,7 +7,7 @@ module Rubee
7
7
  | |_) | | | || _ \| _|
8
8
  | _ <| |__| || |_) | |___
9
9
  |_| \_\\____/ |____/|_____|
10
- Ver: %s
10
+ Ver: %s ...bzzz
11
11
  LOGO
12
12
 
13
13
  class << self
@@ -23,7 +23,7 @@ LOGO
23
23
 
24
24
  port ||= '7000'
25
25
  print_logo
26
- color_puts("Starting takeoff of ruBee server on port #{port}...", color: :yellow)
26
+ color_puts("Starting takeoff of ruBee on port: #{port}...", color: :yellow)
27
27
  command = "#{jit_prefix(jit)}rackup #{ENV['RACKUP_FILE']} -p #{port}"
28
28
  color_puts(command, color: :gray)
29
29
  exec(command)
@@ -9,6 +9,7 @@ module Rubee
9
9
  development: {
10
10
  database_url: '',
11
11
  port: 7000,
12
+ redis_url: '',
12
13
  },
13
14
  production: {},
14
15
  test: {},
@@ -36,6 +37,11 @@ module Rubee
36
37
  @configuraiton[args[:app].to_sym][args[:env].to_sym][:database_url] = args[:url]
37
38
  end
38
39
 
40
+ def redis_url=(args)
41
+ args[:app] ||= :app
42
+ @configuraiton[args[:app].to_sym][args[:env].to_sym][:redis_url] = args[:url]
43
+ end
44
+
39
45
  def async_adapter=(args)
40
46
  args[:app] ||= :app
41
47
  @configuraiton[args[:app].to_sym][args[:env].to_sym][:async_adapter] = args[:async_adapter]
@@ -82,6 +88,16 @@ module Rubee
82
88
  @configuraiton[args[:app].to_sym][ENV['RACK_ENV']&.to_sym || :development][:react] || {}
83
89
  end
84
90
 
91
+ def pubsub_container=(args)
92
+ args[:app] ||= :app
93
+ @configuraiton[args[:app].to_sym][args[:env].to_sym][:pubsub_container] = args[:pubsub_container]
94
+ end
95
+
96
+ def pubsub_container(**args)
97
+ args[:app] ||= :app
98
+ @configuraiton[args[:app].to_sym][ENV['RACK_ENV']&.to_sym || :development][:pubsub_container] || ::Rubee::PubSub::Redis.instance
99
+ end
100
+
85
101
  def method_missing(method_name, *args)
86
102
  return unless method_name.to_s.start_with?('get_')
87
103
 
@@ -51,6 +51,8 @@ module Rubee
51
51
  [status, headers.merge('content-type' => 'application/javascript'), [object]]
52
52
  in :css
53
53
  [status, headers.merge('content-type' => 'text/css'), [object]]
54
+ in :websocket
55
+ object # hash is expected
54
56
  in :file
55
57
  [
56
58
  status,
@@ -101,23 +103,38 @@ module Rubee
101
103
  erb_template.result(binding)
102
104
  end
103
105
 
104
- def params
105
- inputs = @request.env['rack.input'].read
106
- body = begin
107
- JSON.parse(@request.body.read.strip)
108
- rescue StandardError
109
- {}
110
- end
111
- begin
112
- body.merge!(URI.decode_www_form(inputs).to_h.transform_keys(&:to_sym))
113
- rescue StandardError
114
- nil
106
+ def websocket
107
+ action = @params[:action]
108
+ unless ['subscribe', 'unsubscribe', 'publish'].include?(action)
109
+ response_with(object: "Unknown action: #{action}", type: :websocket)
115
110
  end
111
+
112
+ public_send(action)
113
+ end
114
+
115
+ def params
116
+ # Read raw input safely (only once)
117
+ raw_input = @request.body.read.to_s.strip
118
+ @request.body.rewind if @request.body.respond_to?(:rewind)
119
+
120
+ # Try parsing JSON first, fall back to form-encoded data
121
+ parsed_input =
122
+ begin
123
+ JSON.parse(raw_input)
124
+ rescue StandardError
125
+ begin
126
+ URI.decode_www_form(raw_input).to_h.transform_keys(&:to_sym)
127
+ rescue
128
+ {}
129
+ end
130
+ end
131
+
132
+ # Combine route params, request params, and body
116
133
  @params ||= extract_params(@request.path, @route[:path])
117
- .merge(body)
134
+ .merge(parsed_input)
118
135
  .merge(@request.params)
119
136
  .transform_keys(&:to_sym)
120
- .reject { |k, _v| [:_method].include?(k.downcase.to_sym) }
137
+ .reject { |k, _v| k.to_sym == :_method }
121
138
  end
122
139
 
123
140
  def headers
@@ -125,6 +142,10 @@ module Rubee
125
142
  .collect { |key, val| [key.sub(/^HTTP_/, ''), val] }
126
143
  end
127
144
 
145
+ def websocket_connections
146
+ Rubee::WebSocketConnections.instance
147
+ end
148
+
128
149
  def extract_params(path, pattern)
129
150
  regex_pattern = pattern.gsub(/\{(\w+)\}/, '(?<\1>[^/]+)')
130
151
  regex = Regexp.new("^#{regex_pattern}$")
@@ -135,5 +156,26 @@ module Rubee
135
156
 
136
157
  {}
137
158
  end
159
+
160
+ def handle_websocket
161
+ res = Rubee::WebSocket.call(@request.env) do |payload|
162
+ @params = payload
163
+ yield
164
+ end
165
+ res
166
+ end
167
+
168
+ class << self
169
+ def attach_websocket!
170
+ around(
171
+ :websocket, :handle_websocket,
172
+ if: -> do
173
+ redis_available = Rubee::Features.redis_available?
174
+ Rubee::Logger.error(message: 'Please make sure redis server is running') unless redis_available
175
+ redis_available
176
+ end
177
+ )
178
+ end
179
+ end
138
180
  end
139
181
  end
@@ -0,0 +1,22 @@
1
+ module Rubee
2
+ class Features
3
+ class << self
4
+ def redis_available?
5
+ require "redis"
6
+ redis_url = Rubee::Configuration.get_redis_url
7
+ redis = redis_url&.empty? ? Redis.new : Redis.new(url: redis_url)
8
+ redis.ping
9
+ true
10
+ rescue LoadError, Redis::CannotConnectError
11
+ false
12
+ end
13
+
14
+ def websocket_available?
15
+ require "websocket"
16
+ true
17
+ rescue LoadError
18
+ false
19
+ end
20
+ end
21
+ end
22
+ end
@@ -4,7 +4,7 @@ module Rubee
4
4
  using ChargedString
5
5
  using ChargedHash
6
6
 
7
- before :save, :update, :set_timestamps
7
+ before :update, :save, :set_timestamps
8
8
 
9
9
  def destroy(cascade: false, **_options)
10
10
  if cascade
@@ -43,13 +43,15 @@ module Rubee
43
43
  true
44
44
  end
45
45
 
46
- def assign_attributes(args={})
47
- self.class.dataset.columns do |attr|
48
- send("#{attr}=", args[attr.to_sym]) if args[attr.to_sym]
46
+ def assign_attributes(args = {})
47
+ self.class.dataset.columns.each do |attr|
48
+ if args[attr.to_sym]
49
+ send("#{attr}=", args[attr.to_sym])
50
+ end
49
51
  end
50
52
  end
51
53
 
52
- def update(args={})
54
+ def update(args = {})
53
55
  assign_attributes(args)
54
56
  args.merge!(updated:)
55
57
  found_hash = self.class.dataset.where(id:)
@@ -0,0 +1,44 @@
1
+ module Rubee
2
+ module PubSub
3
+ class Container
4
+ def pub(*)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ # Container Implementation of sub
9
+ def sub(*)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ # Container Implementation of unsub
14
+ def unsub(*)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ protected
19
+
20
+ def retrieve_klasses(iterable)
21
+ iterable.map { |clazz| turn_to_class(clazz) }
22
+ end
23
+
24
+ def turn_to_class(string)
25
+ string.split('::').inject(Object) { |o, c| o.const_get(c) }
26
+ end
27
+
28
+ def fan_out(clazzes, args, &block)
29
+ mutex = Mutex.new
30
+
31
+ mutex.synchronize do
32
+ clazzes.each do |clazz|
33
+ if block
34
+ block.call(clazz.name, args)
35
+ else
36
+ clazz.on_pub(clazz.name, args)
37
+ end
38
+ end
39
+ true
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ module Rubee
2
+ module PubSub
3
+ module Publisher
4
+ Error = Class.new(StandardError)
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def pub(channel, args = {}, &block)
12
+ Rubee::Configuration.pubsub_container.pub(channel, args, &block)
13
+ true
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,99 @@
1
+ require "connection_pool"
2
+ require "redis"
3
+ require "singleton"
4
+ require "json"
5
+
6
+ module Rubee
7
+ module PubSub
8
+ class Redis < Container
9
+ include Singleton
10
+
11
+ DEFAULT_POOL_SIZE = 5
12
+ DEFAULT_TIMEOUT = 5
13
+
14
+ def initialize
15
+ redis_url = Rubee::Configuration.get_redis_url
16
+ @pool = ConnectionPool.new(size: DEFAULT_POOL_SIZE, timeout: DEFAULT_TIMEOUT) do
17
+ if redis_url&.empty?
18
+ ::Redis.new
19
+ else
20
+ ::Redis.new(url: redis_url)
21
+ end
22
+ end
23
+ @mutex = Mutex.new
24
+ end
25
+
26
+ # Example: pub("ok", message: "hello")
27
+ def pub(channel, args = {}, &block)
28
+ keys = with_redis { |r| r.scan_each(match: "#{channel}:*").to_a }
29
+ return false if keys.empty?
30
+
31
+ values = with_redis { |r| r.mget(*keys) }
32
+
33
+ iterable = values.each_with_index.each_with_object({}) do |(val, i), hash|
34
+ key = keys[i]
35
+ hash[key] = val
36
+ end
37
+
38
+ clazzes = retrieve_klasses(iterable)
39
+ fan_out(clazzes, args, &block)
40
+ end
41
+
42
+ # Example: sub("ok", "User", ["123"])
43
+ def sub(channel, klass_name, *args, &block)
44
+ @mutex.synchronize do
45
+ id = args.first
46
+ id_string = id ? ":#{id}" : ""
47
+ key = "#{channel}:#{klass_name}#{id_string}"
48
+ existing = with_redis { |r| r.get(key) }
49
+ io = args.last.respond_to?(:call) ? args.pop : nil
50
+
51
+ with_redis { |r| r.set(key, args.join(",")) } unless existing
52
+ block&.call(key, io: io)
53
+ end
54
+ true
55
+ end
56
+
57
+ def unsub(channel, klass_name, *args, &block)
58
+ @mutex.synchronize do
59
+ id = args.first
60
+ id_string = id ? ":#{id}" : ""
61
+ key = "#{channel}:#{klass_name}#{id_string}"
62
+ value = with_redis { |r| r.get(key) }
63
+ return false unless value
64
+
65
+ io = args.pop if args.last.respond_to?(:call)
66
+ with_redis { |r| r.del(key) }
67
+ block&.call(key, io: io)
68
+ end
69
+ true
70
+ end
71
+
72
+ protected
73
+
74
+ def with_redis(&block)
75
+ @pool.with(&block)
76
+ end
77
+
78
+ def retrieve_klasses(iterable)
79
+ iterable.each_with_object({}) do |(key, args), hash|
80
+ channel, clazz, id = key.split(":")
81
+ arg_list = args.to_s.split(",")
82
+ hash[key] = { channel:, clazz:, args: arg_list, id: }
83
+ end
84
+ end
85
+
86
+ def fan_out(clazzes, method_args = {}, &block)
87
+ clazzes.each do |_key, opts|
88
+ clazz = turn_to_class(opts[:clazz])
89
+ clazz_args = opts[:args]
90
+
91
+ clazz.on_pub(opts[:channel], *clazz_args, **method_args) if clazz.respond_to?(:on_pub)
92
+ id_string = opts[:id] ? ":#{opts[:id]}" : ""
93
+ block&.call("#{opts[:channel]}:#{opts[:clazz]}#{id_string}", **method_args)
94
+ end
95
+ true
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,25 @@
1
+ module Rubee
2
+ module PubSub
3
+ module Subscriber
4
+ Error = Class.new(StandardError)
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def sub(channel, *args, &block)
12
+ Rubee::Configuration.pubsub_container.sub(channel, name, *args, &block)
13
+
14
+ true
15
+ end
16
+
17
+ def unsub(channel, *args, &block)
18
+ Rubee::Configuration.pubsub_container.unsub(channel, name, *args, &block)
19
+
20
+ true
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ # class Subscriber
2
+ # include Rubee::PubSub::Subscriber
3
+
4
+ # def self.on_pub(channel, message, options = {})
5
+ # puts "channel=#{channel} message=#{message} options=#{options}"
6
+ # end
7
+ # end
8
+
9
+ # class SubscriberOne
10
+ # include Rubee::PubSub::Subscriber
11
+
12
+ # def self.on_pub(channel, message, options = {})
13
+ # puts "channel=#{channel} message=#{message} options=#{options}"
14
+ # end
15
+ # end
16
+
17
+ # class Publisher
18
+ # include Rubee::PubSub::Publisher
19
+ # end
20
+
21
+ # Subscriber.sub("ok", ["123456"])
22
+
23
+ # SubscriberOne.sub("ok", ["123"])
24
+
25
+ # Publisher.pub("ok", { message: "hello" })
26
+
27
+ # SubscriberOne.unsub("ok", ["123"])
28
+
29
+ # Publisher.pub("ok", { message: "hello" })
@@ -0,0 +1,102 @@
1
+ require 'websocket'
2
+ require 'websocket/frame'
3
+ require 'websocket/handshake'
4
+ require 'websocket/handshake/server'
5
+ require 'json'
6
+ require 'redis'
7
+
8
+ module Rubee
9
+ class WebSocket
10
+ using ChargedHash
11
+ class << self
12
+ def call(env, &controller_block)
13
+ unless env['rack.hijack']
14
+ return [500, { 'Content-Type' => 'text/plain' }, ['Hijack not supported']]
15
+ end
16
+
17
+ env['rack.hijack'].call
18
+ io = env['rack.hijack_io']
19
+
20
+ handshake = ::WebSocket::Handshake::Server.new
21
+ handshake.from_rack(env)
22
+ unless handshake.valid?
23
+ io.write("HTTP/1.1 400 Bad Request\r\n\r\n")
24
+ io.close
25
+ return [-1, {}, []]
26
+ end
27
+
28
+ io.write(handshake.to_s)
29
+ incoming = ::WebSocket::Frame::Incoming::Server.new(version: handshake.version)
30
+
31
+ outgoing = ->(data) do
32
+ frame = ::WebSocket::Frame::Outgoing::Server.new(
33
+ version: handshake.version,
34
+ type: :text,
35
+ data: data.to_json
36
+ )
37
+ io.write(frame.to_s)
38
+ rescue IOError
39
+ nil
40
+ end
41
+
42
+ # --- Listen to incoming data ---
43
+ Thread.new do
44
+ loop do
45
+ data = io.readpartial(1024)
46
+ incoming << data
47
+
48
+ while (frame = incoming.next)
49
+ case frame.type
50
+ when :text
51
+ out = controller_out(frame, outgoing, &controller_block)
52
+ outgoing.call(**out)
53
+
54
+ when :close
55
+ # Client closed connection
56
+ handle_close(frame, io, handshake)
57
+ break
58
+ end
59
+ end
60
+ end
61
+ rescue EOFError, IOError
62
+ begin
63
+ handle_close(frame, io, handshake)
64
+ rescue
65
+ nil
66
+ end
67
+ end
68
+
69
+ [101, handshake.headers, []]
70
+ end
71
+
72
+ def payload(frame)
73
+ JSON.parse(frame.data)
74
+ rescue
75
+ {}
76
+ end
77
+
78
+ def handle_close(frame, io, handshake)
79
+ payload_hash = payload(frame)
80
+ channel = payload_hash["channel"]
81
+ subcriber = payload_hash["subcriber"]
82
+ ::Rubee::WebSocketConnections.instance.remove("#{channel}:#{subcriber}", io)
83
+ io.write(::WebSocket::Frame::Outgoing::Server.new(
84
+ version: handshake.version,
85
+ type: :close
86
+ ).to_s)
87
+ io.close
88
+ end
89
+
90
+ def controller_out(frame, io, &block)
91
+ payload_hash = payload(frame)
92
+ action = payload_hash["action"]
93
+ channel = payload_hash["channel"]
94
+ message = payload_hash["message"]
95
+ options = payload_hash.select { |k, _| !["action", "channel", "message"].include?(k) }
96
+ options.merge!(io:)
97
+
98
+ block.call(channel:, message:, action:, options:)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,35 @@
1
+ module Rubee
2
+ class WebSocketConnections
3
+ include Singleton
4
+ def initialize
5
+ @subscribers ||= Hash.new { |h, k| h[k] = [] }
6
+ end
7
+
8
+ def register(channel, io)
9
+ @subscribers[channel] << io unless @subscribers[channel].include?(io)
10
+ end
11
+
12
+ def remove(channel, io)
13
+ @subscribers[channel].delete(io)
14
+ end
15
+
16
+ def remove_all(io)
17
+ @subscribers.each_value { _1.delete(io) }
18
+ end
19
+
20
+ def flush_all
21
+ @subscribers.each_value(&:clear)
22
+ end
23
+
24
+ def stream(channel, args = {})
25
+ ios = @subscribers[channel]
26
+ if !ios&.empty? && ios.all? { _1.respond_to?(:call) }
27
+ ios.each { _1.call(args) }
28
+ end
29
+ end
30
+
31
+ def clear
32
+ @subscribers = Hash.new { |h, k| h[k] = [] }
33
+ end
34
+ end
35
+ end
data/lib/rubee.rb CHANGED
@@ -16,11 +16,13 @@ module Rubee
16
16
  JS_DIR = File.join(APP_ROOT, LIB, 'js') unless defined?(JS_DIR)
17
17
  CSS_DIR = File.join(APP_ROOT, LIB, 'css') unless defined?(CSS_DIR)
18
18
  ROOT_PATH = File.expand_path(File.join(__dir__, '..')) unless defined?(ROOT_PATH)
19
- VERSION = '1.11'
19
+
20
+ VERSION = '2.0.0'
20
21
 
21
22
  require_relative 'rubee/router'
22
23
  require_relative 'rubee/logger'
23
24
  require_relative 'rubee/generator'
25
+ require_relative 'rubee/features'
24
26
  require_relative 'rubee/autoload'
25
27
  require_relative 'rubee/configuration'
26
28
 
@@ -31,15 +33,10 @@ module Rubee
31
33
  def call(env)
32
34
  # autoload rb files
33
35
  Autoload.call
34
-
35
- # register images paths
36
+ # init rack request
36
37
  request = Rack::Request.new(env)
37
38
  # Add default path for assets
38
- Router.draw do |route|
39
- route.get('/images/{path}', to: 'base#image', namespace: 'Rubee')
40
- route.get('/js/{path}', to: 'base#js', namespace: 'Rubee')
41
- route.get('/css/{path}', to: 'base#css', namespace: 'Rubee')
42
- end
39
+ register_assets_routes
43
40
  # define route
44
41
  route = Router.route_for(request)
45
42
  # if react is the view so we would like to delegate not cauth by rubee routes to it.
@@ -62,5 +59,15 @@ module Rubee
62
59
  # fire the action
63
60
  controller.send(action)
64
61
  end
62
+
63
+ private
64
+
65
+ def register_assets_routes
66
+ Router.draw do |route|
67
+ route.get('/images/{path}', to: 'base#image', namespace: 'Rubee')
68
+ route.get('/js/{path}', to: 'base#js', namespace: 'Rubee')
69
+ route.get('/css/{path}', to: 'base#css', namespace: 'Rubee')
70
+ end
71
+ end
65
72
  end
66
73
  end
@@ -1,6 +1,6 @@
1
1
  require_relative '../test_helper'
2
2
 
3
- class BaseControllerTest < Minitest::Test
3
+ class WebSocketControllerTest < Minitest::Test
4
4
  include Rack::Test::Methods
5
5
 
6
6
  def app
@@ -0,0 +1,41 @@
1
+ require_relative '../test_helper'
2
+
3
+ class UsersControllerTest < Minitest::Test
4
+ include Rack::Test::Methods
5
+
6
+ def app
7
+ Rubee::Application.instance
8
+ end
9
+
10
+ def test_websocket_handshake_written_to_io
11
+ env = Rack::MockRequest.env_for(
12
+ '/ws',
13
+ {
14
+ 'REQUEST_METHOD' => 'GET',
15
+ 'PATH_INFO' => '/ws',
16
+ 'HTTP_CONNECTION' => 'keep-alive, Upgrade',
17
+ 'HTTP_UPGRADE' => 'websocket',
18
+ 'HTTP_HOST' => 'localhost:9292',
19
+ 'HTTP_ORIGIN' => 'http://localhost:9292',
20
+ 'HTTP_SEC_WEBSOCKET_KEY' => 'dGhlIHNhbXBsZSBub25jZQ==',
21
+ 'HTTP_SEC_WEBSOCKET_VERSION' => '13',
22
+ 'rack.url_scheme' => 'http'
23
+ }
24
+ )
25
+
26
+ # Mock hijack interface expected by Rubee::WebSocket
27
+ io = StringIO.new
28
+ env['rack.hijack'] = proc {}
29
+ env['rack.hijack_io'] = io
30
+
31
+ # Call the WebSocket handler
32
+ Rubee::WebSocket.call(env)
33
+
34
+ # Expect the handshake response written to IO
35
+ io.rewind
36
+ handshake_response = io.read
37
+ assert_includes(handshake_response, "HTTP/1.1 101 Switching Protocols")
38
+ assert_includes(handshake_response, "Upgrade: websocket")
39
+ assert_includes(handshake_response, "Connection: Upgrade")
40
+ end
41
+ end
@@ -140,14 +140,14 @@ describe 'User model' do
140
140
  end
141
141
  end
142
142
 
143
- describe 'when udpate existing user with no argumants' do
143
+ describe 'when udpate existing user with no arguments' do
144
144
  it 'update updated field' do
145
145
  user = User.new(email: 'ok-test@test.com', password: '123')
146
146
  user.save
147
147
  updated_field_before_update = user.updated
148
148
 
149
149
  user.update
150
- _(user.updated > updated_field_before_update).must_equal(true)
150
+ _(user.reload.updated > updated_field_before_update).must_equal(true)
151
151
  end
152
152
  end
153
153
  end
@@ -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/readme.md CHANGED
@@ -8,7 +8,8 @@
8
8
 
9
9
  # <img src="lib/images/rubee.svg" alt="RUBEE" height="40"> ... RUBEE
10
10
 
11
- Rubee is a Ruby-based framework designed to streamline the development of modular monolith applications. \
11
+ RUBEE is a Ruby-based web framework designed to streamline the development of modular monolith web applications. \
12
+ Under the hood, it leverages the power of Ruby and Rack backed by Puma, offering a clean, efficient, and flexible architecture. \
12
13
  It offers a structured approach to building scalable, maintainable, and React-ready projects, \
13
14
  making it an ideal choice for developers seeking a balance between monolithic simplicity and modular flexibility.
14
15
 
@@ -18,14 +19,14 @@ Want to get a quick API server up and runing? You can do it for real quick!
18
19
 
19
20
  ## Production ready
20
21
 
21
- Take a look on the rubee demo site with all documentation stored in there: https://rubee.dedyn.io/
22
+ Take a look on the RUBEE demo site with all documentation stored in there: https://rubee.dedyn.io/
22
23
  Want to explore how it built? https://github.com/nucleom42/rubee-site
23
24
 
24
25
  ## Stress tested
25
26
 
26
27
  ```bash
27
- wrk -t4 -c100 -d30s https://rubee.duckdns.org/docs
28
- Running 30s test @ https://rubee.duckdns.org/docs
28
+ wrk -t4 -c100 -d30s https://rubee.dedyn.io/docs
29
+ Running 30s test @ https://rubee.dedyn.io/docs
29
30
  4 threads and 100 connections
30
31
  Thread Stats Avg Stdev Max +/- Stdev
31
32
  Latency 304.95ms 33.22ms 551.86ms 90.38%
@@ -40,9 +41,27 @@ Transfer/sec: 140.07KB
40
41
  - Average latency: ~305 ms
41
42
  - Total requests handled: 9,721
42
43
  - Hardware: Raspberry Pi 5(8 Gb) (single board computer)
43
- - Server: Rubee app hosted via Nginx + HTTPS
44
+ - Server: RUBEE app hosted via Nginx + HTTPS
44
45
 
45
- This demonstrates RUBEE’s efficient architecture and suitability for lightweight deployments — even on low-power hardware.
46
+ This demonstrate RUBEE’s efficient architecture and suitability for lightweight deployments — even on low-power hardware.
47
+
48
+ ## Comparison
49
+ Here below is a **short web frameworks comparison** built with Ruby, so you can evaluate your choice with RUBEE.
50
+
51
+ **Disclaimer:**
52
+ 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
+
54
+ | Feature / Framework | **RUBEE** | Rails | Sinatra | Hanami | Padrino | Grape |
55
+ |---------------------|-----------|-------|---------|--------|---------|-------|
56
+ | **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
+ | **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 |
58
+ | **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) |
59
+ | **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 |
60
+ | **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 |
61
+ | **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) |
62
+ | **Customizability** | High (explicit over implicit, hooks & generators) | Limited without monkey-patching | Very high (you control flow) | High, modular architecture | Medium | High (designed for APIs) |
63
+ | **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 |
64
+ | **Early adopters support** | Personal early adopters support via fast extending and fixing | Not available | Not known | Not known | Not known | Not known |
46
65
 
47
66
  ## Content
48
67
 
@@ -55,6 +74,7 @@ This demonstrates RUBEE’s efficient architecture and suitability for lightweig
55
74
  - [Views](#views)
56
75
  - [Hooks](#hooks)
57
76
  - [JWT based authentification](#jwt-based-authentification)
77
+ - [OAuth2 based authentification](#oauth-authentification)
58
78
  - [Rubee commands](#rubee-commands)
59
79
  - [Generate commands](#generate-commands)
60
80
  - [Migration commands](#migration-commands)
@@ -64,16 +84,16 @@ This demonstrates RUBEE’s efficient architecture and suitability for lightweig
64
84
  - [Modular](#modualar-application)
65
85
  - [Logger](#logger)
66
86
 
67
- You can read it on the demo [site](https://rubee.dedyn.io/)
87
+ You can read it on the demo: [site](https://rubee.dedyn.io/)
68
88
 
69
- 🚧 The doc site is on uodate mode now. We are working on it.
70
- Please refer to downbelow documentation.
89
+ 🚧 The doc site is on update mode now. We are working on it.
90
+ Please refer to the documentation shown below.
71
91
 
72
92
  ## Features
73
93
 
74
94
  Lightweight – A minimal footprint focused on serving Ruby applications efficiently.
75
95
  <br>
76
- Modular – A modular approach to application development. Build modular monoliths with ease by attaching \
96
+ Modular – A modular approach to application development. Build modular monolith app with ease by attaching
77
97
  as many subprojects as you need.
78
98
  <br>
79
99
  Contract-driven – Define your API contracts in a simple, declarative way, then generate all the boilerplate you need.
@@ -86,9 +106,9 @@ Databases – Supports SQLite3, PostgreSQL, MySQL, and more via the Sequel gem.
86
106
  <br>
87
107
  Views – JSON, ERB, and plain HTML out of the box.
88
108
  <br>
89
- React Ready – React is supported as a first-class Rubee view engine.
109
+ React Ready – React is supported as a first-class RUBEE view engine.
90
110
  <br>
91
- Bundlable – Charge your Rubee app with any gem you need. Update effortlessly via Bundler.
111
+ Bundlable – Charge your RUBEE app with any gem you need. Update effortlessly via Bundler.
92
112
  <br>
93
113
  ORM-agnostic – Models are native ORM objects, but you can use them as blueprints for any data source.
94
114
  <br>
@@ -131,7 +151,7 @@ Make sure:
131
151
  bundle install
132
152
  ```
133
153
 
134
- 4. Run RUBEE server. Default port is 7000
154
+ 4. Run RUBER server. Default port is 7000
135
155
  ```bash
136
156
  rubee start # or rubee start_dev for development
137
157
 
@@ -335,7 +355,7 @@ irb(main):023> User.all
335
355
  => []
336
356
  ```
337
357
 
338
- Use complex queries chains and when ready serialize it back to Rubee object.
358
+ Use complex queries chains and when ready serialize it back to RUBEE object.
339
359
  ```Ruby
340
360
  # user model
341
361
  class User < Rubee::SequelObject
@@ -372,7 +392,7 @@ irb(main):009> .where(comment_id: Comment.where(text: "test").last.id)
372
392
  irb(main):010> .then { |dataset| Comment.serialize(dataset) }
373
393
  => [#<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>]
374
394
  ```
375
- This is recommended when you want to run one query and serialize it back to Rubee object only once.
395
+ This is recommended when you want to run one query and serialize it back to RUBEE object only once.
376
396
  So it may safe some resources.
377
397
 
378
398
  [Back to content](#content)
@@ -388,7 +408,7 @@ If you feel comfortable you can play with retry configuration parameters:
388
408
  config.db_busy_timeout = { env:, value: 1000 } # this is busy timeout in ms, before raising bussy error
389
409
  ```
390
410
 
391
- For Rubee model class persist methods create and update retry will be added automatically. However, \
411
+ For RUBEE model class persist methods create and update retry will be added automatically. However, \
392
412
  if you want to do it with Sequel dataset you need to do it yourself:
393
413
 
394
414
  ```ruby
@@ -397,7 +417,7 @@ if you want to do it with Sequel dataset you need to do it yourself:
397
417
  [Back to content](#content)
398
418
 
399
419
  ## Routing
400
- Rubee uses explicit routes. In the routes.rb yout can define routes for any of the main HTTP methods. \
420
+ RUBEE uses explicit routes. In the routes.rb yout can define routes for any of the main HTTP methods. \
401
421
  You can also add any matched parameter denoted by a pair of `{ }` in the path of the route. \
402
422
  Eg. `/path/to/{a_key}/somewhere`
403
423
 
@@ -423,7 +443,7 @@ route.{http_method} {path}, to: "{controller}#{action}",
423
443
  ```
424
444
 
425
445
  ### Defining Model attributes in routes
426
- One of Rubee's unique traits is where we can define our models for generation. \
446
+ One of RUBEE's unique traits is where we can define our models for generation. \
427
447
  You've seen above one possible way you can set up.
428
448
 
429
449
  ```ruby
@@ -558,7 +578,7 @@ Main philosophy of attach functinality is to keep the main project clean and eas
558
578
  share data with the main app. So where to define a border between the main app and subprojects is up to developer.
559
579
  Howerver by attching new subproject you will get a new folder and files configured and namespaced respectively.
560
580
 
561
- So if you need to extend your main app with a separate project, you can do it easily in RUBEE.
581
+ So if you need to extend your main app with a separate project, you can do it easily in ruBEE.
562
582
  1. Attach new subrpoject
563
583
 
564
584
  ```bash
@@ -818,7 +838,8 @@ Starting from ver 1.11 hooks are able to be pinned to class methods.
818
838
 
819
839
  ```ruby
820
840
  class AnyClass
821
- before :print_world, :print_hello, instance_methods: true # you can useinstance method as a handler
841
+ include Rubee::Hookable
842
+ before :print_world, :print_hello, class_methods: true # you can use class method as a handler
822
843
 
823
844
  class << self
824
845
  def print_world
@@ -854,7 +875,7 @@ Feel free to customize it in the /db/create_users.rb file before running migrati
854
875
  Then in the controller you can include the AuthTokenable module and use its methods:
855
876
  ```ruby
856
877
  class UsersController < Rubee::BaseController
857
- include AuthTokenable
878
+ include Rubee::AuthTokenable
858
879
  # List methods you want to restrict
859
880
  auth_methods :index # unless the user is authentificated it will return unauthentificated
860
881
 
@@ -887,10 +908,103 @@ class UsersController < Rubee::BaseController
887
908
  end
888
909
  end
889
910
  ```
911
+ ## OAuth authentification
912
+ If you want to plug in the OAuth 2.0 authentication, you can use the following code using OAuth2 gem:
913
+ First thing you need to do is to add the gem to your Gemfile
914
+ ```bash
915
+ gem 'oauth2'
916
+ ```
917
+ Then use down below code as an example and add yours to your controller
918
+ ```ruby
919
+ class UsersController < Rubee::BaseController
920
+ include Rubee::AuthTokenable
921
+
922
+ REDIRECT_URI = 'https://mysite.com/users/outh_callback'
923
+ CLIENT_ID = ENV['GOOGLE_CLIENT_ID']
924
+ CLIENT_SECRET = ENV['GOOGLE_CLIENT_SECRET']
925
+
926
+ # GET /login (login form page)
927
+ def edit
928
+ response_with
929
+ end
930
+
931
+ # POST /users/login (login logic)
932
+ def login
933
+ Rubee::Logger.info(message: "Login attempt for user #{params[:email]}")
934
+ if authentificate! # AuthTokenable method that init @token_header
935
+ Rubee::Logger.info(message: "Successful login for user #{@authentificated_user.email}")
936
+ response_with(type: :redirect, to: "/sections", headers: @token_header)
937
+ else
938
+ @error = "Wrong email or password"
939
+ response_with(render_view: "users_edit")
940
+ end
941
+ end
942
+
943
+ # GET /users/outh_login
944
+ def outh_login
945
+ response_with(
946
+ type: :redirect,
947
+ to: auth_client.auth_code.authorize_url(
948
+ redirect_uri: REDIRECT_URI,
949
+ scope: 'email profile openid'
950
+ )
951
+ )
952
+ end
953
+
954
+ # GET /users/outh_callback
955
+ def outh_callback
956
+ code = params[:code]
957
+ token = auth_client.auth_code.get_token(code, redirect_uri: REDIRECT_URI)
958
+ user_info = JSON.parse(token.get('https://www.googleapis.com/oauth2/v1/userinfo?alt=json').body)
959
+ Rubee::Logger.debug(object: user_info, method: "outh_callback", class: "UsersController")
960
+
961
+ user = User.where(email: user_info['email'])&.last
962
+ unless user
963
+ raise "User with email #{user_info['email']} not found"
964
+ end
965
+
966
+ params[:email] = user_info['email']
967
+ params[:password] = user.password
968
+
969
+ if authentificate! # AuthTokenable method that init @token_header
970
+ Rubee::Logger.info(message: "Successful Outh login for user #{@authentificated_user.email}")
971
+ response_with(type: :redirect, to: "/sections", headers: @token_header)
972
+ else
973
+ @error = "Something went wrong"
974
+ response_with(render_view: "users_edit")
975
+ end
976
+ rescue OAuth2::Error => e
977
+ @error = "OAuth login failed"
978
+ response_with(render_view: "users_edit")
979
+ rescue StandardError => e
980
+ @error = "Something went wrong"
981
+ response_with(render_view: "users_edit")
982
+ end
983
+
984
+ # POST /users/logout (logout logic)
985
+ def logout
986
+ unauthentificate! # AuthTokenable method aimed to handle logout action.
987
+ # Make sure @zeroed_token_header is paRssed within headers options
988
+ response_with(type: :redirect, to: "/login", headers: @zeroed_token_header)
989
+ end
990
+
991
+ private
992
+
993
+ def auth_client
994
+ @client ||= OAuth2::Client.new(
995
+ CLIENT_ID,
996
+ CLIENT_SECRET,
997
+ site: 'https://accounts.google.com',
998
+ authorize_url: '/o/oauth2/auth',
999
+ token_url: 'https://oauth2.googleapis.com/token'
1000
+ )
1001
+ end
1002
+ end
1003
+ ```
890
1004
 
891
1005
  [Back to content](#content)
892
1006
 
893
- ## Rubee commands
1007
+ ## RUBEE commands
894
1008
  ```bash
895
1009
  rubee start # start the server
896
1010
  rubee start_dev # start the server in dev mode, which restart server on changes
@@ -912,7 +1026,7 @@ rubee db run:create_apples # where create_apples is the name of the migration fi
912
1026
  rubee db structure # generate migration file for the database structure
913
1027
  ```
914
1028
 
915
- ## Rubee console
1029
+ ## RUBEE console
916
1030
  ```bash
917
1031
  rubee console # start the console
918
1032
  # you can reload the console by typing reload, so it will pick up latest changes
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: '1.11'
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oleg Saltykov
@@ -41,6 +41,7 @@ files:
41
41
  - bin/rubee
42
42
  - bin/test.db
43
43
  - lib/Dockerfile
44
+ - lib/app/controllers/users_controller.rb
44
45
  - lib/app/controllers/welcome_controller.rb
45
46
  - lib/app/models/user.rb
46
47
  - lib/app/views/App.tsx
@@ -66,6 +67,7 @@ files:
66
67
  - lib/inits/charged_hash.rb
67
68
  - lib/inits/charged_string.rb
68
69
  - lib/inits/print_colors.rb
70
+ - lib/inits/system.rb
69
71
  - lib/js/app.js
70
72
  - lib/js/app.js.map
71
73
  - lib/js/bundle.js
@@ -254,18 +256,27 @@ files:
254
256
  - lib/rubee/controllers/middlewares/auth_token_middleware.rb
255
257
  - lib/rubee/extensions/hookable.rb
256
258
  - lib/rubee/extensions/serializable.rb
259
+ - lib/rubee/features.rb
257
260
  - lib/rubee/generator.rb
258
261
  - lib/rubee/logger.rb
259
262
  - lib/rubee/models/database_objectable.rb
260
263
  - lib/rubee/models/db_tools.rb
261
264
  - lib/rubee/models/sequel_object.rb
265
+ - lib/rubee/pubsub/container.rb
266
+ - lib/rubee/pubsub/publisher.rb
267
+ - lib/rubee/pubsub/redis.rb
268
+ - lib/rubee/pubsub/subscriber.rb
269
+ - lib/rubee/pubsub/test_one.rb
262
270
  - lib/rubee/router.rb
271
+ - lib/rubee/websocket/websocket.rb
272
+ - lib/rubee/websocket/websocket_connections.rb
263
273
  - lib/tests/async/thread_async_test.rb
264
274
  - lib/tests/cli/attach_test.rb
265
275
  - lib/tests/controllers/auth_tokenable_test.rb
266
276
  - lib/tests/controllers/base_controller_test.rb
267
277
  - lib/tests/controllers/hookable_test.rb
268
278
  - lib/tests/controllers/rubeeapp_test.rb
279
+ - lib/tests/controllers/users_controller_test.rb
269
280
  - lib/tests/example_models/account.rb
270
281
  - lib/tests/example_models/address.rb
271
282
  - lib/tests/example_models/comment.rb
@@ -300,7 +311,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
300
311
  - !ruby/object:Gem::Version
301
312
  version: '0'
302
313
  requirements: []
303
- rubygems_version: 3.7.0
314
+ rubygems_version: 3.7.2
304
315
  specification_version: 4
305
316
  summary: Fast and lightweight Ruby application server designed for minimalism and
306
317
  flexibility