hanami-controller 0.0.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +155 -0
- data/LICENSE.md +22 -0
- data/README.md +1180 -9
- data/hanami-controller.gemspec +19 -12
- data/lib/hanami-controller.rb +1 -0
- data/lib/hanami/action.rb +85 -0
- data/lib/hanami/action/cache.rb +174 -0
- data/lib/hanami/action/cache/cache_control.rb +70 -0
- data/lib/hanami/action/cache/conditional_get.rb +93 -0
- data/lib/hanami/action/cache/directives.rb +99 -0
- data/lib/hanami/action/cache/expires.rb +73 -0
- data/lib/hanami/action/callable.rb +94 -0
- data/lib/hanami/action/callbacks.rb +210 -0
- data/lib/hanami/action/configurable.rb +49 -0
- data/lib/hanami/action/cookie_jar.rb +181 -0
- data/lib/hanami/action/cookies.rb +85 -0
- data/lib/hanami/action/exposable.rb +115 -0
- data/lib/hanami/action/flash.rb +182 -0
- data/lib/hanami/action/glue.rb +66 -0
- data/lib/hanami/action/head.rb +122 -0
- data/lib/hanami/action/mime.rb +493 -0
- data/lib/hanami/action/params.rb +285 -0
- data/lib/hanami/action/rack.rb +270 -0
- data/lib/hanami/action/rack/callable.rb +47 -0
- data/lib/hanami/action/rack/file.rb +33 -0
- data/lib/hanami/action/redirect.rb +59 -0
- data/lib/hanami/action/request.rb +86 -0
- data/lib/hanami/action/session.rb +154 -0
- data/lib/hanami/action/throwable.rb +194 -0
- data/lib/hanami/action/validatable.rb +128 -0
- data/lib/hanami/controller.rb +250 -2
- data/lib/hanami/controller/configuration.rb +705 -0
- data/lib/hanami/controller/error.rb +7 -0
- data/lib/hanami/controller/version.rb +4 -1
- data/lib/hanami/http/status.rb +62 -0
- metadata +124 -16
- data/.gitignore +0 -9
- data/Gemfile +0 -4
- data/Rakefile +0 -2
- data/bin/console +0 -14
- data/bin/setup +0 -8
@@ -0,0 +1,99 @@
|
|
1
|
+
module Hanami
|
2
|
+
module Action
|
3
|
+
module Cache
|
4
|
+
|
5
|
+
# Cache-Control directives which have values
|
6
|
+
#
|
7
|
+
# @since 0.3.0
|
8
|
+
# @api private
|
9
|
+
VALUE_DIRECTIVES = %i(max_age s_maxage min_fresh max_stale).freeze
|
10
|
+
|
11
|
+
# Cache-Control directives which are implicitly true
|
12
|
+
#
|
13
|
+
# @since 0.3.0
|
14
|
+
# @api private
|
15
|
+
NON_VALUE_DIRECTIVES = %i(public private no_cache no_store no_transform must_revalidate proxy_revalidate).freeze
|
16
|
+
|
17
|
+
# Class representing value directives
|
18
|
+
#
|
19
|
+
# ex: max-age=600
|
20
|
+
#
|
21
|
+
# @since 0.3.0
|
22
|
+
# @api private
|
23
|
+
class ValueDirective
|
24
|
+
attr_reader :name
|
25
|
+
|
26
|
+
def initialize(name, value)
|
27
|
+
@name, @value = name, value
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_str
|
31
|
+
"#{@name.to_s.tr('_', '-')}=#{@value.to_i}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def valid?
|
35
|
+
VALUE_DIRECTIVES.include? @name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Class representing non value directives
|
40
|
+
#
|
41
|
+
# ex: no-cache
|
42
|
+
#
|
43
|
+
# @since 0.3.0
|
44
|
+
# @api private
|
45
|
+
class NonValueDirective
|
46
|
+
attr_reader :name
|
47
|
+
|
48
|
+
def initialize(name)
|
49
|
+
@name = name
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_str
|
53
|
+
@name.to_s.tr('_', '-')
|
54
|
+
end
|
55
|
+
|
56
|
+
def valid?
|
57
|
+
NON_VALUE_DIRECTIVES.include? @name
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Collection of value and non value directives
|
62
|
+
#
|
63
|
+
# @since 0.3.0
|
64
|
+
# @api private
|
65
|
+
class Directives
|
66
|
+
include Enumerable
|
67
|
+
|
68
|
+
def initialize(*values)
|
69
|
+
@directives = []
|
70
|
+
values.each do |directive_key|
|
71
|
+
if directive_key.kind_of? Hash
|
72
|
+
directive_key.each { |name, value| self.<< ValueDirective.new(name, value) }
|
73
|
+
else
|
74
|
+
self.<< NonValueDirective.new(directive_key)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def each
|
80
|
+
@directives.each { |d| yield d }
|
81
|
+
end
|
82
|
+
|
83
|
+
def <<(directive)
|
84
|
+
@directives << directive if directive.valid?
|
85
|
+
end
|
86
|
+
|
87
|
+
def values
|
88
|
+
@directives.delete_if do |directive|
|
89
|
+
directive.name == :public && @directives.map(&:name).include?(:private)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def join(separator)
|
94
|
+
values.join(separator)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'hanami/action/cache/cache_control'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Action
|
5
|
+
module Cache
|
6
|
+
|
7
|
+
# Module with Expires logic
|
8
|
+
#
|
9
|
+
# @since 0.3.0
|
10
|
+
# @api private
|
11
|
+
module Expires
|
12
|
+
|
13
|
+
# The HTTP header for Expires
|
14
|
+
#
|
15
|
+
# @since 0.3.0
|
16
|
+
# @api private
|
17
|
+
HEADER = 'Expires'.freeze
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.extend ClassMethods
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def expires(amount, *values)
|
25
|
+
@expires_directives ||= Directives.new(amount, *values)
|
26
|
+
end
|
27
|
+
|
28
|
+
def expires_directives
|
29
|
+
@expires_directives || Object.new.tap do |null_object|
|
30
|
+
def null_object.headers
|
31
|
+
Hash.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Finalize the response including default cache headers into the response
|
38
|
+
#
|
39
|
+
# @since 0.3.0
|
40
|
+
# @api private
|
41
|
+
#
|
42
|
+
# @see Hanami::Action#finish
|
43
|
+
def finish
|
44
|
+
super
|
45
|
+
headers.merge!(self.class.expires_directives.headers) unless headers.include? HEADER
|
46
|
+
end
|
47
|
+
|
48
|
+
# Class which stores Expires directives
|
49
|
+
#
|
50
|
+
# @since 0.3.0
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
#
|
54
|
+
class Directives
|
55
|
+
def initialize(amount, *values)
|
56
|
+
@amount = amount
|
57
|
+
@cache_control = Hanami::Action::Cache::CacheControl::Directives.new(*(values << { max_age: amount }))
|
58
|
+
end
|
59
|
+
|
60
|
+
def headers
|
61
|
+
{ HEADER => time.httpdate }.merge(@cache_control.headers)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def time
|
67
|
+
Time.now + @amount.to_i
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'hanami/action/params'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Action
|
5
|
+
module Callable
|
6
|
+
# Execute application logic.
|
7
|
+
# It implements the Rack protocol.
|
8
|
+
#
|
9
|
+
# The request params are passed as an argument to the `#call` method.
|
10
|
+
#
|
11
|
+
# If routed with Hanami::Router, it extracts the relevant bits from the
|
12
|
+
# Rack `env` (eg the requested `:id`).
|
13
|
+
#
|
14
|
+
# Otherwise everything it's passed as it is: the full Rack `env`
|
15
|
+
# in production, and the given `Hash` for unit tests. See the examples
|
16
|
+
# below.
|
17
|
+
#
|
18
|
+
# Application developers are forced to implement this method in their
|
19
|
+
# actions.
|
20
|
+
#
|
21
|
+
# @param env [Hash] the full Rack env or the params. This value may vary,
|
22
|
+
# see the examples below.
|
23
|
+
#
|
24
|
+
# @return [Array] a serialized Rack response (eg. `[200, {}, ["Hi!"]]`)
|
25
|
+
#
|
26
|
+
# @since 0.1.0
|
27
|
+
#
|
28
|
+
# @example with Hanami::Router
|
29
|
+
# require 'hanami/controller'
|
30
|
+
#
|
31
|
+
# class Show
|
32
|
+
# include Hanami::Action
|
33
|
+
#
|
34
|
+
# def call(params)
|
35
|
+
# # ...
|
36
|
+
# puts params # => { id: 23 } extracted from Rack env
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @example Standalone
|
41
|
+
# require 'hanami/controller'
|
42
|
+
#
|
43
|
+
# class Show
|
44
|
+
# include Hanami::Action
|
45
|
+
#
|
46
|
+
# def call(params)
|
47
|
+
# # ...
|
48
|
+
# puts params
|
49
|
+
# # => { :"rack.version"=>[1, 2],
|
50
|
+
# # :"rack.input"=>#<StringIO:0x007fa563463948>, ... }
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# @example Unit Testing
|
55
|
+
# require 'hanami/controller'
|
56
|
+
#
|
57
|
+
# class Show
|
58
|
+
# include Hanami::Action
|
59
|
+
#
|
60
|
+
# def call(params)
|
61
|
+
# # ...
|
62
|
+
# puts params # => { id: 23, key: 'value' } passed as it is from testing
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# action = Show.new
|
67
|
+
# response = action.call({ id: 23, key: 'value' })
|
68
|
+
def call(env)
|
69
|
+
_rescue do
|
70
|
+
@_env = env
|
71
|
+
@headers = ::Rack::Utils::HeaderHash.new(configuration.default_headers)
|
72
|
+
@params = self.class.params_class.new(@_env)
|
73
|
+
super @params
|
74
|
+
end
|
75
|
+
|
76
|
+
finish
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# Prepare the Rack response before the control is returned to the
|
82
|
+
# webserver.
|
83
|
+
#
|
84
|
+
# @since 0.1.0
|
85
|
+
# @api private
|
86
|
+
#
|
87
|
+
# @see Hanami::Action#finish
|
88
|
+
def finish
|
89
|
+
super
|
90
|
+
response
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'hanami/utils/class_attribute'
|
2
|
+
require 'hanami/utils/callbacks'
|
3
|
+
|
4
|
+
module Hanami
|
5
|
+
module Action
|
6
|
+
# Before and after callbacks
|
7
|
+
#
|
8
|
+
# @since 0.1.0
|
9
|
+
# @see Hanami::Action::ClassMethods#before
|
10
|
+
# @see Hanami::Action::ClassMethods#after
|
11
|
+
module Callbacks
|
12
|
+
# Override Ruby's hook for modules.
|
13
|
+
# It includes callbacks logic
|
14
|
+
#
|
15
|
+
# @param base [Class] the target action
|
16
|
+
#
|
17
|
+
# @since 0.1.0
|
18
|
+
# @api private
|
19
|
+
#
|
20
|
+
# @see http://www.ruby-doc.org/core/Module.html#method-i-included
|
21
|
+
def self.included(base)
|
22
|
+
base.class_eval do
|
23
|
+
extend ClassMethods
|
24
|
+
prepend InstanceMethods
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Callbacks API class methods
|
29
|
+
#
|
30
|
+
# @since 0.1.0
|
31
|
+
# @api private
|
32
|
+
module ClassMethods
|
33
|
+
# Override Ruby's hook for modules.
|
34
|
+
# It includes callbacks logic
|
35
|
+
#
|
36
|
+
# @param base [Class] the target action
|
37
|
+
#
|
38
|
+
# @since 0.1.0
|
39
|
+
# @api private
|
40
|
+
#
|
41
|
+
# @see http://www.ruby-doc.org/core/Module.html#method-i-extended
|
42
|
+
def self.extended(base)
|
43
|
+
base.class_eval do
|
44
|
+
include Utils::ClassAttribute
|
45
|
+
|
46
|
+
class_attribute :before_callbacks
|
47
|
+
self.before_callbacks = Utils::Callbacks::Chain.new
|
48
|
+
|
49
|
+
class_attribute :after_callbacks
|
50
|
+
self.after_callbacks = Utils::Callbacks::Chain.new
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Define a callback for an Action.
|
55
|
+
# The callback will be executed **before** the action is called, in the
|
56
|
+
# order they are added.
|
57
|
+
#
|
58
|
+
# @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
|
59
|
+
# each of them is representing a name of a method available in the
|
60
|
+
# context of the Action.
|
61
|
+
#
|
62
|
+
# @param blk [Proc] an anonymous function to be executed
|
63
|
+
#
|
64
|
+
# @return [void]
|
65
|
+
#
|
66
|
+
# @since 0.3.2
|
67
|
+
#
|
68
|
+
# @see Hanami::Action::Callbacks::ClassMethods#append_after
|
69
|
+
#
|
70
|
+
# @example Method names (symbols)
|
71
|
+
# require 'hanami/controller'
|
72
|
+
#
|
73
|
+
# class Show
|
74
|
+
# include Hanami::Action
|
75
|
+
#
|
76
|
+
# before :authenticate, :set_article
|
77
|
+
#
|
78
|
+
# def call(params)
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# private
|
82
|
+
# def authenticate
|
83
|
+
# # ...
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# # `params` in the method signature is optional
|
87
|
+
# def set_article(params)
|
88
|
+
# @article = Article.find params[:id]
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# # The order of execution will be:
|
93
|
+
# #
|
94
|
+
# # 1. #authenticate
|
95
|
+
# # 2. #set_article
|
96
|
+
# # 3. #call
|
97
|
+
#
|
98
|
+
# @example Anonymous functions (Procs)
|
99
|
+
# require 'hanami/controller'
|
100
|
+
#
|
101
|
+
# class Show
|
102
|
+
# include Hanami::Action
|
103
|
+
#
|
104
|
+
# before { ... } # 1 do some authentication stuff
|
105
|
+
# before {|params| @article = Article.find params[:id] } # 2
|
106
|
+
#
|
107
|
+
# def call(params)
|
108
|
+
# end
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# # The order of execution will be:
|
112
|
+
# #
|
113
|
+
# # 1. authentication
|
114
|
+
# # 2. set the article
|
115
|
+
# # 3. #call
|
116
|
+
def append_before(*callbacks, &blk)
|
117
|
+
before_callbacks.append(*callbacks, &blk)
|
118
|
+
end
|
119
|
+
|
120
|
+
# @since 0.1.0
|
121
|
+
alias_method :before, :append_before
|
122
|
+
|
123
|
+
# Define a callback for an Action.
|
124
|
+
# The callback will be executed **after** the action is called, in the
|
125
|
+
# order they are added.
|
126
|
+
#
|
127
|
+
# @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
|
128
|
+
# each of them is representing a name of a method available in the
|
129
|
+
# context of the Action.
|
130
|
+
#
|
131
|
+
# @param blk [Proc] an anonymous function to be executed
|
132
|
+
#
|
133
|
+
# @return [void]
|
134
|
+
#
|
135
|
+
# @since 0.3.2
|
136
|
+
#
|
137
|
+
# @see Hanami::Action::Callbacks::ClassMethods#append_before
|
138
|
+
def append_after(*callbacks, &blk)
|
139
|
+
after_callbacks.append(*callbacks, &blk)
|
140
|
+
end
|
141
|
+
|
142
|
+
# @since 0.1.0
|
143
|
+
alias_method :after, :append_after
|
144
|
+
|
145
|
+
# Define a callback for an Action.
|
146
|
+
# The callback will be executed **before** the action is called.
|
147
|
+
# It will add the callback at the beginning of the callbacks' chain.
|
148
|
+
#
|
149
|
+
# @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
|
150
|
+
# each of them is representing a name of a method available in the
|
151
|
+
# context of the Action.
|
152
|
+
#
|
153
|
+
# @param blk [Proc] an anonymous function to be executed
|
154
|
+
#
|
155
|
+
# @return [void]
|
156
|
+
#
|
157
|
+
# @since 0.3.2
|
158
|
+
#
|
159
|
+
# @see Hanami::Action::Callbacks::ClassMethods#prepend_after
|
160
|
+
def prepend_before(*callbacks, &blk)
|
161
|
+
before_callbacks.prepend(*callbacks, &blk)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Define a callback for an Action.
|
165
|
+
# The callback will be executed **after** the action is called.
|
166
|
+
# It will add the callback at the beginning of the callbacks' chain.
|
167
|
+
#
|
168
|
+
# @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
|
169
|
+
# each of them is representing a name of a method available in the
|
170
|
+
# context of the Action.
|
171
|
+
#
|
172
|
+
# @param blk [Proc] an anonymous function to be executed
|
173
|
+
#
|
174
|
+
# @return [void]
|
175
|
+
#
|
176
|
+
# @since 0.3.2
|
177
|
+
#
|
178
|
+
# @see Hanami::Action::Callbacks::ClassMethods#prepend_before
|
179
|
+
def prepend_after(*callbacks, &blk)
|
180
|
+
after_callbacks.prepend(*callbacks, &blk)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Callbacks API instance methods
|
185
|
+
#
|
186
|
+
# @since 0.1.0
|
187
|
+
# @api private
|
188
|
+
module InstanceMethods
|
189
|
+
# Implements the Rack/Hanami::Action protocol
|
190
|
+
#
|
191
|
+
# @since 0.1.0
|
192
|
+
# @api private
|
193
|
+
def call(params)
|
194
|
+
_run_before_callbacks(params)
|
195
|
+
super
|
196
|
+
_run_after_callbacks(params)
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
def _run_before_callbacks(params)
|
201
|
+
self.class.before_callbacks.run(self, params)
|
202
|
+
end
|
203
|
+
|
204
|
+
def _run_after_callbacks(params)
|
205
|
+
self.class.after_callbacks.run(self, params)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|