lotus-controller 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,81 @@
1
+ module Lotus
2
+ module Action
3
+ # Exposures API
4
+ #
5
+ # @since 0.1.0
6
+ #
7
+ # @see Lotus::Action::Exposable::ClassMethods#expose
8
+ module Exposable
9
+ def self.included(base)
10
+ base.class_eval do
11
+ extend ClassMethods
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ # Expose the given attributes on the outside of the object with
17
+ # a getter and a special method called #exposures.
18
+ #
19
+ # @param names [Array<Symbol>] the name(s) of the attribute(s) to be
20
+ # exposed
21
+ #
22
+ # @return [void]
23
+ #
24
+ # @since 0.1.0
25
+ #
26
+ # @example
27
+ # require 'lotus/controller'
28
+ #
29
+ # class Show
30
+ # include Lotus::Action
31
+ #
32
+ # expose :article, :tags
33
+ #
34
+ # def call(params)
35
+ # @article = Article.find params[:id]
36
+ # @tags = Tag.for(article)
37
+ # end
38
+ # end
39
+ #
40
+ # action = Show.new
41
+ # action.call({id: 23})
42
+ #
43
+ # action.article # => #<Article ...>
44
+ # action.tags # => [#<Tag ...>, #<Tag ...>]
45
+ #
46
+ # action.exposures # => { :article => #<Article ...>, :tags => [ ... ] }
47
+ def expose(*names)
48
+ class_eval do
49
+ attr_reader *names
50
+ exposures.push *names
51
+ end
52
+ end
53
+
54
+ # Set of exposures attribute names
55
+ #
56
+ # @return [Array] the exposures attribute names
57
+ #
58
+ # @since 0.1.0
59
+ # @api private
60
+ def exposures
61
+ @exposures ||= []
62
+ end
63
+ end
64
+
65
+ # Set of exposures
66
+ #
67
+ # @return [Hash] the exposures
68
+ #
69
+ # @since 0.1.0
70
+ #
71
+ # @see Lotus::Action::Exposable::ClassMethods.expose
72
+ def exposures
73
+ {}.tap do |result|
74
+ self.class.exposures.each do |exposure|
75
+ result[exposure] = send(exposure)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,190 @@
1
+ module Lotus
2
+ module Action
3
+ # Mime type API
4
+ #
5
+ # @since 0.1.0
6
+ #
7
+ # @see Lotus::Action::Mime::ClassMethods#accept
8
+ module Mime
9
+ # The key that returns accepted mime types from the Rack env
10
+ #
11
+ # @since 0.1.0
12
+ HTTP_ACCEPT = 'HTTP_ACCEPT'.freeze
13
+
14
+ # The header key to set the mime type of the response
15
+ #
16
+ # @since 0.1.0
17
+ CONTENT_TYPE = 'Content-Type'.freeze
18
+
19
+ # The default mime type for an incoming HTTP request
20
+ #
21
+ # @since 0.1.0
22
+ DEFAULT_ACCEPT = '*/*'.freeze
23
+
24
+ # The default mime type that is returned in the response
25
+ #
26
+ # @since 0.1.0
27
+ DEFAULT_CONTENT_TYPE = 'application/octet-stream'.freeze
28
+
29
+ def self.included(base)
30
+ base.class_eval do
31
+ extend ClassMethods
32
+ end
33
+ end
34
+
35
+ module ClassMethods
36
+ protected
37
+
38
+ # Restrict the access to the specified mime type symbols.
39
+ #
40
+ # @param mime_types[Array<Symbol>] one or more symbols representing mime type(s)
41
+ #
42
+ # @since 0.1.0
43
+ #
44
+ # @example
45
+ # require 'lotus/controller'
46
+ #
47
+ # class Show
48
+ # include Lotus::Action
49
+ # accept :html, :json
50
+ #
51
+ # def call(params)
52
+ # # ...
53
+ # end
54
+ # end
55
+ #
56
+ # # When called with "*/*" => 200
57
+ # # When called with "text/html" => 200
58
+ # # When called with "application/json" => 200
59
+ # # When called with "application/xml" => 406
60
+ def accept(*mime_types)
61
+ mime_types = mime_types.map do |mt|
62
+ ::Rack::Mime.mime_type ".#{ mt }"
63
+ end
64
+
65
+ before do
66
+ unless mime_types.find {|mt| accept?(mt) }
67
+ throw 406
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ protected
74
+ # Finalize the response by setting the current content type
75
+ #
76
+ # @since 0.1.0
77
+ # @api private
78
+ #
79
+ # @see Lotus::Action#finish
80
+ def finish
81
+ super
82
+ headers.merge! CONTENT_TYPE => content_type
83
+ end
84
+
85
+ # Sets the given content type
86
+ #
87
+ # Lotus::Action sets the proper content type automatically, this method
88
+ # is designed to override that value.
89
+ #
90
+ # @param content_type [String] the content type
91
+ # @return [void]
92
+ #
93
+ # @since 0.1.0
94
+ #
95
+ # @see Lotus::Action::Mime#content_type
96
+ #
97
+ # @example
98
+ # require 'lotus/controller'
99
+ #
100
+ # class Show
101
+ # include Lotus::Action
102
+ #
103
+ # def call(params)
104
+ # # ...
105
+ # self.content_type = 'application/json'
106
+ # end
107
+ # end
108
+ def content_type=(content_type)
109
+ @content_type = content_type
110
+ end
111
+
112
+ # The content type that will be automatically set in the response.
113
+ #
114
+ # It prefers, in order:
115
+ # * Explicit set value (see #content_type=)
116
+ # * Weighted value from Accept
117
+ # * Default content type
118
+ #
119
+ # To override the value, use <tt>#content_type=</tt>
120
+ #
121
+ # @return [String] the content type from the request.
122
+ #
123
+ # @since 0.1.0
124
+ #
125
+ # @see Lotus::Action::Mime#content_type=
126
+ # @see Lotus::Action::Mime#DEFAULT_CONTENT_TYPE
127
+ #
128
+ # @example
129
+ # require 'lotus/controller'
130
+ #
131
+ # class Show
132
+ # include Lotus::Action
133
+ #
134
+ # def call(params)
135
+ # # ...
136
+ # content_type # => 'text/html'
137
+ # end
138
+ # end
139
+ def content_type
140
+ @content_type || accepts || DEFAULT_CONTENT_TYPE
141
+ end
142
+
143
+ # Match the given mime type with the Accept header
144
+ #
145
+ # @return [Boolean] true if the given mime type matches Accept
146
+ #
147
+ # @since 0.1.0
148
+ #
149
+ # @example
150
+ # require 'lotus/controller'
151
+ #
152
+ # class Show
153
+ # include Lotus::Action
154
+ #
155
+ # def call(params)
156
+ # # ...
157
+ # # @_env['HTTP_ACCEPT'] # => 'text/html,application/xhtml+xml,application/xml;q=0.9'
158
+ #
159
+ # accept?('text/html') # => true
160
+ # accept?('application/xml') # => true
161
+ # accept?('application/json') # => false
162
+ #
163
+ #
164
+ #
165
+ # # @_env['HTTP_ACCEPT'] # => '*/*'
166
+ #
167
+ # accept?('text/html') # => true
168
+ # accept?('application/xml') # => true
169
+ # accept?('application/json') # => true
170
+ # end
171
+ # end
172
+ def accept?(mime_type)
173
+ !!::Rack::Utils.q_values(accept).find do |mime, _|
174
+ ::Rack::Mime.match?(mime_type, mime)
175
+ end
176
+ end
177
+
178
+ private
179
+ def accept
180
+ @accept ||= @_env[HTTP_ACCEPT] || DEFAULT_ACCEPT
181
+ end
182
+
183
+ def accepts
184
+ unless accept == DEFAULT_ACCEPT
185
+ ::Rack::Utils.best_q_match(accept, ::Rack::Mime::MIME_TYPES.values)
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,53 @@
1
+ require 'lotus/utils/hash'
2
+
3
+ module Lotus
4
+ module Action
5
+ # A set of params requested by the client
6
+ #
7
+ # It's able to extract the relevant params from a Rack env of from an Hash.
8
+ #
9
+ # There are three scenarios:
10
+ # * When used with Lotus::Router: it contains only the params from the request
11
+ # * When used standalone: it contains all the Rack env
12
+ # * Default: it returns the given hash as it is. It's useful for testing purposes.
13
+ #
14
+ # @since 0.1.0
15
+ class Params < Utils::Hash
16
+ # The key that returns raw input from the Rack env
17
+ #
18
+ # @since 0.1.0
19
+ RACK_INPUT = 'rack.input'.freeze
20
+
21
+ # The key that returns router params from the Rack env
22
+ # This is a builtin integration for Lotus::Router
23
+ #
24
+ # @since 0.1.0
25
+ ROUTER_PARAMS = 'router.params'.freeze
26
+
27
+ # Initialize the params and freeze them.
28
+ #
29
+ # @param env [Hash] a Rack env or an hash of params.
30
+ #
31
+ # @return [Params]
32
+ #
33
+ # @since 0.1.0
34
+ def initialize(env)
35
+ super _extract(env)
36
+ symbolize!
37
+ freeze
38
+ end
39
+
40
+ private
41
+ def _extract(env)
42
+ {}.tap do |result|
43
+ if env.has_key?(RACK_INPUT)
44
+ result.merge! ::Rack::Request.new(env).params
45
+ result.merge! env.fetch(ROUTER_PARAMS, {})
46
+ else
47
+ result.merge! env.fetch(ROUTER_PARAMS, env)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,97 @@
1
+ module Lotus
2
+ module Action
3
+ # Rack integration API
4
+ #
5
+ # @since 0.1.0
6
+ module Rack
7
+ SESSION_KEY = 'rack.session'.freeze
8
+ DEFAULT_RESPONSE_CODE = 200
9
+ DEFAULT_RESPONSE_BODY = []
10
+
11
+ protected
12
+ # Sets the HTTP status code for the response
13
+ #
14
+ # @param status [Fixnum] an HTTP status code
15
+ # @return [void]
16
+ #
17
+ # @since 0.1.0
18
+ #
19
+ # @example
20
+ # require 'lotus/controller'
21
+ #
22
+ # class Create
23
+ # include Lotus::Action
24
+ #
25
+ # def call(params)
26
+ # # ...
27
+ # self.status = 201
28
+ # end
29
+ # end
30
+ def status=(status)
31
+ @_status = status
32
+ end
33
+
34
+ # Sets the body of the response
35
+ #
36
+ # @param body [String] the body of the response
37
+ # @return [void]
38
+ #
39
+ # @since 0.1.0
40
+ #
41
+ # @example
42
+ # require 'lotus/controller'
43
+ #
44
+ # class Show
45
+ # include Lotus::Action
46
+ #
47
+ # def call(params)
48
+ # # ...
49
+ # self.body = 'Hi!'
50
+ # end
51
+ # end
52
+ def body=(body)
53
+ body = Array(body) unless body.respond_to?(:each)
54
+ @_body = body
55
+ end
56
+
57
+ # Gets the headers from the response
58
+ #
59
+ # @return [Hash] the HTTP headers from the response
60
+ #
61
+ # @since 0.1.0
62
+ #
63
+ # @example
64
+ # require 'lotus/controller'
65
+ #
66
+ # class Show
67
+ # include Lotus::Action
68
+ #
69
+ # def call(params)
70
+ # # ...
71
+ # self.headers # => { ... }
72
+ # self.headers.merge!({'X-Custom' => 'OK'})
73
+ # end
74
+ # end
75
+ def headers
76
+ @headers
77
+ end
78
+
79
+ # Returns a serialized Rack response (Array), according to the current
80
+ # status code, headers, and body.
81
+ #
82
+ # @return [Array] the serialized response
83
+ #
84
+ # @since 0.1.0
85
+ # @api private
86
+ #
87
+ # @see Lotus::Action::Rack::DEFAULT_RESPONSE_CODE
88
+ # @see Lotus::Action::Rack::DEFAULT_RESPONSE_BODY
89
+ # @see Lotus::Action::Rack#status=
90
+ # @see Lotus::Action::Rack#headers
91
+ # @see Lotus::Action::Rack#body=
92
+ def response
93
+ [ @_status || DEFAULT_RESPONSE_CODE, headers, @_body || DEFAULT_RESPONSE_BODY.dup ]
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,34 @@
1
+ module Lotus
2
+ module Action
3
+ # HTTP redirect API
4
+ #
5
+ # @since 0.1.0
6
+ module Redirect
7
+
8
+ protected
9
+
10
+ # Redirect to the given URL
11
+ #
12
+ # @param url [String] the destination URL
13
+ # @param status [Fixnum] the http code
14
+ #
15
+ # @since 0.1.0
16
+ #
17
+ # @example
18
+ # require 'lotus/controller'
19
+ #
20
+ # class Create
21
+ # include Lotus::Action
22
+ #
23
+ # def call(params)
24
+ # # ...
25
+ # redirect_to 'http://example.com/articles/23'
26
+ # end
27
+ # end
28
+ def redirect_to(url, status: 302)
29
+ headers.merge!('Location' => url)
30
+ self.status = status
31
+ end
32
+ end
33
+ end
34
+ end