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.
- checksums.yaml +4 -4
- data/.gitignore +7 -14
- data/.travis.yml +5 -0
- data/.yardopts +4 -0
- data/Gemfile +13 -2
- data/README.md +627 -6
- data/Rakefile +17 -1
- data/lib/lotus-controller.rb +1 -0
- data/lib/lotus/action.rb +55 -0
- data/lib/lotus/action/callable.rb +93 -0
- data/lib/lotus/action/callbacks.rb +138 -0
- data/lib/lotus/action/cookie_jar.rb +97 -0
- data/lib/lotus/action/cookies.rb +60 -0
- data/lib/lotus/action/exposable.rb +81 -0
- data/lib/lotus/action/mime.rb +190 -0
- data/lib/lotus/action/params.rb +53 -0
- data/lib/lotus/action/rack.rb +97 -0
- data/lib/lotus/action/redirect.rb +34 -0
- data/lib/lotus/action/session.rb +48 -0
- data/lib/lotus/action/throwable.rb +130 -0
- data/lib/lotus/controller.rb +65 -2
- data/lib/lotus/controller/dsl.rb +54 -0
- data/lib/lotus/controller/version.rb +4 -1
- data/lib/lotus/http/status.rb +103 -0
- data/lib/rack-patch.rb +20 -0
- data/lotus-controller.gemspec +16 -11
- data/test/action/callbacks_test.rb +99 -0
- data/test/action/params_test.rb +29 -0
- data/test/action_test.rb +31 -0
- data/test/controller_test.rb +24 -0
- data/test/cookies_test.rb +36 -0
- data/test/fixtures.rb +501 -0
- data/test/integration/mime_type_test.rb +175 -0
- data/test/integration/routing_test.rb +141 -0
- data/test/integration/sessions_test.rb +63 -0
- data/test/redirect_test.rb +20 -0
- data/test/session_test.rb +19 -0
- data/test/test_helper.rb +24 -0
- data/test/throw_test.rb +93 -0
- data/test/version_test.rb +7 -0
- metadata +112 -9
@@ -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
|