flame 5.0.0.rc5 → 5.0.0.rc6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +921 -0
  3. data/LICENSE.txt +19 -0
  4. data/README.md +135 -0
  5. data/lib/flame.rb +12 -5
  6. data/lib/flame/application.rb +47 -46
  7. data/lib/flame/config.rb +73 -0
  8. data/lib/flame/controller.rb +45 -78
  9. data/lib/flame/controller/actions.rb +122 -0
  10. data/lib/flame/{dispatcher → controller}/cookies.rb +8 -3
  11. data/lib/flame/controller/path_to.rb +34 -10
  12. data/lib/flame/dispatcher.rb +14 -17
  13. data/lib/flame/dispatcher/request.rb +25 -6
  14. data/lib/flame/dispatcher/routes.rb +22 -14
  15. data/lib/flame/dispatcher/static.rb +13 -9
  16. data/lib/flame/errors/argument_not_assigned_error.rb +3 -8
  17. data/lib/flame/errors/config_file_not_found_error.rb +17 -0
  18. data/lib/flame/errors/controller_not_found_error.rb +19 -0
  19. data/lib/flame/errors/route_arguments_order_error.rb +5 -10
  20. data/lib/flame/errors/route_extra_arguments_error.rb +10 -20
  21. data/lib/flame/errors/route_not_found_error.rb +3 -8
  22. data/lib/flame/errors/template_not_found_error.rb +2 -8
  23. data/lib/flame/path.rb +36 -18
  24. data/lib/flame/render.rb +13 -5
  25. data/lib/flame/router.rb +7 -157
  26. data/lib/flame/router/controller_finder.rb +56 -0
  27. data/lib/flame/router/route.rb +9 -0
  28. data/lib/flame/router/routes.rb +58 -8
  29. data/lib/flame/router/routes_refine.rb +144 -0
  30. data/lib/flame/router/routes_refine/mounting.rb +57 -0
  31. data/lib/flame/validators.rb +14 -10
  32. data/lib/flame/version.rb +1 -1
  33. metadata +91 -99
  34. data/bin/flame +0 -16
  35. data/lib/flame/application/config.rb +0 -49
  36. data/template/.editorconfig +0 -15
  37. data/template/.gitignore +0 -28
  38. data/template/.rubocop.yml +0 -14
  39. data/template/Gemfile +0 -55
  40. data/template/Rakefile +0 -824
  41. data/template/application.rb.erb +0 -10
  42. data/template/config.ru.erb +0 -72
  43. data/template/config/config.rb.erb +0 -56
  44. data/template/config/database.example.yml +0 -5
  45. data/template/config/deploy.example.yml +0 -2
  46. data/template/config/puma.rb +0 -56
  47. data/template/config/sequel.rb.erb +0 -22
  48. data/template/config/server.example.yml +0 -32
  49. data/template/config/session.example.yml +0 -7
  50. data/template/controllers/_controller.rb.erb +0 -14
  51. data/template/controllers/site/_controller.rb.erb +0 -18
  52. data/template/controllers/site/index_controller.rb.erb +0 -12
  53. data/template/db/.keep +0 -0
  54. data/template/filewatchers.yml +0 -12
  55. data/template/helpers/.keep +0 -0
  56. data/template/lib/.keep +0 -0
  57. data/template/locales/en.yml +0 -0
  58. data/template/models/.keep +0 -0
  59. data/template/public/.keep +0 -0
  60. data/template/server +0 -200
  61. data/template/services/.keep +0 -0
  62. data/template/views/.keep +0 -0
  63. data/template/views/site/index.html.erb.erb +0 -1
  64. data/template/views/site/layout.html.erb.erb +0 -10
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flame
4
+ ## Comment due to `private_constant`
5
+ class Router
6
+ ## Class for controller constant finding in namespace by names
7
+ class ControllerFinder
8
+ attr_reader :controller
9
+
10
+ def initialize(namespace_name, controller_or_name)
11
+ @namespace =
12
+ namespace_name.empty? ? Object : Object.const_get(namespace_name)
13
+
14
+ if controller_or_name.is_a?(Class)
15
+ @controller = controller_or_name
16
+ @controller_name = controller_or_name.name
17
+ else
18
+ @controller_name = controller_or_name
19
+ @controller = find
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def find
26
+ found_controller_name = controller_name_variations.find do |variation|
27
+ @namespace.const_defined?(variation) unless variation.empty?
28
+ end
29
+
30
+ raise_controller_not_found_error unless found_controller_name
31
+
32
+ controller = @namespace.const_get(found_controller_name)
33
+ return controller if controller < Flame::Controller
34
+
35
+ controller::IndexController
36
+ end
37
+
38
+ using GorillaPatch::Inflections
39
+
40
+ TRASNFORMATION_METHODS = %i[camelize upcase].freeze
41
+
42
+ def controller_name_variations
43
+ TRASNFORMATION_METHODS.each_with_object([]) do |method, result|
44
+ transformed = @controller_name.to_s.send(method)
45
+ result.push transformed, "#{transformed}Controller"
46
+ end
47
+ end
48
+
49
+ def raise_controller_not_found_error
50
+ raise Errors::ControllerNotFoundError.new(@controller_name, @namespace)
51
+ end
52
+ end
53
+
54
+ private_constant :ControllerFinder
55
+ end
56
+ end
@@ -7,6 +7,10 @@ module Flame
7
7
  class Router
