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
data/Rakefile
CHANGED
@@ -1 +1,17 @@
|
|
1
|
-
require
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.pattern = 'test/**/*_test.rb'
|
7
|
+
t.libs.push 'test'
|
8
|
+
end
|
9
|
+
|
10
|
+
namespace :test do
|
11
|
+
task :coverage do
|
12
|
+
ENV['COVERAGE'] = 'true'
|
13
|
+
Rake::Task['test'].invoke
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
task default: :test
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'lotus/controller'
|
data/lib/lotus/action.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'lotus/action/rack'
|
2
|
+
require 'lotus/action/mime'
|
3
|
+
require 'lotus/action/redirect'
|
4
|
+
require 'lotus/action/exposable'
|
5
|
+
require 'lotus/action/throwable'
|
6
|
+
require 'lotus/action/callbacks'
|
7
|
+
require 'lotus/action/callable'
|
8
|
+
|
9
|
+
module Lotus
|
10
|
+
# An HTTP endpoint
|
11
|
+
#
|
12
|
+
# @since 0.1.0
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# require 'lotus/controller'
|
16
|
+
#
|
17
|
+
# class Show
|
18
|
+
# include Lotus::Action
|
19
|
+
#
|
20
|
+
# def call(params)
|
21
|
+
# # ...
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
module Action
|
25
|
+
def self.included(base)
|
26
|
+
base.class_eval do
|
27
|
+
include Rack
|
28
|
+
include Mime
|
29
|
+
include Redirect
|
30
|
+
include Exposable
|
31
|
+
include Throwable
|
32
|
+
include Callbacks
|
33
|
+
prepend Callable
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
# Finalize the response
|
40
|
+
#
|
41
|
+
# This method is abstract and COULD be implemented by included modules in
|
42
|
+
# order to prepare their data before the reponse will be returned to the
|
43
|
+
# webserver.
|
44
|
+
#
|
45
|
+
# @since 0.1.0
|
46
|
+
# @api private
|
47
|
+
# @abstract
|
48
|
+
#
|
49
|
+
# @see Lotus::Action::Mime
|
50
|
+
# @see Lotus::Action::Cookies
|
51
|
+
# @see Lotus::Action::Callable
|
52
|
+
def finish
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'lotus/action/params'
|
2
|
+
|
3
|
+
module Lotus
|
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 Lotus::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 Lotus::Router
|
29
|
+
# require 'lotus/controller'
|
30
|
+
#
|
31
|
+
# class Show
|
32
|
+
# include Lotus::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 'lotus/controller'
|
42
|
+
#
|
43
|
+
# class Show
|
44
|
+
# include Lotus::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 'lotus/controller'
|
56
|
+
#
|
57
|
+
# class Show
|
58
|
+
# include Lotus::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
|
72
|
+
super Params.new(@_env)
|
73
|
+
end
|
74
|
+
|
75
|
+
finish
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
# Prepare the Rack response before the control is returned to the
|
81
|
+
# webserver.
|
82
|
+
#
|
83
|
+
# @since 0.1.0
|
84
|
+
# @api private
|
85
|
+
#
|
86
|
+
# @see Lotus::Action#finish
|
87
|
+
def finish
|
88
|
+
super
|
89
|
+
response
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'lotus/utils/class_attribute'
|
2
|
+
require 'lotus/utils/callbacks'
|
3
|
+
|
4
|
+
module Lotus
|
5
|
+
module Action
|
6
|
+
# Before and after callbacks
|
7
|
+
#
|
8
|
+
# @since 0.1.0
|
9
|
+
# @see Lotus::Action::ClassMethods#before
|
10
|
+
# @see Lotus::Action::ClassMethods#after
|
11
|
+
module Callbacks
|
12
|
+
def self.included(base)
|
13
|
+
base.class_eval do
|
14
|
+
extend ClassMethods
|
15
|
+
prepend InstanceMethods
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def self.extended(base)
|
21
|
+
base.class_eval do
|
22
|
+
include Utils::ClassAttribute
|
23
|
+
|
24
|
+
class_attribute :before_callbacks
|
25
|
+
self.before_callbacks = Utils::Callbacks::Chain.new
|
26
|
+
|
27
|
+
class_attribute :after_callbacks
|
28
|
+
self.after_callbacks = Utils::Callbacks::Chain.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Define a callback for an Action.
|
33
|
+
# The callback will be executed **before** the action is called, in the
|
34
|
+
# order they are added.
|
35
|
+
#
|
36
|
+
# @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
|
37
|
+
# each of them is representing a name of a method available in the
|
38
|
+
# context of the Action.
|
39
|
+
#
|
40
|
+
# @param blk [Proc] an anonymous function to be executed
|
41
|
+
#
|
42
|
+
# @return [void]
|
43
|
+
#
|
44
|
+
# @since 0.1.0
|
45
|
+
#
|
46
|
+
# @example Method names (symbols)
|
47
|
+
# require 'lotus/controller'
|
48
|
+
#
|
49
|
+
# class Show
|
50
|
+
# include Lotus::Action
|
51
|
+
#
|
52
|
+
# before :authenticate, :set_article
|
53
|
+
#
|
54
|
+
# def call(params)
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# private
|
58
|
+
# def authenticate
|
59
|
+
# # ...
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# # `params` in the method signature is optional
|
63
|
+
# def set_article(params)
|
64
|
+
# @article = Article.find params[:id]
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# # The order of execution will be:
|
69
|
+
# #
|
70
|
+
# # 1. #authenticate
|
71
|
+
# # 2. #set_article
|
72
|
+
# # 3. #call
|
73
|
+
#
|
74
|
+
# @example Anonymous functions (Procs)
|
75
|
+
# require 'lotus/controller'
|
76
|
+
#
|
77
|
+
# class Show
|
78
|
+
# include Lotus::Action
|
79
|
+
#
|
80
|
+
# before { ... } # 1 do some authentication stuff
|
81
|
+
# before {|params| @article = Article.find params[:id] } # 2
|
82
|
+
#
|
83
|
+
# def call(params)
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# # The order of execution will be:
|
88
|
+
# #
|
89
|
+
# # 1. authentication
|
90
|
+
# # 2. set the article
|
91
|
+
# # 3. #call
|
92
|
+
def before(*callbacks, &blk)
|
93
|
+
before_callbacks.add *callbacks, &blk
|
94
|
+
end
|
95
|
+
|
96
|
+
# Define a callback for an Action.
|
97
|
+
# The callback will be executed **after** the action is called, in the
|
98
|
+
# order they are added.
|
99
|
+
#
|
100
|
+
# @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
|
101
|
+
# each of them is representing a name of a method available in the
|
102
|
+
# context of the Action.
|
103
|
+
#
|
104
|
+
# @param blk [Proc] an anonymous function to be executed
|
105
|
+
#
|
106
|
+
# @return [void]
|
107
|
+
#
|
108
|
+
# @since 0.1.0
|
109
|
+
#
|
110
|
+
# @see Lotus::Action::Callbacks::ClassMethods#before
|
111
|
+
def after(*callbacks, &blk)
|
112
|
+
after_callbacks.add *callbacks, &blk
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
module InstanceMethods
|
117
|
+
# Implements the Rack/Lotus::Action protocol
|
118
|
+
#
|
119
|
+
# @since 0.1.0
|
120
|
+
# @api private
|
121
|
+
def call(params)
|
122
|
+
_run_before_callbacks(params)
|
123
|
+
super
|
124
|
+
_run_after_callbacks(params)
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
def _run_before_callbacks(params)
|
129
|
+
self.class.before_callbacks.run(self, params)
|
130
|
+
end
|
131
|
+
|
132
|
+
def _run_after_callbacks(params)
|
133
|
+
self.class.after_callbacks.run(self, params)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'lotus/utils/hash'
|
2
|
+
|
3
|
+
module Lotus
|
4
|
+
module Action
|
5
|
+
# A set of HTTP Cookies
|
6
|
+
#
|
7
|
+
# It acts as an Hash
|
8
|
+
#
|
9
|
+
# @since 0.1.0
|
10
|
+
#
|
11
|
+
# @see Lotus::Action::Cookies#cookies
|
12
|
+
class CookieJar < Utils::Hash
|
13
|
+
# The key that returns raw cookies from the Rack env
|
14
|
+
#
|
15
|
+
# @since 0.1.0
|
16
|
+
HTTP_HEADER = 'HTTP_COOKIE'.freeze
|
17
|
+
|
18
|
+
# The key used by Rack to set the cookies as an Hash in the env
|
19
|
+
#
|
20
|
+
# @since 0.1.0
|
21
|
+
COOKIE_HASH_KEY = 'rack.request.cookie_hash'.freeze
|
22
|
+
|
23
|
+
# The key used by Rack to set the cookies as a String in the env
|
24
|
+
#
|
25
|
+
# @since 0.1.0
|
26
|
+
COOKIE_STRING_KEY = 'rack.request.cookie_string'.freeze
|
27
|
+
|
28
|
+
# Initialize the CookieJar
|
29
|
+
#
|
30
|
+
# @param env [Hash] a raw Rack env
|
31
|
+
# @param headers [Hash] the response headers
|
32
|
+
#
|
33
|
+
# @return [CookieJar]
|
34
|
+
#
|
35
|
+
# @since 0.1.0
|
36
|
+
def initialize(env, headers)
|
37
|
+
@_headers = headers
|
38
|
+
|
39
|
+
super(extract(env))
|
40
|
+
symbolize!
|
41
|
+
end
|
42
|
+
|
43
|
+
# Finalize itself, by setting the proper headers to add and remove
|
44
|
+
# cookies, before the response is returned to the webserver.
|
45
|
+
#
|
46
|
+
# @return [void]
|
47
|
+
#
|
48
|
+
# @since 0.1.0
|
49
|
+
#
|
50
|
+
# @see Lotus::Action::Cookies#finish
|
51
|
+
def finish
|
52
|
+
each {|k,v| v.nil? ? delete_cookie(k) : set_cookie(k, v) }
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
# Extract the cookies from the raw Rack env.
|
57
|
+
#
|
58
|
+
# This implementation is borrowed from Rack::Request#cookies.
|
59
|
+
#
|
60
|
+
# @since 0.1.0
|
61
|
+
# @api private
|
62
|
+
def extract(env)
|
63
|
+
hash = env[COOKIE_HASH_KEY] ||= {}
|
64
|
+
string = env[HTTP_HEADER]
|
65
|
+
|
66
|
+
return hash if string == env[COOKIE_STRING_KEY]
|
67
|
+
hash.clear
|
68
|
+
|
69
|
+
# According to RFC 2109:
|
70
|
+
# If multiple cookies satisfy the criteria above, they are ordered in
|
71
|
+
# the Cookie header such that those with more specific Path attributes
|
72
|
+
# precede those with less specific. Ordering with respect to other
|
73
|
+
# attributes (e.g., Domain) is unspecified.
|
74
|
+
cookies = ::Rack::Utils.parse_query(string, ';,') { |s| ::Rack::Utils.unescape(s) rescue s }
|
75
|
+
cookies.each { |k,v| hash[k] = Array === v ? v.first : v }
|
76
|
+
env[COOKIE_STRING_KEY] = string
|
77
|
+
hash
|
78
|
+
end
|
79
|
+
|
80
|
+
# Set a cookie in the headers
|
81
|
+
#
|
82
|
+
# @since 0.1.0
|
83
|
+
# @api private
|
84
|
+
def set_cookie(key, value)
|
85
|
+
::Rack::Utils.set_cookie_header!(@_headers, key, value)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Remove a cookie from the headers
|
89
|
+
#
|
90
|
+
# @since 0.1.0
|
91
|
+
# @api private
|
92
|
+
def delete_cookie(key)
|
93
|
+
::Rack::Utils.delete_cookie_header!(@_headers, key, {})
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'lotus/action/cookie_jar'
|
2
|
+
|
3
|
+
module Lotus
|
4
|
+
module Action
|
5
|
+
# Cookies API
|
6
|
+
#
|
7
|
+
# This module isn't included by default.
|
8
|
+
#
|
9
|
+
# @since 0.1.0
|
10
|
+
#
|
11
|
+
# @see Lotus::Action::Cookies#cookies
|
12
|
+
module Cookies
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
# Finalize the response by flushing cookies into the response
|
17
|
+
#
|
18
|
+
# @since 0.1.0
|
19
|
+
# @api private
|
20
|
+
#
|
21
|
+
# @see Lotus::Action#finish
|
22
|
+
def finish
|
23
|
+
super
|
24
|
+
cookies.finish
|
25
|
+
end
|
26
|
+
|
27
|
+
# Gets the cookies from the request and expose them as an Hash
|
28
|
+
#
|
29
|
+
# @return [Lotus::Action::CookieJar] the cookies
|
30
|
+
#
|
31
|
+
# @since 0.1.0
|
32
|
+
# @api public
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# require 'lotus/controller'
|
36
|
+
# require 'lotus/action/cookies'
|
37
|
+
#
|
38
|
+
# class Show
|
39
|
+
# include Lotus::Action
|
40
|
+
# include Lotus::Action::Cookies
|
41
|
+
#
|
42
|
+
# def call(params)
|
43
|
+
# # ...
|
44
|
+
#
|
45
|
+
# # get a value
|
46
|
+
# cookies[:user_id] # => '23'
|
47
|
+
#
|
48
|
+
# # set a value
|
49
|
+
# cookies[:foo] = 'bar'
|
50
|
+
#
|
51
|
+
# # remove a value
|
52
|
+
# cookies[:bax] = nil
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
def cookies
|
56
|
+
@cookies ||= CookieJar.new(@_env.dup, headers)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|