hanami-controller 1.3.2 → 2.0.0.alpha3

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +299 -537
  5. data/hanami-controller.gemspec +5 -4
  6. data/lib/hanami/action/application_action.rb +112 -0
  7. data/lib/hanami/action/application_configuration/cookies.rb +29 -0
  8. data/lib/hanami/action/application_configuration/sessions.rb +46 -0
  9. data/lib/hanami/action/application_configuration.rb +92 -0
  10. data/lib/hanami/action/base_params.rb +2 -2
  11. data/lib/hanami/action/cache/cache_control.rb +4 -4
  12. data/lib/hanami/action/cache/conditional_get.rb +3 -1
  13. data/lib/hanami/action/cache/directives.rb +1 -1
  14. data/lib/hanami/action/cache/expires.rb +3 -3
  15. data/lib/hanami/action/cache.rb +1 -139
  16. data/lib/hanami/action/configuration.rb +428 -0
  17. data/lib/hanami/action/cookie_jar.rb +3 -3
  18. data/lib/hanami/action/cookies.rb +3 -62
  19. data/lib/hanami/action/csrf_protection.rb +214 -0
  20. data/lib/hanami/action/flash.rb +102 -207
  21. data/lib/hanami/action/glue.rb +5 -31
  22. data/lib/hanami/action/halt.rb +12 -0
  23. data/lib/hanami/action/mime.rb +78 -485
  24. data/lib/hanami/action/params.rb +2 -2
  25. data/lib/hanami/action/rack/file.rb +1 -1
  26. data/lib/hanami/action/request.rb +30 -20
  27. data/lib/hanami/action/response.rb +193 -0
  28. data/lib/hanami/action/session.rb +11 -128
  29. data/lib/hanami/action/standalone_action.rb +579 -0
  30. data/lib/hanami/action/validatable.rb +1 -1
  31. data/lib/hanami/action/view_name_inferrer.rb +46 -0
  32. data/lib/hanami/action.rb +129 -73
  33. data/lib/hanami/controller/version.rb +1 -1
  34. data/lib/hanami/controller.rb +0 -227
  35. data/lib/hanami/http/status.rb +2 -2
  36. metadata +45 -27
  37. data/lib/hanami/action/callable.rb +0 -92
  38. data/lib/hanami/action/callbacks.rb +0 -214
  39. data/lib/hanami/action/configurable.rb +0 -50
  40. data/lib/hanami/action/exposable/guard.rb +0 -104
  41. data/lib/hanami/action/exposable.rb +0 -126
  42. data/lib/hanami/action/head.rb +0 -121
  43. data/lib/hanami/action/rack/callable.rb +0 -47
  44. data/lib/hanami/action/rack.rb +0 -399
  45. data/lib/hanami/action/redirect.rb +0 -59
  46. data/lib/hanami/action/throwable.rb +0 -196
  47. data/lib/hanami/controller/configuration.rb +0 -763
  48. data/lib/hanami-controller.rb +0 -1
