lotus-controller 0.0.0 → 0.1.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.
@@ -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