flame 4.18.1 → 5.0.0.rc6
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 +5 -5
- data/CHANGELOG.md +921 -0
- data/LICENSE.txt +19 -0
- data/README.md +135 -0
- data/lib/flame.rb +12 -4
- data/lib/flame/application.rb +93 -40
- data/lib/flame/config.rb +73 -0
- data/lib/flame/controller.rb +62 -98
- data/lib/flame/controller/actions.rb +122 -0
- data/lib/flame/controller/cookies.rb +44 -0
- data/lib/flame/controller/path_to.rb +63 -0
- data/lib/flame/dispatcher.rb +44 -73
- data/lib/flame/dispatcher/request.rb +33 -4
- data/lib/flame/dispatcher/routes.rb +66 -0
- data/lib/flame/dispatcher/static.rb +26 -15
- data/lib/flame/errors/argument_not_assigned_error.rb +7 -6
- data/lib/flame/errors/config_file_not_found_error.rb +17 -0
- data/lib/flame/errors/controller_not_found_error.rb +19 -0
- data/lib/flame/errors/route_arguments_order_error.rb +9 -8
- data/lib/flame/errors/route_extra_arguments_error.rb +18 -18
- data/lib/flame/errors/route_not_found_error.rb +8 -7
- data/lib/flame/errors/template_not_found_error.rb +6 -6
- data/lib/flame/path.rb +141 -55
- data/lib/flame/render.rb +46 -15
- data/lib/flame/router.rb +41 -127
- data/lib/flame/router/controller_finder.rb +56 -0
- data/lib/flame/router/route.rb +16 -54
- data/lib/flame/router/routes.rb +136 -0
- data/lib/flame/router/routes_refine.rb +144 -0
- data/lib/flame/router/routes_refine/mounting.rb +57 -0
- data/lib/flame/validators.rb +21 -11
- data/lib/flame/version.rb +1 -1
- metadata +139 -84
- data/bin/flame +0 -71
- data/lib/flame/application/config.rb +0 -43
- data/lib/flame/dispatcher/cookies.rb +0 -31
- data/template/.gitignore +0 -11
- data/template/Gemfile +0 -15
- data/template/Rakefile.erb +0 -64
- data/template/app.rb.erb +0 -7
- data/template/config.ru.erb +0 -20
- data/template/config/config.rb.erb +0 -14
- data/template/config/database.example.yml +0 -5
- data/template/config/sequel.rb.erb +0 -15
- data/template/config/thin.example.yml +0 -18
- data/template/controllers/_base_controller.rb.erb +0 -13
- data/template/db/.keep +0 -0
- data/template/helpers/.keep +0 -0
- data/template/lib/.keep +0 -0
- data/template/locales/en.yml +0 -0
- data/template/models/.keep +0 -0
- data/template/public/.keep +0 -0
- data/template/server +0 -49
- data/template/views/.keep +0 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'gorilla_patch/slice'
|
4
|
+
|
5
|
+
module Flame
|
6
|
+
class Controller
|
7
|
+
## Module for work with actions
|
8
|
+
module Actions
|
9
|
+
include Memery
|
10
|
+
using GorillaPatch::Slice
|
11
|
+
|
12
|
+
## Shortcut for not-inherited public methods: actions
|
13
|
+
## @return [Array<Symbol>] array of actions (public instance methods)
|
14
|
+
def actions
|
15
|
+
public_instance_methods(false)
|
16
|
+
end
|
17
|
+
|
18
|
+
## Re-define public instance methods (actions) from parent
|
19
|
+
## @param actions [Array<Symbol>] Actions for inheritance
|
20
|
+
## @param exclude [Array<Symbol>] Actions for excluding from inheritance
|
21
|
+
## @param from [Module]
|
22
|
+
## Module (or Class) from which actions will be inherited
|
23
|
+
## @example Inherit all parent actions
|
24
|
+
## class MyController < BaseController
|
25
|
+
## inherit_actions
|
26
|
+
## end
|
27
|
+
## @example Inherit certain parent actions
|
28
|
+
## class MyController < BaseController
|
29
|
+
## inherit_actions %i[index show]
|
30
|
+
## end
|
31
|
+
## @example Inherit all parent actions exclude certain
|
32
|
+
## class MyController < BaseController
|
33
|
+
## inherit_actions exclude: %i[edit update]
|
34
|
+
## end
|
35
|
+
## @example Inherit certain actions from specific module
|
36
|
+
## class MyController < BaseController
|
37
|
+
## inherit_actions %i[index show], from: ModuleWithActions
|
38
|
+
## end
|
39
|
+
def inherit_actions(actions = nil, exclude: [], from: superclass)
|
40
|
+
actions = from.actions if actions.nil?
|
41
|
+
actions -= exclude
|
42
|
+
|
43
|
+
actions.each do |action|
|
44
|
+
define_method action, from.public_instance_method(action)
|
45
|
+
end
|
46
|
+
|
47
|
+
return unless from.respond_to?(:refined_http_methods)
|
48
|
+
|
49
|
+
refined_http_methods.merge!(
|
50
|
+
from.refined_http_methods.slice(*actions)
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
## Re-define public instance method from module
|
55
|
+
## @param mod [Module] Module for including to controller
|
56
|
+
## @param exclude [Array<Symbol>] Actions for excluding
|
57
|
+
## from module public instance methods
|
58
|
+
## @param only [Array<Symbol>] Actions for re-defining
|
59
|
+
## from module public instance methods
|
60
|
+
## @example Define actions from module in controller
|
61
|
+
## class MyController < BaseController
|
62
|
+
## include with_actions Module1
|
63
|
+
## include with_actions Module2
|
64
|
+
## ....
|
65
|
+
## end
|
66
|
+
## @example Define actions from module exclude some actions in controller
|
67
|
+
## class MyController < BaseController
|
68
|
+
## include with_actions Module1, exclude: %i[action1 action2 ...]
|
69
|
+
## include with_actions Module2, exclude: %i[action1 action2 ...]
|
70
|
+
## ....
|
71
|
+
## end
|
72
|
+
## @example Define actions from module according list in controller
|
73
|
+
## class MyController < BaseController
|
74
|
+
## include with_actions Module1, only: %i[action1 action2 ...]
|
75
|
+
## include with_actions Module2, only: %i[action1 action2 ...]
|
76
|
+
## ....
|
77
|
+
## end
|
78
|
+
def with_actions(mod, exclude: [], only: nil)
|
79
|
+
Module.new do
|
80
|
+
@mod = mod
|
81
|
+
@actions = only || (@mod.public_instance_methods(false) - exclude)
|
82
|
+
|
83
|
+
extend ModuleWithActions
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
memoize def refined_http_methods
|
88
|
+
{}
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
Flame::Router::HTTP_METHODS.each do |http_method|
|
94
|
+
downcased_http_method = http_method.downcase
|
95
|
+
define_method(
|
96
|
+
downcased_http_method
|
97
|
+
) do |action_or_action_path, action = nil|
|
98
|
+
action, action_path =
|
99
|
+
if action
|
100
|
+
[action, action_or_action_path]
|
101
|
+
else
|
102
|
+
[action_or_action_path, nil]
|
103
|
+
end
|
104
|
+
refined_http_methods[action] = [downcased_http_method, action_path]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
## Base module for module `with_actions`
|
109
|
+
module ModuleWithActions
|
110
|
+
using GorillaPatch::Slice
|
111
|
+
|
112
|
+
def included(ctrl)
|
113
|
+
ctrl.include @mod
|
114
|
+
|
115
|
+
ctrl.inherit_actions @actions, from: @mod
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private_constant :ModuleWithActions
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
class Controller
|
5
|
+
## Helper class for cookies
|
6
|
+
class Cookies
|
7
|
+
## Create an instance of Cookies
|
8
|
+
## @param request_cookies [Hash{String=>Object}] cookies from request
|
9
|
+
## @param response [Flame::Dispatcher::Response, Rack::Response]
|
10
|
+
## response object for cookies setting
|
11
|
+
def initialize(request_cookies, response)
|
12
|
+
@request_cookies = request_cookies
|
13
|
+
@response = response
|
14
|
+
end
|
15
|
+
|
16
|
+
## Get request cookies
|
17
|
+
## @param key [String, Symbol] name of cookie
|
18
|
+
## @return [Object] value of cookie
|
19
|
+
def [](key)
|
20
|
+
@request_cookies[key.to_s]
|
21
|
+
end
|
22
|
+
|
23
|
+
## Set (or delete) cookies for response
|
24
|
+
## @param key [String, Symbol] name of cookie
|
25
|
+
## @param new_value [Object, Hash, nil] value of cookie or Hash with `:value` and options
|
26
|
+
## @example Set new value to `cat` cookie
|
27
|
+
## cookies['cat'] = 'nice cat'
|
28
|
+
## @example Set new value to `cat` cookie with `Max-Age` 60 seconds
|
29
|
+
## cookies['cat'] = { value: 'nice cat', max_age: 60 }
|
30
|
+
## @example Delete `cat` cookie
|
31
|
+
## cookies['cat'] = nil
|
32
|
+
def []=(key, new_value)
|
33
|
+
case new_value
|
34
|
+
when NilClass
|
35
|
+
@response.delete_cookie(key.to_s, path: '/')
|
36
|
+
when Hash
|
37
|
+
@response.set_cookie(key.to_s, new_value)
|
38
|
+
else
|
39
|
+
@response.set_cookie(key.to_s, value: new_value, path: '/')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
class Controller
|
5
|
+
## Module with methods for path or URL building
|
6
|
+
module PathTo
|
7
|
+
include Memery
|
8
|
+
|
9
|
+
## Look documentation at {Flame::Dispatcher#path_to}
|
10
|
+
def path_to(*args)
|
11
|
+
add_controller_class(args)
|
12
|
+
add_controller_arguments(args)
|
13
|
+
@dispatcher.path_to(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
## Build a URI to the given controller and action, or path
|
17
|
+
def url_to(*args, **options)
|
18
|
+
path = build_path_for_url(*args, **options)
|
19
|
+
Addressable::URI.join(request.base_url, path).to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
using GorillaPatch::Namespace
|
23
|
+
|
24
|
+
## Path to previous page, or to index action, or to Index controller
|
25
|
+
## @return [String] path to previous page or to index
|
26
|
+
def path_to_back
|
27
|
+
back_path = request.referer
|
28
|
+
return back_path if back_path && back_path != request.url
|
29
|
+
|
30
|
+
return path_to :index if self.class.actions.include?(:index)
|
31
|
+
|
32
|
+
'/'
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def add_controller_class(args)
|
38
|
+
args.unshift(self.class) if args[0].is_a?(Symbol)
|
39
|
+
args.insert(1, :index) if args[0].is_a?(Class) && !args[1].is_a?(Symbol)
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_controller_arguments(args)
|
43
|
+
if args[-1].is_a?(Hash)
|
44
|
+
args[-1] = controller_arguments.merge args[-1]
|
45
|
+
else
|
46
|
+
args.push(controller_arguments)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_path_for_url(*args, **options)
|
51
|
+
first_arg = args.first
|
52
|
+
if first_arg.is_a?(String) || first_arg.is_a?(Flame::Path)
|
53
|
+
find_static(first_arg).path(with_version: options[:version])
|
54
|
+
else
|
55
|
+
path_to(*args, **options)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
memoize :build_path_for_url,
|
60
|
+
condition: -> { config[:environment] == 'production' }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/flame/dispatcher.rb
CHANGED
@@ -1,29 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'gorilla_patch/symbolize'
|
4
|
+
require 'rack'
|
4
5
|
|
5
6
|
require_relative 'dispatcher/request'
|
6
7
|
require_relative 'dispatcher/response'
|
7
8
|
|
8
|
-
require_relative 'dispatcher/cookies'
|
9
9
|
require_relative 'dispatcher/static'
|
10
|
+
require_relative 'dispatcher/routes'
|
10
11
|
|
11
12
|
require_relative 'errors/route_not_found_error'
|
12
13
|
|
13
14
|
module Flame
|
14
15
|
## Helpers for dispatch Flame::Application#call
|
15
16
|
class Dispatcher
|
16
|
-
|
17
|
+
include Memery
|
18
|
+
|
19
|
+
GEM_STATIC_FILES = File.join(__dir__, '../../public').freeze
|
20
|
+
|
21
|
+
extend Forwardable
|
22
|
+
def_delegators :@app_class, :router, :path_to
|
17
23
|
|
18
24
|
attr_reader :request, :response
|
19
25
|
|
20
26
|
include Flame::Dispatcher::Static
|
27
|
+
include Flame::Dispatcher::Routes
|
21
28
|
|
22
29
|
## Initialize Dispatcher from Application#call
|
23
|
-
## @param
|
30
|
+
## @param app_class [Class] application class
|
24
31
|
## @param env Rack-environment object
|
25
|
-
def initialize(
|
26
|
-
@
|
32
|
+
def initialize(app_class, env)
|
33
|
+
@app_class = app_class
|
27
34
|
@env = env
|
28
35
|
@request = Flame::Dispatcher::Request.new(env)
|
29
36
|
@response = Flame::Dispatcher::Response.new
|
@@ -32,12 +39,15 @@ module Flame
|
|
32
39
|
## Start of execution the request
|
33
40
|
def run!
|
34
41
|
catch :halt do
|
35
|
-
|
42
|
+
validate_request
|
43
|
+
|
44
|
+
try_options ||
|
45
|
+
try_static ||
|
36
46
|
try_static(dir: GEM_STATIC_FILES) ||
|
37
47
|
try_route ||
|
38
48
|
halt(404)
|
39
49
|
end
|
40
|
-
response.write body unless request.
|
50
|
+
response.write body unless request.head?
|
41
51
|
response.finish
|
42
52
|
end
|
43
53
|
|
@@ -65,43 +75,27 @@ module Flame
|
|
65
75
|
|
66
76
|
## Parameters of the request
|
67
77
|
def params
|
68
|
-
|
78
|
+
request.params.symbolize_keys(deep: true)
|
79
|
+
rescue ArgumentError => e
|
80
|
+
raise unless e.message.include?('invalid %-encoding')
|
81
|
+
|
82
|
+
{}
|
69
83
|
end
|
84
|
+
memoize :params
|
70
85
|
|
71
86
|
## Session object as Hash
|
72
87
|
def session
|
73
88
|
request.session
|
74
89
|
end
|
75
90
|
|
76
|
-
## Cookies object as Hash
|
77
|
-
def cookies
|
78
|
-
@cookies ||= Cookies.new(request.cookies, response)
|
79
|
-
end
|
80
|
-
|
81
91
|
## Application-config object as Hash
|
82
92
|
def config
|
83
|
-
@
|
93
|
+
@app_class.config
|
84
94
|
end
|
85
95
|
|
86
|
-
##
|
87
|
-
|
88
|
-
|
89
|
-
## @param action [Symbol] method of controller
|
90
|
-
## @param args [Hash] parameters for method of controller
|
91
|
-
## @return [String] path for requested method, controller and parameters
|
92
|
-
## @example Path for `show(id)` method of `ArticlesController` with `id: 2`
|
93
|
-
## path_to ArticlesController, :show, id: 2 # => "/articles/show/2"
|
94
|
-
## @example Path for `new` method of `ArticlesController` with params
|
95
|
-
## path_to ArticlesController, :new, params: { author_id: 1 }
|
96
|
-
## # => "/articles/new?author_id=1"
|
97
|
-
def path_to(ctrl, action = :index, args = {})
|
98
|
-
route = @app.class.router.find_route(controller: ctrl, action: action)
|
99
|
-
raise Errors::RouteNotFoundError.new(ctrl, action) unless route
|
100
|
-
query = Rack::Utils.build_nested_query args.delete(:params)
|
101
|
-
query = nil if query&.empty?
|
102
|
-
path = route.path.assign_arguments(args)
|
103
|
-
path = '/' if path.empty?
|
104
|
-
URI::Generic.build(path: path, query: query).to_s
|
96
|
+
## Available routes endpoint
|
97
|
+
memoize def available_endpoint
|
98
|
+
router.navigate(*request.path.parts)
|
105
99
|
end
|
106
100
|
|
107
101
|
## Interrupt the execution of route, and set new optional data
|
@@ -117,7 +111,7 @@ module Flame
|
|
117
111
|
## @example Halt with 404, render template
|
118
112
|
## halt 404, render('errors/404')
|
119
113
|
## @example Halt with 200, set new headers
|
120
|
-
## halt 200, 'Cats!', 'Content-Type' => 'animal/cat'
|
114
|
+
## halt 200, 'Cats!', 'Content-Type' # => 'animal/cat'
|
121
115
|
def halt(new_status = nil, new_body = nil, new_headers = {})
|
122
116
|
status new_status if new_status
|
123
117
|
body new_body || (default_body_of_nearest_route if body.empty?)
|
@@ -139,55 +133,32 @@ module Flame
|
|
139
133
|
## Generate default body of error page
|
140
134
|
def default_body
|
141
135
|
# response.headers[Rack::CONTENT_TYPE] = 'text/html'
|
142
|
-
|
136
|
+
Rack::Utils::HTTP_STATUS_CODES[status]
|
143
137
|
end
|
144
138
|
|
145
139
|
## All cached tilts (views) for application by Flame::Render
|
146
140
|
def cached_tilts
|
147
|
-
@
|
141
|
+
@app_class.cached_tilts
|
148
142
|
end
|
149
143
|
|
150
144
|
private
|
151
145
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
)
|
158
|
-
return nil unless route
|
159
|
-
status 200
|
160
|
-
execute_route(route)
|
161
|
-
end
|
146
|
+
def validate_request
|
147
|
+
## https://github.com/rack/rack/issues/337#issuecomment-48555831
|
148
|
+
request.params
|
149
|
+
rescue ArgumentError => e
|
150
|
+
raise unless e.message.include?('invalid %-encoding')
|
162
151
|
|
163
|
-
|
164
|
-
## @param route [Flame::Route] route that must be executed
|
165
|
-
def execute_route(route, action = route.action)
|
166
|
-
params.merge! route.path.extract_arguments(request.path)
|
167
|
-
# route.execute(self)
|
168
|
-
controller = route.controller.new(self)
|
169
|
-
controller.send(:execute, action)
|
170
|
-
rescue => exception
|
171
|
-
# p 'rescue from dispatcher'
|
172
|
-
dump_error(exception)
|
173
|
-
status 500
|
174
|
-
controller&.send(:server_error, exception)
|
175
|
-
# p 're raise exception from dispatcher'
|
176
|
-
# raise exception
|
152
|
+
halt 400
|
177
153
|
end
|
178
154
|
|
179
|
-
##
|
180
|
-
def
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
## or it's `default_body` method not defined
|
187
|
-
return default_body unless route
|
188
|
-
## Execute `default_body` method for the founded route
|
189
|
-
execute_route(route, :default_body)
|
190
|
-
default_body if body.empty?
|
155
|
+
## Return response if HTTP-method is OPTIONS
|
156
|
+
def try_options
|
157
|
+
return unless request.http_method == :OPTIONS
|
158
|
+
|
159
|
+
allow = available_endpoint&.allow
|
160
|
+
halt 404 unless allow
|
161
|
+
response.headers['Allow'] = allow
|
191
162
|
end
|
192
163
|
end
|
193
164
|
end
|
@@ -4,14 +4,43 @@ module Flame
|
|
4
4
|
class Dispatcher
|
5
5
|
## Class for requests
|
6
6
|
class Request < Rack::Request
|
7
|
+
include Memery
|
8
|
+
|
7
9
|
## Initialize Flame::Path
|
8
|
-
def path
|
9
|
-
|
10
|
+
memoize def path
|
11
|
+
Flame::Path.new path_info
|
10
12
|
end
|
11
13
|
|
12
14
|
## Override HTTP-method of the request if the param '_method' found
|
13
|
-
def http_method
|
14
|
-
|
15
|
+
memoize def http_method
|
16
|
+
method_from_method =
|
17
|
+
begin
|
18
|
+
params['_method']
|
19
|
+
rescue ArgumentError => e
|
20
|
+
## https://github.com/rack/rack/issues/337#issuecomment-48555831
|
21
|
+
raise unless e.message.include?('invalid %-encoding')
|
22
|
+
end
|
23
|
+
|
24
|
+
(method_from_method || request_method).upcase.to_sym
|
25
|
+
end
|
26
|
+
|
27
|
+
using GorillaPatch::Inflections
|
28
|
+
|
29
|
+
HEADER_PREFIX = 'HTTP_'
|
30
|
+
|
31
|
+
## Helper method for comfortable Camel-Cased Hash of headers
|
32
|
+
memoize def headers
|
33
|
+
env.each_with_object({}) do |(key, value), result|
|
34
|
+
next unless key.start_with?(HEADER_PREFIX)
|
35
|
+
|
36
|
+
## TODO: Replace `String#[]` with `#delete_prefix`
|
37
|
+
## after Ruby < 2.5 dropping
|
38
|
+
camelized_key =
|
39
|
+
key[HEADER_PREFIX.size..-1].downcase.tr('_', '/')
|
40
|
+
.camelize.gsub('::', '-')
|
41
|
+
|
42
|
+
result[camelized_key] = value
|
43
|
+
end
|
15
44
|
end
|
16
45
|
end
|
17
46
|
end
|