@@ -1,92 +0,0 @@
1
- module Hanami
2
- module Action
3
- module Callable
4
- # Execute application logic.
5
- # It implements the Rack protocol.
6
- #
7
- # The request params are passed as an argument to the `#call` method.
8
- #
9
- # If routed with Hanami::Router, it extracts the relevant bits from the
10
- # Rack `env` (eg the requested `:id`).
11
- #
12
- # Otherwise everything it's passed as it is: the full Rack `env`
13
- # in production, and the given `Hash` for unit tests. See the examples
14
- # below.
15
- #
16
- # Application developers are forced to implement this method in their
17
- # actions.
18
- #
19
- # @param env [Hash] the full Rack env or the params. This value may vary,
20
- # see the examples below.
21
- #
22
- # @return [Array] a serialized Rack response (eg. `[200, {}, ["Hi!"]]`)
23
- #
24
- # @since 0.1.0
25
- #
26
- # @example with Hanami::Router
27
- # require 'hanami/controller'
28
- #
29
- # class Show
30
- # include Hanami::Action
31
- #
32
- # def call(params)
33
- # # ...
34
- # puts params # => { id: 23 } extracted from Rack env
35
- # end
36
- # end
37
- #
38
- # @example Standalone
39
- # require 'hanami/controller'
40
- #
41
- # class Show
42
- # include Hanami::Action
43
- #
44
- # def call(params)
45
- # # ...
46
- # puts params
47
- # # => { :"rack.version"=>[1, 2],
48
- # # :"rack.input"=>#<StringIO:0x007fa563463948>, ... }
49
- # end
50
- # end
51
- #
52
- # @example Unit Testing
53
- # require 'hanami/controller'
54
- #
55
- # class Show
56
- # include Hanami::Action
57
- #
58
- # def call(params)
59
- # # ...
60
- # puts params # => { id: 23, key: 'value' } passed as it is from testing
61
- # end
62
- # end
63
- #
64
- # action = Show.new
65
- # response = action.call({ id: 23, key: 'value' })
66
- def call(env)
67
- _rescue do
68
- @_env = env
69
- @headers = ::Rack::Utils::HeaderHash.new(configuration.default_headers)
70
- @params = self.class.params_class.new(@_env)
71
- super @params
72
- end
73
-
74
- finish
75
- end
76
-
77
- private
78
-
79
- # Prepare the Rack response before the control is returned to the
80
- # webserver.
81
- #
82
- # @since 0.1.0
83
- # @api private
84
- #
85
- # @see Hanami::Action#finish
86
- def finish
87
- super
88
- response
89
- end
90
- end
91
- end
92
- end
@@ -1,214 +0,0 @@
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
- # @since 0.1.0
201
- # @api private
202
- def _run_before_callbacks(params)
203
- self.class.before_callbacks.run(self, params)
204
- end
205
-
206
- # @since 0.1.0
207
- # @api private
208
- def _run_after_callbacks(params)
209
- self.class.after_callbacks.run(self, params)
210
- end
211
- end
212
- end
213
- end
214
- end
@@ -1,50 +0,0 @@
1
- require 'hanami/utils/class_attribute'
2
-
3
- module Hanami
4
- module Action
5
- # Configuration API
6
- #
7
- # @since 0.2.0
8
- #
9
- # @see Hanami::Controller::Configuration
10
- module Configurable
11
- # Override Ruby's hook for modules.
12
- # It includes configuration logic
13
- #
14
- # @param base [Class] the target action
15
- #
16
- # @since 0.2.0
17
- # @api private
18
- #
19
- # @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
20
- #
21
- # @example
22
- # require 'hanami/controller'
23
- #
24
- # class Show
25
- # include Hanami::Action
26
- # end
27
- #
28
- # Show.configuration
29
- def self.included(base)
30
- config = Hanami::Controller::Configuration.for(base)
31
-
32
- base.class_eval do
33
- include Utils::ClassAttribute
34
-
35
- class_attribute :configuration
36
- self.configuration = config
37
- end
38
-
39
- config.copy!(base)
40
- end
41
-
42
- private
43
-
44
- # @since 0.2.0
45
- def configuration
46
- self.class.configuration
47
- end
48
- end
49
- end
50
- end
@@ -1,104 +0,0 @@
1
- require 'hanami/controller/error'
2
-
3
- module Hanami
4
- module Controller
5
- # Exposure of reserved words
6
- #
7
- # @since 0.7.1
8
- class IllegalExposureError < Error
9
- end
10
- end
11
-
12
- module Action
13
- module Exposable
14
- # Guard for Exposures API.
15
- # Prevents exposure of reserved words
16
- #
17
- # @since 0.7.1
18
- # @api private
19
- #
20
- # @see Hanami::Action::Exposable::Guard::ClassMethods#expose
21
- # @see Hanami::Action::Exposable::Guard::ClassMethods#reserved_word?
22
- module Guard
23
- # Override Ruby's hook for modules.
24
- # It prepends a guard for the exposures logic
25
- #
26
- # @param base [Class] the target action
27
- #
28
- # @since 0.7.1
29
- # @api private
30
- #
31
- # @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
32
- def self.included(base)
33
- class << base
34
- prepend ClassMethods
35
- end
36
- end
37
-
38
- # Exposures API Guard class methods
39
- #
40
- # @since 0.7.1
41
- # @api private
42
- module ClassMethods
43
- # Prevents exposure if names contain a reserved word.
44
- #
45
- # @param names [Array<Symbol>] the name(s) of the attribute(s) to be
46
- # exposed
47
- #
48
- # @return [void]
49
- #
50
- # @since 0.7.1
51
- # @api private
52
- def expose(*names)
53
- detect_reserved_words!(names)
54
-
55
- super
56
- end
57
-
58
- private
59
-
60
- # Raises error if given names contain a reserved word.
61
- #
62
- # @param names [Array<Symbol>] a list of names to be checked.
63
- #
64
- # @return [void]
65
- #
66
- # @raise [IllegalExposeError] if names contain one or more of reserved
67
- # words
68
- #
69
- # @since 0.7.1
70
- # @api private
71
- def detect_reserved_words!(names)
72
- names.each do |name|
73
- if reserved_word?(name)
74
- raise Hanami::Controller::IllegalExposureError.new("#{name} is a reserved word. It cannot be exposed")
75
- end
76
- end
77
- end
78
-
79
- # Checks if a string is a reserved word
80
- #
81
- # Reserved word is a name of the method defined in one of the modules
82
- # of a given namespace.
83
- #
84
- # @param name [Symbol] the word to be checked
85
- # @param namespace [String] the namespace containing internal modules
86
- #
87
- # @return [true, false]
88
- #
89
- # @since 0.7.1
90
- # @api private
91
- def reserved_word?(name, namespace = 'Hanami')
92
- if method_defined?(name) || private_method_defined?(name)
93
- method_owner = instance_method(name).owner
94
-
95
- Utils::String.namespace(method_owner) == namespace
96
- else
97
- false
98
- end
99
- end
100
- end
101
- end
102
- end
103
- end
104
- end
@@ -1,126 +0,0 @@
1
- require 'hanami/action/exposable/guard'
2
-
3
- module Hanami
4
- module Action
5
- # Exposures API
6
- #
7
- # @since 0.1.0
8
- #
9
- # @see Hanami::Action::Exposable::ClassMethods#expose
10
- module Exposable
11
- # Override Ruby's hook for modules.
12
- # It includes exposures logic
13
- #
14
- # @param base [Class] the target action
15
- #
16
- # @since 0.1.0
17
- # @api private
18
- #
19
- # @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
20
- def self.included(base)
21
- base.class_eval do
22
- extend ClassMethods
23
- include Guard
24
-
25
- _expose :params
26
- end
27
- end
28
-
29
- # Exposures API class methods
30
- #
31
- # @since 0.1.0
32
- # @api private
33
- module ClassMethods
34
- # Expose the given attributes on the outside of the object with
35
- # a getter and a special method called #exposures.
36
- #
37
- # @param names [Array<Symbol>] the name(s) of the attribute(s) to be
38
- # exposed
39
- #
40
- # @return [void]
41
- #
42
- # @since 0.1.0
43
- #
44
- # @example
45
- # require 'hanami/controller'
46
- #
47
- # class Show
48
- # include Hanami::Action
49
- #
50
- # expose :article, :tags
51
- #
52
- # def call(params)
53
- # @article = Article.find params[:id]
54
- # @tags = Tag.for(article)
55
- # end
56
- # end
57
- #
58
- # action = Show.new
59
- # action.call({id: 23})
60
- #
61
- # action.article # => #<Article ...>
62
- # action.tags # => [#<Tag ...>, #<Tag ...>]
63
- #
64
- # action.exposures # => { :article => #<Article ...>, :tags => [ ... ] }
65
- def expose(*names)
66
- class_eval do
67
- names.each do |name|
68
- attr_reader(name) unless attr_reader?(name)
69
- end
70
-
71
- exposures.push(*names)
72
- end
73
- end
74
-
75
- # Alias of #expose to be used in internal modules.
76
- # #_expose is not watched by the Guard
77
- alias _expose expose
78
-
79
- # Set of exposures attribute names
80
- #
81
- # @return [Array] the exposures attribute names
82
- #
83
- # @since 0.1.0
84
- # @api private
85
- def exposures
86
- @exposures ||= []
87
- end
88
-
89
- private
90
- # Check if the attr_reader is already defined
91
- #
92
- # @since 0.3.0
93
- # @api private
94
- def attr_reader?(name)
95
- (instance_methods | private_instance_methods).include?(name)
96
- end
97
- end
98
-
99
- # Set of exposures
100
- #
101
- # @return [Hash] the exposures
102
- #
103
- # @since 0.1.0
104
- #
105
- # @see Hanami::Action::Exposable::ClassMethods.expose
106
- def exposures
107
- @exposures ||= {}.tap do |result|
108
- self.class.exposures.each do |name|
109
- result[name] = send(name)
110
- end
111
- end
112
- end
113
-
114
- # Finalize the response
115
- #
116
- # @since 0.3.0
117
- # @api private
118
- #
119
- # @see Hanami::Action#finish
120
- def finish
121
- super
122
- exposures
123
- end
124
- end
125
- end
126
- end
@@ -1,121 +0,0 @@
1
- module Hanami
2
- module Action
3
- # Ensures to not send body or headers for HEAD requests and/or for status
4
- # codes that doesn't allow them.
5
- #
6
- # @since 0.3.2
7
- #
8
- # @see http://www.ietf.org/rfc/rfc2616.txt
9
- module Head
10
-
11
- # Status codes that by RFC must not include a message body
12
- #
13
- # @since 0.3.2
14
- # @api private
15
- HTTP_STATUSES_WITHOUT_BODY = Set.new((100..199).to_a << 204 << 205 << 304).freeze
16
-
17
-
18
- # Entity headers allowed in blank body responses, according to
19
- # RFC 2616 - Section 10 (HTTP 1.1).
20
- #
21
- # "The response MAY include new or updated metainformation in the form
22
- # of entity-headers".
23
- #
24
- # @since 0.4.0
25
- # @api private
26
- #
27
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.5
28
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html
29
- ENTITY_HEADERS = {
30
- 'Allow' => true,
31
- 'Content-Encoding' => true,
32
- 'Content-Language' => true,
33
- 'Content-Location' => true,
34
- 'Content-MD5' => true,
35
- 'Content-Range' => true,
36
- 'Expires' => true,
37
- 'Last-Modified' => true,
38
- 'extension-header' => true
39
- }.freeze
40
-
41
- # Ensures to not send body or headers for HEAD requests and/or for status
42
- # codes that doesn't allow them.
43
- #
44
- # @since 0.3.2
45
- # @api private
46
- #
47
- # @see Hanami::Action#finish
48
- def finish
49
- super
50
-
51
- if _requires_no_body?
52
- @_body = nil
53
- @headers.reject! {|header,_| !keep_response_header?(header) }
54
- end
55
- end
56
-
57
- protected
58
- # @since 0.3.2
59
- # @api private
60
- def _requires_no_body?
61
- HTTP_STATUSES_WITHOUT_BODY.include?(@_status) || head?
62
- end
63
-
64
- private
65
- # According to RFC 2616, when a response MUST have an empty body, it only
66
- # allows Entity Headers.
67
- #
68
- # For instance, a <tt>204</tt> doesn't allow <tt>Content-Type</tt> or any
69
- # other custom header.
70
- #
71
- # This restriction is enforced by <tt>Hanami::Action::Head#finish</tt>.
72
- #
73
- # However, there are cases that demand to bypass this rule to set meta
74
- # informations via headers.
75
- #
76
- # An example is a <tt>DELETE</tt> request for a JSON API application.
77
- # It returns a <tt>204</tt> but still wants to specify the rate limit
78
- # quota via <tt>X-Rate-Limit</tt>.
79
- #
80
- # @since 0.5.0
81
- #
82
- # @see Hanami::Action::HEAD#finish
83
- #
84
- # @example
85
- # require 'hanami/controller'
86
- #
87
- # module Books
88
- # class Destroy
89
- # include Hanami::Action
90
- #
91
- # def call(params)
92
- # # ...
93
- # self.headers.merge!(
94
- # 'Last-Modified' => 'Fri, 27 Nov 2015 13:32:36 GMT',
95
- # 'X-Rate-Limit' => '4000',
96
- # 'Content-Type' => 'application/json',
97
- # 'X-No-Pass' => 'true'
98
- # )
99
- #
100
- # self.status = 204
101
- # end
102
- #
103
- # private
104
- #
105
- # def keep_response_header?(header)
106
- # super || header == 'X-Rate-Limit'
107
- # end
108
- # end
109
- # end
110
- #
111
- # # Only the following headers will be sent:
112
- # # * Last-Modified - because we used `super' in the method that respects the HTTP RFC
113
- # # * X-Rate-Limit - because we explicitely allow it
114
- #
115
- # # Both Content-Type and X-No-Pass are removed because they're not allowed
116
- def keep_response_header?(header)
117
- ENTITY_HEADERS.include?(header)
118
- end
119
- end
120
- end
121
- end