hanami-controller 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +32 -30
- data/hanami-controller.gemspec +4 -6
- data/lib/hanami/action.rb +7 -3
- data/lib/hanami/action/base_params.rb +151 -0
- data/lib/hanami/action/cache/cache_control.rb +4 -1
- data/lib/hanami/action/cache/expires.rb +4 -1
- data/lib/hanami/action/callable.rb +0 -2
- data/lib/hanami/action/cookie_jar.rb +26 -1
- data/lib/hanami/action/exposable.rb +4 -1
- data/lib/hanami/action/mime.rb +18 -3
- data/lib/hanami/action/params.rb +67 -215
- data/lib/hanami/action/rack.rb +32 -1
- data/lib/hanami/action/session.rb +16 -14
- data/lib/hanami/action/validatable.rb +8 -31
- data/lib/hanami/controller/version.rb +1 -1
- metadata +9 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e9a92b39aa9c373400fe07e64b270d3066af888
|
4
|
+
data.tar.gz: f146d4da77a9c4ced2bfdfea31826db63f0ef992
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 089577daf70c460f7bedaa4d613c65551b0a9bd10af1e7725e0654398ad81c8d5abd7df550f0cce0f97b73f5d00cbf3072f039753a1eb9255218b555705f7e57
|
7
|
+
data.tar.gz: 34ce5a95575dc90669f87469d5494c7b8208b9ac1efc1015c8f6078ca906178d0cdeed585b02f36c4eda64f4977d0a825d0785bfd4f65728da352821cac57df3
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,23 @@
|
|
1
1
|
# Hanami::Controller
|
2
2
|
Complete, fast and testable actions for Rack
|
3
3
|
|
4
|
+
## v0.7.0 - 2016-07-22
|
5
|
+
### Added
|
6
|
+
- [Luca Guidi] Introduced `Hanami::Action::Params#error_messages` which returns a flat collection of full error messages
|
7
|
+
|
8
|
+
### Fixed
|
9
|
+
- [Luca Guidi] Params are deeply symbolized
|
10
|
+
- [Artem Nistratov] Send only changed cookies in HTTP response
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
- [Luca Guidi] Drop support for Ruby 2.0 and 2.1. Official support for JRuby 9.0.5.0+.
|
14
|
+
- [Luca Guidi] Param validations now require you to add `hanami-validations` in `Gemfile`.
|
15
|
+
- [Luca Guidi] Removed "_indifferent access_" for params. Since now on, only symbols are allowed.
|
16
|
+
- [Luca Guidi] Params are immutable
|
17
|
+
- [Luca Guidi] Params validations syntax has changed
|
18
|
+
- [Luca Guidi] `Hanami::Action::Params#errors` now returns a Hash. Keys are symbols representing invalid params, while values are arrays of strings with a message of the failure.
|
19
|
+
- [Vasilis Spilka] Made `Hanami::Action::Session#errors` public
|
20
|
+
|
4
21
|
## v0.6.1 - 2016-02-05
|
5
22
|
### Changed
|
6
23
|
- [Anatolii Didukh] Optimise memory usage by freezing MIME types constant
|
data/README.md
CHANGED
@@ -60,7 +60,7 @@ class Show
|
|
60
60
|
include Hanami::Action
|
61
61
|
|
62
62
|
def call(params)
|
63
|
-
@article =
|
63
|
+
@article = ArticleRepository.find params[:id]
|
64
64
|
end
|
65
65
|
end
|
66
66
|
```
|
@@ -74,7 +74,7 @@ This is important, because you can implement your own initialization strategy.
|
|
74
74
|
__An action is an object__. That's important because __you have the full control on it__.
|
75
75
|
In other words, you have the freedom to instantiate, inject dependencies and test it, both at the unit and integration level.
|
76
76
|
|
77
|
-
In the example below, the default repository is `
|
77
|
+
In the example below, the default repository is `ArticleRepository`. During a unit test we can inject a stubbed version, and invoke `#call` with the params.
|
78
78
|
__We're avoiding HTTP calls__, we're also going to avoid hitting the database (it depends on the stubbed repository), __we're just dealing with message passing__.
|
79
79
|
Imagine how **fast** the unit test could be.
|
80
80
|
|
@@ -82,7 +82,7 @@ Imagine how **fast** the unit test could be.
|
|
82
82
|
class Show
|
83
83
|
include Hanami::Action
|
84
84
|
|
85
|
-
def initialize(repository =
|
85
|
+
def initialize(repository = ArticleRepository)
|
86
86
|
@repository = repository
|
87
87
|
end
|
88
88
|
|
@@ -149,20 +149,21 @@ Params represent an untrusted input.
|
|
149
149
|
For security reasons it's recommended to whitelist them.
|
150
150
|
|
151
151
|
```ruby
|
152
|
+
require 'hanami/validations'
|
152
153
|
require 'hanami/controller'
|
153
154
|
|
154
155
|
class Signup
|
155
156
|
include Hanami::Action
|
156
157
|
|
157
158
|
params do
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
159
|
+
required(:first_name).filled(:str?)
|
160
|
+
required(:last_name).filled(:str?)
|
161
|
+
required(:email).filled(:str?)
|
162
|
+
|
163
|
+
required(:address).schema do
|
164
|
+
required(:line_one).filled(:str?)
|
165
|
+
required(:state).filled(:str?)
|
166
|
+
required(:country).filled(:str?)
|
166
167
|
end
|
167
168
|
end
|
168
169
|
|
@@ -174,7 +175,7 @@ class Signup
|
|
174
175
|
# Whitelist :first_name, but not :admin
|
175
176
|
puts params[:first_name] # => "Luca"
|
176
177
|
puts params[:admin] # => nil
|
177
|
-
|
178
|
+
|
178
179
|
# Whitelist nested params [:address][:line_one], not [:address][:line_two]
|
179
180
|
puts params[:address][:line_one] # => '69 Tender St'
|
180
181
|
puts params[:address][:line_two] # => nil
|
@@ -191,6 +192,7 @@ when params are invalid.
|
|
191
192
|
If you specify the `:type` option, the param will be coerced.
|
192
193
|
|
193
194
|
```ruby
|
195
|
+
require 'hanami/validations'
|
194
196
|
require 'hanami/controller'
|
195
197
|
|
196
198
|
class Signup
|
@@ -198,13 +200,13 @@ class Signup
|
|
198
200
|
include Hanami::Action
|
199
201
|
|
200
202
|
params do
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
203
|
+
required(:first_name).filled(:str?)
|
204
|
+
required(:last_name).filled(:str?)
|
205
|
+
required(:email).confirmation.filled?(:str?, format?: /@/)
|
206
|
+
required(:password).confirmation.filled(:str?)
|
207
|
+
required(:terms_of_service).filled(:bool?)
|
208
|
+
required(:age).filled(:int?, included_in?: 18..99)
|
209
|
+
optional(:avatar).filled(size?: 1..(MEGABYTE * 3))
|
208
210
|
end
|
209
211
|
|
210
212
|
def call(params)
|
@@ -219,10 +221,10 @@ action.call(valid_params) # => [200, {}, ...]
|
|
219
221
|
action.errors.empty? # => true
|
220
222
|
|
221
223
|
action.call(invalid_params) # => [400, {}, ...]
|
222
|
-
action.errors
|
224
|
+
action.errors.empty? # => false
|
223
225
|
|
224
|
-
action.errors.
|
225
|
-
# => [
|
226
|
+
action.errors.fetch(:email)
|
227
|
+
# => ['is missing', 'is in invalid format']
|
226
228
|
```
|
227
229
|
|
228
230
|
### Response
|
@@ -280,7 +282,7 @@ class Show
|
|
280
282
|
expose :article
|
281
283
|
|
282
284
|
def call(params)
|
283
|
-
@article =
|
285
|
+
@article = ArticleRepository.find params[:id]
|
284
286
|
end
|
285
287
|
end
|
286
288
|
|
@@ -312,7 +314,7 @@ class Show
|
|
312
314
|
|
313
315
|
# `params` in the method signature is optional
|
314
316
|
def set_article(params)
|
315
|
-
@article =
|
317
|
+
@article = ArticleRepository.find params[:id]
|
316
318
|
end
|
317
319
|
end
|
318
320
|
```
|
@@ -324,7 +326,7 @@ class Show
|
|
324
326
|
include Hanami::Action
|
325
327
|
|
326
328
|
before { ... } # do some authentication stuff
|
327
|
-
before { |params| @article =
|
329
|
+
before { |params| @article = ArticleRepository.find params[:id] }
|
328
330
|
|
329
331
|
def call(params)
|
330
332
|
end
|
@@ -356,7 +358,7 @@ class Show
|
|
356
358
|
handle_exception RecordNotFound => 404
|
357
359
|
|
358
360
|
def call(params)
|
359
|
-
@article =
|
361
|
+
@article = ArticleRepository.find params[:id]
|
360
362
|
end
|
361
363
|
end
|
362
364
|
|
@@ -397,7 +399,7 @@ class Show
|
|
397
399
|
include Hanami::Action
|
398
400
|
|
399
401
|
def call(params)
|
400
|
-
@article =
|
402
|
+
@article = ArticleRepository.find params[:id]
|
401
403
|
end
|
402
404
|
end
|
403
405
|
|
@@ -423,7 +425,7 @@ module Articles
|
|
423
425
|
end
|
424
426
|
|
425
427
|
def call(params)
|
426
|
-
@article =
|
428
|
+
@article = ArticleRepository.find params[:id]
|
427
429
|
end
|
428
430
|
end
|
429
431
|
end
|
@@ -991,10 +993,10 @@ The following examples are valid constructors:
|
|
991
993
|
def initialize
|
992
994
|
end
|
993
995
|
|
994
|
-
def initialize(repository =
|
996
|
+
def initialize(repository = ArticleRepository)
|
995
997
|
end
|
996
998
|
|
997
|
-
def initialize(repository:
|
999
|
+
def initialize(repository: ArticleRepository)
|
998
1000
|
end
|
999
1001
|
|
1000
1002
|
def initialize(options = {})
|
data/hanami-controller.gemspec
CHANGED
@@ -17,14 +17,12 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.executables = []
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
|
-
spec.required_ruby_version = '>= 2.
|
20
|
+
spec.required_ruby_version = '>= 2.2.0'
|
21
21
|
|
22
|
-
spec.add_dependency 'rack',
|
23
|
-
spec.add_dependency 'hanami-utils',
|
24
|
-
spec.add_dependency 'hanami-validations', '~> 0.5'
|
22
|
+
spec.add_dependency 'rack', '~> 1.6', '>= 1.6.2'
|
23
|
+
spec.add_dependency 'hanami-utils', '~> 0.8'
|
25
24
|
|
26
25
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
27
|
-
spec.add_development_dependency 'minitest', '~> 5'
|
28
26
|
spec.add_development_dependency 'rack-test', '~> 0.6'
|
29
|
-
spec.add_development_dependency 'rake', '~>
|
27
|
+
spec.add_development_dependency 'rake', '~> 11'
|
30
28
|
end
|
data/lib/hanami/action.rb
CHANGED
@@ -5,7 +5,11 @@ require 'hanami/action/redirect'
|
|
5
5
|
require 'hanami/action/exposable'
|
6
6
|
require 'hanami/action/throwable'
|
7
7
|
require 'hanami/action/callbacks'
|
8
|
-
|
8
|
+
begin
|
9
|
+
require 'hanami/validations'
|
10
|
+
require 'hanami/action/validatable'
|
11
|
+
rescue LoadError
|
12
|
+
end
|
9
13
|
require 'hanami/action/head'
|
10
14
|
require 'hanami/action/callable'
|
11
15
|
|
@@ -53,7 +57,7 @@ module Hanami
|
|
53
57
|
include Exposable
|
54
58
|
include Throwable
|
55
59
|
include Callbacks
|
56
|
-
include Validatable
|
60
|
+
include Validatable if defined?(Validatable)
|
57
61
|
include Configurable
|
58
62
|
include Head
|
59
63
|
prepend Callable
|
@@ -65,7 +69,7 @@ module Hanami
|
|
65
69
|
# Finalize the response
|
66
70
|
#
|
67
71
|
# This method is abstract and COULD be implemented by included modules in
|
68
|
-
# order to prepare their data before the
|
72
|
+
# order to prepare their data before the response will be returned to the
|
69
73
|
# webserver.
|
70
74
|
#
|
71
75
|
# @since 0.1.0
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
require 'hanami/utils/hash'
|
3
|
+
|
4
|
+
module Hanami
|
5
|
+
module Action
|
6
|
+
class BaseParams
|
7
|
+
# The key that returns raw input from the Rack env
|
8
|
+
#
|
9
|
+
# @since 0.7.0
|
10
|
+
RACK_INPUT = 'rack.input'.freeze
|
11
|
+
|
12
|
+
# The key that returns router params from the Rack env
|
13
|
+
# This is a builtin integration for Hanami::Router
|
14
|
+
#
|
15
|
+
# @since 0.7.0
|
16
|
+
ROUTER_PARAMS = 'router.params'.freeze
|
17
|
+
|
18
|
+
# Separator for #get
|
19
|
+
#
|
20
|
+
# @since 0.7.0
|
21
|
+
# @api private
|
22
|
+
#
|
23
|
+
# @see Hanami::Action::Params#get
|
24
|
+
GET_SEPARATOR = '.'.freeze
|
25
|
+
|
26
|
+
# @attr_reader env [Hash] the Rack env
|
27
|
+
#
|
28
|
+
# @since 0.7.0
|
29
|
+
# @api private
|
30
|
+
attr_reader :env
|
31
|
+
|
32
|
+
# @attr_reader raw [Hash] the raw params from the request
|
33
|
+
#
|
34
|
+
# @since 0.7.0
|
35
|
+
# @api private
|
36
|
+
attr_reader :raw
|
37
|
+
|
38
|
+
# Initialize the params and freeze them.
|
39
|
+
#
|
40
|
+
# @param env [Hash] a Rack env or an hash of params.
|
41
|
+
#
|
42
|
+
# @return [Params]
|
43
|
+
#
|
44
|
+
# @since 0.7.0
|
45
|
+
def initialize(env)
|
46
|
+
@env = env
|
47
|
+
@raw = _extract_params
|
48
|
+
@params = Utils::Hash.new(@raw).symbolize!.to_h
|
49
|
+
freeze
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the object associated with the given key
|
53
|
+
#
|
54
|
+
# @param key [Symbol] the key
|
55
|
+
#
|
56
|
+
# @return [Object,nil] return the associated object, if found
|
57
|
+
#
|
58
|
+
# @since 0.7.0
|
59
|
+
def [](key)
|
60
|
+
@params[key]
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get an attribute value associated with the given key.
|
64
|
+
# Nested attributes are reached with a dot notation.
|
65
|
+
#
|
66
|
+
# @param key [String] the key
|
67
|
+
#
|
68
|
+
# @return [Object,NilClass] return the associated value, if found
|
69
|
+
#
|
70
|
+
# @raise [NoMethodError] if key is nil
|
71
|
+
#
|
72
|
+
# @since 0.7.0
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
# require 'hanami/controller'
|
76
|
+
#
|
77
|
+
# module Deliveries
|
78
|
+
# class Create
|
79
|
+
# include Hanami::Action
|
80
|
+
#
|
81
|
+
# def call(params)
|
82
|
+
# params.get('customer_name') # => "Luca"
|
83
|
+
# params.get('uknown') # => nil
|
84
|
+
#
|
85
|
+
# params.get('address.city') # => "Rome"
|
86
|
+
# params.get('address.unknown') # => nil
|
87
|
+
#
|
88
|
+
# params.get(nil) # => nil
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
def get(key)
|
93
|
+
key, *keys = key.to_s.split(GET_SEPARATOR)
|
94
|
+
return if key.nil?
|
95
|
+
|
96
|
+
result = self[key.to_sym]
|
97
|
+
|
98
|
+
Array(keys).each do |k|
|
99
|
+
break if result.nil?
|
100
|
+
result = result[k.to_sym]
|
101
|
+
end
|
102
|
+
|
103
|
+
result
|
104
|
+
end
|
105
|
+
|
106
|
+
# Provide a common interface with Params
|
107
|
+
#
|
108
|
+
# @return [TrueClass] always returns true
|
109
|
+
#
|
110
|
+
# @since 0.7.0
|
111
|
+
#
|
112
|
+
# @see Hanami::Action::Params#valid?
|
113
|
+
def valid?
|
114
|
+
true
|
115
|
+
end
|
116
|
+
|
117
|
+
# Serialize params to Hash
|
118
|
+
#
|
119
|
+
# @return [::Hash]
|
120
|
+
#
|
121
|
+
# @since 0.7.0
|
122
|
+
def to_h
|
123
|
+
@params
|
124
|
+
end
|
125
|
+
alias_method :to_hash, :to_h
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
# @since 0.7.0
|
130
|
+
# @api private
|
131
|
+
def _extract_params
|
132
|
+
result = {}
|
133
|
+
|
134
|
+
if env.key?(RACK_INPUT)
|
135
|
+
result.merge! ::Rack::Request.new(env).params
|
136
|
+
result.merge! _router_params
|
137
|
+
else
|
138
|
+
result.merge! _router_params(env)
|
139
|
+
end
|
140
|
+
|
141
|
+
result
|
142
|
+
end
|
143
|
+
|
144
|
+
# @since 0.7.0
|
145
|
+
# @api private
|
146
|
+
def _router_params(fallback = {})
|
147
|
+
env.fetch(ROUTER_PARAMS, fallback)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -69,7 +69,10 @@ module Hanami
|
|
69
69
|
# @see Hanami::Action::Cookies#finish
|
70
70
|
def finish
|
71
71
|
@cookies.delete(RACK_SESSION_KEY)
|
72
|
-
@cookies.each
|
72
|
+
@cookies.each do |k,v|
|
73
|
+
next unless changed?(k)
|
74
|
+
v.nil? ? delete_cookie(k) : set_cookie(k, _merge_default_values(v))
|
75
|
+
end if changed?
|
73
76
|
end
|
74
77
|
|
75
78
|
# Returns the object associated with the given key
|
@@ -103,11 +106,33 @@ module Hanami
|
|
103
106
|
#
|
104
107
|
# @see http://en.wikipedia.org/wiki/HTTP_cookie
|
105
108
|
def []=(key, value)
|
109
|
+
changes << key
|
106
110
|
@cookies[key] = value
|
107
111
|
end
|
108
112
|
|
109
113
|
private
|
110
114
|
|
115
|
+
# Keep track of changed keys
|
116
|
+
#
|
117
|
+
# @since 0.7.0
|
118
|
+
# @api private
|
119
|
+
def changes
|
120
|
+
@changes ||= Set.new
|
121
|
+
end
|
122
|
+
|
123
|
+
# Check if the entire set of cookies has changed within the current request.
|
124
|
+
# If <tt>key</tt> is given, it checks the associated cookie has changed.
|
125
|
+
#
|
126
|
+
# @since 0.7.0
|
127
|
+
# @api private
|
128
|
+
def changed?(key = nil)
|
129
|
+
if key.nil?
|
130
|
+
changes.any?
|
131
|
+
else
|
132
|
+
changes.include?(key)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
111
136
|
# Merge default cookies options with values provided by user
|
112
137
|
#
|
113
138
|
# Cookies values provided by user are respected
|
@@ -16,7 +16,10 @@ module Hanami
|
|
16
16
|
#
|
17
17
|
# @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
|
18
18
|
def self.included(base)
|
19
|
-
base.
|
19
|
+
base.class_eval do
|
20
|
+
extend ClassMethods
|
21
|
+
expose :params
|
22
|
+
end
|
20
23
|
end
|
21
24
|
|
22
25
|
# Exposures API class methods
|
data/lib/hanami/action/mime.rb
CHANGED
@@ -57,7 +57,10 @@ module Hanami
|
|
57
57
|
#
|
58
58
|
# @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
|
59
59
|
def self.included(base)
|
60
|
-
base.
|
60
|
+
base.class_eval do
|
61
|
+
extend ClassMethods
|
62
|
+
prepend InstanceMethods
|
63
|
+
end
|
61
64
|
end
|
62
65
|
|
63
66
|
module ClassMethods
|
@@ -111,12 +114,24 @@ module Hanami
|
|
111
114
|
end
|
112
115
|
end
|
113
116
|
|
117
|
+
# @since 0.7.0
|
118
|
+
# @api private
|
119
|
+
module InstanceMethods
|
120
|
+
# @since 0.7.0
|
121
|
+
# @api private
|
122
|
+
def initialize(*)
|
123
|
+
super
|
124
|
+
@content_type = nil
|
125
|
+
@charset = nil
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
114
129
|
# Returns a symbol representation of the content type.
|
115
130
|
#
|
116
131
|
# The framework automatically detects the request mime type, and returns
|
117
132
|
# the corresponding format.
|
118
133
|
#
|
119
|
-
# However, if this value was
|
134
|
+
# However, if this value was explicitly set by `#format=`, it will return
|
120
135
|
# that value
|
121
136
|
#
|
122
137
|
# @return [Symbol] a symbol that corresponds to the content type
|
@@ -275,7 +290,7 @@ module Hanami
|
|
275
290
|
# If a client asks for an `HTTP_ACCEPT` `*/*`, but we want to force the
|
276
291
|
# response to be a `text/html` we can use this method.
|
277
292
|
#
|
278
|
-
# When the format is set, the framework
|
293
|
+
# When the format is set, the framework searches for a corresponding mime
|
279
294
|
# type to be set as the `Content-Type` header of the response.
|
280
295
|
# This lookup is performed first in the configuration, and then in
|
281
296
|
# `Rack::Mime::MIME_TYPES`. If the lookup fails, it raises an error.
|
data/lib/hanami/action/params.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
require 'hanami/
|
2
|
-
require 'hanami/
|
3
|
-
require 'set'
|
1
|
+
require 'hanami/action/base_params'
|
2
|
+
require 'hanami/validations/form'
|
4
3
|
|
5
4
|
module Hanami
|
6
5
|
module Action
|
@@ -14,130 +13,22 @@ module Hanami
|
|
14
13
|
# * Default: it returns the given hash as it is. It's useful for testing purposes.
|
15
14
|
#
|
16
15
|
# @since 0.1.0
|
17
|
-
class Params
|
18
|
-
|
19
|
-
#
|
20
|
-
# @since 0.1.0
|
21
|
-
RACK_INPUT = 'rack.input'.freeze
|
22
|
-
|
23
|
-
# The key that returns router params from the Rack env
|
24
|
-
# This is a builtin integration for Hanami::Router
|
25
|
-
#
|
26
|
-
# @since 0.1.0
|
27
|
-
ROUTER_PARAMS = 'router.params'.freeze
|
28
|
-
|
29
|
-
# CSRF params key
|
30
|
-
#
|
31
|
-
# This key is shared with <tt>hanamirb</tt> and <tt>hanami-helpers</tt>
|
32
|
-
#
|
33
|
-
# @since 0.4.4
|
34
|
-
# @api private
|
35
|
-
CSRF_TOKEN = '_csrf_token'.freeze
|
36
|
-
|
37
|
-
# Set of params that are never filtered
|
38
|
-
#
|
39
|
-
# @since 0.4.4
|
40
|
-
# @api private
|
41
|
-
DEFAULT_PARAMS = Hash[CSRF_TOKEN => true].freeze
|
42
|
-
|
43
|
-
# Separator for #get
|
44
|
-
#
|
45
|
-
# @since 0.4.0
|
46
|
-
# @api private
|
47
|
-
#
|
48
|
-
# @see Hanami::Action::Params#get
|
49
|
-
GET_SEPARATOR = '.'.freeze
|
50
|
-
|
51
|
-
# Whitelist and validate a parameter
|
52
|
-
#
|
53
|
-
# @param name [#to_sym] The name of the param to whitelist
|
54
|
-
#
|
55
|
-
# @raise [ArgumentError] if one the validations is unknown, or if
|
56
|
-
# the size validator is used with an object that can't be coerced to
|
57
|
-
# integer.
|
58
|
-
#
|
59
|
-
# @return void
|
60
|
-
#
|
61
|
-
# @since 0.3.0
|
62
|
-
#
|
63
|
-
# @see http://rdoc.info/gems/hanami-validations/Hanami/Validations
|
64
|
-
#
|
65
|
-
# @example Whitelisting
|
66
|
-
# require 'hanami/controller'
|
67
|
-
#
|
68
|
-
# class SignupParams < Hanami::Action::Params
|
69
|
-
# param :email
|
70
|
-
# end
|
71
|
-
#
|
72
|
-
# params = SignupParams.new({id: 23, email: 'mjb@example.com'})
|
73
|
-
#
|
74
|
-
# params[:email] # => 'mjb@example.com'
|
75
|
-
# params[:id] # => nil
|
76
|
-
#
|
77
|
-
# @example Validation
|
78
|
-
# require 'hanami/controller'
|
79
|
-
#
|
80
|
-
# class SignupParams < Hanami::Action::Params
|
81
|
-
# param :email, presence: true
|
82
|
-
# end
|
83
|
-
#
|
84
|
-
# params = SignupParams.new({})
|
85
|
-
#
|
86
|
-
# params[:email] # => nil
|
87
|
-
# params.valid? # => false
|
88
|
-
#
|
89
|
-
# @example Unknown validation
|
90
|
-
# require 'hanami/controller'
|
91
|
-
#
|
92
|
-
# class SignupParams < Hanami::Action::Params
|
93
|
-
# param :email, unknown: true # => raise ArgumentError
|
94
|
-
# end
|
95
|
-
#
|
96
|
-
# @example Wrong size validation
|
97
|
-
# require 'hanami/controller'
|
98
|
-
#
|
99
|
-
# class SignupParams < Hanami::Action::Params
|
100
|
-
# param :email, size: 'twentythree'
|
101
|
-
# end
|
102
|
-
#
|
103
|
-
# params = SignupParams.new({})
|
104
|
-
# params.valid? # => raise ArgumentError
|
105
|
-
def self.param(name, options = {}, &block)
|
106
|
-
attribute name, options, &block
|
107
|
-
nil
|
108
|
-
end
|
109
|
-
|
110
|
-
include Hanami::Validations
|
111
|
-
|
112
|
-
def self.whitelisting?
|
113
|
-
defined_attributes.any?
|
114
|
-
end
|
16
|
+
class Params < BaseParams
|
17
|
+
include Hanami::Validations::Form
|
115
18
|
|
116
|
-
#
|
117
|
-
# inherits from Params rather than only Hanami::Validations.
|
19
|
+
# This is a Hanami::Validations extension point
|
118
20
|
#
|
119
|
-
# @since 0.
|
21
|
+
# @since 0.7.0
|
120
22
|
# @api private
|
121
|
-
def self.
|
122
|
-
|
123
|
-
|
124
|
-
true
|
125
|
-
end
|
23
|
+
def self._base_rules
|
24
|
+
lambda do
|
25
|
+
optional(:_csrf_token).filled(:str?)
|
126
26
|
end
|
127
|
-
kls.class_eval(&block)
|
128
|
-
kls
|
129
27
|
end
|
130
28
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
# @api private
|
135
|
-
attr_reader :env
|
136
|
-
|
137
|
-
# @attr_reader raw [Hanami::Utils::Attributes] all request's attributes
|
138
|
-
#
|
139
|
-
# @since 0.3.2
|
140
|
-
attr_reader :raw
|
29
|
+
def self.params(&blk)
|
30
|
+
validations(&blk || ->() {})
|
31
|
+
end
|
141
32
|
|
142
33
|
# Initialize the params and freeze them.
|
143
34
|
#
|
@@ -148,65 +39,59 @@ module Hanami
|
|
148
39
|
# @since 0.1.0
|
149
40
|
def initialize(env)
|
150
41
|
@env = env
|
151
|
-
super(
|
152
|
-
|
42
|
+
super(_extract_params)
|
43
|
+
@result = validate
|
44
|
+
@params = _params
|
45
|
+
freeze
|
153
46
|
end
|
154
47
|
|
155
|
-
# Returns
|
48
|
+
# Returns raw params from Rack env
|
156
49
|
#
|
157
|
-
# @
|
50
|
+
# @return [Hash]
|
158
51
|
#
|
159
|
-
# @
|
160
|
-
|
161
|
-
|
162
|
-
def [](key)
|
163
|
-
@attributes.get(key)
|
52
|
+
# @since 0.3.2
|
53
|
+
def raw
|
54
|
+
@input
|
164
55
|
end
|
165
56
|
|
166
|
-
#
|
167
|
-
# Nested attributes are reached with a dot notation.
|
57
|
+
# Returns structured error messages
|
168
58
|
#
|
169
|
-
# @
|
59
|
+
# @return [Hash]
|
170
60
|
#
|
171
|
-
# @
|
172
|
-
#
|
173
|
-
# @since 0.4.0
|
61
|
+
# @since 0.7.0
|
174
62
|
#
|
175
63
|
# @example
|
176
|
-
#
|
177
|
-
#
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
#
|
183
|
-
# param :customer_name
|
184
|
-
# param :address do
|
185
|
-
# param :city
|
186
|
-
# end
|
187
|
-
# end
|
64
|
+
# params.errors
|
65
|
+
# # => {:email=>["is missing", "is in invalid format"], :name=>["is missing"], :tos=>["is missing"], :age=>["is missing"], :address=>["is missing"]}
|
66
|
+
def errors
|
67
|
+
@result.messages
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns flat collection of full error messages
|
188
71
|
#
|
189
|
-
#
|
190
|
-
# params.get('customer_name') # => "Luca"
|
191
|
-
# params.get('uknown') # => nil
|
72
|
+
# @return [Array]
|
192
73
|
#
|
193
|
-
#
|
194
|
-
# params.get('address.unknown') # => nil
|
74
|
+
# @since 0.7.0
|
195
75
|
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
76
|
+
# @example
|
77
|
+
# params.error_messages
|
78
|
+
# # => ["Email is missing", "Email is in invalid format", "Name is missing", "Tos is missing", "Age is missing", "Address is missing"]
|
79
|
+
def error_messages(error_set = errors)
|
80
|
+
error_set.each_with_object([]) do |(key, messages), result|
|
81
|
+
k = Utils::String.new(key).titleize
|
82
|
+
|
83
|
+
_messages = if messages.is_a?(Hash)
|
84
|
+
error_messages(messages)
|
85
|
+
else
|
86
|
+
messages.map { |message| "#{k} #{message}" }
|
87
|
+
end
|
203
88
|
|
204
|
-
|
205
|
-
break if result.nil?
|
206
|
-
result = result[k]
|
89
|
+
result.concat(_messages)
|
207
90
|
end
|
91
|
+
end
|
208
92
|
|
209
|
-
|
93
|
+
def valid?
|
94
|
+
@result.success?
|
210
95
|
end
|
211
96
|
|
212
97
|
# Serialize params to Hash
|
@@ -215,70 +100,37 @@ module Hanami
|
|
215
100
|
#
|
216
101
|
# @since 0.3.0
|
217
102
|
def to_h
|
218
|
-
@
|
103
|
+
@params
|
219
104
|
end
|
220
105
|
alias_method :to_hash, :to_h
|
221
106
|
|
222
|
-
# Assign CSRF Token.
|
223
|
-
# This method is here for compatibility with <tt>Hanami::Validations</tt>.
|
224
|
-
#
|
225
|
-
# NOTE: When we will not support indifferent access anymore, we can probably
|
226
|
-
# remove this method.
|
227
|
-
#
|
228
|
-
# @since 0.4.4
|
229
|
-
# @api private
|
230
|
-
def _csrf_token=(value)
|
231
|
-
@attributes.set(CSRF_TOKEN, value)
|
232
|
-
end
|
233
|
-
|
234
107
|
private
|
235
|
-
# @since 0.3.1
|
236
|
-
# @api private
|
237
|
-
def _compute_params
|
238
|
-
if self.class.whitelisting?
|
239
|
-
_whitelisted_params
|
240
|
-
else
|
241
|
-
@attributes = _raw
|
242
|
-
end
|
243
|
-
end
|
244
108
|
|
245
|
-
# @since 0.
|
109
|
+
# @since 0.7.0
|
246
110
|
# @api private
|
247
|
-
def
|
248
|
-
|
111
|
+
def _extract_params
|
112
|
+
# FIXME: this is required for dry-v whitelisting
|
113
|
+
stringify!(super)
|
249
114
|
end
|
250
115
|
|
251
|
-
# @since 0.3.1
|
252
|
-
# @api private
|
253
116
|
def _params
|
254
|
-
|
255
|
-
if env.has_key?(RACK_INPUT)
|
256
|
-
result.merge! ::Rack::Request.new(env).params
|
257
|
-
result.merge! env.fetch(ROUTER_PARAMS, {})
|
258
|
-
else
|
259
|
-
result.merge! env.fetch(ROUTER_PARAMS, env)
|
260
|
-
end
|
261
|
-
end
|
117
|
+
@result.output.merge(_router_params)
|
262
118
|
end
|
263
119
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
120
|
+
def stringify!(result)
|
121
|
+
result.keys.each do |key|
|
122
|
+
value = result.delete(key)
|
123
|
+
result[key.to_s] = case value
|
124
|
+
when ::Hash
|
125
|
+
stringify!(value)
|
126
|
+
when ::Array
|
127
|
+
value.map(&:to_s)
|
128
|
+
else
|
129
|
+
value.to_s
|
130
|
+
end
|
273
131
|
end
|
274
|
-
end
|
275
132
|
|
276
|
-
|
277
|
-
#
|
278
|
-
# @since 0.4.4
|
279
|
-
# @api private
|
280
|
-
def assign_attribute?(key)
|
281
|
-
DEFAULT_PARAMS[key.to_s] || super
|
133
|
+
result
|
282
134
|
end
|
283
135
|
end
|
284
136
|
end
|
data/lib/hanami/action/rack.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'securerandom'
|
2
2
|
require 'hanami/action/request'
|
3
|
+
require 'hanami/action/base_params'
|
3
4
|
require 'hanami/action/rack/callable'
|
4
5
|
require 'hanami/action/rack/file'
|
5
6
|
|
@@ -51,7 +52,10 @@ module Hanami
|
|
51
52
|
#
|
52
53
|
# @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
|
53
54
|
def self.included(base)
|
54
|
-
base.
|
55
|
+
base.class_eval do
|
56
|
+
extend ClassMethods
|
57
|
+
prepend InstanceMethods
|
58
|
+
end
|
55
59
|
end
|
56
60
|
|
57
61
|
module ClassMethods
|
@@ -66,6 +70,7 @@ module Hanami
|
|
66
70
|
rack_builder
|
67
71
|
end
|
68
72
|
end
|
73
|
+
|
69
74
|
# Use a Rack middleware
|
70
75
|
#
|
71
76
|
# The middleware will be used as it is.
|
@@ -98,6 +103,32 @@ module Hanami
|
|
98
103
|
def use(middleware, *args, &block)
|
99
104
|
rack_builder.use middleware, *args, &block
|
100
105
|
end
|
106
|
+
|
107
|
+
# Returns the class which defines the params
|
108
|
+
#
|
109
|
+
# Returns the class which has been provided to define the
|
110
|
+
# params. By default this will be Hanami::Action::Params.
|
111
|
+
#
|
112
|
+
# @return [Class] A params class (when whitelisted) or
|
113
|
+
# Hanami::Action::Params
|
114
|
+
#
|
115
|
+
# @api private
|
116
|
+
# @since 0.7.0
|
117
|
+
def params_class
|
118
|
+
@params_class ||= BaseParams
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# @since 0.7.0
|
123
|
+
# @api private
|
124
|
+
module InstanceMethods
|
125
|
+
# @since 0.7.0
|
126
|
+
# @api private
|
127
|
+
def initialize(*)
|
128
|
+
super
|
129
|
+
@_status = nil
|
130
|
+
@_body = nil
|
131
|
+
end
|
101
132
|
end
|
102
133
|
|
103
134
|
protected
|
@@ -61,6 +61,18 @@ module Hanami
|
|
61
61
|
@_env[SESSION_KEY] ||= {}
|
62
62
|
end
|
63
63
|
|
64
|
+
# Read errors from flash or delegate to the superclass
|
65
|
+
#
|
66
|
+
# @return [Hanami::Validations::Errors] A collection of validation errors
|
67
|
+
#
|
68
|
+
# @since 0.3.0
|
69
|
+
#
|
70
|
+
# @see Hanami::Action::Validatable
|
71
|
+
# @see Hanami::Action::Session#flash
|
72
|
+
def errors
|
73
|
+
flash[ERRORS_KEY] || params.respond_to?(:errors) && params.errors
|
74
|
+
end
|
75
|
+
|
64
76
|
private
|
65
77
|
|
66
78
|
# Container useful to transport data with the HTTP session
|
@@ -120,21 +132,11 @@ module Hanami
|
|
120
132
|
# # The validation errors caused by Comments::Create are available
|
121
133
|
# # **after the redirect** in the context of Comments::Index.
|
122
134
|
def redirect_to(*args)
|
123
|
-
|
124
|
-
|
125
|
-
|
135
|
+
if params.respond_to?(:valid?)
|
136
|
+
flash[ERRORS_KEY] = errors.to_a unless params.valid?
|
137
|
+
end
|
126
138
|
|
127
|
-
|
128
|
-
#
|
129
|
-
# @return [Hanami::Validations::Errors] A collection of validation errors
|
130
|
-
#
|
131
|
-
# @since 0.3.0
|
132
|
-
# @api private
|
133
|
-
#
|
134
|
-
# @see Hanami::Action::Validatable
|
135
|
-
# @see Hanami::Action::Session#flash
|
136
|
-
def errors
|
137
|
-
flash[ERRORS_KEY] || super
|
139
|
+
super
|
138
140
|
end
|
139
141
|
|
140
142
|
# Finalize the response
|
@@ -1,17 +1,16 @@
|
|
1
|
+
require 'hanami/action/params'
|
2
|
+
|
1
3
|
module Hanami
|
2
4
|
module Action
|
3
5
|
module Validatable
|
4
|
-
# Defines the class name for
|
6
|
+
# Defines the class name for anonymous params
|
5
7
|
#
|
6
8
|
# @api private
|
7
9
|
# @since 0.3.0
|
8
10
|
PARAMS_CLASS_NAME = 'Params'.freeze
|
9
11
|
|
10
12
|
def self.included(base)
|
11
|
-
base.
|
12
|
-
extend ClassMethods
|
13
|
-
expose :params, :errors
|
14
|
-
end
|
13
|
+
base.extend ClassMethods
|
15
14
|
end
|
16
15
|
|
17
16
|
# Validatable API class methods
|
@@ -93,36 +92,14 @@ module Hanami
|
|
93
92
|
# end
|
94
93
|
# end
|
95
94
|
def params(klass = nil, &blk)
|
96
|
-
if
|
97
|
-
|
98
|
-
|
99
|
-
else
|
100
|
-
@params_class = klass
|
95
|
+
if klass.nil?
|
96
|
+
klass = const_set(PARAMS_CLASS_NAME, Class.new(Params))
|
97
|
+
klass.class_eval { params(&blk) }
|
101
98
|
end
|
102
|
-
end
|
103
99
|
|
104
|
-
|
105
|
-
#
|
106
|
-
# Returns the class which has been provided to define the
|
107
|
-
# params. By default this will be Hanami::Action::Params.
|
108
|
-
#
|
109
|
-
# @return [Class] A params class (when whitelisted) or
|
110
|
-
# Hanami::Action::Params
|
111
|
-
#
|
112
|
-
# @api private
|
113
|
-
# @since 0.3.0
|
114
|
-
def params_class
|
115
|
-
@params_class ||= params { }
|
100
|
+
@params_class = klass
|
116
101
|
end
|
117
|
-
|
118
102
|
end
|
119
103
|
end
|
120
|
-
|
121
|
-
# Expose validation errors
|
122
|
-
#
|
123
|
-
# @since 0.3.0
|
124
|
-
def errors
|
125
|
-
params.errors
|
126
|
-
end
|
127
104
|
end
|
128
105
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hanami-controller
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luca Guidi
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2016-
|
13
|
+
date: 2016-07-22 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
@@ -38,28 +38,14 @@ dependencies:
|
|
38
38
|
requirements:
|
39
39
|
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: '0.
|
41
|
+
version: '0.8'
|
42
42
|
type: :runtime
|
43
43
|
prerelease: false
|
44
44
|
version_requirements: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: '0.
|
49
|
-
- !ruby/object:Gem::Dependency
|
50
|
-
name: hanami-validations
|
51
|
-
requirement: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - "~>"
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: '0.5'
|
56
|
-
type: :runtime
|
57
|
-
prerelease: false
|
58
|
-
version_requirements: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - "~>"
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '0.5'
|
48
|
+
version: '0.8'
|
63
49
|
- !ruby/object:Gem::Dependency
|
64
50
|
name: bundler
|
65
51
|
requirement: !ruby/object:Gem::Requirement
|
@@ -74,20 +60,6 @@ dependencies:
|
|
74
60
|
- - "~>"
|
75
61
|
- !ruby/object:Gem::Version
|
76
62
|
version: '1.6'
|
77
|
-
- !ruby/object:Gem::Dependency
|
78
|
-
name: minitest
|
79
|
-
requirement: !ruby/object:Gem::Requirement
|
80
|
-
requirements:
|
81
|
-
- - "~>"
|
82
|
-
- !ruby/object:Gem::Version
|
83
|
-
version: '5'
|
84
|
-
type: :development
|
85
|
-
prerelease: false
|
86
|
-
version_requirements: !ruby/object:Gem::Requirement
|
87
|
-
requirements:
|
88
|
-
- - "~>"
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
version: '5'
|
91
63
|
- !ruby/object:Gem::Dependency
|
92
64
|
name: rack-test
|
93
65
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,14 +80,14 @@ dependencies:
|
|
108
80
|
requirements:
|
109
81
|
- - "~>"
|
110
82
|
- !ruby/object:Gem::Version
|
111
|
-
version: '
|
83
|
+
version: '11'
|
112
84
|
type: :development
|
113
85
|
prerelease: false
|
114
86
|
version_requirements: !ruby/object:Gem::Requirement
|
115
87
|
requirements:
|
116
88
|
- - "~>"
|
117
89
|
- !ruby/object:Gem::Version
|
118
|
-
version: '
|
90
|
+
version: '11'
|
119
91
|
description: Complete, fast and testable actions for Rack
|
120
92
|
email:
|
121
93
|
- me@lucaguidi.com
|
@@ -131,6 +103,7 @@ files:
|
|
131
103
|
- hanami-controller.gemspec
|
132
104
|
- lib/hanami-controller.rb
|
133
105
|
- lib/hanami/action.rb
|
106
|
+
- lib/hanami/action/base_params.rb
|
134
107
|
- lib/hanami/action/cache.rb
|
135
108
|
- lib/hanami/action/cache/cache_control.rb
|
136
109
|
- lib/hanami/action/cache/conditional_get.rb
|
@@ -172,7 +145,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
172
145
|
requirements:
|
173
146
|
- - ">="
|
174
147
|
- !ruby/object:Gem::Version
|
175
|
-
version: 2.
|
148
|
+
version: 2.2.0
|
176
149
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
177
150
|
requirements:
|
178
151
|
- - ">="
|
@@ -180,9 +153,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
153
|
version: '0'
|
181
154
|
requirements: []
|
182
155
|
rubyforge_project:
|
183
|
-
rubygems_version: 2.
|
156
|
+
rubygems_version: 2.6.4
|
184
157
|
signing_key:
|
185
158
|
specification_version: 4
|
186
159
|
summary: Complete, fast and testable actions for Rack and Hanami
|
187
160
|
test_files: []
|
188
|
-
has_rdoc:
|