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
data/ci.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/lib/common.rb'
|
2
|
+
|
3
|
+
class MountedApp < Sinatra::Base
|
4
|
+
set :app_file, __FILE__
|
5
|
+
set :logging, true
|
6
|
+
mount(Post) do
|
7
|
+
protect :index,
|
8
|
+
:username => 'bliggety',
|
9
|
+
:password => 'blam',
|
10
|
+
:realm => "Use Protection"
|
11
|
+
end
|
12
|
+
|
13
|
+
run!
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/lib/common.rb'
|
2
|
+
require 'rack/cache'
|
3
|
+
|
4
|
+
class MountedApp < Sinatra::Base
|
5
|
+
set :app_file, __FILE__
|
6
|
+
set :logging, true
|
7
|
+
|
8
|
+
use Rack::Cache do
|
9
|
+
set :verbose, true
|
10
|
+
set :metastore, 'heap:/'
|
11
|
+
set :entitystore, 'heap:/'
|
12
|
+
end
|
13
|
+
|
14
|
+
get '/' do
|
15
|
+
redirect '/posts'
|
16
|
+
end
|
17
|
+
|
18
|
+
mount(Post) do
|
19
|
+
finder { |model, params| model.all }
|
20
|
+
record { |model, params| model.first(:id => params[:id]) }
|
21
|
+
|
22
|
+
# Mount children as a nested resource
|
23
|
+
mount(Comment) do
|
24
|
+
finder { |model, params| model.all }
|
25
|
+
record { |model, params| model.first(:id => params[:id]) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
MountedApp.run!
|
data/example/app.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/lib/common.rb'
|
2
|
+
|
3
|
+
class MountedApp < Sinatra::Base
|
4
|
+
set :app_file, __FILE__
|
5
|
+
set :logging, true
|
6
|
+
|
7
|
+
get '/' do
|
8
|
+
"You created a post, and this is a custom response."
|
9
|
+
end
|
10
|
+
|
11
|
+
mount(Post) do
|
12
|
+
finder { |model, params| model.all }
|
13
|
+
record { |model, params| model.first(:id => params[:id]) }
|
14
|
+
|
15
|
+
# Mount children as a nested resource
|
16
|
+
mount(Comment) do
|
17
|
+
finder { |model, params| model.all }
|
18
|
+
record { |model, params| model.first(:id => params[:id]) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
MountedApp.run! if __FILE__ == $0
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..')
|
2
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'sinatra'
|
6
|
+
require 'lib/sinatras-hat'
|
7
|
+
|
8
|
+
# Models
|
9
|
+
require 'post'
|
10
|
+
require 'comment'
|
11
|
+
|
12
|
+
configure do
|
13
|
+
DataMapper.setup(:default, 'sqlite3:db.sqlite3')
|
14
|
+
Post.auto_migrate!
|
15
|
+
Comment.auto_migrate!
|
16
|
+
Post.create :name => 'A test', :body => "Some sort of thing"
|
17
|
+
new_post = Post.create :name => 'Another test', :body => "This is some other sort of thing"
|
18
|
+
new_post.comments.create :body => "A comment"
|
19
|
+
end
|
data/example/lib/post.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
require 'dm-serializer'
|
3
|
+
require 'dm-timestamps'
|
4
|
+
|
5
|
+
class Post
|
6
|
+
include DataMapper::Resource
|
7
|
+
include DataMapper::Serialize
|
8
|
+
|
9
|
+
property :id, Serial, :key => true
|
10
|
+
property :name, String
|
11
|
+
property :body, Text
|
12
|
+
property :created_at, DateTime
|
13
|
+
property :updated_at, DateTime
|
14
|
+
|
15
|
+
has n, :comments
|
16
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1><%= @comment.body %></h1>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<form action="/posts" method="post">
|
2
|
+
<h1>Create a new post:</h1>
|
3
|
+
<input type="text" name="post[name]">
|
4
|
+
<br>
|
5
|
+
<textarea name="post[body]" rows="8" cols="40"></textarea>
|
6
|
+
<p>
|
7
|
+
<input type="submit" value="Create Post!"> or
|
8
|
+
<a href="/posts">Cancel</a>
|
9
|
+
</p>
|
10
|
+
</form>
|
11
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<h1><%= @post.name %></h1>
|
2
|
+
<p><%= @post.body %></p>
|
3
|
+
|
4
|
+
<form action="/posts/<%= @post.id %>" method="post">
|
5
|
+
<input type="hidden" name="_method" value="delete">
|
6
|
+
<p><input type="submit" value="Delete"></p>
|
7
|
+
</form>
|
8
|
+
|
9
|
+
<div>
|
10
|
+
|
11
|
+
<h4><a href="/posts/<%= @post.id %>/comments">Comments</a></h4>
|
12
|
+
<% @post.comments.each do |comment| %>
|
13
|
+
<blockquote>
|
14
|
+
<%= comment.body %>
|
15
|
+
<small>
|
16
|
+
<a href="/posts/<%= @post.id %>/comments/<%= comment.id %>">View it.</a>
|
17
|
+
</small>
|
18
|
+
</blockquote>
|
19
|
+
<% end %>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
<form action="/posts/<%= @post.id %>/comments" method="post">
|
23
|
+
<p>Leave a comment:</p>
|
24
|
+
<textarea name="comment[body]" rows="8" cols="40"></textarea>
|
25
|
+
<p><input type="submit" value="Comment! →"></p>
|
26
|
+
</form>
|
27
|
+
|
28
|
+
<small><a href="/posts">Click here to go back.</a></small>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Feature: Protected actions
|
2
|
+
As a developer
|
3
|
+
I want to protect certain actions
|
4
|
+
So that I maintain privacy of my app
|
5
|
+
|
6
|
+
Scenario: A request to a protected action
|
7
|
+
Given a model that has a record
|
8
|
+
And I mount the model protecing the show action
|
9
|
+
When I make a GET request for that record
|
10
|
+
Then the status code is 401
|
11
|
+
When Make a GET request to the index without a format
|
12
|
+
Then the status code is 200
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Feature: Generating a "create" action
|
2
|
+
As a developer
|
3
|
+
I want to generate a "create" action
|
4
|
+
So that I don't have to manually code it
|
5
|
+
|
6
|
+
Scenario: POSTing valid form parameters
|
7
|
+
Given a mounted model
|
8
|
+
When I make a POST request with valid form params
|
9
|
+
Then a record is created
|
10
|
+
And the response redirects to the record show page
|
11
|
+
|
12
|
+
Scenario: POSTing invalid form parameters
|
13
|
+
Given a mounted model
|
14
|
+
When I make a POST request with invalid form params
|
15
|
+
Then a record is not created
|
16
|
+
And I should see "So, you want to create a new Person?"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Feature: Generating a "destroy" action
|
2
|
+
As a developer
|
3
|
+
I want to generate a "destroy" action
|
4
|
+
So that I don't have to manually code it
|
5
|
+
|
6
|
+
Scenario: Deleting a record
|
7
|
+
Given a model that has a record
|
8
|
+
And I mount the model
|
9
|
+
When I make a DELETE request to the path for that record
|
10
|
+
Then the record gets destroyed
|
11
|
+
And I am redirected to the index action
|
12
|
+
|
13
|
+
Scenario: Trying to delete a non-existent record
|
14
|
+
Given a model that does not have a record
|
15
|
+
And I mount the model
|
16
|
+
When I make a DELETE request to a path for a non-existent record
|
17
|
+
Then the status code is 404
|
18
|
+
And the body is empty
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Feature: Generating an "edit" action
|
2
|
+
As a developer
|
3
|
+
I want to generate an "edit" action
|
4
|
+
So that I don't have to manually code it
|
5
|
+
|
6
|
+
Scenario: Getting the edit page for a record
|
7
|
+
Given a model that has a record
|
8
|
+
And I mount the model
|
9
|
+
When I get the edit page for that record
|
10
|
+
Then I should see "Editing"
|
11
|
+
|
12
|
+
Scenario: Getting the edit page for a non-existent record
|
13
|
+
Given a model that has a record
|
14
|
+
And I mount the model
|
15
|
+
When I get the edit page for a non-existent record
|
16
|
+
Then the status code is 404
|
17
|
+
And the body is empty
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Feature: Custom format handlers
|
2
|
+
As a developer
|
3
|
+
I want to provide custom format handlers
|
4
|
+
So that I expose data in more interesting ways
|
5
|
+
|
6
|
+
Scenario: A valid request with a custom format
|
7
|
+
Given a model that has a record
|
8
|
+
And I mount the model
|
9
|
+
And specify a custom 'ruby' formatter
|
10
|
+
When I make a GET request for that record using the 'ruby' format
|
11
|
+
Then the status code is 200
|
12
|
+
And the body is the custom serialized record
|
13
|
+
|
14
|
+
Scenario: A request with a known format specified
|
15
|
+
Given a mounted model
|
16
|
+
And the model has some records
|
17
|
+
When Make a GET request to the index using the 'ruby' format
|
18
|
+
Then the status code is 200
|
19
|
+
And the result should be custom serialized
|
@@ -0,0 +1,28 @@
|
|
1
|
+
Feature: Setting appropriate headers
|
2
|
+
In order to understand more about a response
|
3
|
+
As a developer
|
4
|
+
I want Sinatra's Hat to set useful headers
|
5
|
+
|
6
|
+
Scenario: Setting the last_modified for show
|
7
|
+
Given a model that has a record
|
8
|
+
And I mount the model
|
9
|
+
When I get the show page for that record
|
10
|
+
Then "Last-Modified" should be the record "updated_at" time
|
11
|
+
|
12
|
+
Scenario: Setting the etag for show
|
13
|
+
Given a model that has a record
|
14
|
+
And I mount the model
|
15
|
+
When I get the show page for that record
|
16
|
+
Then "ETag" should be set
|
17
|
+
|
18
|
+
Scenario: Setting the last_modified for index
|
19
|
+
Given a model that has a record
|
20
|
+
And I mount the model
|
21
|
+
When Make a GET request to the index without a format
|
22
|
+
Then "Last-Modified" should be the record "updated_at" time
|
23
|
+
|
24
|
+
Scenario: Setting the etag for show
|
25
|
+
Given a model that has a record
|
26
|
+
And I mount the model
|
27
|
+
When Make a GET request to the index without a format
|
28
|
+
Then "ETag" should be set
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Feature: Generating an "index" action
|
2
|
+
As a developer
|
3
|
+
I want to generate an "index" action
|
4
|
+
So that I don't have to manually code it
|
5
|
+
|
6
|
+
Scenario: A request without a format specified
|
7
|
+
Given a mounted model
|
8
|
+
And the model has some records
|
9
|
+
When Make a GET request to the index without a format
|
10
|
+
Then I should see "The people:"
|
11
|
+
|
12
|
+
Scenario: A request with a known format specified
|
13
|
+
Given a mounted model
|
14
|
+
And the model has some records
|
15
|
+
When Make a GET request to the index with a known format
|
16
|
+
Then the result should be serialized
|
17
|
+
|
18
|
+
Scenario: A request with an unknown format
|
19
|
+
Given a mounted model
|
20
|
+
And the model has some records
|
21
|
+
When I make a GET request to the index with an unknown format
|
22
|
+
Then the body is empty
|
23
|
+
And the status code is 406
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Feature: Rendering view layouts
|
2
|
+
In order to reduce duplication
|
3
|
+
As a developer
|
4
|
+
I want use common layouts
|
5
|
+
|
6
|
+
Scenario: Rendering the default layout
|
7
|
+
Given a mounted model
|
8
|
+
And the model has some records
|
9
|
+
When Make a GET request to the index without a format
|
10
|
+
Then I should see "TEH LAYOUTZ"
|
11
|
+
And I should see "The people:"
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Feature: Generating an "index" action for a child resource
|
2
|
+
As a developer
|
3
|
+
I want to generate an "index" action for a child resource
|
4
|
+
So that I don't have to manually code it
|
5
|
+
|
6
|
+
Scenario: Getting the template
|
7
|
+
Given a model that has a record
|
8
|
+
And the record has children
|
9
|
+
And I mount the model
|
10
|
+
When Make a GET request to the nested index without a format
|
11
|
+
Then the status code is 200
|
12
|
+
Then I should see "The nested view!"
|
13
|
+
|
14
|
+
Scenario: Requesting a valid format
|
15
|
+
Given a model that has a record
|
16
|
+
And the record has children
|
17
|
+
And I mount the model
|
18
|
+
When Make a GET request to the nested index with a valid format
|
19
|
+
Then the status code is 200
|
20
|
+
Then the body is the serialized list of children
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Feature: Generating a "new" action
|
2
|
+
As a developer
|
3
|
+
I want to generate a "new" action
|
4
|
+
So that I don't have to manually code it
|
5
|
+
|
6
|
+
Scenario: Getting the new action without a format
|
7
|
+
Given a mounted model
|
8
|
+
When I get the new page for that record
|
9
|
+
Then I should see "So, you want to create a new Person?"
|
10
|
+
|
11
|
+
Scenario: Getting the new action with a valid format
|
12
|
+
Given a mounted model
|
13
|
+
When I get the new action with a valid format
|
14
|
+
Then the body is a serialized new record
|
15
|
+
|
16
|
+
Scenario: Getting the new action with an invalid format
|
17
|
+
Given a mounted model
|
18
|
+
When I get the new action with an invalid format
|
19
|
+
Then the status code is 406
|
20
|
+
And the body is empty
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Feature: Limiting the amount of actions defined
|
2
|
+
As a developer
|
3
|
+
I want to generate specify the actions that get generated
|
4
|
+
So that unnecessary actions don't get defined
|
5
|
+
|
6
|
+
Scenario: Calling only in the block
|
7
|
+
Given a model that has a record
|
8
|
+
And I mount the model for only the :index action
|
9
|
+
When Make a GET request to the index without a format
|
10
|
+
Then I should see "The people:"
|
11
|
+
|
12
|
+
When I get the show page for that record
|
13
|
+
Then the status code is 404
|
@@ -0,0 +1,31 @@
|
|
1
|
+
Feature: Generating a "show" action
|
2
|
+
As a developer
|
3
|
+
I want to generate a "show" action
|
4
|
+
So that I don't have to manually code it
|
5
|
+
|
6
|
+
Scenario: A valid request without a format
|
7
|
+
Given a model that has a record
|
8
|
+
And I mount the model
|
9
|
+
When I get the show page for that record
|
10
|
+
Then I should see "The person:"
|
11
|
+
|
12
|
+
Scenario: Trying to show a record that does not exist
|
13
|
+
Given a model that does not have a record
|
14
|
+
And I mount the model
|
15
|
+
When I get the show page for the non-existent record
|
16
|
+
Then the status code is 404
|
17
|
+
And the body is empty
|
18
|
+
|
19
|
+
Scenario: A valid request with a built-in format
|
20
|
+
Given a model that has a record
|
21
|
+
And I mount the model
|
22
|
+
When I make a GET request for that record using the 'xml' format
|
23
|
+
Then the status code is 200
|
24
|
+
And the body is the serialized record
|
25
|
+
|
26
|
+
Scenario: An invalid request with a format
|
27
|
+
Given a model that does not have a record
|
28
|
+
And I mount the model
|
29
|
+
When I get the GET request for a non-existent record with a format
|
30
|
+
Then the status code is 404
|
31
|
+
And the body is empty
|