rage-rb 1.19.1 → 1.20.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/Appraisals +19 -0
  4. data/CHANGELOG.md +20 -0
  5. data/CODE_OF_CONDUCT.md +13 -17
  6. data/Gemfile +3 -0
  7. data/README.md +60 -63
  8. data/Rakefile +14 -0
  9. data/lib/rage/all.rb +3 -0
  10. data/lib/rage/cable/cable.rb +11 -7
  11. data/lib/rage/cable/channel.rb +6 -1
  12. data/lib/rage/cable/connection.rb +4 -0
  13. data/lib/rage/cable/router.rb +14 -9
  14. data/lib/rage/configuration.rb +235 -21
  15. data/lib/rage/controller/api.rb +50 -45
  16. data/lib/rage/cookies.rb +7 -23
  17. data/lib/rage/deferred/context.rb +30 -2
  18. data/lib/rage/deferred/deferred.rb +18 -6
  19. data/lib/rage/deferred/metadata.rb +39 -0
  20. data/lib/rage/deferred/middleware_chain.rb +67 -0
  21. data/lib/rage/deferred/task.rb +45 -17
  22. data/lib/rage/events/events.rb +3 -3
  23. data/lib/rage/events/subscriber.rb +36 -25
  24. data/lib/rage/fiber.rb +33 -31
  25. data/lib/rage/fiber_scheduler.rb +6 -2
  26. data/lib/rage/logger/logger.rb +7 -1
  27. data/lib/rage/middleware/body_finalizer.rb +14 -0
  28. data/lib/rage/middleware/cors.rb +12 -12
  29. data/lib/rage/middleware/request_id.rb +1 -1
  30. data/lib/rage/openapi/openapi.rb +2 -2
  31. data/lib/rage/response.rb +12 -7
  32. data/lib/rage/rspec.rb +17 -17
  33. data/lib/rage/setup.rb +2 -2
  34. data/lib/rage/telemetry/handler.rb +131 -0
  35. data/lib/rage/telemetry/spans/await_fiber.rb +50 -0
  36. data/lib/rage/telemetry/spans/broadcast_cable_stream.rb +50 -0
  37. data/lib/rage/telemetry/spans/create_websocket_connection.rb +50 -0
  38. data/lib/rage/telemetry/spans/dispatch_fiber.rb +48 -0
  39. data/lib/rage/telemetry/spans/enqueue_deferred_task.rb +52 -0
  40. data/lib/rage/telemetry/spans/process_cable_action.rb +56 -0
  41. data/lib/rage/telemetry/spans/process_cable_connection.rb +56 -0
  42. data/lib/rage/telemetry/spans/process_controller_action.rb +56 -0
  43. data/lib/rage/telemetry/spans/process_deferred_task.rb +54 -0
  44. data/lib/rage/telemetry/spans/process_event_subscriber.rb +54 -0
  45. data/lib/rage/telemetry/spans/publish_event.rb +54 -0
  46. data/lib/rage/telemetry/spans/spawn_fiber.rb +50 -0
  47. data/lib/rage/telemetry/telemetry.rb +121 -0
  48. data/lib/rage/telemetry/tracer.rb +97 -0
  49. data/lib/rage/version.rb +1 -1
  50. data/rage.gemspec +5 -4
  51. metadata +42 -9
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Rage::Deferred::MiddlewareChain
4
+ def initialize(enqueue_middleware:, perform_middleware:)
5
+ @enqueue_middleware = enqueue_middleware
6
+ @perform_middleware = perform_middleware
7
+
8
+ build_enqueue_chain!
9
+ build_perform_chain!
10
+ end
11
+
12
+ private
13
+
14
+ def build_enqueue_chain!
15
+ raw_arguments = {
16
+ phase: ":enqueue",
17
+ args: "Rage::Deferred::Context.get_or_create_args(context)",
18
+ kwargs: "Rage::Deferred::Context.get_or_create_kwargs(context)",
19
+ context: "Rage::Deferred::Context.get_or_create_user_context(context)",
20
+ task_class: "Rage::Deferred::Context.get_task(context)",
21
+ delay: "delay",
22
+ delay_until: "delay_until"
23
+ }
24
+
25
+ self.class.class_eval <<~RUBY, __FILE__, __LINE__ + 1
26
+ def with_enqueue_middleware(context, delay:, delay_until:)
27
+ #{build_middleware_chain(:@enqueue_middleware, raw_arguments)}
28
+ end
29
+ RUBY
30
+ end
31
+
32
+ def build_perform_chain!
33
+ raw_arguments = {
34
+ phase: ":perform",
35
+ args: "Rage::Deferred::Context.get_or_create_args(context)",
36
+ kwargs: "Rage::Deferred::Context.get_or_create_kwargs(context)",
37
+ context: "Rage::Deferred::Context.get_or_create_user_context(context)",
38
+ task_class: "task.class",
39
+ task: "task"
40
+ }
41
+
42
+ self.class.class_eval <<~RUBY, __FILE__, __LINE__ + 1
43
+ def with_perform_middleware(context, task:)
44
+ #{build_middleware_chain(:@perform_middleware, raw_arguments)}
45
+ end
46
+ RUBY
47
+ end
48
+
49
+ def build_middleware_chain(middlewares_var, raw_arguments)
50
+ middlewares = instance_variable_get(middlewares_var)
51
+ i = middlewares.length
52
+
53
+ middlewares.reverse.inject("yield") do |memo, middleware_with_args|
54
+ middleware, _, _ = middleware_with_args
55
+ arguments = Rage::Internal.build_arguments(middleware.instance_method(:call), raw_arguments)
56
+ i -= 1
57
+
58
+ <<~RUBY
59
+ middleware, args, block = #{middlewares_var}[#{i}]
60
+
61
+ middleware.new(*args, &block).call(#{arguments}) do
62
+ #{memo}
63
+ end
64
+ RUBY
65
+ end
66
+ end
67
+ end
@@ -37,29 +37,55 @@ module Rage::Deferred::Task
37
37
  BACKOFF_INTERVAL = 5
38
38
  private_constant :BACKOFF_INTERVAL
39
39
 
40
+ # @private
41
+ CONTEXT_KEY = :__rage_deferred_execution_context
42
+
40
43
  def perform
41
44
  end
42
45
 
46
+ # Access metadata for the current task execution.
47
+ # @return [Rage::Deferred::Metadata] the metadata object for the current task execution
48
+ # @example
49
+ # class MyTask
50
+ # include Rage::Deferred::Task
51
+ #
52
+ # def perform
53
+ # puts meta.retries
54
+ # end
55
+ # end
56
+ def meta
57
+ Rage::Deferred::Metadata
58
+ end
59
+
43
60
  # @private
44
61
  def __perform(context)
45
- args = Rage::Deferred::Context.get_args(context)
46
- kwargs = Rage::Deferred::Context.get_kwargs(context)
47
- attempts = Rage::Deferred::Context.get_attempts(context)
48
-
49
62
  restore_log_info(context)
50
63
 
64
+ attempts = Rage::Deferred::Context.get_attempts(context)
51
65
  task_log_context = { task: self.class.name }
52
66
  task_log_context[:attempt] = attempts + 1 if attempts
53
67
 
54
- Rage.logger.with_context(task_log_context) do
55
- perform(*args, **kwargs)
56
- true
57
- rescue Rage::Deferred::TaskFailed
58
- false
59
- rescue Exception => e
60
- Rage.logger.error("Deferred task failed with exception: #{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}")
61
- false
68
+ Fiber[CONTEXT_KEY] = context
69
+
70
+ Rage::Telemetry.tracer.span_deferred_task_process(task: self, context:) do
71
+ Rage::Deferred.__middleware_chain.with_perform_middleware(context, task: self) do
72
+ Rage.logger.with_context(task_log_context) do
73
+ args = Rage::Deferred::Context.get_args(context)
74
+ kwargs = Rage::Deferred::Context.get_kwargs(context)
75
+
76
+ perform(*args, **kwargs)
77
+ end
78
+ end
62
79
  end
80
+
81
+ true
82
+ rescue Exception => e
83
+ unless respond_to?(:__deferred_suppress_exception_logging?, true) && __deferred_suppress_exception_logging?
84
+ Rage.logger.with_context(task_log_context) do
85
+ Rage.logger.error("Deferred task failed with exception: #{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}")
86
+ end
87
+ end
88
+ false
63
89
  end
64
90
 
65
91
  private def restore_log_info(context)
@@ -80,11 +106,13 @@ module Rage::Deferred::Task
80
106
 
81
107
  module ClassMethods
82
108
  def enqueue(*args, delay: nil, delay_until: nil, **kwargs)
83
- Rage::Deferred.__queue.enqueue(
84
- Rage::Deferred::Context.build(self, args, kwargs),
85
- delay:,
86
- delay_until:
87
- )
109
+ context = Rage::Deferred::Context.build(self, args, kwargs)
110
+
111
+ Rage::Telemetry.tracer.span_deferred_task_enqueue(task_class: self, context:) do
112
+ Rage::Deferred.__middleware_chain.with_enqueue_middleware(context, delay:, delay_until:) do
113
+ Rage::Deferred.__queue.enqueue(context, delay:, delay_until:)
114
+ end
115
+ end
88
116
 
89
117
  nil
90
118
  end
@@ -39,7 +39,9 @@ module Rage::Events
39
39
  # Rage::Events.publish(MyEvent.new, context: { published_at: Time.now })
40
40
  def self.publish(event, context: nil)
41
41
  handler = __event_handlers[event.class] || __build_event_handler(event.class)
42
- handler.call(event, context)
42
+ Rage::Telemetry.tracer.span_events_event_publish(event:, context:) do
43
+ handler.call(event, context)
44
+ end
43
45
 
44
46
  nil
45
47
  end
@@ -71,8 +73,6 @@ module Rage::Events
71
73
  # @private
72
74
  def self.__build_event_handler(event_class)
73
75
  subscriber_calls = __get_subscribers(event_class).map do |subscriber_class|
74
- subscriber_class.__register_rescue_handlers
75
-
76
76
  arguments = "event"
77
77
 
78
78
  context_type, _ = subscriber_class.instance_method(:call).parameters.find do |param_type, param_name|
@@ -79,16 +79,28 @@ module Rage::Events::Subscriber
79
79
 
80
80
  # @private
81
81
  def __call(event, context: nil)
82
- Rage.logger.with_context(self.class.__log_context) do
83
- context.nil? ? call(event) : call(event, context: context.freeze)
84
- rescue Exception => _e
85
- e = self.class.__rescue_handlers ? __run_rescue_handlers(_e) : _e
86
-
87
- if e
88
- Rage.logger.error("Subscriber failed with exception: #{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}")
89
- raise Rage::Deferred::TaskFailed if self.class.__is_deferred
82
+ Rage::Telemetry.tracer.span_events_subscriber_process(event:, context:, subscriber: self) do
83
+ Rage.logger.with_context(self.class.__log_context) do
84
+ with_exception_handler do
85
+ context.nil? ? call(event) : call(event, context: context.freeze)
86
+ end
90
87
  end
91
88
  end
89
+ rescue Exception => e
90
+ Rage.logger.with_context(self.class.__log_context) do
91
+ Rage.logger.error("Subscriber failed with exception: #{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}")
92
+ end
93
+ raise e if self.class.__is_deferred
94
+ end
95
+
96
+ private
97
+
98
+ def __deferred_suppress_exception_logging?
99
+ true
100
+ end
101
+
102
+ def with_exception_handler
103
+ yield
92
104
  end
93
105
 
94
106
  module ClassMethods
@@ -136,6 +148,14 @@ module Rage::Events::Subscriber
136
148
  end
137
149
 
138
150
  @__rescue_handlers.unshift([klasses, with])
151
+
152
+ rebuild_exception_handler!
153
+ end
154
+
155
+ # Check if the subscriber is executed in the background.
156
+ # @return [Boolean] `true` if the subscriber is deferred, `false` otherwise
157
+ def deferred?
158
+ @__is_deferred
139
159
  end
140
160
 
141
161
  # @private
@@ -144,29 +164,20 @@ module Rage::Events::Subscriber
144
164
  klass.subscribe_to(*@__event_classes, deferred: @__is_deferred) if @__event_classes
145
165
  end
146
166
 
147
- # @private
148
- def __register_rescue_handlers
149
- return if method_defined?(:__run_rescue_handlers, false) || @__rescue_handlers.nil?
150
-
151
- matcher_calls = @__rescue_handlers.map do |klasses, handler|
152
- handler_call = instance_method(handler).arity == 0 ? handler : "#{handler}(exception)"
167
+ private
153
168
 
169
+ def rebuild_exception_handler!
170
+ rescue_calls = @__rescue_handlers.map do |klasses, handler|
154
171
  <<~RUBY
155
- when #{klasses.join(", ")}
156
- #{handler_call}
157
- nil
172
+ rescue #{klasses.join(", ")} => e
173
+ method(:#{handler}).arity == 0 ? #{handler} : #{handler}(e)
158
174
  RUBY
159
175
  end
160
176
 
161
177
  class_eval <<~RUBY, __FILE__, __LINE__ + 1
162
- def __run_rescue_handlers(exception)
163
- case exception
164
- #{matcher_calls.join("\n")}
165
- else
166
- exception
167
- end
168
- rescue Exception => e
169
- e
178
+ private def with_exception_handler
179
+ yield
180
+ #{rescue_calls.join("\n")}
170
181
  end
171
182
  RUBY
172
183
  end
data/lib/rage/fiber.rb CHANGED
@@ -158,43 +158,45 @@ class Fiber
158
158
  f, fibers = Fiber.current, Array(fibers)
159
159
  await_channel = f.__await_channel(true)
160
160
 
161
- # check which fibers are alive (i.e. have yielded) and which have errored out
162
- i, err, num_wait_for = 0, nil, 0
163
- while i < fibers.length
164
- if fibers[i].alive?
165
- num_wait_for += 1
166
- else
167
- err = fibers[i].__get_err
168
- break if err
161
+ Rage::Telemetry.tracer.span_core_fiber_await(fibers:) do
162
+ # check which fibers are alive (i.e. have yielded) and which have errored out
163
+ i, err, num_wait_for = 0, nil, 0
164
+ while i < fibers.length
165
+ if fibers[i].alive?
166
+ num_wait_for += 1
167
+ else
168
+ err = fibers[i].__get_err
169
+ break if err
170
+ end
171
+ i += 1
169
172
  end
170
- i += 1
171
- end
172
173
 
173
- # raise if one of the fibers has errored out or return the result if none have yielded
174
- if err
175
- raise err
176
- elsif num_wait_for == 0
177
- return fibers.map!(&:__get_result)
178
- end
174
+ # raise if one of the fibers has errored out or return the result if none have yielded
175
+ if err
176
+ raise err
177
+ elsif num_wait_for == 0
178
+ return fibers.map!(&:__get_result)
179
+ end
179
180
 
180
- # wait on async fibers; resume right away if one of the fibers errors out
181
- Iodine.subscribe(await_channel) do |_, err|
182
- if err == AWAIT_ERROR_MESSAGE
183
- f.resume
184
- else
185
- num_wait_for -= 1
186
- f.resume if num_wait_for == 0
181
+ # wait on async fibers; resume right away if one of the fibers errors out
182
+ Iodine.subscribe(await_channel) do |_, err|
183
+ if err == AWAIT_ERROR_MESSAGE
184
+ f.resume
185
+ else
186
+ num_wait_for -= 1
187
+ f.resume if num_wait_for == 0
188
+ end
187
189
  end
188
- end
189
190
 
190
- Fiber.defer(-1)
191
- Iodine.defer { Iodine.unsubscribe(await_channel) }
191
+ Fiber.defer(-1)
192
+ Iodine.defer { Iodine.unsubscribe(await_channel) }
192
193
 
193
- # if num_wait_for is not 0 means we exited prematurely because of an error
194
- if num_wait_for > 0
195
- raise fibers.find(&:__get_err).__get_err
196
- else
197
- fibers.map!(&:__get_result)
194
+ # if num_wait_for is not 0 means we exited prematurely because of an error
195
+ if num_wait_for > 0
196
+ raise fibers.find(&:__get_err).__get_err
197
+ else
198
+ fibers.map!(&:__get_result)
199
+ end
198
200
  end
199
201
  end
200
202
 
@@ -120,7 +120,9 @@ class Rage::FiberScheduler
120
120
  # the fiber to wrap a request in
121
121
  Fiber.new(blocking: false) do
122
122
  Fiber.current.__set_id
123
- Fiber.current.__set_result(block.call)
123
+ Rage::Telemetry.tracer.span_core_fiber_dispatch do
124
+ Fiber.current.__set_result(block.call)
125
+ end
124
126
  end
125
127
  else
126
128
  # the fiber was created in the user code
@@ -128,7 +130,9 @@ class Rage::FiberScheduler
128
130
 
129
131
  Fiber.new(blocking: false) do
130
132
  Thread.current[:rage_logger] = logger
131
- Fiber.current.__set_result(block.call)
133
+ Rage::Telemetry.tracer.span_core_fiber_spawn(parent:) do
134
+ Fiber.current.__set_result(block.call)
135
+ end
132
136
  # send a message for `Fiber.await` to work
133
137
  Iodine.publish(parent.__await_channel, "", Iodine::PubSub::PROCESS) if parent.alive?
134
138
  rescue Exception => e
@@ -209,7 +209,13 @@ class Rage::Logger
209
209
  RUBY
210
210
  elsif @external_logger.is_a?(External::Dynamic)
211
211
  # a callable object is used as a logger
212
- parameters = Rage::Internal.build_arguments(@external_logger.wrapped, {
212
+ call_method = if @external_logger.wrapped.is_a?(Proc)
213
+ @external_logger.wrapped
214
+ else
215
+ @external_logger.wrapped.method(:call)
216
+ end
217
+
218
+ parameters = Rage::Internal.build_arguments(call_method, {
213
219
  severity: ":#{level_name}",
214
220
  tags: "logger[:tags].freeze",
215
221
  context: "logger[:context].freeze",
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Rage::BodyFinalizer
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ response = @app.call(env)
10
+ response[2].close
11
+
12
+ response
13
+ end
14
+ end
@@ -14,20 +14,20 @@ class Rage::Cors
14
14
  end
15
15
 
16
16
  response = @app.call(env)
17
- response[1]["Access-Control-Allow-Credentials"] = @allow_credentials if @allow_credentials
18
- response[1]["Access-Control-Expose-Headers"] = @expose_headers if @expose_headers
17
+ response[1]["access-control-allow-credentials"] = @allow_credentials if @allow_credentials
18
+ response[1]["access-control-expose-headers"] = @expose_headers if @expose_headers
19
19
 
20
20
  response
21
21
  ensure
22
22
  if !$! && (origin = @cors_check.call(env))
23
23
  headers = response[1]
24
- headers["Access-Control-Allow-Origin"] = origin
24
+ headers["access-control-allow-origin"] = origin
25
25
  if @origins != "*"
26
- vary = headers["Vary"]
26
+ vary = headers["vary"]
27
27
  if vary.nil?
28
- headers["Vary"] = "Origin"
28
+ headers["vary"] = "Origin"
29
29
  elsif vary != "Origin"
30
- headers["Vary"] += ", Origin"
30
+ headers["vary"] += ", Origin"
31
31
  end
32
32
  end
33
33
  end
@@ -98,21 +98,21 @@ class Rage::Cors
98
98
 
99
99
  def create_headers
100
100
  headers = {
101
- "Access-Control-Allow-Origin" => "",
102
- "Access-Control-Allow-Methods" => @methods
101
+ "access-control-allow-origin" => "",
102
+ "access-control-allow-methods" => @methods
103
103
  }
104
104
 
105
105
  if @allow_headers
106
- headers["Access-Control-Allow-Headers"] = @allow_headers
106
+ headers["access-control-allow-headers"] = @allow_headers
107
107
  end
108
108
  if @expose_headers
109
- headers["Access-Control-Expose-Headers"] = @expose_headers
109
+ headers["access-control-expose-headers"] = @expose_headers
110
110
  end
111
111
  if @max_age
112
- headers["Access-Control-Max-Age"] = @max_age
112
+ headers["access-control-max-age"] = @max_age
113
113
  end
114
114
  if @allow_credentials
115
- headers["Access-Control-Allow-Credentials"] = @allow_credentials
115
+ headers["access-control-allow-credentials"] = @allow_credentials
116
116
  end
117
117
 
118
118
  headers
@@ -24,7 +24,7 @@ class Rage::RequestId
24
24
  def call(env)
25
25
  env["rage.request_id"] = validate_external_request_id(env["HTTP_X_REQUEST_ID"])
26
26
  response = @app.call(env)
27
- response[1]["X-Request-Id"] = env["rage.request_id"]
27
+ response[1]["x-request-id"] = env["rage.request_id"]
28
28
 
29
29
  response
30
30
  end
@@ -48,13 +48,13 @@ module Rage::OpenAPI
48
48
  spec_url = "#{scheme}://#{host}#{path}/json"
49
49
  page = ERB.new(File.read("#{__dir__}/index.html.erb")).result(binding)
50
50
 
51
- [200, { "Content-Type" => "text/html; charset=UTF-8" }, [page]]
51
+ [200, { "content-type" => "text/html; charset=UTF-8" }, [page]]
52
52
  end
53
53
  end
54
54
 
55
55
  json_app = ->(env) do
56
56
  spec = (__data_cache[[:spec, namespace]] ||= build(namespace:).to_json)
57
- [200, { "Content-Type" => "application/json" }, [spec]]
57
+ [200, { "content-type" => "application/json" }, [spec]]
58
58
  end
59
59
 
60
60
  app = ->(env) do
data/lib/rage/response.rb CHANGED
@@ -4,25 +4,30 @@ require "digest"
4
4
  require "time"
5
5
 
6
6
  class Rage::Response
7
- ETAG_HEADER = "ETag"
8
- LAST_MODIFIED_HEADER = "Last-Modified"
7
+ ETAG_HEADER = "etag"
8
+ LAST_MODIFIED_HEADER = "last-modified"
9
9
 
10
10
  # @private
11
- def initialize(headers, body)
12
- @headers = headers
13
- @body = body
11
+ def initialize(controller)
12
+ @controller = controller
13
+ end
14
+
15
+ # Returns the HTTP status code of the response.
16
+ # @return [Integer]
17
+ def status
18
+ @controller.__status
14
19
  end
15
20
 
16
21
  # Returns the content of the response as a string. This contains the contents of any calls to `render`.
17
22
  # @return [String]
18
23
  def body
19
- @body[0] || ""
24
+ @controller.__body[0] || ""
20
25
  end
21
26
 
22
27
  # Returns the headers for the response.
23
28
  # @return [Hash]
24
29
  def headers
25
- @headers
30
+ @controller.__headers
26
31
  end
27
32
 
28
33
  # Returns ETag response header or +nil+ if it's empty.
data/lib/rage/rspec.rb CHANGED
@@ -14,23 +14,6 @@ require_relative "#{Rage.root}/config/application"
14
14
  # verify the environment
15
15
  abort("The test suite is running in #{Rage.env} mode instead of 'test'!") unless Rage.env.test?
16
16
 
17
- # mock fiber methods as RSpec tests don't run concurrently
18
- class Fiber
19
- def self.schedule(&block)
20
- fiber = Fiber.new(blocking: true) do
21
- Fiber.current.__set_id
22
- Fiber.current.__set_result(block.call)
23
- end
24
- fiber.resume
25
-
26
- fiber
27
- end
28
-
29
- def self.await(fibers)
30
- Array(fibers).map(&:__get_result)
31
- end
32
- end
33
-
34
17
  # define request helpers
35
18
  module RageRequestHelpers
36
19
  include Rack::Test::Methods
@@ -96,6 +79,23 @@ end
96
79
  # include request helpers
97
80
  RSpec.configure do |config|
98
81
  config.include(RageRequestHelpers, type: :request)
82
+
83
+ # mock fiber methods as RSpec tests don't run concurrently
84
+ config.before do
85
+ allow(Fiber).to receive(:schedule) do |&block|
86
+ fiber = Fiber.new(blocking: true) do
87
+ Fiber.current.__set_id
88
+ Fiber.current.__set_result(block.call)
89
+ end
90
+ fiber.resume
91
+
92
+ fiber
93
+ end
94
+
95
+ allow(Fiber).to receive(:await) do |fibers|
96
+ Array(fibers).map(&:__get_result)
97
+ end
98
+ end
99
99
  end
100
100
 
101
101
  # patch MockResponse class
data/lib/rage/setup.rb CHANGED
@@ -14,9 +14,9 @@ require "rage/ext/setup"
14
14
  # Load application classes
15
15
  Rage.code_loader.setup
16
16
 
17
+ require_relative "#{Rage.root}/config/routes"
18
+
17
19
  # Run after_initialize hooks
18
20
  Rage.config.run_after_initialize!
19
21
 
20
- require_relative "#{Rage.root}/config/routes"
21
-
22
22
  Rage.config.internal.initialized!