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 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: