rage-rb 1.3.0 → 1.5.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: cef03d55f96279c7e6d451dc7ce0abce04024a7ca30d07e9c48c0d054c530bb6
4
- data.tar.gz: c399b5b3d060aee5367c2e3064435649bbb8eba7dcb2bf2bd215730a698f69d8
3
+ metadata.gz: 24a5bc4310d226a4072c06ac0a3cb015eb52163e7c37fc85aced61f5705b6ef5
4
+ data.tar.gz: 8bedd5b522c64c945a44fa4cb97e19f48d5b3a3aa46bb5687183c4cf3eac75f3
5
5
  SHA512:
6
- metadata.gz: cc5578e1e51d557a8f30729b0eff9a41d4a651e62c837af53aff3b3f50caee2b827be47a239aeadd47f2e9ff15ff8263f35a032634c1fdb738cb2b2c71787780
7
- data.tar.gz: 28a70f5c33752ea33dd1474bc4e481ba7b56fd76541544f838226c9b5ace3758b80d9a93b5947c1ea8cbf9374cbae204e9c0e6c5ff40f2adcbc40be3208f5597
6
+ metadata.gz: 10feb1fe42789d8470ecf9d7754be26b58db2f160f24c7e82aac8a831a5c4b828d297e01c2535b8e914d167de2bcf40a843b1964c5d1f3102ef4429016fefabd
7
+ data.tar.gz: 87c3ee06201e71f691b7158f911c0cfbf7763d2021801d298885c890772082a934d91fdb5df8c0f6bf65110acd480fd839ef19d6e9adc39d1c78d165a0e2701f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.5.0] - 2024-05-08
4
+
5
+ ### Added
6
+
7
+ - Allow to have both Rails and Rage controllers in one application (#83).
8
+ - Add `authenticate_or_request_with_http_token` (#85).
9
+ - Add the `member` and `controller` route helpers (#86).
10
+
11
+ ### Changed
12
+
13
+ - Deprecate `Rage.load_middlewares` (#83).
14
+
15
+ ### Fixed
16
+
17
+ - Correctly init console in Rails mode (credit to [efibootmgr](https://github.com/efibootmgr)) (#84).
18
+
19
+ ## [1.4.0] - 2024-05-01
20
+
21
+ ### Added
22
+
23
+ - Support cookies and sessions (#69).
24
+
25
+ ### Fixed
26
+
27
+ - Improve compatibility with ActiveRecord 7.1 (#80).
28
+
3
29
  ## [1.3.0] - 2024-04-17
4
30
 
5
31
  ### Added
data/Gemfile CHANGED
@@ -14,3 +14,6 @@ gem "yard"
14
14
  gem "pg"
15
15
  gem "mysql2"
16
16
  gem "connection_pool", "~> 2.0"
17
+
18
+ gem "rbnacl"
19
+ gem "domain_name"
data/README.md CHANGED
@@ -155,8 +155,8 @@ Status | Changes
155
155
  :white_check_mark: | ~~Add request logging.~~
156
156
  :white_check_mark: | ~~Automatic code reloading in development with Zeitwerk.~~
157
157
  :white_check_mark: | ~~Support conditional get with `etag` and `last_modified`.~~
158
+ :white_check_mark: | ~~Expose the `cookies` and `session` objects.~~
158
159
  ⏳ | Expose the `send_data` and `send_file` methods.
159
- ⏳ | Expose the `cookies` and `session` objects.
160
160
  ⏳ | Implement Iodine-based equivalent of Action Cable.
161
161
 
162
162
  ## Development
data/lib/rage/cli.rb CHANGED
@@ -116,10 +116,8 @@ module Rage
116
116
  def environment
117
117
  require File.expand_path("config/application.rb", Dir.pwd)
118
118
 
119
- # in Rails mode we delegate code loading to Rails, and thus need
120
- # to manually load application code for CLI utilities to work
121
119
  if Rage.config.internal.rails_mode
122
- require "rage/setup"
120
+ require File.expand_path("config/environment.rb", Dir.pwd)
123
121
  end
124
122
  end
125
123
 
@@ -15,6 +15,14 @@
15
15
  #
16
16
  # > Defines the verbosity of the Rage logger. This option defaults to `:debug` for all environments except production, where it defaults to `:info`. The available log levels are: `:debug`, `:info`, `:warn`, `:error`, `:fatal`, and `:unknown`.
17
17
  #
18
+ # • _config.secret_key_base_
19
+ #
20
+ # > The `secret_key_base` is used as the input secret to the application's key generator, which is used to encrypt cookies. Rage will fall back to the `SECRET_KEY_BASE` environment variable if this is not set.
21
+ #
22
+ # • _config.fallback_secret_key_base_
23
+ #
24
+ # > Defines one or several old secrets that need to be rotated. Can accept a single key or an array of keys. Rage will fall back to the `FALLBACK_SECRET_KEY_BASE` environment variable if this is not set.
25
+ #
18
26
  # # Middleware Configuration
19
27
  #
20
28
  # • _config.middleware.use_
@@ -100,6 +108,7 @@
100
108
  class Rage::Configuration
101
109
  attr_accessor :logger
102
110
  attr_reader :log_formatter, :log_level
111
+ attr_writer :secret_key_base, :fallback_secret_key_base
103
112
 
104
113
  # used in DSL
105
114
  def config = self
@@ -113,6 +122,14 @@ class Rage::Configuration
113
122
  @log_level = level.is_a?(Symbol) ? Logger.const_get(level.to_s.upcase) : level
114
123
  end
115
124
 
125
+ def secret_key_base
126
+ @secret_key_base || ENV["SECRET_KEY_BASE"]
127
+ end
128
+
129
+ def fallback_secret_key_base
130
+ Array(@fallback_secret_key_base || ENV["FALLBACK_SECRET_KEY_BASE"])
131
+ end
132
+
116
133
  def server
117
134
  @server ||= Server.new
118
135
  end
@@ -324,6 +324,18 @@ class RageController::API
324
324
  @response ||= Rage::Response.new(@__headers, @__body)
325
325
  end
326
326
 
327
+ # Get the cookie object. See {Rage::Cookies}.
328
+ # @return [Rage::Cookies]
329
+ def cookies
330
+ @cookies ||= Rage::Cookies.new(@__env, self)
331
+ end
332
+
333
+ # Get the session object. See {Rage::Session}.
334
+ # @return [Rage::Session]
335
+ def session
336
+ @session ||= Rage::Session.new(self)
337
+ end
338
+
327
339
  # Send a response to the client.
328
340
  #
329
341
  # @param json [String, Object] send a json response to the client; objects like arrays will be serialized automatically
@@ -416,6 +428,28 @@ class RageController::API
416
428
  yield token
417
429
  end
418
430
 
431
+ # Authenticate using an HTTP Bearer token, or otherwise render an HTTP header requesting the client to send a
432
+ # Bearer token. For the authentication to be considered successful, the block should return a non-nil value.
433
+ #
434
+ # @yield [token] token value extracted from the `Authorization` header
435
+ # @example
436
+ # before_action :authenticate
437
+ #
438
+ # def authenticate
439
+ # authenticate_or_request_with_http_token do |token|
440
+ # ApiToken.find_by(token: token)
441
+ # end
442
+ # end
443
+ def authenticate_or_request_with_http_token
444
+ authenticate_with_http_token { |token| yield(token) } || request_http_token_authentication
445
+ end
446
+
447
+ # Render an HTTP header requesting the client to send a Bearer token for authentication.
448
+ def request_http_token_authentication
449
+ headers["Www-Authenticate"] = "Token"
450
+ render plain: "HTTP Token: Access denied.", status: 401
451
+ end
452
+
419
453
  if !defined?(::ActionController::Parameters)
420
454
  # Get the request data. The keys inside the hash are symbols, so `params.keys` returns an array of `Symbol`.<br>
421
455
  # You can also load Strong Params to have Rage automatically wrap `params` in an instance of `ActionController::Parameters`.<br>
@@ -477,4 +511,9 @@ class RageController::API
477
511
  # def append_info_to_payload(payload)
478
512
  # payload[:response] = response.body
479
513
  # end
514
+
515
+ # Reset the entire session. See {Rage::Session}.
516
+ def reset_session
517
+ session.clear
518
+ end
480
519
  end
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "time"
5
+
6
+ if !defined?(DomainName)
7
+ fail <<~ERR
8
+
9
+ rage-rb depends on domain_name to specify the domain name for cookies. Add the following line to your Gemfile:
10
+ gem "domain_name"
11
+
12
+ ERR
13
+ end
14
+
15
+ class Rage::Cookies
16
+ # @private
17
+ def initialize(env, controller)
18
+ @env = env
19
+ @headers = controller.headers
20
+ @request_cookies = {}
21
+ @parsed = false
22
+
23
+ @jar = SimpleJar
24
+ end
25
+
26
+ # Read a cookie.
27
+ #
28
+ # @param key [Symbol]
29
+ # @return [String]
30
+ def [](key)
31
+ value = request_cookies[key]
32
+ @jar.load(value) if value
33
+ end
34
+
35
+ # Get the number of cookies.
36
+ #
37
+ # @return [Integer]
38
+ def size
39
+ request_cookies.count { |_, v| !v.nil? }
40
+ end
41
+
42
+ # Delete a cookie.
43
+ #
44
+ # @param key [Symbol]
45
+ # @param path [String]
46
+ # @param domain [String]
47
+ def delete(key, path: "/", domain: nil)
48
+ @headers.compare_by_identity
49
+
50
+ @request_cookies[key] = nil
51
+ @headers[set_cookie_key(key)] = Rack::Utils.add_cookie_to_header(nil, key, {
52
+ value: "", expires: Time.at(0), path: path, domain: domain
53
+ })
54
+ end
55
+
56
+ # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them
57
+ # for read. If the cookie was tampered with by the user (or a 3rd party), `nil` will be returned.
58
+ #
59
+ # This jar requires that you set a suitable secret for the verification on your app's `secret_key_base`.
60
+ #
61
+ # @example
62
+ # cookies.encrypted[:user_id] = current_user.id
63
+ def encrypted
64
+ dup.tap { |c| c.jar = EncryptedJar }
65
+ end
66
+
67
+ # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now.
68
+ #
69
+ # @example
70
+ # cookies.permanent[:user_id] = current_user.id
71
+ def permanent
72
+ dup.tap { |c| c.expires = Time.now + 20 * 365 * 24 * 60 * 60 }
73
+ end
74
+
75
+ # Set a cookie.
76
+ #
77
+ # @param key [Symbol]
78
+ # @param value [String, Hash]
79
+ # @option value [String] :path
80
+ # @option value [Boolean] :secure
81
+ # @option value [Boolean] :httponly
82
+ # @option value [nil, :none, :lax, :strict] :same_site
83
+ # @option value [Time] :expires
84
+ # @option value [String, Array<String>, :all] :domain
85
+ # @option value [String] :value
86
+ # @example
87
+ # cookie[:user_id] = current_user.id
88
+ # @example
89
+ # cookie[:user_id] = { value: current_user.id, httponly: true, secure: true }
90
+ def []=(key, value)
91
+ @headers.compare_by_identity
92
+
93
+ unless value.is_a?(Hash)
94
+ serialized_value = @jar.dump(value)
95
+ @request_cookies[key] = serialized_value
96
+ @headers[set_cookie_key(key)] = Rack::Utils.add_cookie_to_header(nil, key, { value: serialized_value, expires: @expires })
97
+ return
98
+ end
99
+
100
+ if domain = value[:domain]
101
+ host = @env["HTTP_HOST"]
102
+
103
+ _domain = if domain.is_a?(String)
104
+ domain
105
+ elsif domain == :all
106
+ DomainName(host).domain
107
+ elsif domain.is_a?(Array)
108
+ host if domain.include?(host)
109
+ end
110
+ end
111
+
112
+ serialized_value = @jar.dump(value[:value])
113
+ cookie = Rack::Utils.add_cookie_to_header(nil, key, {
114
+ path: value[:path],
115
+ secure: value[:secure],
116
+ expires: value[:expires] || @expires,
117
+ httponly: value[:httponly],
118
+ same_site: value[:same_site],
119
+ value: serialized_value,
120
+ domain: _domain
121
+ })
122
+
123
+ @request_cookies[key] = serialized_value
124
+ @headers[set_cookie_key(key)] = cookie
125
+ end
126
+
127
+ def inspect
128
+ cookies = request_cookies.transform_values do |v|
129
+ decoded = Base64.urlsafe_decode64(v) rescue nil
130
+ is_encrypted = decoded&.start_with?(EncryptedJar::PADDING)
131
+
132
+ is_encrypted ? "<encrypted>" : v
133
+ end
134
+
135
+ "#<#{self.class.name} @request_cookies=#{cookies.inspect}"
136
+ end
137
+
138
+ private
139
+
140
+ def request_cookies
141
+ return @request_cookies if @parsed
142
+
143
+ @parsed = true
144
+ if cookie_header = @env["HTTP_COOKIE"]
145
+ cookie_header.split(/; */n).each do |cookie|
146
+ next if cookie.empty?
147
+ key, value = cookie.split("=", 2).yield_self { |k, _| [k.to_sym, _] }
148
+ unless @request_cookies.has_key?(key)
149
+ @request_cookies[key] = (Rack::Utils.unescape(value, Encoding::UTF_8) rescue value)
150
+ end
151
+ end
152
+ end
153
+
154
+ @request_cookies
155
+ end
156
+
157
+ def set_cookie_key(key)
158
+ @set_cookie_keys ||= Hash.new { |hash, key| hash[key] = "Set-Cookie".dup }
159
+ @set_cookie_keys[key]
160
+ end
161
+
162
+ protected
163
+
164
+ attr_writer :jar, :expires
165
+
166
+ ####################
167
+ #
168
+ # Cookie Jars
169
+ #
170
+ ####################
171
+
172
+ class SimpleJar
173
+ def self.load(_)
174
+ _
175
+ end
176
+
177
+ def self.dump(value)
178
+ value.to_s
179
+ end
180
+ end
181
+
182
+ class EncryptedJar
183
+ SALT = "encrypted cookie"
184
+ PADDING = "00"
185
+
186
+ class << self
187
+ def load(value)
188
+ box = primary_box
189
+
190
+ begin
191
+ box.decrypt(Base64.urlsafe_decode64(value).byteslice(2..))
192
+ rescue ArgumentError
193
+ nil
194
+ rescue RbNaCl::CryptoError
195
+ i ||= 0
196
+ if box = fallback_boxes[i]
197
+ i += 1
198
+ retry
199
+ end
200
+ end
201
+ end
202
+
203
+ def dump(value)
204
+ # add two bytes to hold meta information, e.g. in case we
205
+ # need to change the encryption algorithm in the future
206
+ Base64.urlsafe_encode64(PADDING + primary_box.encrypt(value.to_s))
207
+ end
208
+
209
+ private
210
+
211
+ def primary_box
212
+ @primary_box ||= begin
213
+ if !defined?(RbNaCl) || !(Gem::Version.create(RbNaCl::VERSION) >= Gem::Version.create("3.3.0") && Gem::Version.create(RbNaCl::VERSION) < Gem::Version.create("8.0.0"))
214
+ fail <<~ERR
215
+
216
+ rage-rb depends on rbnacl [>= 3.3, < 8.0] to encrypt cookies. Add the following line to your Gemfile:
217
+ gem "rbnacl"
218
+
219
+ ERR
220
+ end
221
+
222
+ unless Rage.config.secret_key_base
223
+ raise "Rage.config.secret_key_base should be set to use encrypted cookies"
224
+ end
225
+
226
+ RbNaCl::SimpleBox.from_secret_key(
227
+ RbNaCl::Hash.blake2b(Rage.config.secret_key_base, digest_size: 32, salt: SALT)
228
+ )
229
+ end
230
+ end
231
+
232
+ def fallback_boxes
233
+ @fallback_boxes ||= begin
234
+ Rage.config.fallback_secret_key_base.map do |key|
235
+ RbNaCl::SimpleBox.from_secret_key(RbNaCl::Hash.blake2b(key, digest_size: 32, salt: SALT))
236
+ end
237
+ end
238
+ end
239
+ end # class << self
240
+ end
241
+ end
@@ -4,7 +4,7 @@ if defined?(ActiveSupport::IsolatedExecutionState)
4
4
  end
5
5
 
6
6
  # release ActiveRecord connections on yield
7
- if defined?(ActiveRecord)
7
+ if defined?(ActiveRecord) && ActiveRecord.version < Gem::Version.create("7.1.0")
8
8
  class Fiber
9
9
  def self.defer
10
10
  res = Fiber.yield
data/lib/rage/fiber.rb CHANGED
@@ -91,7 +91,7 @@ class Fiber
91
91
 
92
92
  # @private
93
93
  # under normal circumstances, the method is a copy of `yield`, but it can be overriden to perform
94
- # additional steps on yielding, e.g. releasing AR connections; see "lib/rage/rails.rb"
94
+ # additional steps on yielding, e.g. releasing AR connections; see "lib/rage/ext/setup.rb"
95
95
  class << self
96
96
  alias_method :defer, :yield
97
97
  end
@@ -68,12 +68,18 @@ class Rage::Router::Backend
68
68
  raise "Invalid route handler format, expected to match the 'controller#action' pattern" unless handler =~ STRING_HANDLER_REGEXP
69
69
 
70
70
  controller, action = Rage::Router::Util.path_to_class($1), $2
71
- run_action_method_name = controller.__register_action(action.to_sym)
72
71
 
73
- meta[:controller] = $1
74
- meta[:action] = $2
72
+ if controller.ancestors.include?(RageController::API)
73
+ run_action_method_name = controller.__register_action(action.to_sym)
75
74
 
76
- handler = eval("->(env, params) { #{controller}.new(env, params).#{run_action_method_name} }")
75
+ meta[:controller] = $1
76
+ meta[:action] = $2
77
+
78
+ handler = eval("->(env, params) { #{controller}.new(env, params).#{run_action_method_name} }")
79
+ else
80
+ # this is a Rails controller; notify `Rage::Router::Util::Cascade` to forward the request to Rails
81
+ handler = ->(_, _) { [404, { "X-Cascade" => "pass" }, []] }
82
+ end
77
83
  else
78
84
  raise "Non-string route handler should respond to `call`" unless handler.respond_to?(:call)
79
85
  # while regular handlers are expected to be called with the `env` and `params` objects,
@@ -209,6 +209,7 @@ class Rage::Router::DSL
209
209
  # @param [Hash] opts scope options.
210
210
  # @option opts [String] :module module option
211
211
  # @option opts [String] :path path option
212
+ # @option opts [String] :controller controller option
212
213
  # @example Route `/photos` to `Api::PhotosController`
213
214
  # scope module: "api" do
214
215
  # get "photos", to: "photos#index"
@@ -217,6 +218,11 @@ class Rage::Router::DSL
217
218
  # scope path: "admin" do
218
219
  # get "photos", to: "photos#index"
219
220
  # end
221
+ # @example Route `/like` to `photos#like` and `/dislike` to `photos#dislike`
222
+ # scope controller: "photos" do
223
+ # post "like"
224
+ # post "dislike"
225
+ # end
220
226
  # @example Nested calls
221
227
  # scope module: "admin" do
222
228
  # get "photos", to: "photos#index"
@@ -252,6 +258,19 @@ class Rage::Router::DSL
252
258
  @defaults.pop
253
259
  end
254
260
 
261
+ # Scopes routes to a specific controller.
262
+ #
263
+ # @example
264
+ # controller "photos" do
265
+ # post "like"
266
+ # post "dislike"
267
+ # end
268
+ def controller(controller, &block)
269
+ @controllers << controller
270
+ instance_eval &block
271
+ @controllers.pop
272
+ end
273
+
255
274
  # Add a route to the collection.
256
275
  #
257
276
  # @example Add a `photos/search` path instead of `photos/:photo_id/search`
@@ -267,6 +286,27 @@ class Rage::Router::DSL
267
286
  @path_prefixes = orig_path_prefixes
268
287
  end
269
288
 
289
+ # Add a member route.
290
+ #
291
+ # @example Add a `photos/:id/preview` path instead of `photos/:photo_id/preview`
292
+ # resources :photos do
293
+ # member do
294
+ # get "preview"
295
+ # end
296
+ # end
297
+ def member(&block)
298
+ orig_path_prefixes = @path_prefixes
299
+
300
+ if (param_prefix = @path_prefixes.last)&.start_with?(":") && @controllers.any?
301
+ member_prefix = param_prefix.delete_prefix(":#{to_singular(@controllers.last)}_")
302
+ @path_prefixes = [*@path_prefixes[0...-1], ":#{member_prefix}"]
303
+ end
304
+
305
+ instance_eval &block
306
+
307
+ @path_prefixes = orig_path_prefixes
308
+ end
309
+
270
310
  # Automatically create REST routes for a resource.
271
311
  #
272
312
  # @example Create five REST routes, all mapping to the `Photos` controller:
@@ -30,4 +30,19 @@ class Rage::Router::Util
30
30
  end
31
31
  end
32
32
  end
33
+
34
+ # @private
35
+ class Cascade
36
+ def initialize(rage_app, rails_app)
37
+ @rage_app = rage_app
38
+ @rails_app = rails_app
39
+ end
40
+
41
+ def call(env)
42
+ result = @rage_app.call(env)
43
+ return result if result[0] == :__http_defer__ || result[1]["X-Cascade".freeze] != "pass".freeze
44
+
45
+ @rails_app.call(env)
46
+ end
47
+ end
33
48
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ class Rage::Session
6
+ # @private
7
+ KEY = Rack::RACK_SESSION.to_sym
8
+
9
+ # @private
10
+ def initialize(controller)
11
+ @cookies = controller.cookies.encrypted
12
+ end
13
+
14
+ # Writes the value to the session.
15
+ #
16
+ # @param key [Symbol]
17
+ # @param value [String]
18
+ def []=(key, value)
19
+ write_session(add: { key => value })
20
+ end
21
+
22
+ # Returns the value of the key stored in the session or `nil` if the given key is not found.
23
+ #
24
+ # @param key [Symbol]
25
+ def [](key)
26
+ read_session[key]
27
+ end
28
+
29
+ # Returns the value of the given key from the session, or raises `KeyError` if the given key is not found
30
+ # and no default value is set. Returns the default value if specified.
31
+ #
32
+ # @param key [Symbol]
33
+ def fetch(key, default = nil, &block)
34
+ if default.nil?
35
+ read_session.fetch(key, &block)
36
+ else
37
+ read_session.fetch(key, default, &block)
38
+ end
39
+ end
40
+
41
+ # Deletes the given key from the session.
42
+ #
43
+ # @param key [Symbol]
44
+ def delete(key)
45
+ write_session(remove: key)
46
+ end
47
+
48
+ # Clears the session.
49
+ def clear
50
+ write_session(clear: true)
51
+ end
52
+
53
+ # Returns the session as Hash.
54
+ def to_hash
55
+ read_session
56
+ end
57
+
58
+ alias_method :to_h, :to_hash
59
+
60
+ def empty?
61
+ read_session.empty?
62
+ end
63
+
64
+ # Returns `true` if the given key is present in the session.
65
+ def has_key?(key)
66
+ read_session.has_key?(key)
67
+ end
68
+
69
+ alias_method :key?, :has_key?
70
+ alias_method :include?, :has_key?
71
+
72
+ def each(&block)
73
+ read_session.each(&block)
74
+ end
75
+
76
+ def dig(*keys)
77
+ read_session.dig(*keys)
78
+ end
79
+
80
+ def inspect
81
+ "#<#{self.class.name} @session=#{to_h.inspect}"
82
+ end
83
+
84
+ private
85
+
86
+ def write_session(add: nil, remove: nil, clear: nil)
87
+ if add
88
+ read_session.merge!(add)
89
+ elsif remove && read_session.has_key?(remove)
90
+ read_session.reject! { |k, _| k == remove }
91
+ elsif clear
92
+ read_session.clear
93
+ end
94
+
95
+ @cookies[KEY] = { httponly: true, same_site: :lax, value: read_session.to_json }
96
+ end
97
+
98
+ def read_session
99
+ @session ||= begin
100
+ JSON.parse(@cookies[KEY] || "{}", symbolize_names: true)
101
+ rescue JSON::ParserError
102
+ {}
103
+ end
104
+ end
105
+ end
@@ -1,4 +1,3 @@
1
1
  require_relative "config/application"
2
2
 
3
3
  run Rage.application
4
- Rage.load_middlewares(self)
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.3.0"
4
+ VERSION = "1.5.0"
5
5
  end
data/lib/rage-rb.rb CHANGED
@@ -7,7 +7,25 @@ require "pathname"
7
7
 
8
8
  module Rage
9
9
  def self.application
10
- Application.new(__router)
10
+ app = Application.new(__router)
11
+
12
+ config.middleware.middlewares.reverse.inject(app) do |next_in_chain, (middleware, args, block)|
13
+ # in Rails compatibility mode we first check if the middleware is a part of the Rails middleware stack;
14
+ # if it is - it is expected to be built using `ActionDispatch::MiddlewareStack::Middleware#build`
15
+ if Rage.config.internal.rails_mode
16
+ rails_middleware = Rails.application.config.middleware.middlewares.find { |m| m.name == middleware.name }
17
+ end
18
+
19
+ if rails_middleware
20
+ rails_middleware.build(next_in_chain)
21
+ else
22
+ middleware.new(next_in_chain, *args, &block)
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.multi_application
28
+ Rage::Router::Util::Cascade.new(application, Rails.application)
11
29
  end
12
30
 
13
31
  def self.routes
@@ -43,28 +61,8 @@ module Rage
43
61
  @logger ||= config.logger
44
62
  end
45
63
 
46
- def self.load_middlewares(rack_builder)
47
- config.middleware.middlewares.each do |middleware, args, block|
48
- # in Rails compatibility mode we first check if the middleware is a part of the Rails middleware stack;
49
- # if it is - it is expected to be built using `ActionDispatch::MiddlewareStack::Middleware#build`, but Rack
50
- # expects the middleware to respond to `#new`, so we wrap the middleware into a helper module
51
- if Rage.config.internal.rails_mode
52
- rails_middleware = Rails.application.config.middleware.middlewares.find { |m| m.name == middleware.name }
53
- if rails_middleware
54
- wrapper = Module.new do
55
- extend self
56
- attr_accessor :middleware
57
- def new(app, *, &)
58
- middleware.build(app)
59
- end
60
- end
61
- wrapper.middleware = rails_middleware
62
- middleware = wrapper
63
- end
64
- end
65
-
66
- rack_builder.use(middleware, *args, &block)
67
- end
64
+ def self.load_middlewares(_)
65
+ puts "`Rage.load_middlewares` is deprecated and has been merged into `Rage.application`. Please remove this call."
68
66
  end
69
67
 
70
68
  def self.code_loader
@@ -102,6 +100,9 @@ module Rage
102
100
  autoload :ConnectionPool, "rage/ext/active_record/connection_pool"
103
101
  end
104
102
  end
103
+
104
+ autoload :Cookies, "rage/cookies"
105
+ autoload :Session, "rage/session"
105
106
  end
106
107
 
107
108
  module RageController
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.3.0
4
+ version: 1.5.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-04-17 00:00:00.000000000 Z
11
+ date: 2024-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -105,6 +105,7 @@ files:
105
105
  - lib/rage/code_loader.rb
106
106
  - lib/rage/configuration.rb
107
107
  - lib/rage/controller/api.rb
108
+ - lib/rage/cookies.rb
108
109
  - lib/rage/env.rb
109
110
  - lib/rage/errors.rb
110
111
  - lib/rage/ext/active_record/connection_pool.rb
@@ -130,6 +131,7 @@ files:
130
131
  - lib/rage/router/strategies/host.rb
131
132
  - lib/rage/router/util.rb
132
133
  - lib/rage/rspec.rb
134
+ - lib/rage/session.rb
133
135
  - lib/rage/setup.rb
134
136
  - lib/rage/sidekiq_session.rb
135
137
  - lib/rage/templates/Gemfile