bradphelan-sinatras-hat 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/.gitignore +4 -0
  2. data/LICENSE +22 -0
  3. data/README.md +235 -0
  4. data/Rakefile +59 -0
  5. data/VERSION +1 -0
  6. data/bradphelan-sinatras-hat.gemspec +145 -0
  7. data/ci.rb +9 -0
  8. data/example/app-with-auth.rb +14 -0
  9. data/example/app-with-cache.rb +30 -0
  10. data/example/app.rb +23 -0
  11. data/example/lib/comment.rb +13 -0
  12. data/example/lib/common.rb +19 -0
  13. data/example/lib/post.rb +16 -0
  14. data/example/simple-app.rb +4 -0
  15. data/example/views/comments/index.erb +6 -0
  16. data/example/views/comments/show.erb +1 -0
  17. data/example/views/posts/index.erb +8 -0
  18. data/example/views/posts/new.erb +11 -0
  19. data/example/views/posts/show.erb +28 -0
  20. data/features/authenticated.feature +12 -0
  21. data/features/create.feature +16 -0
  22. data/features/destroy.feature +18 -0
  23. data/features/edit.feature +17 -0
  24. data/features/formats.feature +19 -0
  25. data/features/headers.feature +28 -0
  26. data/features/index.feature +23 -0
  27. data/features/layouts.feature +11 -0
  28. data/features/nested.feature +20 -0
  29. data/features/new.feature +20 -0
  30. data/features/only.feature +13 -0
  31. data/features/show.feature +31 -0
  32. data/features/steps/authenticated_steps.rb +10 -0
  33. data/features/steps/common_steps.rb +77 -0
  34. data/features/steps/create_steps.rb +21 -0
  35. data/features/steps/destroy_steps.rb +16 -0
  36. data/features/steps/edit_steps.rb +7 -0
  37. data/features/steps/format_steps.rb +11 -0
  38. data/features/steps/header_steps.rb +7 -0
  39. data/features/steps/index_steps.rb +26 -0
  40. data/features/steps/nested_steps.rb +11 -0
  41. data/features/steps/new_steps.rb +15 -0
  42. data/features/steps/only_steps.rb +10 -0
  43. data/features/steps/show_steps.rb +24 -0
  44. data/features/steps/update_steps.rb +22 -0
  45. data/features/support/env.rb +17 -0
  46. data/features/support/views/comments/index.erb +5 -0
  47. data/features/support/views/layout.erb +9 -0
  48. data/features/support/views/people/edit.erb +1 -0
  49. data/features/support/views/people/index.erb +1 -0
  50. data/features/support/views/people/layout.erb +9 -0
  51. data/features/support/views/people/new.erb +1 -0
  52. data/features/support/views/people/show.erb +1 -0
  53. data/features/update.feature +25 -0
  54. data/lib/core_ext/array.rb +5 -0
  55. data/lib/core_ext/hash.rb +23 -0
  56. data/lib/core_ext/module.rb +14 -0
  57. data/lib/core_ext/object.rb +45 -0
  58. data/lib/sinatras-hat.rb +22 -0
  59. data/lib/sinatras-hat/actions.rb +81 -0
  60. data/lib/sinatras-hat/authentication.rb +55 -0
  61. data/lib/sinatras-hat/extendor.rb +24 -0
  62. data/lib/sinatras-hat/hash_mutator.rb +18 -0
  63. data/lib/sinatras-hat/logger.rb +36 -0
  64. data/lib/sinatras-hat/maker.rb +187 -0
  65. data/lib/sinatras-hat/model.rb +110 -0
  66. data/lib/sinatras-hat/resource.rb +57 -0
  67. data/lib/sinatras-hat/responder.rb +106 -0
  68. data/lib/sinatras-hat/response.rb +60 -0
  69. data/lib/sinatras-hat/router.rb +46 -0
  70. data/sinatras-hat.gemspec +34 -0
  71. data/spec/actions/create_spec.rb +68 -0
  72. data/spec/actions/destroy_spec.rb +58 -0
  73. data/spec/actions/edit_spec.rb +52 -0
  74. data/spec/actions/index_spec.rb +72 -0
  75. data/spec/actions/new_spec.rb +39 -0
  76. data/spec/actions/show_spec.rb +85 -0
  77. data/spec/actions/update_spec.rb +83 -0
  78. data/spec/extendor_spec.rb +78 -0
  79. data/spec/fixtures/views/articles/edit.erb +1 -0
  80. data/spec/fixtures/views/articles/index.erb +1 -0
  81. data/spec/fixtures/views/articles/new.erb +1 -0
  82. data/spec/fixtures/views/articles/show.erb +1 -0
  83. data/spec/hash_mutator_spec.rb +23 -0
  84. data/spec/maker_spec.rb +411 -0
  85. data/spec/model_spec.rb +152 -0
  86. data/spec/resource_spec.rb +74 -0
  87. data/spec/responder_spec.rb +139 -0
  88. data/spec/response_spec.rb +120 -0
  89. data/spec/router_spec.rb +105 -0
  90. data/spec/spec_helper.rb +80 -0
  91. metadata +161 -0
data/ci.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'dash-ci'
3
+
4
+ CI.register('sinatras-hat') do
5
+ build :specs, 'spec spec/'
6
+ build :cucumber, 'cucumber -f progress features/'
7
+ end
8
+
9
+ CI.run('sinatras-hat', :token => ENV['DASH_TOKEN'])
@@ -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!
@@ -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,13 @@
1
+ require 'dm-core'
2
+ require 'dm-serializer'
3
+
4
+ class Comment
5
+ include DataMapper::Resource
6
+ include DataMapper::Serialize
7
+
8
+ property :id, Serial, :key => true
9
+ property :post_id, Integer
10
+ property :body, Text
11
+
12
+ belongs_to :post
13
+ end
@@ -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
@@ -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,4 @@
1
+ require File.dirname(__FILE__) + '/lib/common.rb'
2
+
3
+ # This is the simplest possible app
4
+ mount(Post)
@@ -0,0 +1,6 @@
1
+ <div>
2
+ <% @comments.each do |comment| %>
3
+ <h3>Comments on <a href="/posts/<%= comment.post.id %>"><%= comment.post.name %></a>:</h3>
4
+ <blockquote><%= comment.body %></blockquote>
5
+ <% end %>
6
+ </div>
@@ -0,0 +1 @@
1
+ <h1><%= @comment.body %></h1>
@@ -0,0 +1,8 @@
1
+ <h1>Posts! <small><a href="/posts/new">Create one.</a></small></h1>
2
+ <div>
3
+ <% @posts.each do |post| %>
4
+ <h1><%= post.name %></h1>
5
+ <p><%= post.body %></p>
6
+ <small><a href="/posts/<%= post.id %>">Click here to go there.</a></small>
7
+ <% end %>
8
+ </div>
@@ -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! &rarr;"></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