hanami-controller 0.0.0 → 0.6.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +155 -0
  3. data/LICENSE.md +22 -0
  4. data/README.md +1180 -9
  5. data/hanami-controller.gemspec +19 -12
  6. data/lib/hanami-controller.rb +1 -0
  7. data/lib/hanami/action.rb +85 -0
  8. data/lib/hanami/action/cache.rb +174 -0
  9. data/lib/hanami/action/cache/cache_control.rb +70 -0
  10. data/lib/hanami/action/cache/conditional_get.rb +93 -0
  11. data/lib/hanami/action/cache/directives.rb +99 -0
  12. data/lib/hanami/action/cache/expires.rb +73 -0
  13. data/lib/hanami/action/callable.rb +94 -0
  14. data/lib/hanami/action/callbacks.rb +210 -0
  15. data/lib/hanami/action/configurable.rb +49 -0
  16. data/lib/hanami/action/cookie_jar.rb +181 -0
  17. data/lib/hanami/action/cookies.rb +85 -0
  18. data/lib/hanami/action/exposable.rb +115 -0
  19. data/lib/hanami/action/flash.rb +182 -0
  20. data/lib/hanami/action/glue.rb +66 -0
  21. data/lib/hanami/action/head.rb +122 -0
  22. data/lib/hanami/action/mime.rb +493 -0
  23. data/lib/hanami/action/params.rb +285 -0
  24. data/lib/hanami/action/rack.rb +270 -0
  25. data/lib/hanami/action/rack/callable.rb +47 -0
  26. data/lib/hanami/action/rack/file.rb +33 -0
  27. data/lib/hanami/action/redirect.rb +59 -0
  28. data/lib/hanami/action/request.rb +86 -0
  29. data/lib/hanami/action/session.rb +154 -0
  30. data/lib/hanami/action/throwable.rb +194 -0
  31. data/lib/hanami/action/validatable.rb +128 -0
  32. data/lib/hanami/controller.rb +250 -2
  33. data/lib/hanami/controller/configuration.rb +705 -0
  34. data/lib/hanami/controller/error.rb +7 -0
  35. data/lib/hanami/controller/version.rb +4 -1
  36. data/lib/hanami/http/status.rb +62 -0
  37. metadata +124 -16
  38. data/.gitignore +0 -9
  39. data/Gemfile +0 -4
  40. data/Rakefile +0 -2
  41. data/bin/console +0 -14
  42. data/bin/setup +0 -8
@@ -4,20 +4,27 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'hanami/controller/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "hanami-controller"
7
+ spec.name = 'hanami-controller'
8
8
  spec.version = Hanami::Controller::VERSION
9
- spec.authors = ["Luca Guidi"]
10
- spec.email = ["me@lucaguidi.com"]
9
+ spec.authors = ['Luca Guidi', 'Trung Lê', 'Alfonso Uceda']
10
+ spec.email = ['me@lucaguidi.com', 'trung.le@ruby-journal.com', 'uceda73@gmail.com']
11
+ spec.description = %q{Complete, fast and testable actions for Rack}
12
+ spec.summary = %q{Complete, fast and testable actions for Rack and Hanami}
13
+ spec.homepage = 'http://hanamirb.org'
14
+ spec.license = 'MIT'
11
15
 
12
- spec.summary = %q{The web, with simplicity}
13
- spec.description = %q{Hanami is a web framework for Ruby}
14
- spec.homepage = "http://hanamirb.org"
16
+ spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md hanami-controller.gemspec`.split($/)
17
+ spec.executables = []
18
+ spec.test_files = spec.files.grep(%r{^(test)/})
19
+ spec.require_paths = ['lib']
20
+ spec.required_ruby_version = '>= 2.0.0'
15
21
 
16
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
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'
20
25
 
21
- spec.add_development_dependency "bundler", "~> 1.11"
22
- spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency 'bundler', '~> 1.6'
27
+ spec.add_development_dependency 'minitest', '~> 5'
28
+ spec.add_development_dependency 'rack-test', '~> 0.6'
29
+ spec.add_development_dependency 'rake', '~> 10'
23
30
  end
@@ -0,0 +1 @@
1
+ require 'hanami/controller'
@@ -0,0 +1,85 @@
1
+ require 'hanami/action/configurable'
2
+ require 'hanami/action/rack'
3
+ require 'hanami/action/mime'
4
+ require 'hanami/action/redirect'
5
+ require 'hanami/action/exposable'
6
+ require 'hanami/action/throwable'
7
+ require 'hanami/action/callbacks'
8
+ require 'hanami/action/validatable'
9
+ require 'hanami/action/head'
10
+ require 'hanami/action/callable'
11
+
12
+ module Hanami
13
+ # An HTTP endpoint
14
+ #
15
+ # @since 0.1.0
16
+ #
17
+ # @example
18
+ # require 'hanami/controller'
19
+ #
20
+ # class Show
21
+ # include Hanami::Action
22
+ #
23
+ # def call(params)
24
+ # # ...
25
+ # end
26
+ # end
27
+ module Action
28
+ # Override Ruby's hook for modules.
29
+ # It includes basic Hanami::Action modules to the given class.
30
+ #
31
+ # @param base [Class] the target action
32
+ #
33
+ # @since 0.1.0
34
+ # @api private
35
+ #
36
+ # @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
37
+ #
38
+ # @see Hanami::Action::Rack
39
+ # @see Hanami::Action::Mime
40
+ # @see Hanami::Action::Http
41
+ # @see Hanami::Action::Redirect
42
+ # @see Hanami::Action::Exposable
43
+ # @see Hanami::Action::Throwable
44
+ # @see Hanami::Action::Callbacks
45
+ # @see Hanami::Action::Validatable
46
+ # @see Hanami::Action::Configurable
47
+ # @see Hanami::Action::Callable
48
+ def self.included(base)
49
+ base.class_eval do
50
+ include Rack
51
+ include Mime
52
+ include Redirect
53
+ include Exposable
54
+ include Throwable
55
+ include Callbacks
56
+ include Validatable
57
+ include Configurable
58
+ include Head
59
+ prepend Callable
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ # Finalize the response
66
+ #
67
+ # 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
69
+ # webserver.
70
+ #
71
+ # @since 0.1.0
72
+ # @api private
73
+ # @abstract
74
+ #
75
+ # @see Hanami::Action::Mime#finish
76
+ # @see Hanami::Action::Exposable#finish
77
+ # @see Hanami::Action::Callable#finish
78
+ # @see Hanami::Action::Session#finish
79
+ # @see Hanami::Action::Cookies#finish
80
+ # @see Hanami::Action::Cache#finish
81
+ # @see Hanami::Action::Head#finish
82
+ def finish
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,174 @@
1
+ require 'hanami/action/cache/cache_control'
2
+ require 'hanami/action/cache/expires'
3
+ require 'hanami/action/cache/conditional_get'
4
+
5
+ module Hanami
6
+ module Action
7
+ # Cache type API
8
+ #
9
+ # @since 0.3.0
10
+ #
11
+ # @see Hanami::Action::Cache::ClassMethods#cache_control
12
+ # @see Hanami::Action::Cache::ClassMethods#expires
13
+ # @see Hanami::Action::Cache::ClassMethods#fresh
14
+ module Cache
15
+ # Override Ruby's hook for modules.
16
+ # It includes exposures logic
17
+ #
18
+ # @param base [Class] the target action
19
+ #
20
+ # @since 0.3.0
21
+ # @api private
22
+ #
23
+ # @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
24
+ def self.included(base)
25
+ base.class_eval do
26
+ include CacheControl, Expires
27
+ end
28
+ end
29
+
30
+ protected
31
+
32
+ # Specify response freshness policy for HTTP caches (Cache-Control header).
33
+ # Any number of non-value directives (:public, :private, :no_cache,
34
+ # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
35
+ # a Hash of value directives (:max_age, :min_stale, :s_max_age).
36
+ #
37
+ # See RFC 2616 / 14.9 for more on standard cache control directives:
38
+ # http://tools.ietf.org/html/rfc2616#section-14.9.1
39
+ #
40
+ # @param values [Array<Symbols, Hash>] mapped to cache_control directives
41
+ # @option values [Symbol] :public
42
+ # @option values [Symbol] :private
43
+ # @option values [Symbol] :no_cache
44
+ # @option values [Symbol] :no_store
45
+ # @option values [Symbol] :must_validate
46
+ # @option values [Symbol] :proxy_revalidate
47
+ # @option values [Hash] :max_age
48
+ # @option values [Hash] :min_stale
49
+ # @option values [Hash] :s_max_age
50
+ #
51
+ # @return void
52
+ #
53
+ # @since 0.3.0
54
+ # @api public
55
+ #
56
+ # @example
57
+ # require 'hanami/controller'
58
+ # require 'hanami/action/cache'
59
+ #
60
+ # class Show
61
+ # include Hanami::Action
62
+ # include Hanami::Action::Cache
63
+ #
64
+ # def call(params)
65
+ # # ...
66
+ #
67
+ # # set Cache-Control directives
68
+ # cache_control :public, max_age: 900, s_maxage: 86400
69
+ #
70
+ # # overwrite previous Cache-Control directives
71
+ # cache_control :private, :no_cache, :no_store
72
+ #
73
+ # => Cache-Control: private, no-store, max-age=900
74
+ #
75
+ # end
76
+ # end
77
+ #
78
+ def cache_control(*values)
79
+ cache_control = CacheControl::Directives.new(*values)
80
+ headers.merge!(cache_control.headers)
81
+ end
82
+
83
+ # Set the Expires header and Cache-Control/max-age directive. Amount
84
+ # can be an integer number of seconds in the future or a Time object
85
+ # indicating when the response should be considered "stale". The remaining
86
+ # "values" arguments are passed to the #cache_control helper:
87
+ #
88
+ # @param amount [Integer,Time] number of seconds or point in time
89
+ # @param values [Array<Symbols>] mapped to cache_control directives
90
+ #
91
+ # @return void
92
+ #
93
+ # @since 0.3.0
94
+ # @api public
95
+ #
96
+ # @example
97
+ # require 'hanami/controller'
98
+ # require 'hanami/action/cache'
99
+ #
100
+ # class Show
101
+ # include Hanami::Action
102
+ # include Hanami::Action::Cache
103
+ #
104
+ # def call(params)
105
+ # # ...
106
+ #
107
+ # # set Cache-Control directives and Expires
108
+ # expires 900, :public
109
+ #
110
+ # # overwrite Cache-Control directives and Expires
111
+ # expires 300, :private, :no_cache, :no_store
112
+ #
113
+ # => Expires: Thu, 26 Jun 2014 12:00:00 GMT
114
+ # => Cache-Control: private, no-cache, no-store max-age=300
115
+ #
116
+ # end
117
+ # end
118
+ #
119
+ def expires(amount, *values)
120
+ expires = Expires::Directives.new(amount, *values)
121
+ headers.merge!(expires.headers)
122
+ end
123
+
124
+ # Set the etag, last_modified, or both headers on the response
125
+ # and halts a 304 Not Modified if the request is still fresh
126
+ # respecting IfNoneMatch and IfModifiedSince request headers
127
+ #
128
+ # @param options [Hash]
129
+ # @option options [Integer] :etag for testing IfNoneMatch conditions
130
+ # @option options [Date] :last_modified for testing IfModifiedSince conditions
131
+ #
132
+ # @return void
133
+ #
134
+ # @since 0.3.0
135
+ # @api public
136
+ #
137
+ # @example
138
+ # require 'hanami/controller'
139
+ # require 'hanami/action/cache'
140
+ #
141
+ # class Show
142
+ # include Hanami::Action
143
+ # include Hanami::Action::Cache
144
+ #
145
+ # def call(params)
146
+ # # ...
147
+ #
148
+ # # set etag response header and halt 304
149
+ # # if request matches IF_NONE_MATCH header
150
+ # fresh etag: @resource.updated_at.to_i
151
+ #
152
+ # # set last_modified response header and halt 304
153
+ # # if request matches IF_MODIFIED_SINCE
154
+ # fresh last_modified: @resource.updated_at
155
+ #
156
+ # # set etag and last_modified response header,
157
+ # # halt 304 if request matches IF_MODIFIED_SINCE
158
+ # # and IF_NONE_MATCH
159
+ # fresh last_modified: @resource.updated_at
160
+ #
161
+ # end
162
+ # end
163
+ def fresh(options)
164
+ conditional_get = ConditionalGet.new(@_env, options)
165
+
166
+ headers.merge!(conditional_get.headers)
167
+
168
+ conditional_get.fresh? do
169
+ halt 304
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,70 @@
1
+ require 'hanami/action/cache/directives'
2
+
3
+ module Hanami
4
+ module Action
5
+ module Cache
6
+
7
+ # Module with Cache-Control logic
8
+ #
9
+ # @since 0.3.0
10
+ # @api private
11
+ module CacheControl
12
+
13
+ # The HTTP header for Cache-Control
14
+ #
15
+ # @since 0.3.0
16
+ # @api private
17
+ HEADER = 'Cache-Control'.freeze
18
+
19
+ def self.included(base)
20
+ base.extend ClassMethods
21
+ end
22
+
23
+ module ClassMethods
24
+ def cache_control(*values)
25
+ @cache_control_directives ||= Directives.new(*values)
26
+ end
27
+
28
+ def cache_control_directives
29
+ @cache_control_directives || Object.new.tap do |null_object|
30
+ def null_object.headers
31
+ Hash.new
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ # Finalize the response including default cache headers into the response
38
+ #
39
+ # @since 0.3.0
40
+ # @api private
41
+ #
42
+ # @see Hanami::Action#finish
43
+ def finish
44
+ super
45
+ headers.merge!(self.class.cache_control_directives.headers) unless headers.include? HEADER
46
+ end
47
+
48
+ # Class which stores CacheControl values
49
+ #
50
+ # @since 0.3.0
51
+ #
52
+ # @api private
53
+ #
54
+ class Directives
55
+ def initialize(*values)
56
+ @directives = Hanami::Action::Cache::Directives.new(*values)
57
+ end
58
+
59
+ def headers
60
+ if @directives.any?
61
+ { HEADER => @directives.join(', ') }
62
+ else
63
+ {}
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,93 @@
1
+ module Hanami
2
+ module Action
3
+ module Cache
4
+ # @since 0.3.0
5
+ # @api private
6
+ IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze
7
+
8
+ # The HTTP header for ETag
9
+ #
10
+ # @since 0.3.0
11
+ # @api private
12
+ ETAG = 'ETag'.freeze
13
+
14
+ # @since 0.3.0
15
+ # @api private
16
+ IF_MODIFIED_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze
17
+
18
+ # The HTTP header for Last-Modified
19
+ #
20
+ # @since 0.3.0
21
+ # @api private
22
+ LAST_MODIFIED = 'Last-Modified'.freeze
23
+
24
+ # ETag value object
25
+ #
26
+ # @since 0.3.0
27
+ # @api private
28
+ #
29
+ class ETag
30
+ def initialize(env, value)
31
+ @env, @value = env, value
32
+ end
33
+
34
+ def fresh?
35
+ none_match && @value == none_match
36
+ end
37
+
38
+ def header
39
+ { ETAG => @value } if none_match
40
+ end
41
+
42
+ private
43
+
44
+ def none_match
45
+ @env[IF_NONE_MATCH]
46
+ end
47
+ end
48
+
49
+ # LastModified value object
50
+ #
51
+ # @since 0.3.0
52
+ # @api private
53
+ class LastModified
54
+ def initialize(env, value)
55
+ @env, @value = env, value
56
+ end
57
+
58
+ def fresh?
59
+ modified_since && Time.httpdate(modified_since).to_i >= @value.to_i
60
+ end
61
+
62
+ def header
63
+ { LAST_MODIFIED => @value.httpdate } if modified_since
64
+ end
65
+
66
+ private
67
+
68
+ def modified_since
69
+ @env[IF_MODIFIED_SINCE]
70
+ end
71
+ end
72
+
73
+ # Class responsible to determine if a given request is fresh
74
+ # based on IF_NONE_MATCH and IF_MODIFIED_SINCE headers
75
+ #
76
+ # @since 0.3.0
77
+ # @api private
78
+ class ConditionalGet
79
+ def initialize(env, options)
80
+ @validations = [ ETag.new(env, options[:etag]), LastModified.new(env, options[:last_modified]) ]
81
+ end
82
+
83
+ def fresh?
84
+ yield if @validations.any?(&:fresh?)
85
+ end
86
+
87
+ def headers
88
+ @validations.map(&:header).compact.reduce Hash.new, :merge
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end