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 ADDED
@@ -0,0 +1 @@
1
+ - LICENSE
data/Gemfile CHANGED
@@ -1,6 +1,9 @@
1
- source "http://rubygems.org"
1
+ source :rubygems
2
2
 
3
3
  gemspec
4
4
 
5
5
  gem "rake"
6
6
  gem "awesome_print"
7
+
8
+ gem "thin"
9
+ gem "shotgun"
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
1
  Routing And Action Controllers For Pakada
2
2
  ===================================================
3
3
 
4
- TODO: Write documentation
4
+ TODO
5
+ ----
6
+
7
+ - Write documentation
@@ -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 = Rack::Mount::RouteSet.new
14
+ @router = HttpRouter.new
17
15
  end
18
16
 
19
- def boot
20
- apply_patches
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 set_app
37
- Pakada.app = proc {|env| request env }
38
- end
39
-
40
- def freeze_router
41
- router.rehash
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
- include Util
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
- attr_reader :request, :response, :params, :options
28
-
29
- def initialize(env)
30
- @request, @response = Rack::Request.new(env), Rack::Response.new
31
- @params = request.env["pakada.dispatch.params"] || {}
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
- def call_action(name, options = {})
35
- method = (request.request_method || "GET").downcase.to_sym
36
- action = self.class.actions.find do |a|
37
- a.name == name && (
38
- a.options[:methods] == :all || a.options[:methods].include?(method))
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 = action.options.merge 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
- options[:methods] ||= :all
113
- unless options[:methods].respond_to?(:each) || options[:methods] == :all
114
- options[:methods] = [options[:methods]]
115
- end
116
- actions << Action.new(name, block, options)
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
- METHODS.each do |m|
157
- eval <<-R, nil, __FILE__, __LINE__
158
- def #{m}(name, options = {}, &block)
159
- action(name, options.merge(:methods => :#{m}), &block)
160
- end
161
- R
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
- include Util
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
- attr_reader :controllers
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
- @controllers = {}
10
- Dir["#{path}/controllers/*.rb"].each do |file|
11
- next unless file.match %r{.*/[a-z0-9_]+\.rb$}
12
- name = file.split("/").last.split(".").first
13
- @controllers[name.to_sym] = Controller.build do
14
- class_eval File.read(file), file, 1
15
- end
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 = nil, opts)
20
- route = opts.keys.find {|k| !k.kind_of? Symbol }
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
@@ -1,5 +1,5 @@
1
1
  class Pakada
2
2
  class Dispatch
3
- VERSION = "0.1.1"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
@@ -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", "~> 0.2"
16
- s.add_dependency "rack-mount"
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 "fakefs"
19
+ s.add_development_dependency "yard"
20
+ s.add_development_dependency "rdiscount"
21
21
 
22
- s.files = `git ls-files`.split("\n") - [".gitignore", ".rvmrc"]
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"]
@@ -1,366 +1,151 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Pakada::Dispatch::Controller do
4
- describe ".build" do
5
- it "builds an empty controller class" do
6
- subject.build.should respond_to(:action)
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
- Pakada::Dispatch::METHODS.each do |m|
103
- describe ".#{m}" do
104
- it "forwards to .action(:#{m}, ...)" do
105
- subject.should_receive(:action).with(:foo, :methods => m)
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
- describe "#initialize" do
112
- it "sets up Rack::Request and Response objects" do
113
- env = {"foo" => "123"}
114
- controller = subject.new env
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 "populates #params with the routing parameters" do
121
- params = {:foo => :bar}
122
- controller = subject.new "pakada.dispatch.params" => params
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
- controller = subject.new({})
126
- controller.params.should == {}
24
+ ctrlr
127
25
  end
128
26
  end
129
27
 
130
- describe "#call_action" do
131
- it "invokes the action's block in the controller's scope" do
132
- subject.action(:foo) { do_something }
133
- controller = subject.new({})
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 "populates #options" do
140
- subject.action(:foo, :key => :value) {}
141
- controller = subject.new({})
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
- describe "when called with the name of a non-existing action" do
148
- it "raises an error" do
149
- controller = subject.new({})
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
- describe "when called with a hash" do
155
- it "overrides the action's options" do
156
- subject.action(:foo, :key => :value) {}
157
- controller = subject.new({})
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
- describe "#finish!" do
166
- it "immediately finishes the action's execution" do
167
- finishing = nil
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
- describe "#redirect" do
180
- describe "when called with a URL string" do
181
- it "redirects to the URL and finishes" do
182
- controller = subject.new({})
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
- describe "when called with an object that responds to #to_url" do
191
- it "redirects to the URL returned by #to_url and finishes" do
192
- controller = subject.new({})
193
- controller.should_receive :finish!
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
- describe "when called with a route name and (optionally) a hash" do
201
- it "redirects to the URL generated by Util#url and finishes" do
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 "accepts an HTTP status code" do
212
- controller = subject.new({})
213
- controller.stub :finish!
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 "uses 303 See Other as the default HTTP status code" do
220
- controller = subject.new({})
221
- controller.stub :finish!
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 "raises an ArgumentError if the arguments don't include a URL" do
228
- controller = subject.new({})
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
- describe "#not_found" do
236
- it "sets a 404 status code, writes a message and finishes" do
237
- subject.action(:foo) { not_found }
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
- describe "when called with :json as argument" do
248
- it "writes the message in JSON format" do
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
- describe "#forbidden" do
265
- it "sets a 403 status code, writes a message and finishes" do
266
- subject.action(:foo) { forbidden }
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
- describe "when called with :json as argument" do
277
- it "writes the message in JSON format" do
278
- subject.action(:foo) { forbidden :json }
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
- describe "#json" do
294
- it "sets the JSON Content-Type header, writes JSON output and finishes" do
295
- subject.action(:foo) { json :key => "value" }
296
- controller = subject.new({})
297
-
298
- controller.should_receive :finish!
299
- controller.call_action :foo
300
-
301
- controller.response.headers["Content-Type"].should == "application/json"
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
- it "tries to call #to_json and use its result as output" do
306
- obj = double "output", :to_json => '{"key":"value"}'
307
- obj.should_receive :to_json
308
-
309
- subject.action(:foo) { json obj }
310
- controller = subject.new({})
311
- controller.call_action :foo
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
- Yajl::Parser.parse(controller.response.body.join).should == {"key" => "value"}
135
+ env.delete "router.params"
136
+ ctrlr.call env
314
137
  end
315
- end
316
- end
317
-
318
- describe Pakada::Dispatch::NOT_FOUND_ENDPOINT do
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
- end
359
-
360
- describe "#options" do
361
- it "returns the actions' options hash" do
362
- options = double "options"
363
- Pakada::Dispatch::Action.new(nil, nil, options).options.should == options
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