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.
- 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
|