hanami-controller 0.6.1 → 0.7.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 +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:
|