rage-rb 1.3.0 → 1.5.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: 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