bradphelan-sinatras-hat 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/LICENSE +22 -0
- data/README.md +235 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/bradphelan-sinatras-hat.gemspec +145 -0
- data/ci.rb +9 -0
- data/example/app-with-auth.rb +14 -0
- data/example/app-with-cache.rb +30 -0
- data/example/app.rb +23 -0
- data/example/lib/comment.rb +13 -0
- data/example/lib/common.rb +19 -0
- data/example/lib/post.rb +16 -0
- data/example/simple-app.rb +4 -0
- data/example/views/comments/index.erb +6 -0
- data/example/views/comments/show.erb +1 -0
- data/example/views/posts/index.erb +8 -0
- data/example/views/posts/new.erb +11 -0
- data/example/views/posts/show.erb +28 -0
- data/features/authenticated.feature +12 -0
- data/features/create.feature +16 -0
- data/features/destroy.feature +18 -0
- data/features/edit.feature +17 -0
- data/features/formats.feature +19 -0
- data/features/headers.feature +28 -0
- data/features/index.feature +23 -0
- data/features/layouts.feature +11 -0
- data/features/nested.feature +20 -0
- data/features/new.feature +20 -0
- data/features/only.feature +13 -0
- data/features/show.feature +31 -0
- data/features/steps/authenticated_steps.rb +10 -0
- data/features/steps/common_steps.rb +77 -0
- data/features/steps/create_steps.rb +21 -0
- data/features/steps/destroy_steps.rb +16 -0
- data/features/steps/edit_steps.rb +7 -0
- data/features/steps/format_steps.rb +11 -0
- data/features/steps/header_steps.rb +7 -0
- data/features/steps/index_steps.rb +26 -0
- data/features/steps/nested_steps.rb +11 -0
- data/features/steps/new_steps.rb +15 -0
- data/features/steps/only_steps.rb +10 -0
- data/features/steps/show_steps.rb +24 -0
- data/features/steps/update_steps.rb +22 -0
- data/features/support/env.rb +17 -0
- data/features/support/views/comments/index.erb +5 -0
- data/features/support/views/layout.erb +9 -0
- data/features/support/views/people/edit.erb +1 -0
- data/features/support/views/people/index.erb +1 -0
- data/features/support/views/people/layout.erb +9 -0
- data/features/support/views/people/new.erb +1 -0
- data/features/support/views/people/show.erb +1 -0
- data/features/update.feature +25 -0
- data/lib/core_ext/array.rb +5 -0
- data/lib/core_ext/hash.rb +23 -0
- data/lib/core_ext/module.rb +14 -0
- data/lib/core_ext/object.rb +45 -0
- data/lib/sinatras-hat.rb +22 -0
- data/lib/sinatras-hat/actions.rb +81 -0
- data/lib/sinatras-hat/authentication.rb +55 -0
- data/lib/sinatras-hat/extendor.rb +24 -0
- data/lib/sinatras-hat/hash_mutator.rb +18 -0
- data/lib/sinatras-hat/logger.rb +36 -0
- data/lib/sinatras-hat/maker.rb +187 -0
- data/lib/sinatras-hat/model.rb +110 -0
- data/lib/sinatras-hat/resource.rb +57 -0
- data/lib/sinatras-hat/responder.rb +106 -0
- data/lib/sinatras-hat/response.rb +60 -0
- data/lib/sinatras-hat/router.rb +46 -0
- data/sinatras-hat.gemspec +34 -0
- data/spec/actions/create_spec.rb +68 -0
- data/spec/actions/destroy_spec.rb +58 -0
- data/spec/actions/edit_spec.rb +52 -0
- data/spec/actions/index_spec.rb +72 -0
- data/spec/actions/new_spec.rb +39 -0
- data/spec/actions/show_spec.rb +85 -0
- data/spec/actions/update_spec.rb +83 -0
- data/spec/extendor_spec.rb +78 -0
- data/spec/fixtures/views/articles/edit.erb +1 -0
- data/spec/fixtures/views/articles/index.erb +1 -0
- data/spec/fixtures/views/articles/new.erb +1 -0
- data/spec/fixtures/views/articles/show.erb +1 -0
- data/spec/hash_mutator_spec.rb +23 -0
- data/spec/maker_spec.rb +411 -0
- data/spec/model_spec.rb +152 -0
- data/spec/resource_spec.rb +74 -0
- data/spec/responder_spec.rb +139 -0
- data/spec/response_spec.rb +120 -0
- data/spec/router_spec.rb +105 -0
- data/spec/spec_helper.rb +80 -0
- metadata +161 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Hat
|
3
|
+
# Handles the logic of generating a path for a given resource,
|
4
|
+
# taking any and all parents into consideration.
|
5
|
+
class Resource
|
6
|
+
def initialize(maker)
|
7
|
+
@maker = maker
|
8
|
+
end
|
9
|
+
|
10
|
+
def path(suffix, record=nil)
|
11
|
+
records = record ? path_records_for(record) : []
|
12
|
+
results = resources.inject("") do |memo, maker|
|
13
|
+
memo += fragment(maker, record)
|
14
|
+
end
|
15
|
+
|
16
|
+
interpolate(clean(results + suffix.dup), records)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def interpolate(uri, records)
|
22
|
+
return uri if records.empty?
|
23
|
+
uri.gsub(/:(\w+)/) { records.pop.to_param }
|
24
|
+
end
|
25
|
+
|
26
|
+
def path_records_for(record)
|
27
|
+
[record].tap do |parents|
|
28
|
+
resources.reverse.each do |resource|
|
29
|
+
parents << resource.model.find_owner(parents.last.attributes)
|
30
|
+
parents.compact!
|
31
|
+
parents.uniq!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def fragment(maker, record)
|
37
|
+
@maker.eql?(maker) ?
|
38
|
+
"/#{maker.prefix}" :
|
39
|
+
"/#{maker.prefix}/" + key(maker)
|
40
|
+
end
|
41
|
+
|
42
|
+
def key(maker)
|
43
|
+
maker.model.foreign_key.inspect
|
44
|
+
end
|
45
|
+
|
46
|
+
def clean(s)
|
47
|
+
s.downcase!
|
48
|
+
s.gsub!(%r(/$), '')
|
49
|
+
s
|
50
|
+
end
|
51
|
+
|
52
|
+
def resources
|
53
|
+
@maker.parents + [@maker]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Hat
|
3
|
+
# The responder assigns data to instance variables, then either
|
4
|
+
# gets the appropriate response proc and instance_exec's it in the
|
5
|
+
# context of a new Response object, or serializes the data.
|
6
|
+
class Responder
|
7
|
+
delegate :model, :to => :maker
|
8
|
+
|
9
|
+
attr_reader :maker
|
10
|
+
|
11
|
+
def initialize(maker)
|
12
|
+
@maker = maker
|
13
|
+
end
|
14
|
+
|
15
|
+
def defaults
|
16
|
+
@defaults ||= {
|
17
|
+
:show => {
|
18
|
+
:success => proc { |data| render(:show) },
|
19
|
+
:failure => proc { |data| redirect('/') }
|
20
|
+
},
|
21
|
+
|
22
|
+
:index => {
|
23
|
+
:success => proc { |data| render(:index) },
|
24
|
+
:failure => proc { |data| redirect('/') }
|
25
|
+
},
|
26
|
+
|
27
|
+
:create => {
|
28
|
+
:success => proc { |data| redirect(data) },
|
29
|
+
:failure => proc { |data| render(:new) }
|
30
|
+
},
|
31
|
+
|
32
|
+
:new => {
|
33
|
+
:success => proc { |data| render(:new) },
|
34
|
+
:failure => proc { |data| redirect('/') }
|
35
|
+
},
|
36
|
+
|
37
|
+
:edit => {
|
38
|
+
:success => proc { |data| render(:edit) }
|
39
|
+
},
|
40
|
+
|
41
|
+
:destroy => {
|
42
|
+
:success => proc { |data| redirect(resource_path('/')) }
|
43
|
+
},
|
44
|
+
|
45
|
+
:update => {
|
46
|
+
:success => proc { |data| redirect(data) },
|
47
|
+
:failure => proc { |data| render(:edit) }
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Called when a request is handled successfully. For most GET
|
53
|
+
# requests, this is always the case. For update/create actions,
|
54
|
+
# it is when the record is created/updated successfully.
|
55
|
+
def success(name, request, data)
|
56
|
+
handle(:success, name, request, data)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Called when a request is not able to handled. This could be
|
60
|
+
# because a record could not be created or saved.
|
61
|
+
def failure(name, request, data)
|
62
|
+
handle(:failure, name, request, data)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Serializes the data passed in, first looking for a custom formatter,
|
66
|
+
# then falling back on trying to call to_[format] on the data. If neither
|
67
|
+
# are available, returns an error with the status code 406.
|
68
|
+
def serialize(data, format)
|
69
|
+
return nil unless format
|
70
|
+
name = format.to_sym
|
71
|
+
mime = get_mime_type(format)
|
72
|
+
formatter = to_format(name)
|
73
|
+
formatter[data] ? [formatter[data], mime] : nil
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def handle(result, name, request, data)
|
79
|
+
if format = request.params[:format] || maker.format
|
80
|
+
response, mime = serialize(data, format)
|
81
|
+
request.content_type(mime) if mime
|
82
|
+
response or request.error(406)
|
83
|
+
else
|
84
|
+
request.instance_variable_set(ivar_name(data), data)
|
85
|
+
response = Response.new(maker, request)
|
86
|
+
response.instance_exec(data, &defaults[name][result])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_mime_type(format)
|
91
|
+
Rack::Mime::MIME_TYPES['.' + format.to_s]
|
92
|
+
end
|
93
|
+
|
94
|
+
def ivar_name(data)
|
95
|
+
"@" + (data.kind_of?(Array) ? model.plural : model.singular)
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_format(name)
|
99
|
+
maker.formats[name] || Proc.new do |data|
|
100
|
+
method_name = "to_#{name}"
|
101
|
+
data.respond_to?(method_name) ? data.send(method_name) : nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Sinatra
|
2
|
+
class NoTemplateError < StandardError; end
|
3
|
+
|
4
|
+
module Hat
|
5
|
+
# Tells Sinatra what to do next.
|
6
|
+
class Response
|
7
|
+
attr_reader :maker
|
8
|
+
|
9
|
+
delegate :model, :resource_path, :to => :maker
|
10
|
+
|
11
|
+
def initialize(maker, request)
|
12
|
+
@maker = maker
|
13
|
+
@request = request
|
14
|
+
end
|
15
|
+
|
16
|
+
# Now uses haml by default
|
17
|
+
def render(action, options={})
|
18
|
+
begin
|
19
|
+
options.each { |sym, value| @request.send(sym, value) }
|
20
|
+
@request.haml "#{maker.prefix}/#{action}.html".to_sym
|
21
|
+
rescue Errno::ENOENT
|
22
|
+
no_template! "Can't find #{File.expand_path(File.join(views, action.to_s))}.html.haml"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def redirect(*args)
|
27
|
+
@request.redirect url_for(*args)
|
28
|
+
end
|
29
|
+
|
30
|
+
def url_for(resource, *args)
|
31
|
+
case resource
|
32
|
+
when String then resource
|
33
|
+
when Symbol then resource_path(Maker.actions[resource][:path], *args)
|
34
|
+
else maker_for(resource).resource_path('/:id', resource)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def no_template!(msg)
|
41
|
+
raise NoTemplateError.new(msg)
|
42
|
+
end
|
43
|
+
|
44
|
+
def views
|
45
|
+
@views ||= begin
|
46
|
+
if views_dir = @request.options.views
|
47
|
+
File.join(views_dir, maker.prefix)
|
48
|
+
else
|
49
|
+
no_template! "Make sure you set the :views option!"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def maker_for(record)
|
55
|
+
resource = record.is_a?(model.klass) ? maker : maker.parents.detect { |m| record.is_a?(m.model.klass) }
|
56
|
+
resource || maker
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Hat
|
3
|
+
# Tells Sinatra which routes to generate. The routes
|
4
|
+
# created automatically when the actions are loaded.
|
5
|
+
class Router
|
6
|
+
delegate :resource_path, :logger, :to => :maker
|
7
|
+
|
8
|
+
attr_reader :maker, :app
|
9
|
+
|
10
|
+
def self.cache
|
11
|
+
@cache ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(maker)
|
15
|
+
@maker = maker
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate(app)
|
19
|
+
@app = app
|
20
|
+
|
21
|
+
Router.cache.each do |route|
|
22
|
+
map(*route)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def map(method, action, path)
|
29
|
+
path = resource_path(path)
|
30
|
+
|
31
|
+
handler = lambda do |request|
|
32
|
+
maker.handle(action, request)
|
33
|
+
end
|
34
|
+
|
35
|
+
logger.info ">> route for #{maker.klass} #{action}:\t#{method.to_s.upcase}\t#{path}"
|
36
|
+
|
37
|
+
app.send(method, "#{path}*" + "/?") do
|
38
|
+
id, format = (params[:id].to_s + params[:splat].join).split('.')
|
39
|
+
params['id'] = id
|
40
|
+
params['format'] = format
|
41
|
+
handler[self]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{sinatras-hat}
|
5
|
+
s.version = "0.1.2"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Pat Nakajima"]
|
9
|
+
s.date = %q{2009-01-17}
|
10
|
+
s.email = %q{patnakajima@gmail.com}
|
11
|
+
s.files = ["README.md", "lib/core_ext", "lib/core_ext/array.rb", "lib/core_ext/hash.rb", "lib/core_ext/module.rb", "lib/core_ext/object.rb", "lib/sinatras-hat", "lib/sinatras-hat/actions.rb", "lib/sinatras-hat/authentication.rb", "lib/sinatras-hat/extendor.rb", "lib/sinatras-hat/hash_mutator.rb", "lib/sinatras-hat/logger.rb", "lib/sinatras-hat/maker.rb", "lib/sinatras-hat/model.rb", "lib/sinatras-hat/resource.rb", "lib/sinatras-hat/responder.rb", "lib/sinatras-hat/response.rb", "lib/sinatras-hat/router.rb", "lib/sinatras-hat.rb"]
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.rubygems_version = %q{1.3.1}
|
14
|
+
s.summary = %q{Simple REST-ful resources with Sinatra.}
|
15
|
+
|
16
|
+
if s.respond_to? :specification_version then
|
17
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
18
|
+
s.specification_version = 2
|
19
|
+
|
20
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
21
|
+
s.add_runtime_dependency(%q<extlib>, [">= 0"])
|
22
|
+
s.add_runtime_dependency(%q<metaid>, [">= 0"])
|
23
|
+
s.add_runtime_dependency(%q<sinatra>, [">= 0"])
|
24
|
+
else
|
25
|
+
s.add_dependency(%q<extlib>, [">= 0"])
|
26
|
+
s.add_dependency(%q<metaid>, [">= 0"])
|
27
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
28
|
+
end
|
29
|
+
else
|
30
|
+
s.add_dependency(%q<extlib>, [">= 0"])
|
31
|
+
s.add_dependency(%q<metaid>, [">= 0"])
|
32
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe "handle create" do
|
4
|
+
attr_reader :maker, :app, :request, :article
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
build_models!
|
8
|
+
mock_app { }
|
9
|
+
@maker = new_maker(Article)
|
10
|
+
@request = fake_request("article[name]" => "The article")
|
11
|
+
stub(request).redirect(anything)
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle(*args)
|
15
|
+
maker.handle(:create, *args)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "instantiates a new record and saves it" do
|
19
|
+
mock.proxy(article = Article.new).save
|
20
|
+
mock.proxy(maker.model).new("article[name]" => "The article") { article }
|
21
|
+
handle(request)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "responding" do
|
25
|
+
attr_reader :new_article
|
26
|
+
|
27
|
+
before(:each) do
|
28
|
+
@new_article = Article.new
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when the save is successful" do
|
32
|
+
before(:each) do
|
33
|
+
stub(Article).new(anything).returns(new_article)
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when there's no format" do
|
37
|
+
it "redirects" do
|
38
|
+
mock(request).redirect(anything)
|
39
|
+
mock.proxy(maker.responder).success(:create, request, new_article)
|
40
|
+
handle(request)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when the save is not successful" do
|
46
|
+
before(:each) do
|
47
|
+
stub(Article).new(anything).returns(new_article)
|
48
|
+
stub(new_article).save { false }
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when there's no format" do
|
52
|
+
it "renders edit template" do
|
53
|
+
mock(request).erb :"articles/new"
|
54
|
+
mock.proxy(maker.responder).failure(:create, request, new_article)
|
55
|
+
handle(request)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# context "when there is a format" do
|
60
|
+
# it "serializes the record" do
|
61
|
+
# request_with_format = fake_request(:format => "yaml")
|
62
|
+
# mock.proxy(maker.responder).serialize("yaml", article)
|
63
|
+
# handle(request_with_format)
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe "handle destroy" do
|
4
|
+
attr_reader :maker, :app, :request, :article
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
build_models!
|
8
|
+
mock_app { }
|
9
|
+
@maker = new_maker(Article)
|
10
|
+
@request = fake_request(:id => @article.to_param)
|
11
|
+
stub(maker.model).find(:id => @article.to_param) { @article }
|
12
|
+
stub(request).redirect(anything)
|
13
|
+
end
|
14
|
+
|
15
|
+
def handle(*args)
|
16
|
+
maker.handle(:destroy, *args)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "takes a request" do
|
20
|
+
handle(request)
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when the record exists" do
|
24
|
+
it "loads correct record" do
|
25
|
+
mock.proxy(maker.model).find(:id => @article.to_param) { article }
|
26
|
+
handle(request)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "destroys the record" do
|
30
|
+
mock.proxy(article).destroy
|
31
|
+
handle(request)
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "rendering a response" do
|
35
|
+
before(:each) do
|
36
|
+
params = { :id => @article.to_param }
|
37
|
+
@request = fake_request(params)
|
38
|
+
stub(maker.model).find(params).returns(article)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "redirects to the index" do
|
42
|
+
mock(request).redirect('/articles')
|
43
|
+
handle(request)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when the record does not exist" do
|
49
|
+
before(:each) do
|
50
|
+
stub(maker.model).find(:id => @article.to_param) { nil }
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns not_found" do
|
54
|
+
mock.proxy(request).not_found
|
55
|
+
catch(:halt) { handle(request) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe "handle show" do
|
4
|
+
attr_reader :maker, :app, :request
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
build_models!
|
8
|
+
mock_app { }
|
9
|
+
@maker = new_maker(Article)
|
10
|
+
@request = fake_request(:id => @article.to_param)
|
11
|
+
end
|
12
|
+
|
13
|
+
def handle(*args)
|
14
|
+
maker.handle(:edit, *args)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "takes a request" do
|
18
|
+
handle(request)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "rendering not_found" do
|
22
|
+
before(:each) do
|
23
|
+
stub(maker.model).find(request.params).returns(nil)
|
24
|
+
stub(request).not_found # because it throws :halt otherwise
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns not_found" do
|
28
|
+
mock(request).not_found
|
29
|
+
handle(request)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "rendering a successful response" do
|
34
|
+
it "loads correct record" do
|
35
|
+
mock.proxy(maker.model).find(:id => @article.to_param) { :article }
|
36
|
+
handle(request)
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when there's no :format param" do
|
40
|
+
before(:each) do
|
41
|
+
params = { :id => @article.to_param }
|
42
|
+
@request = fake_request(params)
|
43
|
+
stub(maker.model).find(params).returns(:article)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "renders the show template" do
|
47
|
+
mock.proxy(maker.responder).success(:edit, request, :article)
|
48
|
+
handle(request)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|