rage-rb 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile +1 -0
- data/README.md +5 -5
- data/lib/rage/all.rb +20 -0
- data/lib/rage/application.rb +2 -1
- data/lib/rage/cli.rb +33 -41
- data/lib/rage/controller/api.rb +51 -3
- data/lib/rage/errors.rb +4 -0
- data/lib/rage/params_parser.rb +45 -0
- data/lib/rage/request.rb +44 -0
- data/lib/rage/router/dsl.rb +225 -6
- data/lib/rage/router/handler_storage.rb +2 -2
- data/lib/rage/templates/Gemfile +5 -1
- data/lib/rage/templates/config-application.rb +2 -2
- data/lib/rage/uploaded_file.rb +70 -0
- data/lib/rage/version.rb +1 -1
- data/lib/rage-rb.rb +0 -14
- data/rage.gemspec +1 -1
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: adfca83c806d24e4baf6e3c6401c7268e7e88495eaee9120da3a55fca08400b8
|
4
|
+
data.tar.gz: 7769fd22482975665a66741d0362ce5bf75318192bd6ee2850f98da952f5fe35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66667fbd047a4c27ebb840f4fc6fd2f60fb50d90b7f25b38048afeb5a92b1ad6f5310a4f0eacbab42b2d11a0c0de44ff4a131d197737abe13f1817a7b2267147
|
7
|
+
data.tar.gz: e75d8f5c615844fe837015ddaf7d2f9c4a96cdebc321ad63c79a3a1645924f2b3a7ef3fe75007243042a14bae18b001bd6cb0c919754719fb339ae42d0e90075
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.4.0] - 2023-10-31
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Expose the `params` object.
|
8
|
+
- Support header authentication with `authenticate_with_http_token`.
|
9
|
+
- Add the `resources` and `namespace` route helpers.
|
10
|
+
- Add the `mount` and `match` route helpers.
|
11
|
+
- Allow to access request headers.
|
12
|
+
- Support custom ports when starting the app with `rage s`.
|
13
|
+
|
3
14
|
## [0.3.0] - 2023-10-08
|
4
15
|
|
5
16
|
### Added
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -11,7 +11,7 @@ Inspired by [Deno](https://deno.com) and built on top of [Iodine](https://github
|
|
11
11
|
|
12
12
|
* **High performance** - some think performance is not a major metric for a framework, but it's not true. Poor performance is a risk, and in today's world, companies refuse to use risky technologies.
|
13
13
|
|
14
|
-
* **API-only** - the only technology we should be using to create web UI is JavaScript. Check out [Vite](https://vitejs.dev) if you don't know where to start.
|
14
|
+
* **API-only** - the only technology we should be using to create web UI is JavaScript. Using native technologies is always the most flexible, scalable, and simple solution in the long run. Check out [Vite](https://vitejs.dev) if you don't know where to start.
|
15
15
|
|
16
16
|
* **Acceptance of modern Ruby** - the framework includes a fiber scheduler, which means your code never blocks while waiting on IO.
|
17
17
|
|
@@ -46,9 +46,9 @@ This gem is designed to be a drop-in replacement for Rails in API mode. Public A
|
|
46
46
|
|
47
47
|
Check out in-depth API docs for more information:
|
48
48
|
|
49
|
-
- [Controller API](https://rage-rb.
|
50
|
-
- [Routing API](https://rage-rb.
|
51
|
-
- [Fiber API](https://rage-rb.
|
49
|
+
- [Controller API](https://rage-rb.pages.dev/RageController/API)
|
50
|
+
- [Routing API](https://rage-rb.pages.dev/Rage/Router/DSL/Handler)
|
51
|
+
- [Fiber API](https://rage-rb.pages.dev/Fiber)
|
52
52
|
|
53
53
|
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.
|
54
54
|
|
@@ -143,7 +143,7 @@ Version | Changes
|
|
143
143
|
------- |------------
|
144
144
|
0.2 :white_check_mark: | ~~Gem configuration by env.<br>Add `skip_before_action`.<br>Add `rescue_from`.<br>Router updates:<br> • make the `root` helper work correctly with `scope`;<br> • support the `defaults` option;~~
|
145
145
|
0.3 :white_check_mark: | ~~CLI updates:<br> • `routes` task;<br> • `console` task;<br>Support the `:if` and `:unless` options in `before_action`.<br>Allow to set response headers.~~
|
146
|
-
0.4 | Expose the `params` object.<br>Support header authentication with `authenticate_with_http_token`.<br>Router updates:<br> • add the `resources` route helper;<br> • add the `namespace` route helper;<br
|
146
|
+
0.4 :white_check_mark: | ~~Expose the `params` object.<br>Support header authentication with `authenticate_with_http_token`.<br>Router updates:<br> • add the `resources` route helper;<br> • add the `namespace` route helper;<br>~~ • support regexp constraints (postponed)
|
147
147
|
0.5 | Implement Iodine-based equivalent of `ActionController::Live`.<br>Use `ActionDispatch::RemoteIp`.
|
148
148
|
0.6 | Expose the `cookies` object.<br>Expose the `send_data` and `send_file` methods.<br>Support conditional get with `etag` and `last_modified`.
|
149
149
|
0.7 | Add request logging.
|
data/lib/rage/all.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative "../rage-rb"
|
2
|
+
|
3
|
+
require_relative "version"
|
4
|
+
require_relative "application"
|
5
|
+
require_relative "fiber"
|
6
|
+
require_relative "fiber_scheduler"
|
7
|
+
require_relative "configuration"
|
8
|
+
require_relative "request"
|
9
|
+
require_relative "uploaded_file"
|
10
|
+
require_relative "errors"
|
11
|
+
require_relative "params_parser"
|
12
|
+
|
13
|
+
require_relative "router/strategies/host"
|
14
|
+
require_relative "router/backend"
|
15
|
+
require_relative "router/constrainer"
|
16
|
+
require_relative "router/dsl"
|
17
|
+
require_relative "router/handler_storage"
|
18
|
+
require_relative "router/node"
|
19
|
+
|
20
|
+
require_relative "controller/api"
|
data/lib/rage/application.rb
CHANGED
@@ -13,7 +13,8 @@ class Rage::Application
|
|
13
13
|
handler = @router.lookup(env)
|
14
14
|
|
15
15
|
if handler
|
16
|
-
|
16
|
+
params = Rage::ParamsParser.prepare(env, handler[:params])
|
17
|
+
handler[:handler].call(env, params)
|
17
18
|
else
|
18
19
|
[404, {}, ["Not Found"]]
|
19
20
|
end
|
data/lib/rage/cli.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "thor"
|
3
|
-
require "rage"
|
3
|
+
require "rage/all"
|
4
4
|
require "irb"
|
5
5
|
|
6
6
|
module Rage
|
@@ -15,11 +15,12 @@ module Rage
|
|
15
15
|
end
|
16
16
|
|
17
17
|
desc "s", "Start the app server."
|
18
|
+
option :port, aliases: "-p", desc: "Runs Rage on the specified port - defaults to 3000."
|
18
19
|
def server
|
19
20
|
app = ::Rack::Builder.parse_file("config.ru")
|
20
21
|
app = app[0] if app.is_a?(Array)
|
21
22
|
|
22
|
-
::Iodine.listen service: :http, handler: app, port: Rage.config.port
|
23
|
+
::Iodine.listen service: :http, handler: app, port: options[:port] || Rage.config.port
|
23
24
|
::Iodine.threads = Rage.config.threads_count
|
24
25
|
::Iodine.workers = Rage.config.workers_count
|
25
26
|
|
@@ -30,57 +31,48 @@ module Rage
|
|
30
31
|
option :grep, aliases: "-g", desc: "Filter routes by pattern"
|
31
32
|
def routes
|
32
33
|
# the result would be something like this:
|
33
|
-
#
|
34
|
-
#
|
34
|
+
# Verb Path Controller#Action
|
35
|
+
# GET / application#index
|
35
36
|
|
36
37
|
# load config/application.rb
|
37
38
|
environment
|
38
39
|
|
39
40
|
routes = Rage.__router.routes
|
40
|
-
|
41
41
|
pattern = options[:grep]
|
42
|
+
routes.unshift({ method: "Verb", path: "Path", raw_handler: "Controller#Action" })
|
43
|
+
|
44
|
+
grouped_routes = routes.each_with_object({}) do |route, memo|
|
45
|
+
if pattern && !memo.empty?
|
46
|
+
next unless route[:path].match?(pattern) || route[:raw_handler].to_s.match?(pattern) || route[:method].match?(pattern)
|
47
|
+
end
|
42
48
|
|
43
|
-
|
44
|
-
|
45
|
-
|
49
|
+
key = [route[:path], route[:raw_handler]]
|
50
|
+
if memo[key]
|
51
|
+
memo[key][:method] += "|#{route[:method]}"
|
52
|
+
else
|
53
|
+
memo[key] = route
|
46
54
|
end
|
47
55
|
end
|
48
56
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# longest_path is either the length of the longest path or 5
|
55
|
-
longest_path = routes.map { |route| route[:path].length }.max + 3
|
56
|
-
longest_path = longest_path > 5 ? longest_path : 5
|
57
|
-
|
58
|
-
longest_verb = routes.map { |route| route[:method].length }.max + 3
|
59
|
-
longest_verb = longest_verb > 4 ? longest_verb : 7
|
60
|
-
|
61
|
-
# longest_handler is either the length of the longest handler or 7, since DELETE is the longest HTTP method
|
62
|
-
longest_handler = routes.map { |route| route[:raw_handler].is_a?(Proc) ? 7 : route[:raw_handler].split('#').last.length }.max + 3
|
63
|
-
longest_handler = longest_handler > 7 ? longest_handler : 7
|
64
|
-
|
65
|
-
# longest_controller is either the length of the longest controller or 12, since Controller#{length} is the longest controller
|
66
|
-
longest_controller = routes.map { |route| route[:raw_handler].is_a?(Proc) ? 7 : route[:raw_handler].to_s.length }.max + 3
|
67
|
-
longest_controller = longest_controller > 12 ? longest_controller : 12
|
68
|
-
|
69
|
-
routes.each do |route|
|
70
|
-
table << [
|
71
|
-
format("%- #{longest_handler}s", route[:raw_handler].is_a?(Proc) ? 'Lambda' : route[:raw_handler].split('#').last),
|
72
|
-
format("%- #{longest_verb}s", route[:method]),
|
73
|
-
format("%- #{longest_path}s", route[:path]),
|
74
|
-
format("%- #{longest_controller}s", route[:raw_handler].is_a?(Proc) ? 'Lambda' : route[:raw_handler])
|
75
|
-
]
|
57
|
+
longest_path = longest_method = 0
|
58
|
+
grouped_routes.each do |_, route|
|
59
|
+
longest_path = route[:path].length if route[:path].length > longest_path
|
60
|
+
longest_method = route[:method].length if route[:method].length > longest_method
|
76
61
|
end
|
77
62
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
63
|
+
margin = 3
|
64
|
+
longest_path += margin
|
65
|
+
longest_method += margin
|
66
|
+
|
67
|
+
grouped_routes.each_with_index do |(_, route), i|
|
68
|
+
meta = route[:constraints]
|
69
|
+
meta.merge!(route[:defaults]) if route[:defaults]
|
70
|
+
|
71
|
+
handler = route[:raw_handler]
|
72
|
+
handler = "#{handler} #{meta}" unless meta&.empty?
|
73
|
+
|
74
|
+
puts format("%-#{longest_method}s%-#{longest_path}s%s", route[:method], route[:path], handler)
|
75
|
+
puts "\n" if i == 0
|
84
76
|
end
|
85
77
|
end
|
86
78
|
|
data/lib/rage/controller/api.rb
CHANGED
@@ -103,7 +103,7 @@ class RageController::API
|
|
103
103
|
# rescue_from User::NotAuthorized do |_|
|
104
104
|
# head :forbidden
|
105
105
|
# end
|
106
|
-
# @note Unlike Rails, the handler must always take an argument. Use `_` if you don't care about the actual exception.
|
106
|
+
# @note Unlike in Rails, the handler must always take an argument. Use `_` if you don't care about the actual exception.
|
107
107
|
def rescue_from(*klasses, with: nil, &block)
|
108
108
|
unless with
|
109
109
|
if block_given?
|
@@ -167,7 +167,7 @@ class RageController::API
|
|
167
167
|
if: _if,
|
168
168
|
unless: _unless
|
169
169
|
}
|
170
|
-
|
170
|
+
|
171
171
|
action[:if] = define_tmp_method(action[:if]) if action[:if].is_a?(Proc)
|
172
172
|
action[:unless] = define_tmp_method(action[:unless]) if action[:unless].is_a?(Proc)
|
173
173
|
|
@@ -189,7 +189,7 @@ class RageController::API
|
|
189
189
|
# skip_before_action :find_photo, only: :create
|
190
190
|
def skip_before_action(action_name, only: nil, except: nil)
|
191
191
|
i = @__before_actions&.find_index { |a| a[:name] == action_name }
|
192
|
-
raise "The following action was specified to be skipped but
|
192
|
+
raise "The following action was specified to be skipped but couldn't be found: #{self}##{action_name}" unless i
|
193
193
|
|
194
194
|
@__before_actions = @__before_actions.dup if @__before_actions.frozen?
|
195
195
|
|
@@ -221,6 +221,11 @@ class RageController::API
|
|
221
221
|
@__rendered = false
|
222
222
|
end
|
223
223
|
|
224
|
+
# Get the request object. See {Rage::Request}.
|
225
|
+
def request
|
226
|
+
@request ||= Rage::Request.new(@__env)
|
227
|
+
end
|
228
|
+
|
224
229
|
# Send a response to the client.
|
225
230
|
#
|
226
231
|
# @param json [String, Object] send a json response to the client; objects like arrays will be serialized automatically
|
@@ -283,4 +288,47 @@ class RageController::API
|
|
283
288
|
@__headers = {}.merge!(@__headers) if DEFAULT_HEADERS.equal?(@__headers)
|
284
289
|
@__headers
|
285
290
|
end
|
291
|
+
|
292
|
+
# Authenticate using an HTTP Bearer token. Returns the value of the block if a token is found. Returns `nil` if no token is found.
|
293
|
+
#
|
294
|
+
# @yield [token] token value extracted from the `Authorization` header
|
295
|
+
# @example
|
296
|
+
# user = authenticate_with_http_token do |token|
|
297
|
+
# User.find_by(key: token)
|
298
|
+
# end
|
299
|
+
def authenticate_with_http_token
|
300
|
+
auth_header = @__env["HTTP_AUTHORIZATION"]
|
301
|
+
|
302
|
+
if auth_header&.start_with?("Bearer")
|
303
|
+
yield auth_header[7..]
|
304
|
+
elsif auth_header&.start_with?("Token")
|
305
|
+
yield auth_header[6..]
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
if !defined?(::ActionController::Parameters)
|
310
|
+
# Get the request data. The keys inside the hash are symbols, so `params.keys` returns an array of `Symbol`.<br>
|
311
|
+
# You can also load Strong Params to have Rage automatically wrap `params` in an instance of `ActionController::Parameters`.<br>
|
312
|
+
# At the same time, if you are not implementing complex filtering rules or working with nested structures, consider using native `Hash#fetch` and `Hash#slice` instead.
|
313
|
+
#
|
314
|
+
# For multipart file uploads, the uploaded files are represented by an instance of {Rage::UploadedFile}.
|
315
|
+
#
|
316
|
+
# @return [Hash{Symbol=>String,Array,Hash,Numeric,NilClass,TrueClass,FalseClass}]
|
317
|
+
# @example
|
318
|
+
# # make sure to load strong params before the `require "rage/all"` call
|
319
|
+
# require "active_support/all"
|
320
|
+
# require "action_controller/metal/strong_parameters"
|
321
|
+
#
|
322
|
+
# params.permit(:user).require(:full_name, :dob)
|
323
|
+
# @example
|
324
|
+
# # without strong params
|
325
|
+
# params.fetch(:user).slice(:full_name, :dob)
|
326
|
+
def params
|
327
|
+
@__params
|
328
|
+
end
|
329
|
+
else
|
330
|
+
def params
|
331
|
+
@params ||= ActionController::Parameters.new(@__params)
|
332
|
+
end
|
333
|
+
end
|
286
334
|
end
|
data/lib/rage/errors.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Rage::ParamsParser
|
4
|
+
def self.prepare(env, url_params)
|
5
|
+
has_body, query_string, content_type = env["IODINE_HAS_BODY"], env["QUERY_STRING"], env["CONTENT_TYPE"]
|
6
|
+
|
7
|
+
query_params = Iodine::Rack::Utils.parse_nested_query(query_string) if query_string != ""
|
8
|
+
unless has_body
|
9
|
+
if query_params
|
10
|
+
return query_params.merge!(url_params)
|
11
|
+
else
|
12
|
+
return url_params
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
request_params = if content_type.start_with?("application/json")
|
17
|
+
json_parse(env["rack.input"].read)
|
18
|
+
elsif content_type.start_with?("application/x-www-form-urlencoded")
|
19
|
+
Iodine::Rack::Utils.parse_urlencoded_nested_query(env["rack.input"].read)
|
20
|
+
else
|
21
|
+
Iodine::Rack::Utils.parse_multipart(env["rack.input"], content_type)
|
22
|
+
end
|
23
|
+
|
24
|
+
if request_params && !query_params
|
25
|
+
request_params.merge!(url_params)
|
26
|
+
elsif request_params && query_params
|
27
|
+
request_params.merge!(query_params, url_params)
|
28
|
+
else
|
29
|
+
url_params
|
30
|
+
end
|
31
|
+
|
32
|
+
rescue => e
|
33
|
+
raise Rage::Errors::BadRequest
|
34
|
+
end
|
35
|
+
|
36
|
+
if defined?(::FastJsonparser)
|
37
|
+
def self.json_parse(json)
|
38
|
+
FastJsonparser.parse(json, symbolize_keys: true)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
def self.json_parse(json)
|
42
|
+
JSON.parse(json, symbolize_names: true)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/rage/request.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Rage::Request
|
4
|
+
# @private
|
5
|
+
def initialize(env)
|
6
|
+
@env = env
|
7
|
+
end
|
8
|
+
|
9
|
+
# Get the request headers.
|
10
|
+
# @example
|
11
|
+
# request.headers["Content-Type"] # => "application/json"
|
12
|
+
# request.headers["Connection"] # => "keep-alive"
|
13
|
+
def headers
|
14
|
+
@headers ||= Headers.new(@env)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @private
|
18
|
+
class Headers
|
19
|
+
HTTP = "HTTP_"
|
20
|
+
|
21
|
+
def initialize(env)
|
22
|
+
@env = env
|
23
|
+
end
|
24
|
+
|
25
|
+
def [](requested_header)
|
26
|
+
if requested_header.start_with?(HTTP)
|
27
|
+
@env[requested_header]
|
28
|
+
else
|
29
|
+
(requested_header = requested_header.tr("-", "_")).upcase!
|
30
|
+
|
31
|
+
if "CONTENT_TYPE" == requested_header || "CONTENT_LENGTH" == requested_header
|
32
|
+
@env[requested_header]
|
33
|
+
else
|
34
|
+
@env["#{HTTP}#{requested_header}"]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def inspect
|
40
|
+
headers = @env.select { |k| k == "CONTENT_TYPE" || k == "CONTENT_LENGTH" || k.start_with?(HTTP) }
|
41
|
+
"#<#{self.class.name} @headers=#{headers.inspect}"
|
42
|
+
end
|
43
|
+
end # class Headers
|
44
|
+
end
|
data/lib/rage/router/dsl.rb
CHANGED
@@ -9,14 +9,54 @@ class Rage::Router::DSL
|
|
9
9
|
Handler.new(@router).instance_eval(&block)
|
10
10
|
end
|
11
11
|
|
12
|
+
##
|
13
|
+
# This class implements routing logic for your application, providing API similar to Rails.
|
14
|
+
#
|
15
|
+
# Compared to Rails router, the most notable difference is that a wildcard segment can only be in the last section of the path and cannot be named.
|
16
|
+
# Example:
|
17
|
+
# ```ruby
|
18
|
+
# get "/photos/*"
|
19
|
+
# ```
|
20
|
+
#
|
21
|
+
# Also, as this is an API-only framework, route helpers, like `photos_path` or `photos_url` are not being generated.
|
22
|
+
#
|
23
|
+
# #### Constraints
|
24
|
+
#
|
25
|
+
# Currently, the only constraint supported is the `host` constraint. The constraint value can be either string or a regular expression.
|
26
|
+
# Example:
|
27
|
+
# ```ruby
|
28
|
+
# get "/photos", to: "photos#index", constraints: { host: "myhost.com" }
|
29
|
+
# ```
|
30
|
+
#
|
31
|
+
# Parameter constraints are likely to be added in the future versions. Custom/lambda constraints are unlikely to be ever added.
|
32
|
+
#
|
33
|
+
# @example Set up a root handler
|
34
|
+
# root to: "pages#main"
|
35
|
+
# @example Set up multiple resources
|
36
|
+
# resources :magazines do
|
37
|
+
# resources :ads
|
38
|
+
# end
|
39
|
+
# @example Scope a set of routes to the given default options.
|
40
|
+
# scope path: ":account_id" do
|
41
|
+
# resources :projects
|
42
|
+
# end
|
43
|
+
# @example Scope routes to a specific namespace.
|
44
|
+
# namespace :admin do
|
45
|
+
# resources :posts
|
46
|
+
# end
|
12
47
|
class Handler
|
13
48
|
# @private
|
14
49
|
def initialize(router)
|
15
50
|
@router = router
|
16
51
|
|
52
|
+
@default_actions = %i(index create show update destroy)
|
53
|
+
@default_match_methods = %i(get post put patch delete head)
|
54
|
+
@scope_opts = %i(module path controller)
|
55
|
+
|
17
56
|
@path_prefixes = []
|
18
57
|
@module_prefixes = []
|
19
58
|
@defaults = []
|
59
|
+
@controllers = []
|
20
60
|
end
|
21
61
|
|
22
62
|
# Register a new GET route.
|
@@ -29,7 +69,7 @@ class Rage::Router::DSL
|
|
29
69
|
# get "/photos/:id", to: "photos#show", constraints: { host: /myhost/ }
|
30
70
|
# @example
|
31
71
|
# get "/photos(/:id)", to: "photos#show", defaults: { id: "-1" }
|
32
|
-
def get(path, to
|
72
|
+
def get(path, to: nil, constraints: nil, defaults: nil)
|
33
73
|
__on("GET", path, to, constraints, defaults)
|
34
74
|
end
|
35
75
|
|
@@ -43,7 +83,7 @@ class Rage::Router::DSL
|
|
43
83
|
# post "/photos", to: "photos#create", constraints: { host: /myhost/ }
|
44
84
|
# @example
|
45
85
|
# post "/photos", to: "photos#create", defaults: { format: "jpg" }
|
46
|
-
def post(path, to
|
86
|
+
def post(path, to: nil, constraints: nil, defaults: nil)
|
47
87
|
__on("POST", path, to, constraints, defaults)
|
48
88
|
end
|
49
89
|
|
@@ -57,7 +97,7 @@ class Rage::Router::DSL
|
|
57
97
|
# put "/photos/:id", to: "photos#update", constraints: { host: /myhost/ }
|
58
98
|
# @example
|
59
99
|
# put "/photos(/:id)", to: "photos#update", defaults: { id: "-1" }
|
60
|
-
def put(path, to
|
100
|
+
def put(path, to: nil, constraints: nil, defaults: nil)
|
61
101
|
__on("PUT", path, to, constraints, defaults)
|
62
102
|
end
|
63
103
|
|
@@ -71,7 +111,7 @@ class Rage::Router::DSL
|
|
71
111
|
# patch "/photos/:id", to: "photos#update", constraints: { host: /myhost/ }
|
72
112
|
# @example
|
73
113
|
# patch "/photos(/:id)", to: "photos#update", defaults: { id: "-1" }
|
74
|
-
def patch(path, to
|
114
|
+
def patch(path, to: nil, constraints: nil, defaults: nil)
|
75
115
|
__on("PATCH", path, to, constraints, defaults)
|
76
116
|
end
|
77
117
|
|
@@ -85,7 +125,7 @@ class Rage::Router::DSL
|
|
85
125
|
# delete "/photos/:id", to: "photos#destroy", constraints: { host: /myhost/ }
|
86
126
|
# @example
|
87
127
|
# delete "/photos(/:id)", to: "photos#destroy", defaults: { id: "-1" }
|
88
|
-
def delete(path, to
|
128
|
+
def delete(path, to: nil, constraints: nil, defaults: nil)
|
89
129
|
__on("DELETE", path, to, constraints, defaults)
|
90
130
|
end
|
91
131
|
|
@@ -98,6 +138,70 @@ class Rage::Router::DSL
|
|
98
138
|
__on("GET", "/", to, nil, nil)
|
99
139
|
end
|
100
140
|
|
141
|
+
# Match a URL pattern to one or more routes.
|
142
|
+
#
|
143
|
+
# @param path [String] the path for the route handler
|
144
|
+
# @param to [String, #call] the route handler in the format of "controller#action" or a callable
|
145
|
+
# @param constraints [Hash] a hash of constraints for the route
|
146
|
+
# @param defaults [Hash] a hash of default parameters for the route
|
147
|
+
# @param via [Symbol, Array<Symbol>] an array of HTTP methods to accept
|
148
|
+
# @example
|
149
|
+
# match "/photos/:id", to: "photos#show", via: [:get, :post]
|
150
|
+
# @example
|
151
|
+
# match "/photos/:id", to: "photos#show", via: :all
|
152
|
+
# @example
|
153
|
+
# match "/health", to: -> (env) { [200, {}, ["healthy"]] }
|
154
|
+
def match(path, to:, constraints: {}, defaults: nil, via: :all)
|
155
|
+
# via is either nil, or an array of symbols or its :all
|
156
|
+
http_methods = via
|
157
|
+
# if its :all or nil, then we use the default HTTP methods
|
158
|
+
if via == :all || via.nil?
|
159
|
+
http_methods = @default_match_methods
|
160
|
+
else
|
161
|
+
# if its an array of symbols, then we use the symbols as HTTP methods
|
162
|
+
http_methods = Array(via)
|
163
|
+
# then we check if the HTTP methods are valid
|
164
|
+
http_methods.each do |method|
|
165
|
+
raise ArgumentError, "Invalid HTTP method: #{method}" unless @default_match_methods.include?(method)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
http_methods.each do |method|
|
170
|
+
__on(method.to_s.upcase, path, to, constraints, defaults)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Register a new namespace.
|
175
|
+
#
|
176
|
+
# @param path [String] the path for the namespace
|
177
|
+
# @param options [Hash] a hash of options for the namespace
|
178
|
+
# @option options [String] :module the module name for the namespace
|
179
|
+
# @option options [String] :path the path for the namespace
|
180
|
+
# @example
|
181
|
+
# namespace :admin do
|
182
|
+
# get "/photos", to: "photos#index"
|
183
|
+
# end
|
184
|
+
# @example
|
185
|
+
# namespace :admin, path: "panel" do
|
186
|
+
# get "/photos", to: "photos#index"
|
187
|
+
# end
|
188
|
+
# @example
|
189
|
+
# namespace :admin, module: "admin" do
|
190
|
+
# get "/photos", to: "photos#index"
|
191
|
+
# end
|
192
|
+
def namespace(path, **options, &block)
|
193
|
+
path_prefix = options[:path] || path
|
194
|
+
module_prefix = options[:module] || path
|
195
|
+
|
196
|
+
@path_prefixes << path_prefix
|
197
|
+
@module_prefixes << module_prefix
|
198
|
+
|
199
|
+
instance_eval &block
|
200
|
+
|
201
|
+
@path_prefixes.pop
|
202
|
+
@module_prefixes.pop
|
203
|
+
end
|
204
|
+
|
101
205
|
# Scopes a set of routes to the given default options.
|
102
206
|
#
|
103
207
|
# @param [Hash] opts scope options.
|
@@ -120,15 +224,17 @@ class Rage::Router::DSL
|
|
120
224
|
# end
|
121
225
|
# end
|
122
226
|
def scope(opts, &block)
|
123
|
-
raise ArgumentError, "only
|
227
|
+
raise ArgumentError, "only :module, :path, and :controller options are accepted" if (opts.keys - @scope_opts).any?
|
124
228
|
|
125
229
|
@path_prefixes << opts[:path].delete_prefix("/").delete_suffix("/") if opts[:path]
|
126
230
|
@module_prefixes << opts[:module] if opts[:module]
|
231
|
+
@controllers << opts[:controller] if opts[:controller]
|
127
232
|
|
128
233
|
instance_eval &block
|
129
234
|
|
130
235
|
@path_prefixes.pop if opts[:path]
|
131
236
|
@module_prefixes.pop if opts[:module]
|
237
|
+
@controllers.pop if opts[:controller]
|
132
238
|
end
|
133
239
|
|
134
240
|
# Specify default parameters for a set of routes.
|
@@ -144,14 +250,109 @@ class Rage::Router::DSL
|
|
144
250
|
@defaults.pop
|
145
251
|
end
|
146
252
|
|
253
|
+
# Add a route to the collection.
|
254
|
+
#
|
255
|
+
# @example Add a `photos/search` path instead of `photos/:photo_id/search`
|
256
|
+
# resources :photos do
|
257
|
+
# collection do
|
258
|
+
# get "search"
|
259
|
+
# end
|
260
|
+
# end
|
261
|
+
def collection(&block)
|
262
|
+
orig_path_prefixes = @path_prefixes
|
263
|
+
@path_prefixes = @path_prefixes[0...-1] if @path_prefixes.last&.start_with?(":")
|
264
|
+
instance_eval &block
|
265
|
+
@path_prefixes = orig_path_prefixes
|
266
|
+
end
|
267
|
+
|
268
|
+
# Automatically create REST routes for a resource.
|
269
|
+
#
|
270
|
+
# @example Create five REST routes, all mapping to the `Photos` controller:
|
271
|
+
# resources :photos
|
272
|
+
# # GET /photos => photos#index
|
273
|
+
# # POST /photos => photos#create
|
274
|
+
# # GET /photos/:id => photos#show
|
275
|
+
# # PATCH/PUT /photos/:id => photos#update
|
276
|
+
# # DELETE /photos/:id => photos#destroy
|
277
|
+
# @note This helper doesn't generate the `new` and `edit` routes.
|
278
|
+
def resources(*_resources, **opts, &block)
|
279
|
+
# support calls with multiple resources, e.g. `resources :albums, :photos`
|
280
|
+
if _resources.length > 1
|
281
|
+
_resources.each { |_resource| resources(_resource, **opts, &block) }
|
282
|
+
return
|
283
|
+
end
|
284
|
+
|
285
|
+
_module, _path, _only, _except, _param = opts.values_at(:module, :path, :only, :except, :param)
|
286
|
+
raise ":param option can't contain colons" if _param&.include?(":")
|
287
|
+
|
288
|
+
_only = Array(_only) if _only
|
289
|
+
_except = Array(_except) if _except
|
290
|
+
actions = @default_actions.select do |action|
|
291
|
+
(_only.nil? || _only.include?(action)) && (_except.nil? || !_except.include?(action))
|
292
|
+
end
|
293
|
+
|
294
|
+
resource = _resources[0].to_s
|
295
|
+
_path ||= resource
|
296
|
+
_param ||= "id"
|
297
|
+
|
298
|
+
scope_opts = { path: _path }
|
299
|
+
scope_opts[:module] = _module if _module
|
300
|
+
|
301
|
+
scope(scope_opts) do
|
302
|
+
get("/", to: "#{resource}#index") if actions.include?(:index)
|
303
|
+
post("/", to: "#{resource}#create") if actions.include?(:create)
|
304
|
+
get("/:#{_param}", to: "#{resource}#show") if actions.include?(:show)
|
305
|
+
patch("/:#{_param}", to: "#{resource}#update") if actions.include?(:update)
|
306
|
+
put("/:#{_param}", to: "#{resource}#update") if actions.include?(:update)
|
307
|
+
delete("/:#{_param}", to: "#{resource}#destroy") if actions.include?(:destroy)
|
308
|
+
|
309
|
+
scope(path: ":#{to_singular(resource)}_#{_param}", controller: resource, &block) if block
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Mount a Rack-based application to be used within the application.
|
314
|
+
#
|
315
|
+
# @example
|
316
|
+
# mount Sidekiq::Web => "/sidekiq"
|
317
|
+
# @example
|
318
|
+
# mount Sidekiq::Web, at: "/sidekiq", via: :get
|
319
|
+
def mount(*args)
|
320
|
+
if args.first.is_a?(Hash)
|
321
|
+
app = args.first.keys.first
|
322
|
+
at = args.first.values.first
|
323
|
+
via = args[0][:via]
|
324
|
+
else
|
325
|
+
app = args.first
|
326
|
+
at = args[1][:at]
|
327
|
+
via = args[1][:via]
|
328
|
+
end
|
329
|
+
|
330
|
+
# Use match with via: :all to mount the Rack-based application
|
331
|
+
match(at, to: app, via:)
|
332
|
+
end
|
333
|
+
|
147
334
|
private
|
148
335
|
|
149
336
|
def __on(method, path, to, constraints, defaults)
|
337
|
+
# handle calls without controller inside resources:
|
338
|
+
# resources :comments do
|
339
|
+
# post :like
|
340
|
+
# end
|
341
|
+
if !to
|
342
|
+
if @controllers.any?
|
343
|
+
to = "#{@controllers.last}##{path}"
|
344
|
+
else
|
345
|
+
raise "Missing :to key on routes definition, please check your routes."
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# process path to ensure it starts with "/" and doesn't end with "/"
|
150
350
|
if path != "/"
|
151
351
|
path = "/#{path}" unless path.start_with?("/")
|
152
352
|
path = path.delete_suffix("/") if path.end_with?("/")
|
153
353
|
end
|
154
354
|
|
355
|
+
# correctly process root helpers inside `scope` calls
|
155
356
|
if path == "/" && @path_prefixes.any?
|
156
357
|
path = ""
|
157
358
|
end
|
@@ -166,5 +367,23 @@ class Rage::Router::DSL
|
|
166
367
|
@router.on(method, "#{path_prefix}#{path}", to, constraints: constraints || {}, defaults: defaults)
|
167
368
|
end
|
168
369
|
end
|
370
|
+
|
371
|
+
def to_singular(str)
|
372
|
+
@active_support_loaded ||= str.respond_to?(:singularize) || :false
|
373
|
+
return str.singularize if @active_support_loaded != :false
|
374
|
+
|
375
|
+
@endings ||= {
|
376
|
+
"ves" => "fe",
|
377
|
+
"ies" => "y",
|
378
|
+
"i" => "us",
|
379
|
+
"zes" => "ze",
|
380
|
+
"ses" => "s",
|
381
|
+
"es" => "",
|
382
|
+
"s" => ""
|
383
|
+
}
|
384
|
+
@regexp ||= Regexp.new("(#{@endings.keys.join("|")})$")
|
385
|
+
|
386
|
+
str.sub(@regexp, @endings)
|
387
|
+
end
|
169
388
|
end
|
170
389
|
end
|
@@ -51,12 +51,12 @@ class Rage::Router::HandlerStorage
|
|
51
51
|
lines = []
|
52
52
|
|
53
53
|
param_keys.each_with_index do |key, i|
|
54
|
-
lines << "
|
54
|
+
lines << ":#{key} => param_values[#{i}]"
|
55
55
|
end
|
56
56
|
|
57
57
|
if defaults
|
58
58
|
defaults.except(*param_keys.map(&:to_sym)).each do |key, value|
|
59
|
-
lines << "
|
59
|
+
lines << ":#{key} => '#{value}'"
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
data/lib/rage/templates/Gemfile
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
source "https://rubygems.org"
|
2
2
|
|
3
|
-
gem "rage-rb"
|
3
|
+
gem "rage-rb", "<%= Rage::VERSION %>"
|
4
4
|
|
5
5
|
# Build JSON APIs with ease
|
6
6
|
# gem "alba"
|
7
|
+
|
8
|
+
# Get 50% to 150% boost when parsing JSON.
|
9
|
+
# Rage will automatically use FastJsonparser if it is available.
|
10
|
+
# gem "fast_jsonparser"
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Models uploaded files.
|
5
|
+
#
|
6
|
+
# The actual file is accessible via the `file` accessor, though some
|
7
|
+
# of its interface is available directly for convenience.
|
8
|
+
#
|
9
|
+
# Rage will automatically unlink the files, so there is no need to clean them with a separate maintenance task.
|
10
|
+
class Rage::UploadedFile
|
11
|
+
# The basename of the file in the client.
|
12
|
+
attr_reader :original_filename
|
13
|
+
|
14
|
+
# A string with the MIME type of the file.
|
15
|
+
attr_reader :content_type
|
16
|
+
|
17
|
+
# A `File` object with the actual uploaded file. Note that some of its interface is available directly.
|
18
|
+
attr_reader :file
|
19
|
+
alias_method :tempfile, :file
|
20
|
+
|
21
|
+
def initialize(file, original_filename, content_type)
|
22
|
+
@file = file
|
23
|
+
@original_filename = original_filename
|
24
|
+
@content_type = content_type
|
25
|
+
end
|
26
|
+
|
27
|
+
# Shortcut for `file.read`.
|
28
|
+
def read(length = nil, buffer = nil)
|
29
|
+
@file.read(length, buffer)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Shortcut for `file.open`.
|
33
|
+
def open
|
34
|
+
@file.open
|
35
|
+
end
|
36
|
+
|
37
|
+
# Shortcut for `file.close`.
|
38
|
+
def close(unlink_now = false)
|
39
|
+
@file.close(unlink_now)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Shortcut for `file.path`.
|
43
|
+
def path
|
44
|
+
@file.path
|
45
|
+
end
|
46
|
+
|
47
|
+
# Shortcut for `file.to_path`.
|
48
|
+
def to_path
|
49
|
+
@file.to_path
|
50
|
+
end
|
51
|
+
|
52
|
+
# Shortcut for `file.rewind`.
|
53
|
+
def rewind
|
54
|
+
@file.rewind
|
55
|
+
end
|
56
|
+
|
57
|
+
# Shortcut for `file.size`.
|
58
|
+
def size
|
59
|
+
@file.size
|
60
|
+
end
|
61
|
+
|
62
|
+
# Shortcut for `file.eof?`.
|
63
|
+
def eof?
|
64
|
+
@file.eof?
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_io
|
68
|
+
@file.to_io
|
69
|
+
end
|
70
|
+
end
|
data/lib/rage/version.rb
CHANGED
data/lib/rage-rb.rb
CHANGED
@@ -46,17 +46,3 @@ end
|
|
46
46
|
|
47
47
|
module RageController
|
48
48
|
end
|
49
|
-
|
50
|
-
require_relative "rage/application"
|
51
|
-
require_relative "rage/fiber"
|
52
|
-
require_relative "rage/fiber_scheduler"
|
53
|
-
require_relative "rage/configuration"
|
54
|
-
|
55
|
-
require_relative "rage/router/strategies/host"
|
56
|
-
require_relative "rage/router/backend"
|
57
|
-
require_relative "rage/router/constrainer"
|
58
|
-
require_relative "rage/router/dsl"
|
59
|
-
require_relative "rage/router/handler_storage"
|
60
|
-
require_relative "rage/router/node"
|
61
|
-
|
62
|
-
require_relative "rage/controller/api"
|
data/rage.gemspec
CHANGED
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: 0.
|
4
|
+
version: 0.4.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: 2023-10-
|
11
|
+
date: 2023-10-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '2.0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '2.0'
|
55
55
|
description:
|
56
56
|
email:
|
57
57
|
- rsamoi@icloud.com
|
@@ -71,12 +71,16 @@ files:
|
|
71
71
|
- exe/rage
|
72
72
|
- lib/rage-rb.rb
|
73
73
|
- lib/rage.rb
|
74
|
+
- lib/rage/all.rb
|
74
75
|
- lib/rage/application.rb
|
75
76
|
- lib/rage/cli.rb
|
76
77
|
- lib/rage/configuration.rb
|
77
78
|
- lib/rage/controller/api.rb
|
79
|
+
- lib/rage/errors.rb
|
78
80
|
- lib/rage/fiber.rb
|
79
81
|
- lib/rage/fiber_scheduler.rb
|
82
|
+
- lib/rage/params_parser.rb
|
83
|
+
- lib/rage/request.rb
|
80
84
|
- lib/rage/router/README.md
|
81
85
|
- lib/rage/router/backend.rb
|
82
86
|
- lib/rage/router/constrainer.rb
|
@@ -96,6 +100,7 @@ files:
|
|
96
100
|
- lib/rage/templates/lib-.keep
|
97
101
|
- lib/rage/templates/log-.keep
|
98
102
|
- lib/rage/templates/public-.keep
|
103
|
+
- lib/rage/uploaded_file.rb
|
99
104
|
- lib/rage/version.rb
|
100
105
|
- rage.gemspec
|
101
106
|
homepage: https://github.com/rage-rb/rage
|