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 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