rage-rb 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +5 -1
- data/lib/rage/all.rb +1 -0
- data/lib/rage/configuration.rb +2 -2
- data/lib/rage/fiber.rb +52 -1
- data/lib/rage/logger/json_formatter.rb +2 -2
- data/lib/rage/logger/logger.rb +17 -7
- data/lib/rage/logger/text_formatter.rb +2 -2
- data/lib/rage/rails.rb +0 -6
- data/lib/rage/router/backend.rb +1 -19
- data/lib/rage/router/handler_storage.rb +5 -4
- data/lib/rage/router/util.rb +33 -0
- data/lib/rage/rspec.rb +2 -2
- data/lib/rage/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd10469efc73f6134f72f79fe21871238b8860e958c085499c47b312adf91764
|
4
|
+
data.tar.gz: 34990a4885df4a4acd55fcbabf8d3a67e57e0609db406ee55d5f71cab0655ffb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc28afd194f122bf436c215cb6d88dfe3d923874f4e07c3c1db5dd3f33a4676354731fad3f6ffc29326423086b59dbb0d1860ecb5cb71cf4c7f0412a252af16f
|
7
|
+
data.tar.gz: 58cd0cea6daba63d67f9285d3b616980d2e69c778620c54b14c7c5cb1eccf093bfbc0644dec081c325d4cdc5f6c032b7f399ccb8ea5b052c1da401dcde08ed6e
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.1.0] - 2024-03-25
|
4
|
+
|
5
|
+
### Changed
|
6
|
+
|
7
|
+
- Change the way controller names are logged (#72).
|
8
|
+
- Use formatters in console (#71).
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
|
12
|
+
- Fix Fiber.await behavior in RSpec (#70).
|
13
|
+
|
3
14
|
## [1.0.0] - 2024-03-13
|
4
15
|
|
5
16
|
### Added
|
data/README.md
CHANGED
@@ -53,8 +53,12 @@ Check out in-depth API docs for more information:
|
|
53
53
|
- [Fiber API](https://rage-rb.pages.dev/Fiber)
|
54
54
|
- [Logger API](https://rage-rb.pages.dev/Rage/Logger)
|
55
55
|
- [Configuration API](https://rage-rb.pages.dev/Rage/Configuration)
|
56
|
+
- [CORS middleware](https://rage-rb.pages.dev/Rage/Cors)
|
56
57
|
|
57
|
-
Also, see the
|
58
|
+
Also, see the following integration guides:
|
59
|
+
|
60
|
+
- [Rails integration](https://github.com/rage-rb/rage/wiki/Rails-integration)
|
61
|
+
- [RSpec integration](https://github.com/rage-rb/rage/wiki/RSpec-integration)
|
58
62
|
|
59
63
|
### Example
|
60
64
|
|
data/lib/rage/all.rb
CHANGED
data/lib/rage/configuration.rb
CHANGED
@@ -19,7 +19,7 @@
|
|
19
19
|
#
|
20
20
|
# • _config.middleware.use_
|
21
21
|
#
|
22
|
-
# > Adds a middleware to the top of the middleware stack. **This is the
|
22
|
+
# > Adds a middleware to the top of the middleware stack. **This is the recommended way of adding a middleware.**
|
23
23
|
# > ```
|
24
24
|
# config.middleware.use Rack::Cors do
|
25
25
|
# allow do
|
@@ -166,7 +166,7 @@ class Rage::Configuration
|
|
166
166
|
|
167
167
|
# @private
|
168
168
|
class Internal
|
169
|
-
attr_accessor :rails_mode
|
169
|
+
attr_accessor :rails_mode
|
170
170
|
|
171
171
|
def inspect
|
172
172
|
"#<#{self.class.name}>"
|
data/lib/rage/fiber.rb
CHANGED
@@ -1,5 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
##
|
4
|
+
# Rage provides a simple and efficient API to wait on several instances of IO at the same time - {Fiber.await}.
|
5
|
+
#
|
6
|
+
# Let's say we have the following controller:
|
7
|
+
# ```ruby
|
8
|
+
# class UsersController < RageController::API
|
9
|
+
# def show
|
10
|
+
# user = Net::HTTP.get(URI("http://users.service/users/#{params[:id]}"))
|
11
|
+
# bookings = Net::HTTP.get(URI("http://bookings.service/bookings?user_id=#{params[:id]}"))
|
12
|
+
# render json: { user: user, bookings: bookings }
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
# ```
|
16
|
+
# This code will fire two consecutive HTTP requests. If each request takes 1 second to execute, the total execution time will be 2 seconds.<br>
|
17
|
+
# With {Fiber.await}, we can significantly decrease the overall execution time by changing the code to fire the requests concurrently.
|
18
|
+
#
|
19
|
+
# To do this, we will need to:
|
20
|
+
#
|
21
|
+
# 1. Wrap every request in a separate fiber using {Fiber.schedule};
|
22
|
+
# 2. Pass newly created fibers into {Fiber.await};
|
23
|
+
#
|
24
|
+
# ```ruby
|
25
|
+
# class UsersController < RageController::API
|
26
|
+
# def show
|
27
|
+
# user, bookings = Fiber.await([
|
28
|
+
# Fiber.schedule { Net::HTTP.get(URI("http://users.service/users/#{params[:id]}")) },
|
29
|
+
# Fiber.schedule { Net::HTTP.get(URI("http://bookings.service/bookings?user_id=#{params[:id]}")) }
|
30
|
+
# ])
|
31
|
+
#
|
32
|
+
# render json: { user: user, bookings: bookings }
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
# ```
|
36
|
+
# With this change, if each request takes 1 second to execute, the total execution time will still be 1 second.
|
37
|
+
#
|
38
|
+
# ## Creating fibers
|
39
|
+
# Many developers see fibers as "lightweight threads" that should be used in conjunction with fiber pools, the same way we use thread pools for threads.<br>
|
40
|
+
# Instead, it makes sense to think of fibers as regular Ruby objects. We don't use a pool of arrays when we need to create an array - we create a new object and let Ruby and the GC do their job.<br>
|
41
|
+
# Same applies to fibers. Feel free to create as many fibers as you need on demand.
|
3
42
|
class Fiber
|
4
43
|
# @private
|
5
44
|
AWAIT_ERROR_MESSAGE = "err"
|
@@ -58,7 +97,7 @@ class Fiber
|
|
58
97
|
end
|
59
98
|
|
60
99
|
# Wait on several fibers at the same time. Calling this method will automatically pause the current fiber, allowing the
|
61
|
-
#
|
100
|
+
# server to process other requests. Once all fibers have completed, the current fiber will be automatically resumed.
|
62
101
|
#
|
63
102
|
# @param fibers [Fiber, Array<Fiber>] one or several fibers to wait on. The fibers must be created using the `Fiber.schedule` call.
|
64
103
|
# @example
|
@@ -109,4 +148,16 @@ class Fiber
|
|
109
148
|
fibers.map!(&:__get_result)
|
110
149
|
end
|
111
150
|
end
|
151
|
+
|
152
|
+
# @!method self.schedule(&block)
|
153
|
+
# Create a non-blocking fiber. Should mostly be used in conjunction with `Fiber.await`.
|
154
|
+
# @example
|
155
|
+
# Fiber.await([
|
156
|
+
# Fiber.schedule { request_1 },
|
157
|
+
# Fiber.schedule { request_2 }
|
158
|
+
# ])
|
159
|
+
# @example
|
160
|
+
# fiber_1 = Fiber.schedule { request_1 }
|
161
|
+
# fiber_2 = Fiber.schedule { request_2 }
|
162
|
+
# Fiber.await([fiber_1, fiber_2])
|
112
163
|
end
|
@@ -17,8 +17,8 @@ class Rage::JSONFormatter
|
|
17
17
|
|
18
18
|
if final = logger[:final]
|
19
19
|
params, env = final[:params], final[:env]
|
20
|
-
if params
|
21
|
-
return "{\"tags\":[\"#{tags[0]}\"],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"info\",\"method\":\"#{env["REQUEST_METHOD"]}\",\"path\":\"#{env["PATH_INFO"]}\",\"controller\":\"#{params[:controller]}\",\"action\":\"#{params[:action]}\",#{context_msg}\"status\":#{final[:response][0]},\"duration\":#{final[:duration]}}\n"
|
20
|
+
if params && params[:controller]
|
21
|
+
return "{\"tags\":[\"#{tags[0]}\"],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"info\",\"method\":\"#{env["REQUEST_METHOD"]}\",\"path\":\"#{env["PATH_INFO"]}\",\"controller\":\"#{Rage::Router::Util.path_to_name(params[:controller])}\",\"action\":\"#{params[:action]}\",#{context_msg}\"status\":#{final[:response][0]},\"duration\":#{final[:duration]}}\n"
|
22
22
|
else
|
23
23
|
# no controller/action keys are written if there are no params
|
24
24
|
return "{\"tags\":[\"#{tags[0]}\"],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"info\",\"method\":\"#{env["REQUEST_METHOD"]}\",\"path\":\"#{env["PATH_INFO"]}\",#{context_msg}\"status\":#{final[:response][0]},\"duration\":#{final[:duration]}}\n"
|
data/lib/rage/logger/logger.rb
CHANGED
@@ -33,6 +33,23 @@ require "logger"
|
|
33
33
|
# Rage.logger.info("Initializing")
|
34
34
|
# Rage.logger.debug { "This is a " + potentially + " expensive operation" }
|
35
35
|
# ```
|
36
|
+
#
|
37
|
+
# ## Using the logger
|
38
|
+
# The recommended approach to logging with Rage is to make sure your code always logs the same message no matter what the input is.
|
39
|
+
# You can achieve this by using the {with_context} and {tagged} methods. So, a code like this:
|
40
|
+
# ```ruby
|
41
|
+
# def process_purchase(user_id:, product_id:)
|
42
|
+
# Rage.logger.info "processing purchase with user_id = #{user_id}; product_id = #{product_id}"
|
43
|
+
# end
|
44
|
+
# ```
|
45
|
+
# turns into this:
|
46
|
+
# ```ruby
|
47
|
+
# def process_purchase(user_id:, product_id:)
|
48
|
+
# Rage.logger.with_context(user_id: user_id, product_id: product_id) do
|
49
|
+
# Rage.logger.info "processing purchase"
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
# ```
|
36
53
|
class Rage::Logger
|
37
54
|
METHODS_MAP = {
|
38
55
|
"debug" => Logger::DEBUG,
|
@@ -129,13 +146,6 @@ class Rage::Logger
|
|
129
146
|
false
|
130
147
|
end
|
131
148
|
RUBY
|
132
|
-
elsif (Rage.config.internal.rails_mode ? Rage.config.internal.rails_console : defined?(IRB))
|
133
|
-
# the call was made from the console - don't use the formatter
|
134
|
-
<<-RUBY
|
135
|
-
def #{level_name}(msg = nil)
|
136
|
-
@logdev.write((msg || yield) + "\n")
|
137
|
-
end
|
138
|
-
RUBY
|
139
149
|
elsif @formatter.class.name.start_with?("Rage::")
|
140
150
|
# the call was made from within the application and a built-in formatter is used;
|
141
151
|
# in such case we use the `gen_timestamp` method which is much faster than `Time.now.strftime`;
|
@@ -17,8 +17,8 @@ class Rage::TextFormatter
|
|
17
17
|
|
18
18
|
if final = logger[:final]
|
19
19
|
params, env = final[:params], final[:env]
|
20
|
-
if params
|
21
|
-
return "[#{tags[0]}] timestamp=#{timestamp} pid=#{@pid} level=info method=#{env["REQUEST_METHOD"]} path=#{env["PATH_INFO"]} controller=#{params[:controller]} action=#{params[:action]} #{context_msg}status=#{final[:response][0]} duration=#{final[:duration]}\n"
|
20
|
+
if params && params[:controller]
|
21
|
+
return "[#{tags[0]}] timestamp=#{timestamp} pid=#{@pid} level=info method=#{env["REQUEST_METHOD"]} path=#{env["PATH_INFO"]} controller=#{Rage::Router::Util.path_to_name(params[:controller])} action=#{params[:action]} #{context_msg}status=#{final[:response][0]} duration=#{final[:duration]}\n"
|
22
22
|
else
|
23
23
|
# no controller/action keys are written if there are no params
|
24
24
|
return "[#{tags[0]}] timestamp=#{timestamp} pid=#{@pid} level=info method=#{env["REQUEST_METHOD"]} path=#{env["PATH_INFO"]} #{context_msg}status=#{final[:response][0]} duration=#{final[:duration]}\n"
|
data/lib/rage/rails.rb
CHANGED
@@ -11,12 +11,6 @@ Iodine.patch_rack
|
|
11
11
|
# configure the framework
|
12
12
|
Rage.config.internal.rails_mode = true
|
13
13
|
|
14
|
-
# make sure log formatter is not used in console
|
15
|
-
Rails.application.console do
|
16
|
-
Rage.config.internal.rails_console = true
|
17
|
-
Rage.logger.level = Rage.logger.level if Rage.logger # trigger redefining log methods
|
18
|
-
end
|
19
|
-
|
20
14
|
# patch ActiveRecord's connection pool
|
21
15
|
if defined?(ActiveRecord)
|
22
16
|
Rails.configuration.after_initialize do
|
data/lib/rage/router/backend.rb
CHANGED
@@ -67,7 +67,7 @@ class Rage::Router::Backend
|
|
67
67
|
if handler.is_a?(String)
|
68
68
|
raise "Invalid route handler format, expected to match the 'controller#action' pattern" unless handler =~ STRING_HANDLER_REGEXP
|
69
69
|
|
70
|
-
controller, action =
|
70
|
+
controller, action = Rage::Router::Util.path_to_class($1), $2
|
71
71
|
run_action_method_name = controller.__register_action(action.to_sym)
|
72
72
|
|
73
73
|
meta[:controller] = $1
|
@@ -273,22 +273,4 @@ class Rage::Router::Backend
|
|
273
273
|
end
|
274
274
|
end
|
275
275
|
end
|
276
|
-
|
277
|
-
def to_controller_class(str)
|
278
|
-
str.capitalize!
|
279
|
-
str.gsub!(/([\/_])([a-zA-Z0-9]+)/) do
|
280
|
-
if $1 == "/"
|
281
|
-
"::#{$2.capitalize}"
|
282
|
-
else
|
283
|
-
$2.capitalize
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
klass = "#{str}Controller"
|
288
|
-
if Object.const_defined?(klass)
|
289
|
-
Object.const_get(klass)
|
290
|
-
else
|
291
|
-
raise Rage::Errors::RouterError, "Routing error: could not find the #{klass} class"
|
292
|
-
end
|
293
|
-
end
|
294
276
|
end
|
@@ -48,10 +48,11 @@ class Rage::Router::HandlerStorage
|
|
48
48
|
private
|
49
49
|
|
50
50
|
def compile_create_params_object(param_keys, defaults, meta)
|
51
|
-
lines = [
|
52
|
-
":controller => '#{meta[:controller]}'.freeze",
|
53
|
-
|
54
|
-
|
51
|
+
lines = if meta[:controller]
|
52
|
+
[":controller => '#{meta[:controller]}'.freeze", ":action => '#{meta[:action]}'.freeze"]
|
53
|
+
else
|
54
|
+
[]
|
55
|
+
end
|
55
56
|
|
56
57
|
param_keys.each_with_index do |key, i|
|
57
58
|
lines << ":#{key} => param_values[#{i}]"
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Rage::Router::Util
|
2
|
+
class << self
|
3
|
+
# converts controller name in a path form into a class
|
4
|
+
# `api/v1/users` => `Api::V1::UsersController`
|
5
|
+
def path_to_class(str)
|
6
|
+
str = str.capitalize
|
7
|
+
str.gsub!(/([\/_])([a-zA-Z0-9]+)/) do
|
8
|
+
if $1 == "/"
|
9
|
+
"::#{$2.capitalize}"
|
10
|
+
else
|
11
|
+
$2.capitalize
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
klass = "#{str}Controller"
|
16
|
+
if Object.const_defined?(klass)
|
17
|
+
Object.const_get(klass)
|
18
|
+
else
|
19
|
+
raise Rage::Errors::RouterError, "Routing error: could not find the #{klass} class"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
@@names_map = {}
|
24
|
+
|
25
|
+
# converts controller name in a path form into a string representation of a class
|
26
|
+
# `api/v1/users` => `"Api::V1::UsersController"`
|
27
|
+
def path_to_name(str)
|
28
|
+
@@names_map[str] || begin
|
29
|
+
@@names_map[str] = path_to_class(str).name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/rage/rspec.rb
CHANGED
data/lib/rage/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rage-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roman Samoilov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -126,6 +126,7 @@ files:
|
|
126
126
|
- lib/rage/router/handler_storage.rb
|
127
127
|
- lib/rage/router/node.rb
|
128
128
|
- lib/rage/router/strategies/host.rb
|
129
|
+
- lib/rage/router/util.rb
|
129
130
|
- lib/rage/rspec.rb
|
130
131
|
- lib/rage/setup.rb
|
131
132
|
- lib/rage/sidekiq_session.rb
|