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
@@ -0,0 +1,99 @@
1
+ module Hanami
2
+ module Action
3
+ module Cache
4
+
5
+ # Cache-Control directives which have values
6
+ #
7
+ # @since 0.3.0
8
+ # @api private
9
+ VALUE_DIRECTIVES = %i(max_age s_maxage min_fresh max_stale).freeze
10
+
11
+ # Cache-Control directives which are implicitly true
12
+ #
13
+ # @since 0.3.0
14
+ # @api private
15
+ NON_VALUE_DIRECTIVES = %i(public private no_cache no_store no_transform must_revalidate proxy_revalidate).freeze
16
+
17
+ # Class representing value directives
18
+ #
19
+ # ex: max-age=600
20
+ #
21
+ # @since 0.3.0
22
+ # @api private
23
+ class ValueDirective
24
+ attr_reader :name
25
+
26
+ def initialize(name, value)
27
+ @name, @value = name, value
28
+ end
29
+
30
+ def to_str
31
+ "#{@name.to_s.tr('_', '-')}=#{@value.to_i}"
32
+ end
33
+
34
+ def valid?
35
+ VALUE_DIRECTIVES.include? @name
36
+ end
37
+ end
38
+
39
+ # Class representing non value directives
40
+ #
41
+ # ex: no-cache
42
+ #
43
+ # @since 0.3.0
44
+ # @api private
45
+ class NonValueDirective
46
+ attr_reader :name
47
+
48
+ def initialize(name)
49
+ @name = name
50
+ end
51
+
52
+ def to_str
53
+ @name.to_s.tr('_', '-')
54
+ end
55
+
56
+ def valid?
57
+ NON_VALUE_DIRECTIVES.include? @name
58
+ end
59
+ end
60
+
61
+ # Collection of value and non value directives
62
+ #
63
+ # @since 0.3.0
64
+ # @api private
65
+ class Directives
66
+ include Enumerable
67
+
68
+ def initialize(*values)
69
+ @directives = []
70
+ values.each do |directive_key|
71
+ if directive_key.kind_of? Hash
72
+ directive_key.each { |name, value| self.<< ValueDirective.new(name, value) }
73
+ else
74
+ self.<< NonValueDirective.new(directive_key)
75
+ end
76
+ end
77
+ end
78
+
79
+ def each
80
+ @directives.each { |d| yield d }
81
+ end
82
+
83
+ def <<(directive)
84
+ @directives << directive if directive.valid?
85
+ end
86
+
87
+ def values
88
+ @directives.delete_if do |directive|
89
+ directive.name == :public && @directives.map(&:name).include?(:private)
90
+ end
91
+ end
92
+
93
+ def join(separator)
94
+ values.join(separator)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,73 @@
1
+ require 'hanami/action/cache/cache_control'
2
+
3
+ module Hanami
4
+ module Action
5
+ module Cache
6
+
7
+ # Module with Expires logic
8
+ #
9
+ # @since 0.3.0
10
+ # @api private
11
+ module Expires
12
+
13
+ # The HTTP header for Expires
14
+ #
15
+ # @since 0.3.0
16
+ # @api private
17
+ HEADER = 'Expires'.freeze
18
+
19
+ def self.included(base)
20
+ base.extend ClassMethods
21
+ end
22
+
23
+ module ClassMethods
24
+ def expires(amount, *values)
25
+ @expires_directives ||= Directives.new(amount, *values)
26
+ end
27
+
28
+ def expires_directives
29
+ @expires_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.expires_directives.headers) unless headers.include? HEADER
46
+ end
47
+
48
+ # Class which stores Expires directives
49
+ #
50
+ # @since 0.3.0
51
+ #
52
+ # @api private
53
+ #
54
+ class Directives
55
+ def initialize(amount, *values)
56
+ @amount = amount
57
+ @cache_control = Hanami::Action::Cache::CacheControl::Directives.new(*(values << { max_age: amount }))
58
+ end
59
+
60
+ def headers
61
+ { HEADER => time.httpdate }.merge(@cache_control.headers)
62
+ end
63
+
64
+ private
65
+
66
+ def time
67
+ Time.now + @amount.to_i
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,94 @@
1
+ require 'hanami/action/params'
2
+
3
+ module Hanami
4
+ module Action
5
+ module Callable
6
+ # Execute application logic.
7
+ # It implements the Rack protocol.
8
+ #
9
+ # The request params are passed as an argument to the `#call` method.
10
+ #
11
+ # If routed with Hanami::Router, it extracts the relevant bits from the
12
+ # Rack `env` (eg the requested `:id`).
13
+ #
14
+ # Otherwise everything it's passed as it is: the full Rack `env`
15
+ # in production, and the given `Hash` for unit tests. See the examples
16
+ # below.
17
+ #
18
+ # Application developers are forced to implement this method in their
19
+ # actions.
20
+ #
21
+ # @param env [Hash] the full Rack env or the params. This value may vary,
22
+ # see the examples below.
23
+ #
24
+ # @return [Array] a serialized Rack response (eg. `[200, {}, ["Hi!"]]`)
25
+ #
26
+ # @since 0.1.0
27
+ #
28
+ # @example with Hanami::Router
29
+ # require 'hanami/controller'
30
+ #
31
+ # class Show
32
+ # include Hanami::Action
33
+ #
34
+ # def call(params)
35
+ # # ...
36
+ # puts params # => { id: 23 } extracted from Rack env
37
+ # end
38
+ # end
39
+ #
40
+ # @example Standalone
41
+ # require 'hanami/controller'
42
+ #
43
+ # class Show
44
+ # include Hanami::Action
45
+ #
46
+ # def call(params)
47
+ # # ...
48
+ # puts params
49
+ # # => { :"rack.version"=>[1, 2],
50
+ # # :"rack.input"=>#<StringIO:0x007fa563463948>, ... }
51
+ # end
52
+ # end
53
+ #
54
+ # @example Unit Testing
55
+ # require 'hanami/controller'
56
+ #
57
+ # class Show
58
+ # include Hanami::Action
59
+ #
60
+ # def call(params)
61
+ # # ...
62
+ # puts params # => { id: 23, key: 'value' } passed as it is from testing
63
+ # end
64
+ # end
65
+ #
66
+ # action = Show.new
67
+ # response = action.call({ id: 23, key: 'value' })
68
+ def call(env)
69
+ _rescue do
70
+ @_env = env
71
+ @headers = ::Rack::Utils::HeaderHash.new(configuration.default_headers)
72
+ @params = self.class.params_class.new(@_env)
73
+ super @params
74
+ end
75
+
76
+ finish
77
+ end
78
+
79
+ private
80
+
81
+ # Prepare the Rack response before the control is returned to the
82
+ # webserver.
83
+ #
84
+ # @since 0.1.0
85
+ # @api private
86
+ #
87
+ # @see Hanami::Action#finish
88
+ def finish
89
+ super
90
+ response
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,210 @@
1
+ require 'hanami/utils/class_attribute'
2
+ require 'hanami/utils/callbacks'
3
+
4
+ module Hanami
5
+ module Action
6
+ # Before and after callbacks
7
+ #
8
+ # @since 0.1.0
9
+ # @see Hanami::Action::ClassMethods#before
10
+ # @see Hanami::Action::ClassMethods#after
11
+ module Callbacks
12
+ # Override Ruby's hook for modules.
13
+ # It includes callbacks logic
14
+ #
15
+ # @param base [Class] the target action
16
+ #
17
+ # @since 0.1.0
18
+ # @api private
19
+ #
20
+ # @see http://www.ruby-doc.org/core/Module.html#method-i-included
21
+ def self.included(base)
22
+ base.class_eval do
23
+ extend ClassMethods
24
+ prepend InstanceMethods
25
+ end
26
+ end
27
+
28
+ # Callbacks API class methods
29
+ #
30
+ # @since 0.1.0
31
+ # @api private
32
+ module ClassMethods
33
+ # Override Ruby's hook for modules.
34
+ # It includes callbacks logic
35
+ #
36
+ # @param base [Class] the target action
37
+ #
38
+ # @since 0.1.0
39
+ # @api private
40
+ #
41
+ # @see http://www.ruby-doc.org/core/Module.html#method-i-extended
42
+ def self.extended(base)
43
+ base.class_eval do
44
+ include Utils::ClassAttribute
45
+
46
+ class_attribute :before_callbacks
47
+ self.before_callbacks = Utils::Callbacks::Chain.new
48
+
49
+ class_attribute :after_callbacks
50
+ self.after_callbacks = Utils::Callbacks::Chain.new
51
+ end
52
+ end
53
+
54
+ # Define a callback for an Action.
55
+ # The callback will be executed **before** the action is called, in the
56
+ # order they are added.
57
+ #
58
+ # @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
59
+ # each of them is representing a name of a method available in the
60
+ # context of the Action.
61
+ #
62
+ # @param blk [Proc] an anonymous function to be executed
63
+ #
64
+ # @return [void]
65
+ #
66
+ # @since 0.3.2
67
+ #
68
+ # @see Hanami::Action::Callbacks::ClassMethods#append_after
69
+ #
70
+ # @example Method names (symbols)
71
+ # require 'hanami/controller'
72
+ #
73
+ # class Show
74
+ # include Hanami::Action
75
+ #
76
+ # before :authenticate, :set_article
77
+ #
78
+ # def call(params)
79
+ # end
80
+ #
81
+ # private
82
+ # def authenticate
83
+ # # ...
84
+ # end
85
+ #
86
+ # # `params` in the method signature is optional
87
+ # def set_article(params)
88
+ # @article = Article.find params[:id]
89
+ # end
90
+ # end
91
+ #
92
+ # # The order of execution will be:
93
+ # #
94
+ # # 1. #authenticate
95
+ # # 2. #set_article
96
+ # # 3. #call
97
+ #
98
+ # @example Anonymous functions (Procs)
99
+ # require 'hanami/controller'
100
+ #
101
+ # class Show
102
+ # include Hanami::Action
103
+ #
104
+ # before { ... } # 1 do some authentication stuff
105
+ # before {|params| @article = Article.find params[:id] } # 2
106
+ #
107
+ # def call(params)
108
+ # end
109
+ # end
110
+ #
111
+ # # The order of execution will be:
112
+ # #
113
+ # # 1. authentication
114
+ # # 2. set the article
115
+ # # 3. #call
116
+ def append_before(*callbacks, &blk)
117
+ before_callbacks.append(*callbacks, &blk)
118
+ end
119
+
120
+ # @since 0.1.0
121
+ alias_method :before, :append_before
122
+
123
+ # Define a callback for an Action.
124
+ # The callback will be executed **after** the action is called, in the
125
+ # order they are added.
126
+ #
127
+ # @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
128
+ # each of them is representing a name of a method available in the
129
+ # context of the Action.
130
+ #
131
+ # @param blk [Proc] an anonymous function to be executed
132
+ #
133
+ # @return [void]
134
+ #
135
+ # @since 0.3.2
136
+ #
137
+ # @see Hanami::Action::Callbacks::ClassMethods#append_before
138
+ def append_after(*callbacks, &blk)
139
+ after_callbacks.append(*callbacks, &blk)
140
+ end
141
+
142
+ # @since 0.1.0
143
+ alias_method :after, :append_after
144
+
145
+ # Define a callback for an Action.
146
+ # The callback will be executed **before** the action is called.
147
+ # It will add the callback at the beginning of the callbacks' chain.
148
+ #
149
+ # @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
150
+ # each of them is representing a name of a method available in the
151
+ # context of the Action.
152
+ #
153
+ # @param blk [Proc] an anonymous function to be executed
154
+ #
155
+ # @return [void]
156
+ #
157
+ # @since 0.3.2
158
+ #
159
+ # @see Hanami::Action::Callbacks::ClassMethods#prepend_after
160
+ def prepend_before(*callbacks, &blk)
161
+ before_callbacks.prepend(*callbacks, &blk)
162
+ end
163
+
164
+ # Define a callback for an Action.
165
+ # The callback will be executed **after** the action is called.
166
+ # It will add the callback at the beginning of the callbacks' chain.
167
+ #
168
+ # @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
169
+ # each of them is representing a name of a method available in the
170
+ # context of the Action.
171
+ #
172
+ # @param blk [Proc] an anonymous function to be executed
173
+ #
174
+ # @return [void]
175
+ #
176
+ # @since 0.3.2
177
+ #
178
+ # @see Hanami::Action::Callbacks::ClassMethods#prepend_before
179
+ def prepend_after(*callbacks, &blk)
180
+ after_callbacks.prepend(*callbacks, &blk)
181
+ end
182
+ end
183
+
184
+ # Callbacks API instance methods
185
+ #
186
+ # @since 0.1.0
187
+ # @api private
188
+ module InstanceMethods
189
+ # Implements the Rack/Hanami::Action protocol
190
+ #
191
+ # @since 0.1.0
192
+ # @api private
193
+ def call(params)
194
+ _run_before_callbacks(params)
195
+ super
196
+ _run_after_callbacks(params)
197
+ end
198
+
199
+ private
200
+ def _run_before_callbacks(params)
201
+ self.class.before_callbacks.run(self, params)
202
+ end
203
+
204
+ def _run_after_callbacks(params)
205
+ self.class.after_callbacks.run(self, params)
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end