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
data/LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2015-2017 Alexander Popov (AlexWayfer)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"),
|
5
|
+
to deal in the Software without restriction, including without limitation
|
6
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
7
|
+
and/or sell copies of the Software, and to permit persons to whom
|
8
|
+
the Software is furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included
|
11
|
+
in all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
14
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
15
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
16
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
17
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
18
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
19
|
+
DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
<p align="center">
|
2
|
+
<img
|
3
|
+
src="https://raw.githubusercontent.com/AlexWayfer/flame/master/public/favicon.ico"
|
4
|
+
height="150"
|
5
|
+
alt="Flame Logo"
|
6
|
+
title="Logo from open-source Elusive-Iconfont (https://github.com/reduxframework/elusive-iconfont)"
|
7
|
+
/>
|
8
|
+
</p>
|
9
|
+
|
10
|
+
<h1 align="center">Flame</h1>
|
11
|
+
|
12
|
+
<p align="center">
|
13
|
+
<a href="https://cirrus-ci.com/github/AlexWayfer/flame/master"><img
|
14
|
+
src="https://api.cirrus-ci.com/github/AlexWayfer/flame.svg?branch=master"
|
15
|
+
alt="Cirrus CI"
|
16
|
+
/></a>
|
17
|
+
<a href="https://codecov.io/gh/AlexWayfer/flame"><img
|
18
|
+
src="https://img.shields.io/codecov/c/github/AlexWayfer/flame.svg?style=flat-square"
|
19
|
+
alt="Codecov"
|
20
|
+
/></a>
|
21
|
+
<a href="https://codeclimate.com/github/AlexWayfer/flame"><img
|
22
|
+
src="https://img.shields.io/codeclimate/maintainability/AlexWayfer/flame.svg?style=flat-square"
|
23
|
+
alt="Code Climate"
|
24
|
+
/></a>
|
25
|
+
<a href="https://depfu.com/repos/AlexWayfer/flame"><img
|
26
|
+
src="https://img.shields.io/depfu/AlexWayfer/flame.svg?style=flat-square"
|
27
|
+
alt="Depfu"
|
28
|
+
/></a>
|
29
|
+
<a href="http://inch-ci.org/github/AlexWayfer/flame"><img
|
30
|
+
src="http://inch-ci.org/github/AlexWayfer/flame.svg?branch=master&style=flat-square"
|
31
|
+
alt="Docs"
|
32
|
+
/></a>
|
33
|
+
<a href="https://rubygems.org/gems/flame"><img
|
34
|
+
src="https://img.shields.io/gem/v/flame.svg?style=flat-square"
|
35
|
+
alt="Gem"
|
36
|
+
/></a>
|
37
|
+
<a href="https://github.com/AlexWayfer/flame/blob/master/LICENSE.txt"><img
|
38
|
+
src="https://img.shields.io/github/license/AlexWayfer/flame.svg?style=flat-square"
|
39
|
+
alt="MIT license"
|
40
|
+
/></a>
|
41
|
+
</p>
|
42
|
+
|
43
|
+
Flame is a small Ruby web framework, built on [Rack](https://github.com/rack/rack),
|
44
|
+
inspired by [Gin](https://github.com/0jcasts/gin) (which follows class-controllers style),
|
45
|
+
designed as a replacement [Sinatra](https://github.com/sinatra/sinatra)
|
46
|
+
or maybe even [Rails](https://github.com/rails/rails).
|
47
|
+
|
48
|
+
## Why?
|
49
|
+
|
50
|
+
I didn't like class methods, especially for controller's hooks — OOP is prettier without it.
|
51
|
+
And I found a way to implement controller's hooks without using class methods,
|
52
|
+
but with the inheritance (including the including of modules).
|
53
|
+
Moreover, with class methods an insufficiently obvious order of hooks (especially with inheritance)
|
54
|
+
and complicated implementation of conditions are obtained.
|
55
|
+
In this framework everything is Ruby-native as it can be.
|
56
|
+
|
57
|
+
## Installation
|
58
|
+
|
59
|
+
Using the built-in `gem`:
|
60
|
+
|
61
|
+
```bash
|
62
|
+
$ gem install flame
|
63
|
+
```
|
64
|
+
|
65
|
+
or with [Bundler](http://bundler.io/):
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
# Gemfile
|
69
|
+
gem 'flame'
|
70
|
+
```
|
71
|
+
|
72
|
+
## Usage
|
73
|
+
|
74
|
+
The simplest example:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
# index_controller.rb
|
78
|
+
|
79
|
+
class IndexController < Flame::Controller
|
80
|
+
def index
|
81
|
+
view :index # or just `view`, Symbol as method-name by default
|
82
|
+
end
|
83
|
+
|
84
|
+
def hello_world
|
85
|
+
"Hello World!"
|
86
|
+
end
|
87
|
+
|
88
|
+
def goodbye
|
89
|
+
"Goodbye World!"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# app.rb
|
94
|
+
|
95
|
+
class App < Flame::Application
|
96
|
+
mount IndexController do
|
97
|
+
# all methods will be mounted automatically, it's just an example of refinement
|
98
|
+
get '/hello', :hello_world
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# config.ru
|
103
|
+
|
104
|
+
require_relative './index_controller'
|
105
|
+
|
106
|
+
require_relative './app'
|
107
|
+
|
108
|
+
run App.new # or `run App`
|
109
|
+
```
|
110
|
+
|
111
|
+
More at [Wiki](https://github.com/AlexWayfer/flame/wiki).
|
112
|
+
|
113
|
+
## Benchmark
|
114
|
+
|
115
|
+
The last benchmark can be viewed [here](https://github.com/luislavena/bench-micro).
|
116
|
+
|
117
|
+
## Development
|
118
|
+
|
119
|
+
After checking out the repo, run `bundle install` to install dependencies.
|
120
|
+
|
121
|
+
Then, run `toys rspec` to run the tests.
|
122
|
+
|
123
|
+
To install this gem onto your local machine, run `toys gem install`.
|
124
|
+
|
125
|
+
To release a new version, run `toys gem release %version%`.
|
126
|
+
See how it works [here](https://github.com/AlexWayfer/gem_toys#release).
|
127
|
+
|
128
|
+
## Contributing
|
129
|
+
|
130
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/AlexWayfer/flame).
|
131
|
+
|
132
|
+
## License
|
133
|
+
|
134
|
+
The gem is available as open source under the terms of the
|
135
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
data/lib/flame.rb
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'gorilla_patch/inflections'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
## Base module
|
6
|
+
module Flame
|
7
|
+
using GorillaPatch::Inflections
|
8
|
+
|
9
|
+
%i[Config Application Controller VERSION]
|
10
|
+
.each do |constant_name|
|
11
|
+
autoload(
|
12
|
+
constant_name, "#{__dir__}/flame/#{constant_name.to_s.underscore}"
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
data/lib/flame/application.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'addressable'
|
4
|
+
|
4
5
|
require_relative 'router'
|
5
6
|
require_relative 'dispatcher'
|
6
7
|
|
@@ -8,27 +9,37 @@ module Flame
|
|
8
9
|
## Core class, like Framework::Application
|
9
10
|
class Application
|
10
11
|
class << self
|
11
|
-
|
12
|
+
include Memery
|
13
|
+
|
14
|
+
## Remember root directory when inherited
|
15
|
+
def inherited(app)
|
16
|
+
super
|
17
|
+
app.root_dir = File.dirname caller(2..2).first.split(':')[0]
|
18
|
+
end
|
19
|
+
|
20
|
+
memoize def config
|
21
|
+
Flame::Config.new root_dir
|
22
|
+
end
|
12
23
|
|
13
24
|
## Router for routing
|
14
|
-
def router
|
15
|
-
|
25
|
+
memoize def router
|
26
|
+
Flame::Router.new(self)
|
16
27
|
end
|
17
28
|
|
18
|
-
def cached_tilts
|
19
|
-
|
29
|
+
memoize def cached_tilts
|
30
|
+
{}
|
20
31
|
end
|
21
32
|
|
22
|
-
##
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
)
|
31
|
-
|
33
|
+
## Require project directories, exclude executable files
|
34
|
+
## @param dirs [Array<String>] Array of directories names
|
35
|
+
## @example Regular require of project
|
36
|
+
## Flame::Application.require_dirs(
|
37
|
+
## %w[config lib models helpers mailers services controllers]
|
38
|
+
## )
|
39
|
+
def require_dirs(dirs, ignore: [])
|
40
|
+
dirs.each do |dir|
|
41
|
+
require_dir File.join(root_dir, dir), ignore: ignore
|
42
|
+
end
|
32
43
|
end
|
33
44
|
|
34
45
|
## Make available `run Application` without `.new` for `rackup`
|
@@ -37,53 +48,95 @@ module Flame
|
|
37
48
|
@app.call env
|
38
49
|
end
|
39
50
|
|
51
|
+
using GorillaPatch::DeepDup
|
52
|
+
|
53
|
+
## Build a path to the given controller and action
|
54
|
+
##
|
55
|
+
## @param ctrl [Flame::Controller] class of controller
|
56
|
+
## @param action [Symbol] method of controller
|
57
|
+
## @param args [Hash] parameters for method of controller
|
58
|
+
## @return [String] path for requested method, controller and parameters
|
59
|
+
## @example Path for `show(id)` method of `ArticlesController`
|
60
|
+
## path_to ArticlesController, :show, id: 2 # => "/articles/show/2"
|
61
|
+
## @example Path for `new` method of `ArticlesController` with query
|
62
|
+
## path_to ArticlesController, :new, author_id: 1
|
63
|
+
## # => "/articles/new?author_id=1"
|
64
|
+
def path_to(ctrl, action = :index, args = {})
|
65
|
+
path = router.path_of(ctrl, action)
|
66
|
+
|
67
|
+
raise Errors::RouteNotFoundError.new(ctrl, action) unless path
|
68
|
+
|
69
|
+
args = args.deep_dup
|
70
|
+
path = path.assign_arguments(args)
|
71
|
+
path = '/' if path.empty?
|
72
|
+
query = Rack::Utils.build_nested_query args unless args.empty?
|
73
|
+
Addressable::URI.new(path: path, query: query).to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
attr_accessor :root_dir
|
79
|
+
|
40
80
|
private
|
41
81
|
|
82
|
+
def require_dir(dir, ignore: [])
|
83
|
+
files =
|
84
|
+
Dir[File.join(dir, '**/*.rb')]
|
85
|
+
.reject do |file|
|
86
|
+
File.executable?(file) ||
|
87
|
+
ignore.any? { |regexp| regexp.match?(file) }
|
88
|
+
end
|
89
|
+
files.sort_by! do |file|
|
90
|
+
[File.basename(file).start_with?('_') ? 1 : 2, file]
|
91
|
+
end
|
92
|
+
files.each { |file| require File.expand_path(file) }
|
93
|
+
end
|
94
|
+
|
42
95
|
## Mount controller in application class
|
43
|
-
## @param
|
96
|
+
## @param controller [Symbol] the snake-cased name of mounted controller
|
97
|
+
## (without `Controller` or `::IndexController` for namespaces)
|
44
98
|
## @param path [String, nil] root path for the mounted controller
|
45
|
-
## @yield refine defaults
|
99
|
+
## @yield refine defaults paths for a methods of the mounted controller
|
46
100
|
## @example Mount controller with defaults
|
47
|
-
## mount ArticlesController
|
101
|
+
## mount :articles # ArticlesController
|
48
102
|
## @example Mount controller with specific path
|
49
|
-
## mount
|
103
|
+
## mount :home, '/welcome' # HomeController
|
50
104
|
## @example Mount controller with specific path of methods
|
51
|
-
## mount
|
105
|
+
## mount :home do # HomeController
|
52
106
|
## get '/bye', :goodbye
|
53
107
|
## post '/greetings', :new
|
54
108
|
## defaults
|
55
109
|
## end
|
56
|
-
|
57
|
-
|
110
|
+
## @example Mount controller with nested controllers
|
111
|
+
## mount :cabinet do # Cabinet::IndexController
|
112
|
+
## mount :articles # Cabinet::ArticlesController
|
113
|
+
## end
|
114
|
+
def mount(controller, path = nil, nested: true, &block)
|
115
|
+
## Add routes from controller to glob array
|
116
|
+
router.add Router::RoutesRefine.new(
|
117
|
+
namespace_name, controller, path, nested: nested, &block
|
118
|
+
)
|
58
119
|
end
|
59
120
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
121
|
+
using GorillaPatch::Namespace
|
122
|
+
|
123
|
+
def namespace_name
|
124
|
+
namespace = self
|
125
|
+
while namespace.name.nil? && namespace.superclass != Flame::Application
|
126
|
+
namespace = superclass
|
65
127
|
end
|
66
|
-
|
128
|
+
namespace.deconstantize
|
67
129
|
end
|
68
130
|
end
|
69
131
|
|
70
|
-
## Framework configuration
|
71
|
-
def config
|
72
|
-
self.class.config
|
73
|
-
end
|
74
|
-
|
75
|
-
def router
|
76
|
-
self.class.router
|
77
|
-
end
|
78
|
-
|
79
132
|
def initialize(app = nil)
|
80
133
|
@app = app
|
81
134
|
end
|
82
135
|
|
83
|
-
## Request
|
136
|
+
## Request receiving method
|
84
137
|
def call(env)
|
85
138
|
@app.call(env) if @app.respond_to? :call
|
86
|
-
Flame::Dispatcher.new(self, env).run!
|
139
|
+
Flame::Dispatcher.new(self.class, env).run!
|
87
140
|
end
|
88
141
|
end
|
89
142
|
end
|
data/lib/flame/config.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'errors/config_file_not_found_error'
|
4
|
+
|
5
|
+
module Flame
|
6
|
+
## Class for application configuration
|
7
|
+
class Config < Hash
|
8
|
+
DEFAULT_DIRS =
|
9
|
+
%i[config log public tmp views].each_with_object({}) do |key, result|
|
10
|
+
result[:"#{key}_dir"] = proc { File.join(self[:root_dir], key.to_s) }
|
11
|
+
end.freeze
|
12
|
+
|
13
|
+
## Create an instance of application config
|
14
|
+
## @param app [Flame::Application] application
|
15
|
+
## @param hash [Hash] config content
|
16
|
+
def initialize(root_dir)
|
17
|
+
super()
|
18
|
+
replace DEFAULT_DIRS.merge(
|
19
|
+
root_dir: File.realpath(root_dir),
|
20
|
+
environment: ENV['RACK_ENV'] || 'development'
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
## Get config value by key
|
25
|
+
## @param key [Symbol] config key
|
26
|
+
## @return [Object] config value
|
27
|
+
def [](key)
|
28
|
+
result = super(key)
|
29
|
+
result = instance_exec(&result) if result.class <= Proc && result.parameters.empty?
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
## Method for loading YAML-files from config directory
|
34
|
+
## @param file [String, Symbol]
|
35
|
+
## file name (typecast to String with '.yaml')
|
36
|
+
## @param key [Symbol, String, nil]
|
37
|
+
## key for allocating YAML in config Hash (typecast to Symbol)
|
38
|
+
## @param set [Boolean] allocating YAML in Config Hash
|
39
|
+
## @param require [Boolean] don't raise an error if file not found and not required
|
40
|
+
## @example Load SMTP file from `config/smtp.yaml' to config[]
|
41
|
+
## config.load_yaml('smtp.yaml')
|
42
|
+
## @example Load SMTP file without extension, by Symbol
|
43
|
+
## config.load_yaml(:smtp)
|
44
|
+
## @example Load SMTP file with other key to config[:mail]
|
45
|
+
## config.load_yaml('smtp.yaml', key: :mail)
|
46
|
+
## @example Load SMTP file without allocating in config[]
|
47
|
+
## config.load_yaml('smtp.yaml', set: false)
|
48
|
+
## @example Try to load nonexistent SMTP file without raising an error
|
49
|
+
## config.load_yaml('smtp.yaml', require: false)
|
50
|
+
def load_yaml(file, key: nil, set: true, required: true)
|
51
|
+
file = "#{file}.y{a,}ml" if file.is_a? Symbol
|
52
|
+
|
53
|
+
file_path = find_config_file file, required: required
|
54
|
+
return unless file_path
|
55
|
+
|
56
|
+
yaml = YAML.load_file(file_path)
|
57
|
+
key ||= File.basename(file, '.*')
|
58
|
+
self[key.to_sym] = yaml if set
|
59
|
+
yaml
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def find_config_file(filename, required:)
|
65
|
+
file_path = Dir[File.join(self[:config_dir], filename)].first
|
66
|
+
return file_path if file_path || !required
|
67
|
+
|
68
|
+
raise Errors::ConfigFileNotFoundError.new(
|
69
|
+
filename, self[:config_dir].sub(self[:root_dir], '')
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/flame/controller.rb
CHANGED
@@ -1,21 +1,50 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'forwardable'
|
4
|
-
|
4
|
+
require 'gorilla_patch/namespace'
|
5
5
|
|
6
|
+
require_relative 'router'
|
7
|
+
|
8
|
+
require_relative 'controller/actions'
|
9
|
+
require_relative 'controller/cookies'
|
10
|
+
require_relative 'controller/path_to'
|
11
|
+
|
12
|
+
## Just because of `autoload`
|
6
13
|
module Flame
|
14
|
+
autoload :Render, "#{__dir__}/render"
|
15
|
+
|
7
16
|
## Class initialize when Dispatcher found route with it
|
8
17
|
## For new request and response
|
9
18
|
class Controller
|
10
|
-
extend
|
19
|
+
extend Actions
|
20
|
+
include Memery
|
11
21
|
|
12
|
-
|
22
|
+
class << self
|
23
|
+
attr_accessor :path_arguments
|
24
|
+
|
25
|
+
def path
|
26
|
+
return self::PATH if const_defined?(:PATH)
|
27
|
+
|
28
|
+
default_path
|
29
|
+
end
|
13
30
|
|
14
|
-
|
15
|
-
|
16
|
-
|
31
|
+
private
|
32
|
+
|
33
|
+
using GorillaPatch::Inflections
|
34
|
+
|
35
|
+
## Default root path of the controller for requests
|
36
|
+
def default_path
|
37
|
+
modules = underscore.split('/')
|
38
|
+
parts = modules.pop.split('_')
|
39
|
+
parts.shift if parts.first == 'index'
|
40
|
+
parts.pop if %w[controller ctrl].include? parts.last
|
41
|
+
parts = [modules.last] if parts.empty?
|
42
|
+
Flame::Path.merge nil, parts.join('_')
|
43
|
+
end
|
17
44
|
end
|
18
45
|
|
46
|
+
extend Forwardable
|
47
|
+
|
19
48
|
def_delegators(
|
20
49
|
:@dispatcher,
|
21
50
|
:config, :request, :params, :halt, :session, :response, :status, :body,
|
@@ -23,29 +52,17 @@ module Flame
|
|
23
52
|
)
|
24
53
|
|
25
54
|
## Initialize the controller for request execution
|
26
|
-
## @param dispatcher [Flame::Dispatcher] dispatcher
|
55
|
+
## @param dispatcher [Flame::Dispatcher] host dispatcher
|
27
56
|
def initialize(dispatcher)
|
28
57
|
@dispatcher = dispatcher
|
29
58
|
end
|
30
59
|
|
31
|
-
##
|
32
|
-
def
|
33
|
-
|
34
|
-
@dispatcher.path_to(*args)
|
60
|
+
## Cookies object as Hash
|
61
|
+
memoize def cookies
|
62
|
+
Cookies.new(request.cookies, response)
|
35
63
|
end
|
36
64
|
|
37
|
-
|
38
|
-
def url_to(*args, **options)
|
39
|
-
first_arg = args.first
|
40
|
-
path =
|
41
|
-
if first_arg.is_a?(String) || first_arg.is_a?(Flame::Path)
|
42
|
-
static_file = find_static(first_arg)
|
43
|
-
static_file.path(with_version: options[:version])
|
44
|
-
else
|
45
|
-
path_to(*args, **options)
|
46
|
-
end
|
47
|
-
"#{request.scheme}://#{request.host_with_port}#{path}"
|
48
|
-
end
|
65
|
+
include Flame::Controller::PathTo
|
49
66
|
|
50
67
|
## Redirect for response
|
51
68
|
## @overload redirect(path, status)
|
@@ -98,6 +115,7 @@ module Flame
|
|
98
115
|
content_dis = 'Content-Disposition'
|
99
116
|
response[content_dis] = disposition.to_s
|
100
117
|
return unless filename
|
118
|
+
|
101
119
|
response[content_dis] << "; filename=\"#{File.basename(filename)}\""
|
102
120
|
ext = File.extname(filename)
|
103
121
|
response.content_type = ext unless ext.empty?
|
@@ -107,7 +125,7 @@ module Flame
|
|
107
125
|
## @param path [Symbol, nil] path to the template file
|
108
126
|
## @param options [Hash] options for the `Flame::Render` rendering
|
109
127
|
## @return [String] rendered template
|
110
|
-
def view(path = nil, options = {})
|
128
|
+
def view(path = nil, options = {}, &block)
|
111
129
|
cache = options.delete(:cache)
|
112
130
|
cache = config[:environment] == 'production' if cache.nil?
|
113
131
|
template = Flame::Render.new(
|
@@ -115,7 +133,7 @@ module Flame
|
|
115
133
|
(path || caller_locations(1, 1)[0].label.to_sym),
|
116
134
|
options
|
117
135
|
)
|
118
|
-
template.render(cache: cache)
|
136
|
+
template.render(cache: cache, &block)
|
119
137
|
end
|
120
138
|
alias render view
|
121
139
|
|
@@ -127,8 +145,16 @@ module Flame
|
|
127
145
|
body send(method, *extract_params_for(method))
|
128
146
|
end
|
129
147
|
|
148
|
+
def not_found
|
149
|
+
default_body
|
150
|
+
end
|
151
|
+
|
130
152
|
## Default method for Internal Server Error, can be inherited
|
131
|
-
|
153
|
+
## @param _exception [Exception] exception from code executing
|
154
|
+
## @return [String] content of exception page
|
155
|
+
def server_error(exception)
|
156
|
+
raise exception if Object.const_defined?(:BetterErrors)
|
157
|
+
|
132
158
|
body default_body
|
133
159
|
end
|
134
160
|
|
@@ -149,87 +175,25 @@ module Flame
|
|
149
175
|
body
|
150
176
|
end
|
151
177
|
|
178
|
+
using GorillaPatch::Slice
|
179
|
+
|
180
|
+
def controller_arguments
|
181
|
+
params.slice(*self.class.path_arguments)
|
182
|
+
end
|
183
|
+
|
152
184
|
def extract_params_for(action)
|
153
|
-
|
185
|
+
## Take parameters from action method
|
154
186
|
parameters = method(action).parameters
|
155
|
-
|
187
|
+
## Fill variables with values from params
|
156
188
|
req_values, opt_values = %i[req opt].map! do |type|
|
157
189
|
params.values_at(
|
158
190
|
*parameters.select { |key, _value| key == type }.map!(&:last)
|
159
191
|
)
|
160
192
|
end
|
161
|
-
|
193
|
+
## Remove nils from the end of optional values
|
162
194
|
opt_values.pop while opt_values.last.nil? && !opt_values.empty?
|
163
|
-
|
195
|
+
## Concat values
|
164
196
|
req_values + opt_values
|
165
197
|
end
|
166
|
-
|
167
|
-
def add_controller_class(args)
|
168
|
-
args.unshift(self.class) if args[0].is_a?(Symbol)
|
169
|
-
args.insert(1, :index) if args[0].is_a?(Class) && !args[1].is_a?(Symbol)
|
170
|
-
end
|
171
|
-
|
172
|
-
class << self
|
173
|
-
using GorillaPatch::Inflections
|
174
|
-
|
175
|
-
## Default root path of the controller for requests
|
176
|
-
def default_path
|
177
|
-
modules = underscore.split('/')
|
178
|
-
parts = modules.pop.split('_')
|
179
|
-
parts.shift if parts.first == 'index'
|
180
|
-
parts.pop if %w[controller ctrl].include? parts.last
|
181
|
-
parts = [modules.last] if parts.empty?
|
182
|
-
Flame::Path.merge nil, parts.join('_')
|
183
|
-
end
|
184
|
-
|
185
|
-
## Re-define public instance method from parent
|
186
|
-
## @example Inherit controller with parent actions by method
|
187
|
-
## class MyController < BaseController.with_actions
|
188
|
-
## end
|
189
|
-
## @example Define actions from module in controller
|
190
|
-
## class MyController < BaseController
|
191
|
-
## include with_actions Module1
|
192
|
-
## include with_actions Module2
|
193
|
-
## ....
|
194
|
-
## end
|
195
|
-
def with_actions(mod = nil)
|
196
|
-
return mod.extend(ModuleActions) if mod
|
197
|
-
@with_actions ||= Class.new(self) { extend ParentActions }
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
## Extension for modules whose public methods will be defined as actions
|
202
|
-
## via including
|
203
|
-
module ModuleActions
|
204
|
-
def included(ctrl)
|
205
|
-
public_instance_methods.each do |meth|
|
206
|
-
ctrl.send :define_method, meth, public_instance_method(meth)
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
## Module for public instance methods re-defining from superclass
|
212
|
-
## @example Inherit controller with parent actions without forbidden
|
213
|
-
## actions by `extend`
|
214
|
-
## class MyController < BaseController
|
215
|
-
## FORBIDDEN_ACTIONS = %[foo bar baz].freeze
|
216
|
-
## extend Flame::Controller::ParentActions
|
217
|
-
## end
|
218
|
-
module ParentActions
|
219
|
-
def inherited(ctrl)
|
220
|
-
ctrl.define_parent_actions
|
221
|
-
end
|
222
|
-
|
223
|
-
def self.extended(ctrl)
|
224
|
-
ctrl.define_parent_actions
|
225
|
-
end
|
226
|
-
|
227
|
-
def define_parent_actions
|
228
|
-
(superclass.actions - self::FORBIDDEN_ACTIONS).each do |public_method|
|
229
|
-
um = superclass.public_instance_method(public_method)
|
230
|
-
define_method public_method, um
|
231
|
-
end
|
232
|
-
end
|
233
|
-
end
|
234
198
|
end
|
235
199
|
end
|