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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d9cfb818515da7bb2f825a1016ebfd221cfb3c75
4
- data.tar.gz: e459b2afa444cd3a7e5ddb6f77ee7594cedf59c0
3
+ metadata.gz: 4e9a92b39aa9c373400fe07e64b270d3066af888
4
+ data.tar.gz: f146d4da77a9c4ced2bfdfea31826db63f0ef992
5
5
  SHA512:
6
- metadata.gz: bd1689d659b55ee1a01114eba2c37e27768ad926c0c3e1adf9b90c5030a92e5b35383e297c494e7e4ce037183a0b356c6410d76b3b6a948a3db2ece4f7cade22
7
- data.tar.gz: a2fa0502c2e59c9eb6d22f5850ed0454fe4ea59531460575f7fcdc383fc90f9ca399e487013940b3b7c6fb743035cd190765fd67d12fd1ffc2c18f678c308cea
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 = Article.find params[:id]
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 `Article`. During a unit test we can inject a stubbed version, and invoke `#call` with the params.
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 = Article)
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
- param :first_name
159
- param :last_name
160
- param :email
161
-
162
- param :address do
163
- param :line_one
164
- param :state
165
- param :country
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
- param :first_name, presence: true
202
- param :last_name, presence: true
203
- param :email, presence: true, format: /@/, confirmation: true
204
- param :password, presence: true, confirmation: true
205
- param :terms_of_service, acceptance: true
206
- param :avatar, size: 0..(MEGABYTE * 3)
207
- param :age, type: Integer, size: 18..99
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 # => #<Hanami::Validations::Errors:0x007fabe4b433d0 @errors={...}>
224
+ action.errors.empty? # => false
223
225
 
224
- action.errors.for(:email)
225
- # => [#<Hanami::Validations::Error:0x007fabe4b432e0 @attribute=:email, @validation=:presence, @expected=true, @actual=nil>]
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 = Article.find params[:id]
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 = Article.find params[:id]
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 = Article.find params[:id] }
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 = Article.find params[:id]
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 = Article.find params[:id]
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 = Article.find params[:id]
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 = Article)
996
+ def initialize(repository = ArticleRepository)
995
997
  end
996
998
 
997
- def initialize(repository: Article)
999
+ def initialize(repository: ArticleRepository)
998
1000
  end
999
1001
 
1000
1002
  def initialize(options = {})
@@ -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.0.0'
20
+ spec.required_ruby_version = '>= 2.2.0'
21
21
 
22
- spec.add_dependency 'rack', '~> 1.6', '>= 1.6.2'
23
- spec.add_dependency 'hanami-utils', '~> 0.7'
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', '~> 10'
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
- require 'hanami/action/validatable'
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 reponse will be returned to 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
@@ -17,7 +17,10 @@ module Hanami
17
17
  HEADER = 'Cache-Control'.freeze
18
18
 
19
19
  def self.included(base)
20
- base.extend ClassMethods
20
+ base.class_eval do
21
+ extend ClassMethods
22
+ @cache_control_directives = nil
23
+ end
21
24
  end
22
25
 
23
26
  module ClassMethods
@@ -17,7 +17,10 @@ module Hanami
17
17
  HEADER = 'Expires'.freeze
18
18
 
19
19
  def self.included(base)
20
- base.extend ClassMethods
20
+ base.class_eval do
21
+ extend ClassMethods
22
+ @expires_directives = nil
23
+ end
21
24
  end
22
25
 
23
26
  module ClassMethods
@@ -1,5 +1,3 @@
1
- require 'hanami/action/params'
2
-
3
1
  module Hanami
4
2
  module Action
5
3
  module Callable
@@ -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 { |k,v| v.nil? ? delete_cookie(k) : set_cookie(k, _merge_default_values(v)) }
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.extend ClassMethods
19
+ base.class_eval do
20
+ extend ClassMethods
21
+ expose :params
22
+ end
20
23
  end
21
24
 
22
25
  # Exposures API class methods
