pakada-dispatch 0.1.1 → 0.2.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.
- data/.yardopts +1 -0
- data/Gemfile +4 -1
- data/README.md +4 -1
- data/lib/pakada/dispatch.rb +10 -29
- data/lib/pakada/dispatch/controller.rb +32 -142
- data/lib/pakada/dispatch/module.rb +27 -56
- data/lib/pakada/dispatch/version.rb +1 -1
- data/pakada-dispatch.gemspec +5 -5
- data/spec/controller_spec.rb +101 -316
- data/spec/controllers/foo.rb +1 -0
- data/spec/dispatch_spec.rb +44 -60
- data/spec/module_spec.rb +61 -258
- data/spec/spec_helper.rb +11 -10
- metadata +62 -76
- data/lib/pakada/dispatch/util.rb +0 -12
- data/spec/util_spec.rb +0 -21
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
- LICENSE
|
data/Gemfile
CHANGED
data/README.md
CHANGED
data/lib/pakada/dispatch.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
+
require "http_router"
|
1
2
|
require "pakada"
|
2
|
-
require "rack/mount"
|
3
|
-
require "yajl"
|
4
3
|
|
5
|
-
require "pakada/dispatch/util"
|
6
4
|
require "pakada/dispatch/controller"
|
7
5
|
require "pakada/dispatch/module"
|
8
6
|
require "pakada/dispatch/version"
|
@@ -13,35 +11,18 @@ class Pakada::Dispatch
|
|
13
11
|
attr_reader :router
|
14
12
|
|
15
13
|
def initialize
|
16
|
-
@router =
|
14
|
+
@router = HttpRouter.new
|
17
15
|
end
|
18
16
|
|
19
|
-
def
|
20
|
-
|
21
|
-
load_module_controllers
|
22
|
-
set_app
|
23
|
-
freeze_router
|
24
|
-
end
|
25
|
-
|
26
|
-
def apply_patches
|
27
|
-
Pakada.modules.each_value do |m|
|
28
|
-
m.singleton_class.send :include, Module
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def load_module_controllers
|
33
|
-
Pakada.modules.each_value {|m| m.load_controllers }
|
17
|
+
def hooks
|
18
|
+
Pakada.instance.instead_of :request, @router.method(:call)
|
34
19
|
end
|
35
20
|
|
36
|
-
def
|
37
|
-
Pakada.
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
def request(env)
|
45
|
-
router.call env
|
21
|
+
def boot
|
22
|
+
Pakada.modules.each_value {|mod|
|
23
|
+
mod.extend Pakada.safety(::Pakada::Dispatch::Module)
|
24
|
+
mod.load_controllers
|
25
|
+
mod.routes if mod.respond_to? :routes
|
26
|
+
}
|
46
27
|
end
|
47
28
|
end
|
@@ -1,169 +1,59 @@
|
|
1
|
+
require "rack/request"
|
2
|
+
require "rack/response"
|
3
|
+
|
1
4
|
class Pakada
|
2
5
|
class Dispatch
|
3
|
-
Action = Struct.new :name, :block, :options
|
4
|
-
|
5
|
-
METHODS = [:get, :head, :post, :put, :delete]
|
6
|
-
|
7
|
-
NOT_FOUND_ENDPOINT = proc do |env|
|
8
|
-
[404, {"X-Cascade" => "pass"}, ["Not Found"]]
|
9
|
-
end
|
10
|
-
METHOD_NOT_ALLOWED_ENDPOINT = proc do |methods|
|
11
|
-
proc do |env|
|
12
|
-
allow = methods.map {|m| m.to_s.upcase }.join(", ")
|
13
|
-
[405, {"Allow" => allow}, ["Method Not Allowed"]]
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
6
|
module Controller
|
18
|
-
|
19
|
-
|
20
|
-
def self.build(&block)
|
21
|
-
Class.new do
|
22
|
-
include Controller
|
23
|
-
class_eval &block if block
|
24
|
-
end
|
7
|
+
def self.included(klass)
|
8
|
+
klass.extend Pakada.safety(self::ClassMethods)
|
25
9
|
end
|
26
10
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
11
|
+
def self.create(&block)
|
12
|
+
Class.new.tap {|cls|
|
13
|
+
cls.send :include, self
|
14
|
+
cls.class_eval &block
|
15
|
+
}
|
32
16
|
end
|
33
17
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
raise ArgumentError, "No such action - #{name} (#{method})" unless action
|
18
|
+
attr_reader :options, :params, :request, :response
|
19
|
+
|
20
|
+
def initialize(env, options = {})
|
21
|
+
@request = Rack::Request.new(env)
|
22
|
+
@response = options[:response] || Rack::Response.new
|
41
23
|
|
42
|
-
@options =
|
43
|
-
catch(:finish) { instance_eval &action.block }
|
24
|
+
@options, @params = options, env["router.params"]
|
44
25
|
end
|
45
26
|
|
46
27
|
def finish!
|
47
28
|
throw :finish
|
48
29
|
end
|
49
30
|
|
50
|
-
def json(obj)
|
51
|
-
response.headers["Content-Type"] = "application/json"
|
52
|
-
response.write obj.respond_to?(:to_json) ? obj.to_json : Yajl::Encoder.encode(obj)
|
53
|
-
finish!
|
54
|
-
end
|
55
|
-
|
56
|
-
def redirect(*args)
|
57
|
-
response.status = (Fixnum === args[0]) ? args.shift : 303
|
58
|
-
|
59
|
-
if Symbol === args[0]
|
60
|
-
url = url(*args)
|
61
|
-
elsif args[0].respond_to? :to_url
|
62
|
-
url = args[0].to_url
|
63
|
-
elsif args[0]
|
64
|
-
url = args[0]
|
65
|
-
else
|
66
|
-
raise ArgumentError, "Controller#redirect needs a URL"
|
67
|
-
end
|
68
|
-
|
69
|
-
response.headers["Location"] = url
|
70
|
-
finish!
|
71
|
-
end
|
72
|
-
|
73
|
-
def forbidden(format = nil)
|
74
|
-
response.status = 403
|
75
|
-
if format == :json
|
76
|
-
response.headers["Content-Type"] = "application/json"
|
77
|
-
response.write Yajl::Encoder.encode({
|
78
|
-
:status => {
|
79
|
-
:code => 403,
|
80
|
-
:message => "Forbidden"
|
81
|
-
}
|
82
|
-
})
|
83
|
-
else
|
84
|
-
response.write "403 Forbidden"
|
85
|
-
end
|
86
|
-
finish!
|
87
|
-
end
|
88
|
-
|
89
|
-
def not_found(format = nil)
|
90
|
-
response.status = 404
|
91
|
-
if format == :json
|
92
|
-
response.headers["Content-Type"] = "application/json"
|
93
|
-
response.write Yajl::Encoder.encode({
|
94
|
-
:status => {
|
95
|
-
:code => 404,
|
96
|
-
:message => "Not Found"
|
97
|
-
}
|
98
|
-
})
|
99
|
-
else
|
100
|
-
response.write "404 Not Found"
|
101
|
-
end
|
102
|
-
finish!
|
103
|
-
end
|
104
|
-
|
105
31
|
module ClassMethods
|
106
32
|
def actions
|
107
|
-
@actions ||=
|
33
|
+
@actions ||= {}
|
108
34
|
end
|
109
35
|
|
110
36
|
def action(name, options = {}, &block)
|
111
37
|
if block
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
return
|
118
|
-
end
|
119
|
-
|
120
|
-
matches = actions.select {|a| a.name == name }
|
121
|
-
return NOT_FOUND_ENDPOINT if matches.empty?
|
122
|
-
|
123
|
-
proc do |env|
|
124
|
-
method = (env["REQUEST_METHOD"] || "GET").downcase.to_sym
|
125
|
-
allowed = matches.select do |a|
|
126
|
-
a.options[:methods] == :all || a.options[:methods].include?(method)
|
127
|
-
end
|
128
|
-
|
129
|
-
if allowed.empty?
|
130
|
-
allow = matches.inject([]) {|m, a| m.push(*a.options[:methods]); m }
|
131
|
-
METHOD_NOT_ALLOWED_ENDPOINT.call(allow).call(env)
|
132
|
-
else
|
133
|
-
controller = new env
|
134
|
-
controller.call_action allowed.first.name, options
|
135
|
-
controller.response.finish
|
136
|
-
end
|
38
|
+
actions[name] = proc {|env, opts = {}|
|
39
|
+
c = self.new(env, options.merge(opts))
|
40
|
+
catch(:finish) { c.instance_eval &block }
|
41
|
+
c.response.finish
|
42
|
+
}
|
137
43
|
end
|
138
|
-
|
139
|
-
# elsif actions[name]
|
140
|
-
# proc do |env|
|
141
|
-
# method = env["REQUEST_METHOD"]
|
142
|
-
# allowed = actions[name].options[:methods]
|
143
|
-
# if method && allowed != :all && !allowed.include?(method.downcase.to_sym)
|
144
|
-
# [405, {"Allow" => allowed.map {|m| m.to_s.upcase }.join(", ")}, ["Method Not Allowed"]]
|
145
|
-
# else
|
146
|
-
# controller = new env
|
147
|
-
# controller.call_action name, options
|
148
|
-
# controller.response.finish
|
149
|
-
# end
|
150
|
-
# end
|
151
|
-
# else
|
152
|
-
# proc {|env| [404, {"X-Cascade" => "pass"}, []] }
|
153
|
-
# end
|
44
|
+
actions[name]
|
154
45
|
end
|
155
46
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
47
|
+
def to_proc
|
48
|
+
method(:call).to_proc
|
49
|
+
end
|
50
|
+
|
51
|
+
def call(env)
|
52
|
+
env["router.params"] ||= {}
|
53
|
+
name = env["router.params"][:action] ||
|
54
|
+
env["REQUEST_METHOD"].downcase
|
55
|
+
action(name.to_sym).call env
|
162
56
|
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def self.included(klass)
|
166
|
-
klass.extend ClassMethods
|
167
57
|
end
|
168
58
|
end
|
169
59
|
end
|
@@ -1,67 +1,38 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
1
3
|
class Pakada
|
2
4
|
class Dispatch
|
3
5
|
module Module
|
4
|
-
|
6
|
+
def controller_path
|
7
|
+
# TODO change this when Pakada uses Pathname for module paths
|
8
|
+
#@controller_path ||= path.join("controllers")
|
9
|
+
@controller_path ||= Pathname.new(path).join("controllers")
|
10
|
+
end
|
5
11
|
|
6
|
-
|
12
|
+
def controllers
|
13
|
+
@controllers ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def controller(name, &block)
|
17
|
+
if block
|
18
|
+
controllers[name] = Pakada.safety(Controller).create(&block)
|
19
|
+
else
|
20
|
+
controllers[name]
|
21
|
+
end
|
22
|
+
end
|
7
23
|
|
8
24
|
def load_controllers
|
9
|
-
|
10
|
-
|
11
|
-
next unless file
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end if path
|
25
|
+
controller_path.each_entry {|path|
|
26
|
+
path = controller_path.join(path)
|
27
|
+
next unless path.file?
|
28
|
+
controller(path.basename(".rb").to_s.to_sym) {
|
29
|
+
class_eval File.read(path), path.to_s, 1
|
30
|
+
}
|
31
|
+
} if controller_path.exist?
|
17
32
|
end
|
18
33
|
|
19
|
-
def route(name
|
20
|
-
|
21
|
-
target = opts.delete route
|
22
|
-
splats = []
|
23
|
-
unless route.is_a? Regexp
|
24
|
-
route = route.gsub(/\((.*)\)/, '(\1)?')
|
25
|
-
.gsub(/:([a-z0-9\-_]+)/, '(?<\1>[a-z0-9\-_]+)')
|
26
|
-
.gsub(/\*([a-z0-9\-_]+)/) do |match|
|
27
|
-
splats << $1.to_sym
|
28
|
-
'(?<' + $1 + '>([a-z0-9\-_]/?)*)'
|
29
|
-
end
|
30
|
-
route = Regexp.compile "^#{route}$"
|
31
|
-
end
|
32
|
-
|
33
|
-
defaults = opts.dup
|
34
|
-
target = "::" + target unless target.include? "::"
|
35
|
-
target = target + "#" unless target.include? "#"
|
36
|
-
parts = target.split /::|#/
|
37
|
-
defaults[:module] = parts[0].to_sym unless !parts[0] || parts[0].empty?
|
38
|
-
defaults[:controller] = parts[1].to_sym unless !parts[1] || parts[1].empty?
|
39
|
-
defaults[:action] = parts[2].to_sym unless !parts[2] || parts[2].empty?
|
40
|
-
|
41
|
-
app = proc do |env|
|
42
|
-
env["pakada.dispatch.route"] = name
|
43
|
-
params = env["pakada.dispatch.params"] = env["rack.routing_args"].dup
|
44
|
-
|
45
|
-
params.each_pair do |key, value|
|
46
|
-
if splats.include? key
|
47
|
-
params[key] = value.split("/").select {|v| !v.empty? }
|
48
|
-
end
|
49
|
-
if [:module, :controller, :action].include? key
|
50
|
-
params[key] = value.to_sym
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
mod = Pakada[params[:module]]
|
55
|
-
controller = mod.controllers[params[:controller].to_sym] if mod
|
56
|
-
if controller
|
57
|
-
controller.action(params[:action]).call(env)
|
58
|
-
else
|
59
|
-
[404, {"X-Cascade" => "pass"}, []]
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
defaults[:module] = module_name unless defaults[:module]
|
64
|
-
Pakada[:dispatch].router.add_route app, {:path_info => route}, defaults, name
|
34
|
+
def route(name, pattern, &block)
|
35
|
+
Pakada[:dispatch].router.add(pattern).name(name).to &block
|
65
36
|
end
|
66
37
|
end
|
67
38
|
end
|
data/pakada-dispatch.gemspec
CHANGED
@@ -12,14 +12,14 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.summary = %q{Routing And Action Controllers For Pakada}
|
13
13
|
#s.description = %q{TODO: Write a gem description}
|
14
14
|
|
15
|
-
s.add_dependency "pakada"
|
16
|
-
s.add_dependency "
|
17
|
-
s.add_dependency "yajl-ruby"
|
15
|
+
s.add_dependency "pakada"
|
16
|
+
s.add_dependency "http_router"
|
18
17
|
|
19
18
|
s.add_development_dependency "rspec"
|
20
|
-
s.add_development_dependency "
|
19
|
+
s.add_development_dependency "yard"
|
20
|
+
s.add_development_dependency "rdiscount"
|
21
21
|
|
22
|
-
s.files = `git ls-files`.split("\n") - [".gitignore", ".
|
22
|
+
s.files = `git ls-files`.split("\n") - [".gitignore", "config.ru"]
|
23
23
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
24
24
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
25
25
|
s.require_paths = ["lib"]
|
data/spec/controller_spec.rb
CHANGED
@@ -1,366 +1,151 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Pakada::Dispatch::Controller do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
subject.build.should have(0).actions
|
8
|
-
end
|
9
|
-
|
10
|
-
describe "when called with a block" do
|
11
|
-
it "executes the block in the class' context" do
|
12
|
-
subject.build { action(:foo) {} }.should have(1).actions
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
describe "SomeController" do
|
19
|
-
subject do
|
20
|
-
Pakada::Dispatch::Controller.build
|
21
|
-
end
|
22
|
-
|
23
|
-
describe ".action" do
|
24
|
-
describe "when called with a block" do
|
25
|
-
it "adds an action to the controller" do
|
26
|
-
block = proc {}
|
27
|
-
subject.action :foo, &block
|
28
|
-
subject.actions.find {|a| a.name == :foo }.block.should == block
|
29
|
-
end
|
30
|
-
|
31
|
-
describe "and twice with the same name" do
|
32
|
-
it "adds a second action with the same name" do
|
33
|
-
2.times { subject.action :foo, &proc {} }
|
34
|
-
subject.should have(2).actions
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
describe "when called with a block and a hash" do
|
40
|
-
it "adds an action with options" do
|
41
|
-
block = proc {}
|
42
|
-
subject.action :foo, {}, &block
|
43
|
-
subject.actions.find {|a| a.name == :foo }.options.should respond_to(:[])
|
44
|
-
end
|
45
|
-
|
46
|
-
it "defaults the :methods option to :all" do
|
47
|
-
subject.action :foo, &proc {}
|
48
|
-
subject.actions.find {|a| a.name == :foo }.options[:methods].should == :all
|
49
|
-
|
50
|
-
subject.actions.clear
|
51
|
-
subject.action :foo, {:methods => :get}, &proc {}
|
52
|
-
subject.actions.find {|a| a.name == :foo }.options[:methods].should == [:get]
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
describe "when called without a block" do
|
57
|
-
it "builds a Rack end-point" do
|
58
|
-
subject.action(:foo) {}
|
59
|
-
|
60
|
-
env = {"foo" => "123"}
|
61
|
-
options = {:key => :value}
|
62
|
-
response = stub "response", :finish => [200, {}, []]
|
63
|
-
controller = stub "controller", :response => response
|
64
|
-
|
65
|
-
subject.should_receive(:new).twice.with(env) { controller }
|
66
|
-
|
67
|
-
controller.should_receive(:call_action).once.with(:foo, {})
|
68
|
-
subject.action(:foo).call(env).should == response.finish
|
69
|
-
|
70
|
-
controller.should_receive(:call_action).once.with(:foo, options)
|
71
|
-
subject.action(:foo, options).call(env)
|
72
|
-
end
|
73
|
-
|
74
|
-
describe "and with the name of a non-existing action" do
|
75
|
-
it "returns a 404 end-point" do
|
76
|
-
subject.action(:foo).call({}).should == [
|
77
|
-
404, {"X-Cascade" => "pass"}, ["Not Found"]
|
78
|
-
]
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
it "checks if the :methods constraint matches" do
|
83
|
-
subject.action :foo, :methods => [:get, :head], &proc {}
|
84
|
-
subject.action(:foo).call("REQUEST_METHOD" => "POST").should == [
|
85
|
-
405, {"Allow" => "GET, HEAD"}, ["Method Not Allowed"]
|
86
|
-
]
|
87
|
-
subject.action(:foo).call("REQUEST_METHOD" => "GET")[0].should == 200
|
88
|
-
subject.action(:foo).call({})[0].should == 200
|
89
|
-
|
90
|
-
subject.action :foo, :methods => :all, &proc {}
|
91
|
-
subject.action(:foo).call("REQUEST_METHOD" => "PUT")[0].should == 200
|
92
|
-
end
|
93
|
-
|
94
|
-
it "tries the next equal-named action if the constraint doesn't match" do
|
95
|
-
subject.action(:foo, :methods => :get) { not_found }
|
96
|
-
subject.action(:foo) {}
|
97
|
-
subject.action(:foo).call("REQUEST_METHOD" => "POST")[0].should == 200
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
4
|
+
let(:block) { proc {} }
|
5
|
+
let(:ctrlr_mod) { Pakada.safety Pakada::Dispatch::Controller }
|
6
|
+
let(:ctrlr) { ctrlr_mod.create &block }
|
101
7
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
subject.send(m, :foo, &proc {})
|
107
|
-
end
|
8
|
+
context ".included hook" do
|
9
|
+
it "extends the includer class with ClassMethods" do
|
10
|
+
mod = Pakada.safety(Pakada::Dispatch::Controller::ClassMethods)
|
11
|
+
ctrlr.singleton_class.included_modules.should include(mod)
|
108
12
|
end
|
109
13
|
end
|
110
14
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
controller.request.env.should == env
|
117
|
-
controller.response.should respond_to(:finish)
|
118
|
-
end
|
15
|
+
context ".create(name) { ... }" do
|
16
|
+
let(:klass) { stub "class" }
|
17
|
+
|
18
|
+
before { Class.stub :new => klass }
|
119
19
|
|
120
|
-
it "
|
121
|
-
|
122
|
-
|
123
|
-
controller.params.should == params
|
20
|
+
it "includes itself into a new class and calls the block in its context" do
|
21
|
+
klass.should_receive(:include).with ctrlr_mod
|
22
|
+
klass.should_receive(:class_eval) {|&blk| block.should equal(blk) }
|
124
23
|
|
125
|
-
|
126
|
-
controller.params.should == {}
|
24
|
+
ctrlr
|
127
25
|
end
|
128
26
|
end
|
129
27
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
controller.should_receive :do_something
|
135
|
-
|
136
|
-
controller.call_action :foo
|
137
|
-
end
|
28
|
+
context "#initialize(env, options)" do
|
29
|
+
let(:env) { {"router.params" => stub("router params")} }
|
30
|
+
let(:options) { {:response => stub("response")} }
|
31
|
+
let(:ctrlr_obj) { ctrlr.new env, options }
|
138
32
|
|
139
|
-
it "
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
controller.call_action :foo
|
144
|
-
controller.options[:key].should == :value
|
33
|
+
it "sets the options and routing params objects" do
|
34
|
+
ctrlr_obj.options.should equal(options)
|
35
|
+
ctrlr_obj.params.should equal(env["router.params"])
|
145
36
|
end
|
146
37
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
proc { controller.call_action :foo }.should raise_error(ArgumentError)
|
151
|
-
end
|
38
|
+
it "sets the request and response objects" do
|
39
|
+
ctrlr_obj.request.env.should equal(env)
|
40
|
+
ctrlr_obj.response.should equal(options[:response])
|
152
41
|
end
|
153
42
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
controller.call_action :foo, :key => :value2
|
160
|
-
controller.options[:key].should == :value2
|
161
|
-
end
|
43
|
+
it "defaults to an empty response object" do
|
44
|
+
old_response = options.delete :response
|
45
|
+
ctrlr_obj.response.status.should equal(200)
|
46
|
+
ctrlr_obj.response.should_not equal(old_response)
|
162
47
|
end
|
163
48
|
end
|
164
49
|
|
165
|
-
|
166
|
-
it "
|
167
|
-
|
168
|
-
subject.action :foo do
|
169
|
-
finishing = :now
|
170
|
-
finish!
|
171
|
-
finishing = :nooot
|
172
|
-
end
|
173
|
-
|
174
|
-
subject.new({}).call_action :foo
|
175
|
-
finishing.should == :now
|
50
|
+
context "#finish!" do
|
51
|
+
it "throws :finish" do
|
52
|
+
proc { ctrlr.new({}).finish! }.should throw_symbol(:finish)
|
176
53
|
end
|
177
54
|
end
|
178
55
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
controller.should_receive :finish!
|
184
|
-
controller.redirect "/something"
|
185
|
-
|
186
|
-
controller.response.headers["Location"].should == "/something"
|
187
|
-
end
|
56
|
+
context ".actions" do
|
57
|
+
it "is an empty Hash" do
|
58
|
+
ctrlr.actions.should be_an(Hash)
|
59
|
+
ctrlr.actions.should be_empty
|
188
60
|
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context ".action(name, options) { ... }" do
|
64
|
+
let(:block) { proc {} }
|
65
|
+
let(:options) { {:foo => stub("foo option")} }
|
66
|
+
let(:override_options) { {:bar => stub("bar option")} }
|
67
|
+
let(:merged_options) { options.merge override_options }
|
68
|
+
let(:env) { stub "env" }
|
69
|
+
let(:ctrlr_obj) { stub "controller object", :response => response }
|
70
|
+
let(:response) { stub "response", :finish => stub("finished response") }
|
189
71
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
controller.redirect stub("object", :to_url => "/something")
|
195
|
-
|
196
|
-
controller.response.headers["Location"].should == "/something"
|
197
|
-
end
|
198
|
-
end
|
72
|
+
before {
|
73
|
+
ctrlr.action :foo, options, &block
|
74
|
+
ctrlr.stub :new => ctrlr_obj
|
75
|
+
}
|
199
76
|
|
200
|
-
|
201
|
-
|
202
|
-
controller = subject.new({})
|
203
|
-
controller.should_receive :finish!
|
204
|
-
controller.should_receive(:url).with(:myroute, :key => "value") { "/something" }
|
205
|
-
controller.redirect :myroute, :key => "value"
|
206
|
-
|
207
|
-
controller.response.headers["Location"].should == "/something"
|
208
|
-
end
|
77
|
+
it "creates an action proc" do
|
78
|
+
ctrlr.action(:foo).should respond_to(:call)
|
209
79
|
end
|
210
80
|
|
211
|
-
it "
|
212
|
-
|
213
|
-
|
214
|
-
controller.redirect 302, "/"
|
215
|
-
|
216
|
-
controller.response.status.should == 302
|
81
|
+
it "'s proc creates a controller object passing env and options" do
|
82
|
+
ctrlr.should_receive(:new).with(env, merged_options) { ctrlr_obj }
|
83
|
+
ctrlr.action(:foo).call env, override_options
|
217
84
|
end
|
218
85
|
|
219
|
-
it "
|
220
|
-
|
221
|
-
|
222
|
-
controller.redirect "/"
|
223
|
-
|
224
|
-
controller.response.status.should == 303
|
86
|
+
it "'s proc instance_evals the action proc catching :finish" do
|
87
|
+
ctrlr_obj.should_receive(:instance_eval) {|&blk| block.should equal(blk) }
|
88
|
+
ctrlr.action(:foo).call env
|
225
89
|
end
|
226
90
|
|
227
|
-
it "
|
228
|
-
|
229
|
-
|
230
|
-
proc { controller.redirect }.should raise_error(ArgumentError)
|
231
|
-
proc { controller.redirect 303 }.should raise_error(ArgumentError)
|
91
|
+
it "'s proc returns the finished response" do
|
92
|
+
ctrlr.action(:foo).call(env).should equal(response.finish)
|
232
93
|
end
|
233
94
|
end
|
234
95
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
controller = subject.new({})
|
239
|
-
|
240
|
-
controller.should_receive :finish!
|
241
|
-
controller.call_action :foo
|
242
|
-
|
243
|
-
controller.response.status.should == 404
|
244
|
-
controller.response.body.join.should == "404 Not Found"
|
245
|
-
end
|
96
|
+
context ".action(name)" do
|
97
|
+
let(:action) { stub "action" }
|
98
|
+
before { ctrlr.actions[:foo] = action }
|
246
99
|
|
247
|
-
|
248
|
-
|
249
|
-
subject.action(:foo) { not_found :json }
|
250
|
-
controller = subject.new({})
|
251
|
-
controller.call_action :foo
|
252
|
-
|
253
|
-
controller.response.headers["Content-Type"].should == "application/json"
|
254
|
-
Yajl::Parser.parse(controller.response.body.join).should == {
|
255
|
-
"status" => {
|
256
|
-
"code" => 404,
|
257
|
-
"message" => "Not Found"
|
258
|
-
}
|
259
|
-
}
|
260
|
-
end
|
100
|
+
it "returns the specified action" do
|
101
|
+
ctrlr.action(:foo).should equal(action)
|
261
102
|
end
|
262
103
|
end
|
263
104
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
controller = subject.new({})
|
268
|
-
|
269
|
-
controller.should_receive :finish!
|
270
|
-
controller.call_action :foo
|
271
|
-
|
272
|
-
controller.response.status.should == 403
|
273
|
-
controller.response.body.join.should == "403 Forbidden"
|
274
|
-
end
|
105
|
+
context ".to_proc" do
|
106
|
+
let(:env) { stub "env" }
|
107
|
+
let(:response) { stub "response" }
|
275
108
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
controller = subject.new({})
|
280
|
-
controller.call_action :foo
|
281
|
-
|
282
|
-
controller.response.headers["Content-Type"].should == "application/json"
|
283
|
-
Yajl::Parser.parse(controller.response.body.join).should == {
|
284
|
-
"status" => {
|
285
|
-
"code" => 403,
|
286
|
-
"message" => "Forbidden"
|
287
|
-
}
|
288
|
-
}
|
289
|
-
end
|
109
|
+
it "forwards to #call" do
|
110
|
+
ctrlr.should_receive(:call).with(env) { response }
|
111
|
+
ctrlr.to_proc.call(env).should equal(response)
|
290
112
|
end
|
291
113
|
end
|
292
114
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
Yajl::Parser.parse(controller.response.body.join).should == {"key" => "value"}
|
303
|
-
end
|
115
|
+
context ".call(env)" do
|
116
|
+
let(:env) {
|
117
|
+
{
|
118
|
+
"router.params" => {
|
119
|
+
:action => :foo
|
120
|
+
},
|
121
|
+
"REQUEST_METHOD" => "bar"
|
122
|
+
}
|
123
|
+
}
|
304
124
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
125
|
+
before {
|
126
|
+
ctrlr.action(:foo) {}
|
127
|
+
ctrlr.action(:bar) {}
|
128
|
+
}
|
129
|
+
|
130
|
+
it "sets default routing params" do
|
131
|
+
ctrlr.action(:bar).should_receive(:call) {|e|
|
132
|
+
e["router.params"].should == {}
|
133
|
+
}
|
312
134
|
|
313
|
-
|
135
|
+
env.delete "router.params"
|
136
|
+
ctrlr.call env
|
314
137
|
end
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
subject { Pakada::Dispatch::NOT_FOUND_ENDPOINT.call({}) }
|
320
|
-
|
321
|
-
it "returns a 404 response" do
|
322
|
-
subject[0].should == 404
|
323
|
-
subject[2].join.should == "Not Found"
|
324
|
-
end
|
325
|
-
|
326
|
-
it "is stackable" do
|
327
|
-
subject[1]["X-Cascade"].should == "pass"
|
328
|
-
end
|
329
|
-
end
|
330
|
-
|
331
|
-
describe Pakada::Dispatch::METHOD_NOT_ALLOWED_ENDPOINT do
|
332
|
-
subject do
|
333
|
-
Pakada::Dispatch::METHOD_NOT_ALLOWED_ENDPOINT.call([:get, :post]).call({})
|
334
|
-
end
|
335
|
-
|
336
|
-
it "returns a 405 response" do
|
337
|
-
subject[0].should == 405
|
338
|
-
subject[2].join.should == "Method Not Allowed"
|
339
|
-
end
|
340
|
-
|
341
|
-
it "returns an appropriate Allow header" do
|
342
|
-
subject[1]["Allow"].should == "GET, POST"
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
describe Pakada::Dispatch::Action do
|
347
|
-
describe "#name" do
|
348
|
-
it "returns the action's name" do
|
349
|
-
Pakada::Dispatch::Action.new(:asdf).name.should == :asdf
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
describe "#block" do
|
354
|
-
it "returns the action's code block" do
|
355
|
-
block = double "block"
|
356
|
-
Pakada::Dispatch::Action.new(nil, block).block.should == block
|
138
|
+
|
139
|
+
it "calls the action from routing params by default" do
|
140
|
+
ctrlr.action(:foo).should_receive :call
|
141
|
+
ctrlr.call env
|
357
142
|
end
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
143
|
+
|
144
|
+
it "calls the REQUEST_METHOD action as fallback" do
|
145
|
+
ctrlr.action(:bar).should_receive :call
|
146
|
+
|
147
|
+
env.delete "router.params"
|
148
|
+
ctrlr.call env
|
364
149
|
end
|
365
150
|
end
|
366
151
|
end
|