rage-rb 1.0.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bb19c01aee898bea43a41450feeb655f94bde8277a4f9c18cab6b9d1accbb47
4
- data.tar.gz: fe4806d8a2bfbb71496a720371cc8416b5283c9b8284c1a72ec46384ee234348
3
+ metadata.gz: fd10469efc73f6134f72f79fe21871238b8860e958c085499c47b312adf91764
4
+ data.tar.gz: 34990a4885df4a4acd55fcbabf8d3a67e57e0609db406ee55d5f71cab0655ffb
5
5
  SHA512:
6
- metadata.gz: 43d06881f297512724588c06637a29eefcb8308fc1c086a09b013c4291d4ca58cf7b2ab482b1c1276a3e4a88544a71e95289268cc896d479cb92135f31555bf8
7
- data.tar.gz: 9af4f8816208451c18ff3b31d7d5a7e230561af93753d1ee0f8177f515b92f3dd2c47007b7f65be823ceeb69b6338683a05c57eb1a13e17cfe769b87e4ef2540
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 [changelog](https://github.com/rage-rb/rage/blob/master/CHANGELOG.md) and [upcoming-releases](https://github.com/rage-rb/rage#upcoming-releases) for currently supported and planned features.
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
@@ -18,6 +18,7 @@ require_relative "router/constrainer"
18
18
  require_relative "router/dsl"
19
19
  require_relative "router/handler_storage"
20
20
  require_relative "router/node"
21
+ require_relative "router/util"
21
22
 
22
23
  require_relative "controller/api"
23
24
 
@@ -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 preferred way of adding a middleware.**
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, :rails_console
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
- # server to process other requests. Once all fibers have completed, the current fiber will be automatically resumed.
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"
@@ -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
@@ -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 = to_controller_class($1), $2
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
- ":action => '#{meta[:action]}'.freeze"
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
@@ -26,8 +26,8 @@ class Fiber
26
26
  fiber
27
27
  end
28
28
 
29
- def self.await(_)
30
- # no-op
29
+ def self.await(fibers)
30
+ Array(fibers).map(&:__get_result)
31
31
  end
32
32
  end
33
33
 
data/lib/rage/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rage
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
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.0.0
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-13 00:00:00.000000000 Z
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