@@ -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.extend ClassMethods
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 explicitely set by `#format=`, it will return
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 searchs for a corresponding mime
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.
@@ -1,6 +1,5 @@
1
- require 'hanami/validations'
2
- require 'hanami/utils/attributes'
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
- # The key that returns raw input from the Rack env
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
- # Overrides the method in Hanami::Validation to build a class that
117
- # inherits from Params rather than only Hanami::Validations.
19
+ # This is a Hanami::Validations extension point
118
20
  #
119
- # @since 0.3.2
21
+ # @since 0.7.0
120
22
  # @api private
121
- def self.build_validation_class(&block)
122
- kls = Class.new(Params) do
123
- def hanami_nested_attributes?
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
- # @attr_reader env [Hash] the Rack env
132
- #
133
- # @since 0.2.0
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(_compute_params)
152
- # freeze
42
+ super(_extract_params)
43
+ @result = validate
44
+ @params = _params
45
+ freeze
153
46
  end
154
47
 
155
- # Returns the object associated with the given key
48
+ # Returns raw params from Rack env
156
49
  #
157
- # @param key [Symbol] the key
50
+ # @return [Hash]
158
51
  #
159
- # @return [Object,nil] return the associated object, if found
160
- #
161
- # @since 0.2.0
162
- def [](key)
163
- @attributes.get(key)
52
+ # @since 0.3.2
53
+ def raw
54
+ @input
164
55
  end
165
56
 
166
- # Get an attribute value associated with the given key.
167
- # Nested attributes are reached with a dot notation.
57
+ # Returns structured error messages
168
58
  #
169
- # @param key [String] the key
59
+ # @return [Hash]
170
60
  #
171
- # @return [Object,NilClass] return the associated value, if found
172
- #
173
- # @since 0.4.0
61
+ # @since 0.7.0
174
62
  #
175
63
  # @example
176
- # require 'hanami/controller'
177
- #
178
- # module Deliveries
179
- # class Create
180
- # include Hanami::Action
181
- #
182
- # params do
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
- # def call(params)
190
- # params.get('customer_name') # => "Luca"
191
- # params.get('uknown') # => nil
72
+ # @return [Array]
192
73
  #
193
- # params.get('address.city') # => "Rome"
194
- # params.get('address.unknown') # => nil
74
+ # @since 0.7.0
195
75
  #
196
- # params.get(nil) # => nil
197
- # end
198
- # end
199
- # end
200
- def get(key)
201
- key, *keys = key.to_s.split(GET_SEPARATOR)
202
- result = self[key]
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
- Array(keys).each do |k|
205
- break if result.nil?
206
- result = result[k]
89
+ result.concat(_messages)
207
90
  end
91
+ end
208
92
 
209
- result
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
- @attributes.to_h
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.3.2
109
+ # @since 0.7.0
246
110
  # @api private
247
- def _raw
248
- @raw ||= Utils::Attributes.new(_params)
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
- {}.tap do |result|
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
- # @since 0.3.1
265
- # @api private
266
- def _whitelisted_params
267
- {}.tap do |result|
268
- _raw.to_h.each do |k, v|
269
- next unless assign_attribute?(k)
270
-
271
- result[k] = v
272
- end
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
- # Override <tt>Hanami::Validations</tt> method
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
@@ -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.extend ClassMethods
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
- flash[ERRORS_KEY] = errors.to_a unless params.valid?
124
- super
125
- end
135
+ if params.respond_to?(:valid?)
136
+ flash[ERRORS_KEY] = errors.to_a unless params.valid?
137
+ end
126
138
 
127
- # Read errors from flash or delegate to the superclass
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 anoymous params
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.class_eval do
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 block_given?
97
- @params_class = const_set(PARAMS_CLASS_NAME,
98
- Class.new(Params, &blk))
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
- # Returns the class which defines the params
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
@@ -3,6 +3,6 @@ module Hanami
3
3
  # Defines the version
4
4
  #
5
5
  # @since 0.1.0
6
- VERSION = '0.6.1'.freeze
6
+ VERSION = '0.7.0'.freeze
7
7
  end
8
8
  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.6.1
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-02-05 00:00:00.000000000 Z
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.7'
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.7'
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: '10'
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: '10'
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.0.0
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.5.1
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: