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
@@ -0,0 +1,4 @@
1
+ *.sqlite3
2
+ *.*.sw*
3
+ vendor/*
4
+ pkg/*
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2008-2009 Pat Nakajima
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,235 @@
1
+ # Sinatra's Hat
2
+
3
+ Easy REST-ful apps with Sinatra.
4
+
5
+ Using Sinatra's Hat is centered around the `mount` method, which is
6
+ added to `Sinatra::Base`. It takes at bare minimum a model class. This
7
+ class will be mounted as a REST-ful resource, giving you all the CRUD
8
+ actions, as well as `new` and `edit` actions. Let's look at some code:
9
+
10
+ <pre>
11
+ mount Article
12
+ </pre>
13
+
14
+ Now you've basically got the same functionality as you'll get in Rails
15
+ by running `script/generate scaffold Article`. The views for your `Article`
16
+ will live in `views/articles`, and be named `index.erb`, `show.erb`, etc.
17
+
18
+ You can look at my [Hatter](http://github.com/nakajima/hatter/tree/master)
19
+ project, or the `examples/` directory in this one to see this in action.
20
+
21
+ Go ahead, try it.
22
+
23
+ ## ORM agnostic? Or ORM atheist?
24
+
25
+ By default, Sinatra's Hat works with ActiveRecord. That means that going
26
+ to `/articles` will simply call `Article.all` to populate the `@articles`
27
+ instance variable. Going to `/articles/2` will call `Article.find_by_id(2)`
28
+ to populate the `@article` instance variable.
29
+
30
+ We call `find_by_id` instead of `find` because the `record` option should
31
+ simply return `nil` when the record can't be found.
32
+
33
+ Not every class is an ActiveRecord though (especially if you're not using
34
+ ActiveRecord). That's why you can use the `finder` and `record` options.
35
+
36
+ This example will show you how to use DataMapper with Sinatra's Hat:
37
+
38
+ <pre>
39
+ mount Article do
40
+ finder { |model, params| model.all }
41
+ record { |model, params| model.first(:id => params[:id]) }
42
+ end
43
+ </pre>
44
+
45
+ As you can see, both `finder` and `record` take a block, which will get
46
+ passed the "model" and `params` for each request. The reason you should use
47
+ the "model" argument instead of referencing the class directly is that
48
+ when you start nesting mounted models, then Sinatra's Hat will attempt to
49
+ pass the association proxy as the model argument instead of the class itself.
50
+
51
+ "Nested mounted models?" you ask?
52
+
53
+ ## Nested mounted models.
54
+
55
+ You don't want to have to expose your entire application at the top level
56
+ of URL paths. That wouldn't be very RESTful, and more importantly, it'd be
57
+ damn ugly. So Sinatra's Hat allows you to nest resources:
58
+
59
+ <pre>
60
+ mount Article do
61
+ mount Comment
62
+ end
63
+ </pre>
64
+
65
+ With this example, you'd get `/articles/1/comments`, `/articles/1/comments/1`
66
+ and all the rest of the actions you get for articles, just nested. As long
67
+ as your `Article` model supports a `comments` association proxy, then the `finder`
68
+ and `record` options for `Comment` will automatically scope their results by
69
+ the parent `Article`.
70
+
71
+ ## Limiting routes
72
+
73
+ By default, Sinatra's Hat creates seven routes for each mounted model (the
74
+ four ones for <acronym title="Create|Read|Update|Destroy">CRUD</acronym>
75
+ actions plus the routes for index, new and edit action), but you can reduce
76
+ the number of available routes with `only`:
77
+
78
+ <pre>
79
+ mount Article do
80
+ only :index, :show
81
+ end
82
+ </pre>
83
+
84
+ Only the listed actions will return valid responses; requests for the
85
+ "missing" routes will produce 404 "Not Found" HTTP responses.
86
+
87
+ ## Basic Auth
88
+
89
+ To protect actions using basic authentication, you can use the `protect` method.
90
+
91
+ <pre>
92
+ mount Article do
93
+ protect :create, :update, :destroy, :username => "foo", :password => "bar", :realm => "BLOGZ"
94
+ end
95
+ </pre>
96
+
97
+ The above snippet will protect your <acronym title="Create|Update|Destroy">CUD</acronym>
98
+ actions with basic auth, using the username "foo" and password "bar". The realm
99
+ for the basic auth prompt will say "BLOGZ".
100
+
101
+ If you want to protect all of your actions, you cay say `protect :all`.
102
+
103
+ ## `.xml`, `.json`, `.yaml`, and whatever else you want
104
+
105
+ If a request has a format extensions, then Sinatra's Hat will first check
106
+ to see if it has a custom way of serializing that format. To specify a
107
+ custom formatter, you can use the `formats` hash:
108
+
109
+ <pre>
110
+ mount Article do
111
+ formats[:ruby] = { |data| data.inspect }
112
+ end
113
+ </pre>
114
+
115
+ With that custom formatter, a request to `/articles.ruby` will return
116
+ the equivalent of `Article.all.inspect`.
117
+
118
+ ### Automatic formatters
119
+
120
+ If you don't specify a custom formatter, then Sinatra's Hat will try to
121
+ call `to_#{format}` on the record object. That means that with most ORMs,
122
+ things like `to_xml`, `to_json`, and `to_yaml` will be supported right out
123
+ of the box.
124
+
125
+ Requests for unknow formats will produce 406 "Not Acceptable" HTTP responses.
126
+
127
+ ## Default Flows
128
+
129
+ Sinatra's Hat has some default flows:
130
+
131
+ ### After the `create` action
132
+
133
+ **On Success**: If a record is successfully created, Sinatra's Hat will redirect to that
134
+ record's show page.
135
+
136
+ **On Failure**: If a record cannot be saved, Sinatra's Hat will render the `new` action.
137
+
138
+ ### After the `Update` action
139
+
140
+ **On Success**: If a record is successfully updated, Sinatra's Hat will redirect to that
141
+ record's show page.
142
+
143
+ **On Failure**: If a record cannot be updated, Sinatra's Hat will render the `edit` action.
144
+
145
+ ## Custom Flows
146
+
147
+ To specify custom flows for your actions, you can use the `after` method.
148
+
149
+ Let's say that after a user creates an Article, you want to render the
150
+ article's edit action, and if it can't be created, you want to redirect
151
+ back to the articles index.
152
+
153
+ <pre>
154
+ mount Article do
155
+ after :create do |on|
156
+ on.success { |record| render(:edit) }
157
+ on.failure { |record| redirect(:index) }
158
+ end
159
+ end
160
+ </pre>
161
+
162
+ Only `:create` and `:update` actions allow to handle success and failure
163
+ differently; for the other actions you can customize only the `success` result
164
+ and if something goes wrong (i.e., when a record cannot be found) they will
165
+ simply return a 404 "Not Found" HTTP response.
166
+
167
+ ### `redirect` options
168
+
169
+ When specifying a custom redirect, you can pass one of a few things:
170
+
171
+ #### A String
172
+
173
+ When you pass `redirect` a string, the redirect will go to that string.
174
+
175
+ <pre>
176
+ after :create do |on|
177
+ on.success { |record| redirect("/articles/#{record.to_param}") }
178
+ end
179
+ </pre>
180
+
181
+ #### A Record
182
+
183
+ When you pass `redirect` a record, the redirect will go to the show
184
+ action for that record.
185
+
186
+ <pre>
187
+ after :create do |on|
188
+ on.success { |record| redirect(record) }
189
+ end
190
+ </pre>
191
+
192
+ #### A symbol
193
+
194
+ If you pass `redirect` the name of an action as a symbol (like `:index`),
195
+ then the redirect will go to the correct path for that option:
196
+
197
+ <pre>
198
+ after :create do |on|
199
+ on.success { redirect(:index) }
200
+ end
201
+ </pre>
202
+
203
+ When the action requires a record (like `:show`), then just pass the
204
+ record as a second argument:
205
+
206
+ <pre>
207
+ after :create do |on|
208
+ on.success { |record| redirect(:show, record) }
209
+ end
210
+ </pre>
211
+
212
+ ### Responding with a `render`
213
+
214
+ When you want your response to just render a template, just call `render`
215
+ with the name of the template:
216
+
217
+ <pre>
218
+ after :create do |on|
219
+ on.failure { |record| render(:new) }
220
+ end
221
+ </pre>
222
+
223
+ ## Todo
224
+
225
+ * Make `last_modified` calls more efficient
226
+ * Investigate other forms of caching
227
+
228
+ ## Other Info
229
+
230
+ * [View the Lighthouse Project](http://nakajima.lighthouseapp.com/projects/24609-sinatras-hat/overview)
231
+ * [View the CI build](http://ci.patnakajima.com/sinatra-s-hat)
232
+ * Thanks a ton to the [Sinatra team](http://github.com/sinatra) for such an
233
+ awesome framework.
234
+
235
+ (c) Copyright 2008-2009 Pat Nakajima. All Rights Reserved.
@@ -0,0 +1,59 @@
1
+ require 'spec/rake/spectask'
2
+ require 'rake/classic_namespace'
3
+ require 'cucumber/rake/task'
4
+
5
+ task :default => [:spec, :features]
6
+
7
+ desc "Run all specs"
8
+ Spec::Rake::SpecTask.new('spec') do |t|
9
+ t.spec_files = FileList['spec/**/*.rb']
10
+ t.spec_opts = ['--colour']
11
+ end
12
+
13
+ begin
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gemspec|
16
+ gemspec.name = "bradphelan-sinatras-hat"
17
+ gemspec.summary = "Easy peasy CRUD with sinatra"
18
+ gemspec.description = "Easy peasy CRUD with sinatra"
19
+ gemspec.email = "bradphelan@xtargets.com"
20
+ gemspec.homepage = "http://github.com/bradphelan/sinatras-hat"
21
+ gemspec.authors = ["Pat Nakajima", "Brad Phelan" ]
22
+ end
23
+ Jeweler::GemcutterTasks.new
24
+ rescue LoadError
25
+ puts "Jeweler not available. Install it with: gem install jeweler"
26
+ end
27
+
28
+ Cucumber::Rake::Task.new do |c|
29
+ c.cucumber_opts = '--format progress'
30
+ end
31
+
32
+ namespace :sinatra do
33
+ desc "Clone edge Sinatra"
34
+ task :clone do
35
+ vendor_dir = File.join(File.dirname(__FILE__), 'vendor')
36
+ FileUtils.mkdir_p(vendor_dir)
37
+ puts "* cloning git://github.com/rtomayko/sinatra.git"
38
+ system("git clone git://github.com/rtomayko/sinatra.git #{File.expand_path(vendor_dir)}/sinatra")
39
+ puts "* done."
40
+ end
41
+
42
+ desc "Update edge Sinatra"
43
+ task :pull do
44
+ sinatra_dir = File.join(File.dirname(__FILE__), 'vendor', 'sinatra')
45
+ Task["sinatra:clone"].invoke unless File.exists?(sinatra_dir)
46
+
47
+ puts "* pulling from git://github.com/rtomayko/sinatra.git"
48
+ system("cd #{File.expand_path(sinatra_dir)} && git pull git://github.com/rtomayko/sinatra.git master")
49
+ puts "* done."
50
+ end
51
+
52
+ desc "Install edge Sinatra"
53
+ task :install => :pull do
54
+ sinatra_dir = File.join(File.dirname(__FILE__), 'vendor', 'sinatra')
55
+ Task["sinatra:clone"].invoke unless File.exists?(sinatra_dir)
56
+ puts "* installing edge sinatra"
57
+ system("cd #{File.expand_path(sinatra_dir)} && rake install")
58
+ end
59
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.2
@@ -0,0 +1,145 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{bradphelan-sinatras-hat}
8
+ s.version = "0.1.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Pat Nakajima", "Brad Phelan"]
12
+ s.date = %q{2010-03-27}
13
+ s.description = %q{Easy peasy CRUD with sinatra}
14
+ s.email = %q{bradphelan@xtargets.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.md",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "bradphelan-sinatras-hat.gemspec",
26
+ "ci.rb",
27
+ "example/app-with-auth.rb",
28
+ "example/app-with-cache.rb",
29
+ "example/app.rb",
30
+ "example/lib/comment.rb",
31
+ "example/lib/common.rb",
32
+ "example/lib/post.rb",
33
+ "example/simple-app.rb",
34
+ "example/views/comments/index.erb",
35
+ "example/views/comments/show.erb",
36
+ "example/views/posts/index.erb",
37
+ "example/views/posts/new.erb",
38
+ "example/views/posts/show.erb",
39
+ "features/authenticated.feature",
40
+ "features/create.feature",
41
+ "features/destroy.feature",
42
+ "features/edit.feature",
43
+ "features/formats.feature",
44
+ "features/headers.feature",
45
+ "features/index.feature",
46
+ "features/layouts.feature",
47
+ "features/nested.feature",
48
+ "features/new.feature",
49
+ "features/only.feature",
50
+ "features/show.feature",
51
+ "features/steps/authenticated_steps.rb",
52
+ "features/steps/common_steps.rb",
53
+ "features/steps/create_steps.rb",
54
+ "features/steps/destroy_steps.rb",
55
+ "features/steps/edit_steps.rb",
56
+ "features/steps/format_steps.rb",
57
+ "features/steps/header_steps.rb",
58
+ "features/steps/index_steps.rb",
59
+ "features/steps/nested_steps.rb",
60
+ "features/steps/new_steps.rb",
61
+ "features/steps/only_steps.rb",
62
+ "features/steps/show_steps.rb",
63
+ "features/steps/update_steps.rb",
64
+ "features/support/env.rb",
65
+ "features/support/views/comments/index.erb",
66
+ "features/support/views/layout.erb",
67
+ "features/support/views/people/edit.erb",
68
+ "features/support/views/people/index.erb",
69
+ "features/support/views/people/layout.erb",
70
+ "features/support/views/people/new.erb",
71
+ "features/support/views/people/show.erb",
72
+ "features/update.feature",
73
+ "lib/core_ext/array.rb",
74
+ "lib/core_ext/hash.rb",
75
+ "lib/core_ext/module.rb",
76
+ "lib/core_ext/object.rb",
77
+ "lib/sinatras-hat.rb",
78
+ "lib/sinatras-hat/actions.rb",
79
+ "lib/sinatras-hat/authentication.rb",
80
+ "lib/sinatras-hat/extendor.rb",
81
+ "lib/sinatras-hat/hash_mutator.rb",
82
+ "lib/sinatras-hat/logger.rb",
83
+ "lib/sinatras-hat/maker.rb",
84
+ "lib/sinatras-hat/model.rb",
85
+ "lib/sinatras-hat/resource.rb",
86
+ "lib/sinatras-hat/responder.rb",
87
+ "lib/sinatras-hat/response.rb",
88
+ "lib/sinatras-hat/router.rb",
89
+ "sinatras-hat.gemspec",
90
+ "spec/actions/create_spec.rb",
91
+ "spec/actions/destroy_spec.rb",
92
+ "spec/actions/edit_spec.rb",
93
+ "spec/actions/index_spec.rb",
94
+ "spec/actions/new_spec.rb",
95
+ "spec/actions/show_spec.rb",
96
+ "spec/actions/update_spec.rb",
97
+ "spec/extendor_spec.rb",
98
+ "spec/fixtures/views/articles/edit.erb",
99
+ "spec/fixtures/views/articles/index.erb",
100
+ "spec/fixtures/views/articles/new.erb",
101
+ "spec/fixtures/views/articles/show.erb",
102
+ "spec/hash_mutator_spec.rb",
103
+ "spec/maker_spec.rb",
104
+ "spec/model_spec.rb",
105
+ "spec/resource_spec.rb",
106
+ "spec/responder_spec.rb",
107
+ "spec/response_spec.rb",
108
+ "spec/router_spec.rb",
109
+ "spec/spec_helper.rb"
110
+ ]
111
+ s.homepage = %q{http://github.com/bradphelan/sinatras-hat}
112
+ s.rdoc_options = ["--charset=UTF-8"]
113
+ s.require_paths = ["lib"]
114
+ s.rubygems_version = %q{1.3.5}
115
+ s.summary = %q{Easy peasy CRUD with sinatra}
116
+ s.test_files = [
117
+ "spec/actions/update_spec.rb",
118
+ "spec/actions/new_spec.rb",
119
+ "spec/actions/destroy_spec.rb",
120
+ "spec/actions/show_spec.rb",
121
+ "spec/actions/index_spec.rb",
122
+ "spec/actions/edit_spec.rb",
123
+ "spec/actions/create_spec.rb",
124
+ "spec/extendor_spec.rb",
125
+ "spec/resource_spec.rb",
126
+ "spec/router_spec.rb",
127
+ "spec/response_spec.rb",
128
+ "spec/maker_spec.rb",
129
+ "spec/model_spec.rb",
130
+ "spec/responder_spec.rb",
131
+ "spec/hash_mutator_spec.rb",
132
+ "spec/spec_helper.rb"
133
+ ]
134
+
135
+ if s.respond_to? :specification_version then
136
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
137
+ s.specification_version = 3
138
+
139
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
140
+ else
141
+ end
142
+ else
143
+ end
144
+ end
145
+