8
8
  ## Class for Route in Router.routes
9
9
  class Route
10
+ extend Forwardable
11
+
12
+ def_delegators :to_s, :inspect
13
+
10
14
  attr_reader :controller, :action
11
15
 
12
16
  ## Create a new instance
@@ -22,12 +26,17 @@ module Flame
22
26
  ## @return [true, false] equal or not
23
27
  def ==(other)
24
28
  return false unless other.is_a? self.class
29
+
25
30
  %i[controller action].reduce(true) do |result, method|
26
31
  result && (
27
32
  public_send(method) == other.public_send(method)
28
33
  )
29
34
  end
30
35
  end
36
+
37
+ def to_s
38
+ "#{controller}##{action}"
39
+ end
31
40
  end
32
41
  end
33
42
  end
@@ -12,10 +12,15 @@ module Flame
12
12
  ## Flame::Router::Routes.new('/foo/bar/baz')
13
13
  ## # => { 'foo' => { 'bar' => { 'baz' => {} } } }
14
14
  def initialize(*path_parts)
15
+ super()
16
+
15
17
  path = Flame::Path.new(*path_parts)
16
18
  return if path.parts.empty?
19
+
17
20
  nested_routes = self.class.new Flame::Path.new(*path.parts[1..-1])
18
- # path.parts.reduce(result) { |hash, part| hash[part] ||= self.class.new }
21
+ # path.parts.reduce(result) do |hash, part|
22
+ # hash[part] ||= self.class.new
23
+ # end
19
24
  self[path.parts.first] = nested_routes
20
25
  end
21
26
 
@@ -38,6 +43,12 @@ module Flame
38
43
  super
39
44
  end
40
45
 
46
+ ## Return the first available route (at the first level).
47
+ ## @return [Flame::Router::Route] the first route
48
+ def first_route
49
+ values.find { |value| value.is_a?(Route) }
50
+ end
51
+
41
52
  ## Navigate to Routes or Route through static parts or arguments
42
53
  ## @param path_parts [Array<String, Flame::Path, Flame::Path::Part>]
43
54
  ## path or path parts as keys for navigating
@@ -48,31 +59,70 @@ module Flame
48
59
  def navigate(*path_parts)
49
60
  path_parts = Flame::Path.new(*path_parts).parts
50
61
  return dig_through_opt_args if path_parts.empty?
62
+
51
63
  endpoint =
52
- self[path_parts.first] ||
53
- dig(first_opt_arg_key, path_parts.first) ||
54
- self[first_arg_key]
55
- endpoint&.navigate(*path_parts[1..-1])
64
+ self[path_parts.first] || dig(first_opt_arg_key, path_parts.first)
65
+
66
+ endpoint&.navigate(*path_parts[1..-1]) ||
67
+ find_among_arg_keys(path_parts[1..-1])
56
68
  end
57
69
 
58
70
  ## Dig through optional arguments as keys
59
71
  ## @return [Flame::Router::Routes] return most nested end-point
60
72
  ## without optional arguments
61
73
  def dig_through_opt_args
62
- self[first_opt_arg_key]&.dig_through_opt_args || self
74
+ [
75
+ self[first_opt_arg_key]&.dig_through_opt_args,
76
+ self
77
+ ]
78
+ .compact.find(&:first_route)
63
79
  end
64
80
 
65
81
  def allow
66
82
  methods = keys.select { |key| key.is_a? Symbol }
67
83
  return if methods.empty?
84
+
68
85
  methods.push(:OPTIONS).join(', ')
69
86
  end
70
87
 
88
+ PADDING_SIZE = Router::HTTP_METHODS.map(&:size).max
89
+ PADDING_FORMAT = "%#{PADDING_SIZE}.#{PADDING_SIZE}s"
90
+
91
+ ## Output routes in human readable format
92
+ def to_s(prefix = '/')
93
+ sort.map do |key, value|
94
+ if key.is_a?(Symbol)
95
+ <<~OUTPUT
96
+ \e[1m#{format PADDING_FORMAT, key} #{prefix}\e[22m
97
+ #{' ' * PADDING_SIZE} \e[3m\e[36m#{value}\e[0m\e[23m
98
+ OUTPUT
99
+ else
100
+ value.to_s(Flame::Path.new(prefix, key))
101
+ end
102
+ end.join
103
+ end
104
+
105
+ ## Sort routes for human readability
106
+ def sort
107
+ sort_by do |key, _value|
108
+ [
109
+ if key.is_a?(Symbol)
110
+ then Router::HTTP_METHODS.index(key)
111
+ else Float::INFINITY
112
+ end,
113
+ key.to_s
114
+ ]
115
+ end
116
+ end
117
+
71
118
  private
72
119
 
73
- def first_arg_key
120
+ def find_among_arg_keys(path_parts)
74
121
  keys.find do |key|
75
- key.is_a?(Flame::Path::Part) && key.arg?
122
+ next unless key.is_a?(Flame::Path::Part) && key.arg?
123
+
124
+ result = self[key].navigate(*path_parts)
125
+ break result if result
76
126
  end
77
127
  end
78
128
 
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'routes_refine/mounting'
4
+
5
+ module Flame
6
+ class Router
7
+ ## Helper class for controller routing refine
8
+ class RoutesRefine
9
+ attr_reader :routes, :reverse_routes
10
+
11
+ class << self
12
+ include Memery
13
+
14
+ ## Defaults REST routes (methods, pathes, controllers actions)
15
+ def rest_routes
16
+ [
17
+ { method: :GET, path: '/', action: :index },
18
+ { method: :POST, path: '/', action: :create },
19
+ { method: :GET, path: '/', action: :show },
20
+ { method: :PUT, path: '/', action: :update },
21
+ { method: :DELETE, path: '/', action: :delete }
22
+ ]
23
+ end
24
+ end
25
+
26
+ using GorillaPatch::Namespace
27
+
28
+ def initialize(
29
+ namespace_name, controller_or_name, path, nested: true, &block
30
+ )
31
+ @controller =
32
+ ControllerFinder.new(namespace_name, controller_or_name).controller
33
+ @namespace_name = @controller.deconstantize
34
+ @path = Flame::Path.new(path || @controller.path)
35
+ @controller.path_arguments = @path.parts.select(&:arg?).map(&:to_sym)
36
+ @routes, @endpoint = @path.to_routes_with_endpoint
37
+ @reverse_routes = {}
38
+ @mount_nested = nested
39
+ execute(&block)
40
+ end
41
+
42
+ private
43
+
44
+ HTTP_METHODS.each do |http_method|
45
+ ## Define refine methods for all HTTP methods
46
+ ## @overload post(path, action)
47
+ ## Execute action on requested path and HTTP method
48
+ ## @param path [String] path of method for the request
49
+ ## @param action [Symbol] name of method for the request
50
+ ## @example
51
+ ## Set path to '/bye' and method to :POST for action `goodbye`
52
+ ## post '/bye', :goodbye
53
+ ## @overload post(action)
54
+ ## Execute action on requested HTTP method
55
+ ## @param action [Symbol] name of method for the request
56
+ ## @example Set method to :POST for action `goodbye`
57
+ ## post :goodbye
58
+ define_method(http_method.downcase) do |action_path, action = nil|
59
+ ## Swap arguments if action in path variable
60
+ unless action
61
+ action = action_path.to_sym
62
+ action_path = nil
63
+ end
64
+ ## Initialize new route
65
+ route = Route.new(@controller, action)
66
+ ## Make path by controller method with parameners
67
+ action_path = Flame::Path.new(action_path).adapt(@controller, action)
68
+ ## Validate action path
69
+ validate_action_path(action, action_path)
70
+ ## Merge action path with controller path
71
+ path = Flame::Path.new(@path, action_path)
72
+ ## Remove the same route if needed
73
+ remove_old_routes(action, route)
74
+ ## Add new route
75
+ add_new_route(route, action, path, http_method)
76
+ end
77
+ end
78
+
79
+ ## Assign remaining methods of the controller
80
+ ## to defaults pathes and HTTP methods
81
+ def defaults
82
+ rest
83
+
84
+ @controller.actions.each do |action|
85
+ get action unless find_reverse_route(action)
86
+ end
87
+
88
+ mount_nested_controllers if @mount_nested && (
89
+ @controller.demodulize == 'IndexController' &&
90
+ !@namespace_name.empty?
91
+ )
92
+ end
93
+
94
+ ## Assign methods of the controller to REST architecture
95
+ def rest
96
+ self.class.rest_routes.each do |rest_route|
97
+ action = rest_route[:action]
98
+ next if !@controller.actions.include?(action) || find_reverse_route(action)
99
+
100
+ send(*rest_route.values.map(&:downcase))
101
+ end
102
+ end
103
+
104
+ include Mounting
105
+
106
+ ## Execute block of refinings end sorting routes
107
+ def execute(&block)
108
+ @controller.refined_http_methods
109
+ .each do |action, (http_method, action_path)|
110
+ send(http_method, action_path, action)
111
+ end
112
+ instance_exec(&block) if block
113
+ defaults
114
+ end
115
+
116
+ def find_reverse_route(action)
117
+ @reverse_routes.dig(@controller.to_s, action)
118
+ end
119
+
120
+ def validate_action_path(action, action_path)
121
+ Validators::RouteArgumentsValidator
122
+ .new(@controller, action_path, action)
123
+ .valid?
124
+ end
125
+
126
+ def remove_old_routes(action, new_route)
127
+ old_path = @reverse_routes[@controller.to_s]&.delete(action)
128
+ return unless old_path
129
+
130
+ @routes.dig(*old_path.parts)
131
+ .delete_if { |_method, old_route| old_route == new_route }
132
+ end
133
+
134
+ using GorillaPatch::DeepMerge
135
+
136
+ def add_new_route(route, action, path, http_method)
137
+ path_routes, endpoint = path.to_routes_with_endpoint
138
+ endpoint[http_method] = route
139
+ @routes.deep_merge!(path_routes)
140
+ (@reverse_routes[@controller.to_s] ||= {})[action] = path
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flame
4
+ class Router
5
+ class RoutesRefine
6
+ ## Module for mounting in RoutesRefine
7
+ module Mounting
8
+ private
9
+
10
+ using GorillaPatch::DeepMerge
11
+
12
+ ## Mount controller inside other (parent) controller
13
+ ## @param controller [Flame::Controller] class of mounting controller
14
+ ## @param path [String, nil] root path for mounting controller
15
+ ## @yield Block of code for routes refine
16
+ def mount(controller_name, path = nil, &block)
17
+ routes_refine = self.class.new(
18
+ @namespace_name, controller_name, path, &block
19
+ )
20
+
21
+ @endpoint.deep_merge! routes_refine.routes
22
+
23
+ @reverse_routes.merge!(
24
+ routes_refine.reverse_routes.transform_values do |hash|
25
+ hash.transform_values { |action_path| @path + action_path }
26
+ end
27
+ )
28
+ end
29
+
30
+ using GorillaPatch::Namespace
31
+
32
+ def mount_nested_controllers
33
+ namespace = Object.const_get(@namespace_name)
34
+
35
+ namespace.constants.each do |constant_name|
36
+ constant = namespace.const_get(constant_name)
37
+ if constant < Flame::Controller || constant.instance_of?(Module)
38
+ mount_nested_controller constant
39
+ end
40
+ end
41
+ end
42
+
43
+ def mount_nested_controller(nested_controller)
44
+ mount nested_controller if should_be_mounted? nested_controller
45
+ end
46
+
47
+ def should_be_mounted?(controller)
48
+ if controller.instance_of?(Module)
49
+ controller.const_defined?(:IndexController, false)
50
+ else
51
+ controller.actions.any? && !@reverse_routes.key?(controller.to_s)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -7,6 +7,8 @@ module Flame
7
7
  module Validators
8
8
  ## Compare arguments from path and from controller's action
9
9
  class RouteArgumentsValidator
10
+ include Memery
11
+
10
12
  ## Create a new instance of validator
11
13
  ## @param ctrl [Flame::Controller] controller of route
12
14
  ## @param path [Flame::Path, String] path of route
@@ -29,6 +31,7 @@ module Flame
29
31
  extra_arguments = first_extra_arguments
30
32
  ## Raise error if extra arguments
31
33
  return true unless extra_arguments
34
+
32
35
  raise Errors::RouteExtraArgumentsError.new(
33
36
  @ctrl, @action, @path, extra_arguments
34
37
  )
@@ -37,27 +40,28 @@ module Flame
37
40
  def order_valid?
38
41
  wrong_ordered_arguments = first_wrong_ordered_arguments
39
42
  return true unless wrong_ordered_arguments
43
+
40
44
  raise Errors::RouteArgumentsOrderError.new(
41
45
  @path, wrong_ordered_arguments
42
46
  )
43
47
  end
44
48
 
45
49
  ## Split path to args array
46
- def path_arguments
47
- @path_arguments ||= @path.parts
48
- .each_with_object(req: [], opt: []) do |part, hash|
49
- ## Take only argument parts
50
- next unless part.arg?
51
- ## Memorize arguments
52
- hash[part.opt_arg? ? :opt : :req] << part.clean.to_sym
53
- end
50
+ memoize def path_arguments
51
+ @path.parts.each_with_object(req: [], opt: []) do |part, hash|
52
+ ## Take only argument parts
53
+ next unless part.arg?
54
+
55
+ ## Memorize arguments
56
+ hash[part.opt_arg? ? :opt : :req] << part.to_sym
57
+ end
54
58
  end
55
59
 
56
60
  ## Take args from controller's action
57
- def action_arguments
61
+ memoize def action_arguments
58
62
  ## Get all parameters (arguments) from method
59
63
  ## Than collect and sort parameters into hash
60
- @action_arguments ||= @ctrl.instance_method(@action).parameters
64
+ @ctrl.instance_method(@action).parameters
61
65
  .each_with_object(req: [], opt: []) do |param, hash|
62
66
  ## Only required parameters must be in `:req`
63
67
  hash[param[0]] << param[1]