darkhelmet-sinatra_more 0.3.33

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.
Files changed (121) hide show
  1. data/.document +5 -0
  2. data/.gitignore +23 -0
  3. data/IDEAS.md +54 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +756 -0
  6. data/ROADMAP +88 -0
  7. data/Rakefile +63 -0
  8. data/TODO +60 -0
  9. data/VERSION +1 -0
  10. data/bin/sinatra_gen +6 -0
  11. data/generators/base_app/.gitignore +7 -0
  12. data/generators/base_app/Gemfile +13 -0
  13. data/generators/base_app/app/.empty_directory +0 -0
  14. data/generators/base_app/app/helpers/.empty_directory +0 -0
  15. data/generators/base_app/app/helpers/view_helpers.rb +3 -0
  16. data/generators/base_app/app/mailers/.empty_directory +0 -0
  17. data/generators/base_app/app/models/.empty_directory +0 -0
  18. data/generators/base_app/app/routes/.empty_directory +0 -0
  19. data/generators/base_app/app/views/.empty_directory +0 -0
  20. data/generators/base_app/config.ru.tt +10 -0
  21. data/generators/base_app/config/boot.rb.tt +42 -0
  22. data/generators/base_app/config/dependencies.rb.tt +41 -0
  23. data/generators/base_app/lib/.empty_directory +0 -0
  24. data/generators/base_app/public/images/.empty_directory +0 -0
  25. data/generators/base_app/public/images/.gitignore +0 -0
  26. data/generators/base_app/public/javascripts/.empty_directory +0 -0
  27. data/generators/base_app/public/stylesheets/.empty_directory +0 -0
  28. data/generators/base_app/test/models/.empty_directory +0 -0
  29. data/generators/base_app/test/routes/.empty_directory +0 -0
  30. data/generators/base_app/test/test_config.rb.tt +5 -0
  31. data/generators/base_app/vendor/gems/.empty_directory +0 -0
  32. data/generators/components/component_actions.rb +48 -0
  33. data/generators/components/mocks/mocha_mock_gen.rb +8 -0
  34. data/generators/components/mocks/rr_mock_gen.rb +8 -0
  35. data/generators/components/orms/activerecord_orm_gen.rb +99 -0
  36. data/generators/components/orms/couchrest_orm_gen.rb +64 -0
  37. data/generators/components/orms/datamapper_orm_gen.rb +52 -0
  38. data/generators/components/orms/mongomapper_orm_gen.rb +101 -0
  39. data/generators/components/orms/sequel_orm_gen.rb +61 -0
  40. data/generators/components/renderers/erb_renderer_gen.rb +7 -0
  41. data/generators/components/renderers/haml_renderer_gen.rb +22 -0
  42. data/generators/components/scripts/jquery_script_gen.rb +9 -0
  43. data/generators/components/scripts/prototype_script_gen.rb +10 -0
  44. data/generators/components/scripts/rightjs_script_gen.rb +10 -0
  45. data/generators/components/tests/bacon_test_gen.rb +21 -0
  46. data/generators/components/tests/riot_test_gen.rb +19 -0
  47. data/generators/components/tests/rspec_test_gen.rb +19 -0
  48. data/generators/components/tests/shoulda_test_gen.rb +19 -0
  49. data/generators/components/tests/testspec_test_gen.rb +19 -0
  50. data/generators/generator_actions.rb +79 -0
  51. data/generators/skeleton_generator.rb +53 -0
  52. data/lib/sinatra/mailer_plugin.rb +14 -0
  53. data/lib/sinatra/mailer_plugin/mail_object.rb +39 -0
  54. data/lib/sinatra/mailer_plugin/mailer_base.rb +75 -0
  55. data/lib/sinatra/markup_plugin.rb +19 -0
  56. data/lib/sinatra/markup_plugin/asset_tag_helpers.rb +109 -0
  57. data/lib/sinatra/markup_plugin/form_builder/abstract_form_builder.rb +133 -0
  58. data/lib/sinatra/markup_plugin/form_builder/standard_form_builder.rb +31 -0
  59. data/lib/sinatra/markup_plugin/form_helpers.rb +194 -0
  60. data/lib/sinatra/markup_plugin/format_helpers.rb +74 -0
  61. data/lib/sinatra/markup_plugin/output_helpers.rb +98 -0
  62. data/lib/sinatra/markup_plugin/tag_helpers.rb +42 -0
  63. data/lib/sinatra/render_plugin.rb +12 -0
  64. data/lib/sinatra/render_plugin/render_helpers.rb +63 -0
  65. data/lib/sinatra/routing_plugin.rb +50 -0
  66. data/lib/sinatra/routing_plugin/named_route.rb +27 -0
  67. data/lib/sinatra/routing_plugin/routing_helpers.rb +22 -0
  68. data/lib/sinatra/support_lite.rb +19 -0
  69. data/lib/sinatra/warden_plugin.rb +51 -0
  70. data/lib/sinatra/warden_plugin/warden_helpers.rb +60 -0
  71. data/lib/sinatra_more.rb +7 -0
  72. data/sinatra_more.gemspec +215 -0
  73. data/test/active_support_helpers.rb +7 -0
  74. data/test/fixtures/mailer_app/app.rb +51 -0
  75. data/test/fixtures/mailer_app/views/demo_mailer/sample_mail.erb +1 -0
  76. data/test/fixtures/mailer_app/views/sample_mailer/anniversary_message.erb +2 -0
  77. data/test/fixtures/mailer_app/views/sample_mailer/birthday_message.erb +2 -0
  78. data/test/fixtures/markup_app/app.rb +66 -0
  79. data/test/fixtures/markup_app/views/capture_concat.erb +14 -0
  80. data/test/fixtures/markup_app/views/capture_concat.haml +13 -0
  81. data/test/fixtures/markup_app/views/content_for.erb +11 -0
  82. data/test/fixtures/markup_app/views/content_for.haml +9 -0
  83. data/test/fixtures/markup_app/views/content_tag.erb +11 -0
  84. data/test/fixtures/markup_app/views/content_tag.haml +9 -0
  85. data/test/fixtures/markup_app/views/fields_for.erb +8 -0
  86. data/test/fixtures/markup_app/views/fields_for.haml +6 -0
  87. data/test/fixtures/markup_app/views/form_for.erb +56 -0
  88. data/test/fixtures/markup_app/views/form_for.haml +47 -0
  89. data/test/fixtures/markup_app/views/form_tag.erb +57 -0
  90. data/test/fixtures/markup_app/views/form_tag.haml +45 -0
  91. data/test/fixtures/markup_app/views/link_to.erb +5 -0
  92. data/test/fixtures/markup_app/views/link_to.haml +4 -0
  93. data/test/fixtures/markup_app/views/mail_to.erb +3 -0
  94. data/test/fixtures/markup_app/views/mail_to.haml +3 -0
  95. data/test/fixtures/markup_app/views/meta_tag.erb +3 -0
  96. data/test/fixtures/markup_app/views/meta_tag.haml +3 -0
  97. data/test/fixtures/render_app/app.rb +54 -0
  98. data/test/fixtures/render_app/views/erb/test.erb +1 -0
  99. data/test/fixtures/render_app/views/haml/test.haml +1 -0
  100. data/test/fixtures/render_app/views/template/_user.haml +7 -0
  101. data/test/fixtures/render_app/views/template/haml_template.haml +1 -0
  102. data/test/fixtures/render_app/views/template/some_template.haml +2 -0
  103. data/test/fixtures/routing_app/app.rb +48 -0
  104. data/test/fixtures/routing_app/views/index.haml +7 -0
  105. data/test/fixtures/warden_app/app.rb +75 -0
  106. data/test/fixtures/warden_app/views/dashboard.haml +6 -0
  107. data/test/generators/test_skeleton_generator.rb +190 -0
  108. data/test/helper.rb +73 -0
  109. data/test/mailer_plugin/test_mail_object.rb +24 -0
  110. data/test/mailer_plugin/test_mailer_base.rb +81 -0
  111. data/test/markup_plugin/test_asset_tag_helpers.rb +171 -0
  112. data/test/markup_plugin/test_form_builder.rb +627 -0
  113. data/test/markup_plugin/test_form_helpers.rb +417 -0
  114. data/test/markup_plugin/test_format_helpers.rb +96 -0
  115. data/test/markup_plugin/test_output_helpers.rb +63 -0
  116. data/test/markup_plugin/test_tag_helpers.rb +73 -0
  117. data/test/test_mailer_plugin.rb +33 -0
  118. data/test/test_render_plugin.rb +78 -0
  119. data/test/test_routing_plugin.rb +94 -0
  120. data/test/test_warden_plugin.rb +105 -0
  121. metadata +307 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ webrat.log
23
+ tmp/**/*
data/IDEAS.md ADDED
@@ -0,0 +1,54 @@
1
+ # sinatra_more Ideas
2
+
3
+ before/after filters only for certain routes:
4
+
5
+ before :only => [:show, "/account"] do
6
+ # ... actions ...
7
+ end
8
+
9
+ after :only => [:show, "/account"] do
10
+ # ... actions ...
11
+ end
12
+
13
+ resource routing (defines mapped routes)
14
+
15
+ map(:user).resource
16
+ # => EQUIVALENT TO:
17
+ map(:new_user).to('/users/new') # NEW
18
+ map(:edit_user).to('/user/:id/edit') # EDIT
19
+ map(:user).to('/user/:id') # SHOW, UPDATE, DESTROY
20
+ map(:users).to('/users') # INDEX, CREATE
21
+
22
+ benchmarking and logging
23
+
24
+ create logger and Sinatra.logger methods
25
+ to make logging with severity easy
26
+
27
+ Sinatra.logger.info "Print something to log"
28
+
29
+ model generators (creates model, test, migrate)
30
+
31
+ sinatra_gen model Article
32
+ sinatra_gen --destroy model Article
33
+
34
+ migration generator (creates migration file)
35
+
36
+ sinatra_gen migration CreateArticleIndices
37
+
38
+ controller generators (routes, test, helpers)
39
+
40
+ sinatra_gen controller articles
41
+ sinatra_gen --destroy controller articles
42
+
43
+ mailer generators (mailer file, view path, test)
44
+
45
+ sinatra_gen mailer UserNotifier confirmation
46
+
47
+ task support (in generated app)
48
+
49
+ padrino console
50
+ padrino test
51
+ padrino db:create
52
+ padrino db:migrate
53
+
54
+ (Note in mailers, check out adv\_attr\_accessor)
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Nathan Esquenazi
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,756 @@
1
+ = sinatra_more
2
+
3
+ == Preface
4
+
5
+ This gem has been designed to work with Sinatra (http://www.sinatrarb.com).
6
+ Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort.
7
+ The canonical example of how to create an entire simple web application with Sinatra is something like:
8
+
9
+ # myapp.rb
10
+ require 'rubygems'
11
+ require 'sinatra'
12
+ get '/' do
13
+ 'Hello world!'
14
+ end
15
+
16
+ and then to run the application:
17
+
18
+ $ ruby myapp.rb
19
+
20
+ The extreme simplicity of the framework is quite refreshing. I have been using Sinatra a great deal
21
+ for recent projects. First for small and simple json and xml web services and then even
22
+ for more complex full-featured applications. This gem represents my attempt to make it as fun and easy
23
+ as possible to code increasingly advanced view-heavy web applications in Sinatra.
24
+
25
+ == Introduction
26
+
27
+ Note: This library is still experimental and may not be ready for production just yet.
28
+ That being said the gem is being actively used on a number of sinatra projects.
29
+ In addition, the gem has fairly solid test coverage ensuring that everything works as expected.
30
+
31
+ This will be a plugin which expands sinatra's capabilities in a variety of ways.
32
+ Note that all extensions have been created to work with haml, erb, and erubis.
33
+ This gem is intended to be template-agnostic in providing helpers wherever possible.
34
+
35
+ Let me expand briefly on what I want to accomplish with this gem. I love sinatra but if I want to use it
36
+ for any non-trivial application I very quickly miss a lot of the extra tools provided by rails.
37
+
38
+ Now the obvious question is "Why not just use rails then?" Well, in many cases that might be the right decision.
39
+ Still, at least until version 3 comes along, Rails is quite a large framework with a 'take it or leave it' attitude.
40
+ Personally, I have come to love the spirit of sinatra which acts as a thin wrapper on top of rack
41
+ often allowing middleware to do most of the work and pulling in additional complexity only as required.
42
+
43
+ My goal with this extension is to match the spirit of Sinatra and at the same time create a standard library
44
+ of tools, helpers and components that will make Sinatra suitable for more complex applications.
45
+
46
+ Here is a small list of what sinatra_more contains:
47
+
48
+ * Code generators for creating new sinatra applications (using <tt>sinatra_gen</tt> on command line)
49
+ * Generic view and tag helpers (<tt>tag</tt>, <tt>content_tag</tt>, <tt>input_tag</tt>, ...)
50
+ * Asset tag helpers (<tt>link_to</tt>, <tt>image_tag</tt>, <tt>javascript_include_tag</tt>, ...)
51
+ * Full form helpers and builders support (<tt>form_tag</tt>, <tt>form_for</tt>, <tt>field_set_tag</tt>, <tt>text_field</tt>, ...)
52
+ * Full url named route support to avoid hardcoding url paths in sinatra (<tt>map</tt>, <tt>url_for</tt>)
53
+ * Generally useful formatting extensions (<tt>relative_time_ago</tt>, <tt>js_escape_html</tt>, <tt>sanitize_html</tt>)
54
+ * Simple 'mailer' support for sinatra (akin to <tt>ActionMailer</tt> but simpler and powered by <tt>pony</tt>)
55
+ * Plug and play setup for the excellent Warden authentication system
56
+
57
+ Keep in mind, the user will be able to pull in these components seperately and leave out those that are not required.
58
+
59
+ Please help me brainstorm and fork the project if you have any ideas to contribute.
60
+
61
+ == Installation
62
+
63
+ If you want to use the WardenPlugin component, then the 'warden' gem would need to be installed.
64
+
65
+ To install sinatra_more, simply grab the latest version from gemcutter:
66
+
67
+ $ sudo gem install sinatra_more --source http://gemcutter.org
68
+
69
+ Now you are ready to use this gem in your sinatra project.
70
+
71
+ == Usage
72
+
73
+ This extension can be easily registered into any existing sinatra application. You can require
74
+ different components based on which pieces are useful for your particular application.
75
+
76
+ # app.rb
77
+ require 'sinatra/base'
78
+ require 'sinatra_more' # or require 'sinatra_more/markup_plugin' for precise inclusion
79
+
80
+ class Application < Sinatra::Base
81
+ register Sinatra::MarkupPlugin
82
+ register Sinatra::RenderPlugin
83
+ register Sinatra::WardenPlugin
84
+ register Sinatra::MailerPlugin
85
+ register Sinatra::RoutingPlugin
86
+ end
87
+
88
+ This will then allow you to use the components that have been registered. A breakdown of components is below:
89
+
90
+ === MarkupPlugin
91
+
92
+ This component provides a great deal of view helpers related to html markup generation.
93
+ There are helpers for generating tags, forms, links, images, and more. Most of the basic
94
+ methods should be very familiar to anyone who has used rails view helpers.
95
+
96
+ ==== Output Helpers
97
+
98
+ * <tt>content_for(key, &block)</tt>
99
+ * Capture a block of content to be rendered at a later time.
100
+ * <tt>content_for(:head) { ...content... }</tt>
101
+ * Also supports arguments passed to the content block
102
+ * <tt>content_for(:head) { |param1, param2| ...content... }</tt>
103
+ * <tt>yield_content(key, *args)</tt>
104
+ * Render the captured content blocks for a given key.
105
+ * <tt>yield_content :head</tt>
106
+ * Also supports arguments yielded to the content block
107
+ * <tt>yield_content :head, param1, param2</tt>
108
+ * <tt>capture_html(*args, &block)</tt>
109
+ * Captures the html from a block of template code for erb or haml
110
+ * <tt>capture_html(&block)</tt> => "...html..."
111
+ * <tt>concat_content(text="")</tt>
112
+ * Outputs the given text to the templates buffer directly in erb or haml
113
+ * <tt>concat_content("This will be output to the template buffer in erb or haml")</tt>
114
+
115
+ ==== Tag Helpers
116
+
117
+ * <tt>tag(name, options={})</tt>
118
+ * Creates an html tag with the given name and options
119
+ * <tt>tag(:br, :style => 'clear:both')</tt> => <br style="clear:both" />
120
+ * <tt>tag(:p, :content => "demo", :class => 'large')</tt> => <p class="large">demo</p>
121
+ * <tt>content_tag(name, content, options={})</tt>
122
+ * Creates an html tag with given name, content and options
123
+ * <tt>content_tag(:p, "demo", :class => 'light')</tt> => <p class="light">demo</p>
124
+ * <tt>content_tag(:p, :class => 'dark') { ...content... }</tt> => <p class="dark">...content...</p>
125
+ * <tt>input_tag(type, options = {})</tt>
126
+ * Creates an html input field with given type and options
127
+ * <tt>input_tag :text, :class => "demo"</tt>
128
+ * <tt>input_tag :password, :value => "secret", :class => "demo"</tt>
129
+
130
+ ==== Asset Helpers
131
+
132
+ * <tt>flash_tag(kind, options={})</tt>
133
+ * Creates a div to display the flash of given type if it exists
134
+ * <tt>flash_tag(:notice, :class => 'flash', :id => 'flash-notice')</tt>
135
+ * <tt>link_to(*args, &block)</tt>
136
+ * Creates a link element with given name, url and options
137
+ * <tt>link_to 'click me', '/dashboard', :class => 'linky'</tt>
138
+ * <tt>link_to('/dashboard', :class => 'blocky') { ...content... }</tt>
139
+ * <tt>mail_to(email, caption=nil, mail_options={})</tt>
140
+ * Creates a mailto link tag to the specified email_address
141
+ * <tt>mail_to "me@demo.com"</tt>
142
+ * <tt>mail_to "me@demo.com", "My Email", :subject => "Feedback", :cc => 'test@demo.com'</tt>
143
+ * <tt>image_tag(url, options={})</tt>
144
+ * Creates an image element with given url and options
145
+ * <tt>image_tag('icons/avatar.png')</tt>
146
+ * <tt>stylesheet_link_tag(*sources)</tt>
147
+ * Returns a stylesheet link tag for the sources specified as arguments
148
+ * <tt>stylesheet_link_tag 'style', 'application', 'layout'</tt>
149
+ * <tt>javascript_include_tag(*sources)</tt>
150
+ * Returns an html script tag for each of the sources provided.
151
+ * <tt>javascript_include_tag 'application', 'special'</tt>
152
+
153
+ ==== Form Helpers
154
+
155
+ * <tt>form_tag(url, options={}, &block)</tt>
156
+ * Constructs a form without object based on options
157
+ * Supports form methods 'put' and 'delete' through hidden field
158
+ * <tt>form_tag('/register', :class => 'example') { ... }</tt>
159
+ * <tt>field_set_tag(*args, &block)</tt>
160
+ * Constructs a field_set to group fields with given options
161
+ * <tt>field_set_tag(:class => 'office-set') { }</tt>
162
+ * <tt>field_set_tag("Office", :class => 'office-set') { }</tt>
163
+ * <tt>error_messages_for(record, options={})</tt>
164
+ * Constructs list html for the errors for a given object
165
+ * <tt>error_messages_for @user</tt>
166
+ * <tt>label_tag(name, options={}, &block)</tt>
167
+ * Constructs a label tag from the given options
168
+ * <tt>label_tag :username, :class => 'long-label'</tt>
169
+ * <tt>label_tag(:username, :class => 'blocked-label') { ... }</tt>
170
+ * <tt>hidden_field_tag(name, options={})</tt>
171
+ * Constructs a hidden field input from the given options
172
+ * <tt>hidden_field_tag :session_key, :value => 'secret'</tt>
173
+ * <tt>text_field_tag(name, options={})</tt>
174
+ * Constructs a text field input from the given options
175
+ * <tt>text_field_tag :username, :class => 'long'</tt>
176
+ * <tt>text_area_tag(name, options={})</tt>
177
+ * Constructs a text area input from the given options
178
+ * <tt>text_area_tag :username, :class => 'long'</tt>
179
+ * <tt>password_field_tag(name, options={})</tt>
180
+ * Constructs a password field input from the given options
181
+ * <tt>password_field_tag :password, :class => 'long'</tt>
182
+ * <tt>check_box_tag(name, options={})</tt>
183
+ * Constructs a checkbox input from the given options
184
+ * <tt>check_box_tag :remember_me, :checked => true</tt>
185
+ * <tt>radio_button_tag(name, options={})</tt>
186
+ * Constructs a radio button input from the given options
187
+ * <tt>radio_button_tag :gender, :value => 'male'</tt>
188
+ * <tt>select_tag(name, settings={})</tt>
189
+ * Constructs a select tag with options from the given settings
190
+ * <tt>select_tag(:favorite_color, :options => ['1', '2', '3'], :selected => '1')</tt>
191
+ * <tt>select_tag(:more_color, :options => [['label', '1'], ['label2', '2']])</tt>
192
+ * <tt>select_tag(:multiple_color, :options => [...], :multiple => true)</tt>
193
+ * <tt>file_field_tag(name, options={})</tt>
194
+ * Constructs a file field input from the given options
195
+ * <tt>file_field_tag :photo, :class => 'long'</tt>
196
+ * <tt>submit_tag(caption, options={})</tt>
197
+ * Constructs a submit button from the given options
198
+ * <tt>submit_tag "Create", :class => 'success'</tt>
199
+ * <tt>button_tag(caption, options={})</tt>
200
+ * Constructs an input (type => 'button') from the given options
201
+ * <tt>button_tag "Cancel", :class => 'clear'</tt>
202
+ * <tt>image_submit_tag(source, options={})</tt>
203
+ * Constructs an image submit button from the given options
204
+ * <tt>image_submit_tag "submit.png", :class => 'success'</tt>
205
+
206
+ A form_tag might look like:
207
+
208
+ - form_tag '/destroy', :class => 'destroy-form', :method => 'delete' do
209
+ = flash_tag(:notice)
210
+ - field_set_tag do
211
+ %p
212
+ = label_tag :username, :class => 'first'
213
+ = text_field_tag :username, :value => params[:username]
214
+ %p
215
+ = label_tag :password, :class => 'first'
216
+ = password_field_tag :password, :value => params[:password]
217
+ %p
218
+ = label_tag :strategy
219
+ = select_tag :strategy, :options => ['delete', 'destroy'], :selected => 'delete'
220
+ %p
221
+ = check_box_tag :confirm_delete
222
+ - field_set_tag(:class => 'buttons') do
223
+ = submit_tag "Remove"
224
+
225
+ ==== FormBuilders
226
+
227
+ * <tt>form_for(object, url, settings={}, &block)</tt>
228
+ * Constructs a form using given or default form_builder
229
+ * Supports form methods 'put' and 'delete' through hidden field
230
+ * Defaults to StandardFormBuilder but you can easily create your own!
231
+ * <tt>form_for(@user, '/register', :id => 'register') { |f| ...field-elements... }</tt>
232
+ * <tt>form_for(:user, '/register', :id => 'register') { |f| ...field-elements... }</tt>
233
+ * <tt>fields_for(object, settings={}, &block)</tt>
234
+ * Constructs fields for a given object for use in an existing form
235
+ * Defaults to StandardFormBuilder but you can easily create your own!
236
+ * <tt>fields_for @user.assignment do |assignment| ... end</tt>
237
+ * <tt>fields_for :assignment do |assigment| ... end</tt>
238
+
239
+ The following are fields provided by AbstractFormBuilder that can be used within a form_for or fields_for:
240
+
241
+ * <tt>error_messages(options={})</tt>
242
+ * Displays list html for the errors on form object
243
+ * <tt>f.errors_messages</tt>
244
+ * <tt>label(field, options={})</tt>
245
+ * <tt>f.label :name, :class => 'long'</tt>
246
+ * <tt>text_field(field, options={})</tt>
247
+ * <tt>f.text_field :username, :class => 'long'</tt>
248
+ * <tt>check_box(field, options={})</tt>
249
+ * Uses hidden field to provide a 'unchecked' value for field
250
+ * <tt>f.check_box :remember_me, :uncheck_value => 'false'</tt>
251
+ * <tt>radio_button(field, options={})</tt>
252
+ * <tt>f.radio_button :gender, :value => 'male'</tt>
253
+ * <tt>hidden_field(field, options={})</tt>
254
+ * <tt>f.hidden_field :session_id, :class => 'hidden'</tt>
255
+ * <tt>text_area(field, options={})</tt>
256
+ * <tt>f.text_area :summary, :class => 'long'</tt>
257
+ * <tt>password_field(field, options={})</tt>
258
+ * <tt>f.password_field :secret, :class => 'long'</tt>
259
+ * <tt>file_field(field, options={})</tt>
260
+ * <tt>f.file_field :photo, :class => 'long'</tt>
261
+ * <tt>select(field, options={})</tt>
262
+ * <tt>f.select(:state, :options => ['California', 'Texas', 'Wyoming'])</tt>
263
+ * <tt>f.select(:state, :collection => @states, :fields => [:name, :id])</tt>
264
+ * <tt>f.select(:state, :options => [...], :include_blank => true)</tt>
265
+ * <tt>submit(caption, options={})</tt>
266
+ * <tt>f.submit "Update", :class => 'long'</tt>
267
+ * <tt>image_submit(source, options={})</tt>
268
+ * <tt>f.image_submit "submit.png", :class => 'long'</tt>
269
+
270
+ A form_for using these basic fields might look like:
271
+
272
+ - form_for @user, '/register', :id => 'register' do |f|
273
+ = f.error_messages
274
+ %p
275
+ = f.label :username, :caption => "Nickname"
276
+ = f.text_field :username
277
+ %p
278
+ = f.label :email
279
+ = f.text_field :email
280
+ %p
281
+ = f.label :password
282
+ = f.password_field :password
283
+ %p
284
+ = f.label :is_admin, :caption => "Admin User?"
285
+ = f.check_box :is_admin
286
+ %p
287
+ = f.label :color, :caption => "Favorite Color?"
288
+ = f.select :color, :options => ['red', 'black']
289
+ %p
290
+ - fields_for @user.location do |location|
291
+ = location.text_field :street
292
+ = location.text_field :city
293
+ %p
294
+ = f.submit "Create", :class => 'button'
295
+
296
+ There is also a StandardFormBuilder which builds on the abstract fields that can be used within a form_for:
297
+
298
+ * <tt>text_field_block(field, options={}, label_options={})</tt>
299
+ * <tt>text_field_block(:nickname, :class => 'big', :caption => "Username")</tt>
300
+ * <tt>text_area_block(field, options={}, label_options={})</tt>
301
+ * <tt>text_area_block(:about, :class => 'big')</tt>
302
+ * <tt>password_field_block(field, options={}, label_options={})</tt>
303
+ * <tt>password_field_block(:code, :class => 'big')</tt>
304
+ * <tt>file_field_block(field, options={}, label_options={})</tt>
305
+ * <tt>file_field_block(:photo, :class => 'big')</tt>
306
+ * <tt>check_box_block(field, options={}, label_options={})</tt>
307
+ * <tt>check_box_block(:remember_me, :class => 'big')</tt>
308
+ * <tt>select_block(field, options={}, label_options={})</tt>
309
+ * <tt>select_block(:country, :option => ['USA', 'Canada'])</tt>
310
+ * <tt>submit_block(caption, options={})</tt>
311
+ * <tt>submit_block(:username, :class => 'big')</tt>
312
+ * <tt>image_submit_block(source, options={})</tt>
313
+ * <tt>image_submit_block('submit.png', :class => 'big')</tt>
314
+
315
+ A form_for using these standard fields might look like:
316
+
317
+ - form_for @user, '/register', :id => 'register' do |f|
318
+ = f.error_messages
319
+ = f.text_field_block :name, :caption => "Full name"
320
+ = f.text_field_block :email
321
+ = f.check_box_block :remember_me
322
+ = f.select_block :fav_color, :options => ['red', 'blue']
323
+ = f.password_field_block :password
324
+ = f.submit_block "Create", :class => 'button'
325
+
326
+ and would generate this html:
327
+
328
+ <form id="register" action="/register" method="post">
329
+ <p><label for="user_name">Full name: </label><input type="text" id="user_name" name="user[name]"></p>
330
+ ...omitted...
331
+ <p><input type="submit" value="Create" class="button"></p>
332
+ </form>
333
+
334
+ You can also easily build your own FormBuilder which allows for customized fields:
335
+
336
+ class MyCustomFormBuilder < AbstractFormBuilder
337
+ # Here we have access to a number of useful variables
338
+ #
339
+ # * template (use this to invoke any helpers)(ex. template.hidden_field_tag(...))
340
+ # * object (the record for this form) (ex. object.valid?)
341
+ # * object_name (object's underscored type) (ex. object_name => 'admin_user')
342
+ #
343
+ # We also have access to self.field_types => [:text_field, :text_area, ...]
344
+ # In addition, we have access to all the existing field tag helpers (text_field, hidden_field, file_field, ...)
345
+ end
346
+
347
+ Once a custom builder is defined, any call to form_for can use the new builder:
348
+
349
+ - form_for @user, '/register', :builder => 'MyCustomFormBuilder', :id => 'register' do |f|
350
+ ...fields here...
351
+
352
+ The form builder can even be made into the default builder when form_for is invoked:
353
+
354
+ # anywhere in the sinatra application
355
+ set :default_builder, 'MyCustomFormBuilder'
356
+
357
+ And there you have it, a fairly complete form builder solution for sinatra.
358
+ I hope to create or merge in an even better 'default' form_builder in the near future.
359
+
360
+ ==== Format Helpers
361
+
362
+ * <tt>escape_html</tt> (alias <tt>h</tt> and <tt>h!</tt>)
363
+ * (from RackUtils) Escape ampersands, brackets and quotes to their HTML/XML entities.
364
+ * <tt>relative_time_ago(date)</tt>
365
+ * Returns relative time in words referencing the given date
366
+ * <tt>relative_time_ago(2.days.ago)</tt> => "2 days"
367
+ * <tt>relative_time_ago(5.minutes.ago)</tt> => "5 minutes"
368
+ * <tt>relative_time_ago(2800.days.ago)</tt> => "over 7 years"
369
+ * <tt>time_in_words(date)</tt>
370
+ * Returns relative time in the past or future using appropriate date format
371
+ * <tt>time_in_words(2.days.ago)</tt> => "2 days ago"
372
+ * <tt>time_in_words(100.days.ago)</tt> => "Tuesday, July 21"
373
+ * <tt>time_in_words(1.day.from_now)</tt> => "tomorrow"
374
+ * <tt>escape_javascript(html_content)</tt>
375
+ * Escapes html to allow passing information to javascript. Used for passing data inside an ajax .js.erb template
376
+ * <tt>escape_javascript("<h1>Hey</h1>")</tt>
377
+
378
+ See the wiki article for additional information: <http://wiki.github.com/nesquena/sinatra_more/markupplugin>
379
+
380
+ === RenderPlugin
381
+
382
+ This component provides a number of rendering helpers for sinatra, making the process
383
+ of displaying templates far smoother. This plugin also has support for useful additions
384
+ such as partials (with support for :collection) into the templating system.
385
+
386
+ * <tt>erb_template(template_path, options={})</tt>
387
+ * Renders a erb template based on the given path
388
+ * <tt>erb_template 'users/new'</tt>
389
+ * <tt>haml_template(template_path, options={})</tt>
390
+ * Renders a haml template based on the given path
391
+ * <tt>haml_template 'users/new'</tt>
392
+ * <tt>render_template(template_path, options={})</tt>
393
+ * Renders the first detected template based on the given path
394
+ * <tt>render_template 'users/new'</tt>
395
+ * <tt>render_template 'users/index', :template_engine => 'haml'</tt>
396
+ * <tt>partial(template, *args)</tt>
397
+ * Renders the html related to the partial template for object or collection
398
+ * <tt>partial 'photo/_item', :object => @photo, :locals => { :foo => 'bar' }</tt>
399
+ * <tt>partial 'photo/_item', :collection => @photos</tt>
400
+
401
+ Using render plugin helpers is extremely simple. If you want to render an erb template in your view path:
402
+
403
+ erb_template 'path/to/my/template'
404
+
405
+ or using haml templates works just as well:
406
+
407
+ haml_template 'path/to/haml/template'
408
+
409
+ There is also a method which renders the first view matching the path and removes the need to define an engine:
410
+
411
+ render_template 'path/to/any/template'
412
+
413
+ It is worth noting these are mostly for convenience. When I had more complex view files in sinatra, I got tired of writing:
414
+
415
+ haml :"the/path/to/file"
416
+ erb "/path/to/file".to_sym
417
+
418
+ Finally, we have the all-important partials support for rendering mini-templates onto a page:
419
+
420
+ partial 'photo/_item', :object => @photo, :locals => { :foo => 'bar' }
421
+ partial 'photo/_item', :collection => @photos
422
+
423
+ This works as you would expect and also supports the collection counter inside the partial <tt>item_counter</tt>
424
+
425
+ # /views/photo/_item.haml
426
+ # Access to collection counter with <partial_name>_counter i.e item_counter
427
+ # Access the object with the partial_name i.e item
428
+
429
+ And that concludes the render plugin for now but I am open to adding more methods as time progresses.
430
+
431
+ See the wiki article for additional information: <http://wiki.github.com/nesquena/sinatra_more/renderplugin>
432
+
433
+ === WardenPlugin
434
+
435
+ This component provides out-of-the-box support for Warden authentication. With this
436
+ plugin registered, warden will be automatically required, configured and helpers will be
437
+ provided to make interacting with warden dead simple.
438
+
439
+ * <tt>current_user</tt>
440
+ * Returns the current authenticated user
441
+ * <tt>authenticate_user!</tt>
442
+ * Login the user through the default warden strategies
443
+ * <tt>logout_user!</tt>
444
+ * Signs out the user from the session
445
+ * <tt>logged_in?</tt>
446
+ * Returns true if the user has been authenticated
447
+ * <tt>authenticated?(&block)</tt>
448
+ * If a block is given, only yields the content if the user is authenticated
449
+ * <tt>authenticated? { puts "I am authenticated!" }</tt>
450
+ * <tt>must_be_authorized!(failure_path=nil)</tt>
451
+ * Forces a user to return to a fail path unless they are authorized
452
+ * Used to require a user be authenticated before routing to an action
453
+ * <tt>must_be_authorized!('/login')</tt>
454
+
455
+ There are a few configuration options and details you need to be aware of. By default, the WardenPlugin
456
+ assumes you have a User class which represents the authenticating class type. If your user class has a different
457
+ name then you need to specify that as follows:
458
+
459
+ Sinatra::WardenPlugin::PasswordStrategy.user_class = CustomUser
460
+
461
+ In addition, the strategy used expects that you have an <tt>authenticate</tt> method with the specific signature below:
462
+
463
+ class CustomUser
464
+ # ...
465
+ # Returns user record if user and password match; otherwise return false
466
+ def authenticate(username, password)
467
+ user = User.find(username)
468
+ user.has_password?(password) ? user : false
469
+ end
470
+ # ...
471
+ end
472
+
473
+ Using this plugin you also do need to define your own routes for managing warden sessions. An example is below:
474
+
475
+ get '/login/?' do
476
+ haml_template 'session/login'
477
+ end
478
+
479
+ post '/login/?' do
480
+ authenticate_user!
481
+ redirect "/dashboard"
482
+ end
483
+
484
+ post '/unauthenticated/?' do
485
+ flash[:notice] = "That username and password are not correct!"
486
+ status 401
487
+ haml_template 'session/login'
488
+ end
489
+
490
+ get '/logout/?' do
491
+ logout_user!
492
+ redirect '/session/login'
493
+ end
494
+
495
+ I was made aware of other sinatra/warden plugins which work very similarly to the system I outline for this plugin.
496
+ Most notably is the sinatra_warden plugin by Justin Smestad (http://github.com/jsmestad/sinatra_warden)
497
+ Eventually I plan to vendor that gem or just remove support for this piece of my plugin completely.
498
+
499
+ If anybody has any thoughts on this or the warden integration in general, please let me know.
500
+ Nonetheless, there is a certain convenience for me having access to a plug-and-play warden solution directly from this gem.
501
+
502
+ See the wiki article for additional information: <http://wiki.github.com/nesquena/sinatra_more/wardenplugin>
503
+
504
+ === MailerPlugin
505
+
506
+ This component uses an enhanced version of the excellent <tt>pony</tt> library (vendored) for a powerful but simple
507
+ mailer system within Sinatra. There is full support for using an html content type as well as for file attachments.
508
+ The MailerPlugin has many similarities to ActionMailer but is much lighterweight and (arguably) easier to use.
509
+
510
+ Let's take a look at using the MailerPlugin in an application. By default, MailerPlugin uses the built-in sendmail
511
+ functionality on the server. However, smtp is also supported using the following configuration:
512
+
513
+ Sinatra::MailerBase.smtp_settings = {
514
+ :host => 'smtp.gmail.com',
515
+ :port => '587',
516
+ :tls => true,
517
+ :user => 'user',
518
+ :pass => 'pass',
519
+ :auth => :plain
520
+ }
521
+
522
+ Once those have been defined, the default will become smtp delivery unless overwritten in an individual mail definition.
523
+ Next, we should define a custom mailer extended from <tt>Sinatra::MailerBase</tt>.
524
+
525
+ # app/mailers/sample_mailer.rb
526
+ class SampleMailer < Sinatra::MailerBase
527
+ def registration_email(name, user_email_address)
528
+ from 'admin@site.com'
529
+ to user_email_address
530
+ subject 'Welcome to the site!'
531
+ body :name => name
532
+ type 'html' # optional, defaults to plain/text
533
+ charset 'windows-1252' # optional, defaults to utf-8
534
+ via :sendmail # optional, to smtp if defined otherwise sendmail
535
+ end
536
+ end
537
+
538
+ This defines a mail called '<tt>registration_mail</tt>' with the specified attributes for delivery. The <tt>body</tt> method
539
+ is passing the <tt>name</tt> attribute to the body message template which should be defined in
540
+ <tt>[views_path]/sample_mailer/registration_email.erb</tt> as shown below:
541
+
542
+ # ./views/sample_mailer/registration_email.erb
543
+ This is the body of the email and can access the <%= name %> that was passed in from the mailer definition
544
+ That's all there is to defining the body of the email which can be plain text or html
545
+
546
+ Once the mailer definition has been completed and the template has been defined, the email can be sent using:
547
+
548
+ SampleMailer.deliver(:registration_email, "Bob", "bob@bobby.com")
549
+
550
+ or if you like the method_missing approach:
551
+
552
+ SampleMailer.deliver_registration_email "Bob", 'bob@bobby.com'
553
+
554
+ And that will then deliver the email according the the configured options. This is really all you need to send emails.
555
+ A few variations are shown below for completeness.
556
+
557
+ If we want to attach files to our email:
558
+
559
+ # app/mailers/sample_mailer.rb
560
+ class SampleMailer < Sinatra::MailerBase
561
+ def attachment_email(name, user_email_address)
562
+ from 'admin@site.com'
563
+ to user_email_address
564
+ # ...
565
+ attachments { "foo.zip" => File.read("path/to/foo.zip"), "file.txt" => "this is a text file!" }
566
+ end
567
+ end
568
+
569
+ or perhaps we want to have a short body without the need for a template file:
570
+
571
+ # app/mailers/sample_mailer.rb
572
+ class SampleMailer < Sinatra::MailerBase
573
+ def short_email(name, user_email_address)
574
+ from 'admin@site.com'
575
+ to user_email_address
576
+ subject 'Welcome to the site!'
577
+ body "This is a short body defined right in the mailer itself"
578
+ end
579
+ end
580
+
581
+ See the wiki article for additional information: <http://wiki.github.com/nesquena/sinatra_more/mailerplugin>
582
+
583
+ === RoutingPlugin
584
+
585
+ This component provides Sinatra with an enhanced url routing system which enables named route aliases to be defined
586
+ and used throughout your application to refer to urls. The benefits of this is that instead of having to hard-code route urls
587
+ into every area of your application, now we can just define the urls in a single spot and then attach an alias
588
+ which can be used to refer to the url throughout the rest.
589
+
590
+ Let's take a look at how to define named route mappings:
591
+
592
+ # /app/routes/example.rb
593
+ require 'sinatra_more'
594
+
595
+ class RoutingDemo < Sinatra::Application
596
+ register Sinatra::RoutingPlugin
597
+
598
+ # Define the named route mappings
599
+ map(:account).to("/the/accounts/:name/and/:id")
600
+ map(:accounts).to("/the/accounts/index")
601
+
602
+ # Configure the routes using the named alias
603
+ get(:account) { "name: params[:name] - id: params[:id]" }
604
+ get(:accounts) { "I am the body for the url /the/accounts/index" }
605
+ end
606
+
607
+ Notice we simply create a route alias using the <tt>map</tt> function and then pass in the corresponding url into the <tt>to</tt> method.
608
+ You can then define the routes using the named symbol representing the url. The route aliases can be accessed using <tt>url_for</tt>
609
+
610
+ url_for(:accounts)
611
+ url_for(:account, :id => 1, :name => 'first')
612
+
613
+ You can also refer to the url in views using <tt>url_for</tt>
614
+
615
+ # /app/views/index.erb
616
+ <p>Go to the <%= link_to 'accounts dashboard', url_for(:accounts) %> to view your accounts</p>
617
+ <p>Go to account for <%= link_to 'first account', url_for(:account, :id => 1, :name => 'first') %>
618
+
619
+ Simply invoking <tt>url_for(name, *parameters)</tt> will return the full mapped url for use in links or anywhere else
620
+ that the url might be required.
621
+
622
+ The routing system also supports url route configuration namespaces:
623
+
624
+ # /app/routes/example.rb
625
+ map(:admin, :show).to("/admin/:id/show")
626
+
627
+ namespace :admin do
628
+ get :show do
629
+ "admin show for #{params[:id]}"
630
+ end
631
+ end
632
+
633
+ You could also define the route aliases themselves using a namespace for convenience:
634
+
635
+ # /app/routes/example.rb
636
+ map :admin do |namespace|
637
+ namespace.map(:show).to("/admin/:id/show")
638
+ namespace.map(:destroy).to("/admin/:id/destroy")
639
+ end
640
+
641
+ namespace :admin do
642
+ get :show do
643
+ "admin show for #{params[:id]}"
644
+ end
645
+
646
+ get :destroy do
647
+ "admin destroy for #{params[:id]}"
648
+ end
649
+ end
650
+
651
+ You can then reference the urls using the same <tt>url_for</tt> method:
652
+
653
+ <%= link_to 'admin page', url_for(:admin, :show, :id => 25) %>
654
+ <%= link_to 'admin page', url_for(:admin, :update, :id => 25) %>
655
+ <%= link_to 'admin page', url_for(:admin, :show, :id => 25) %>
656
+
657
+ You can freely use both named route aliases and traditional Sinatra routes in the same application without conflict.
658
+
659
+ See the wiki article for additional information: <http://wiki.github.com/nesquena/sinatra_more/routingplugin>
660
+
661
+ == Sinatra Generators
662
+
663
+ In addition to the support provided above, sinatra_more also comes preloaded with flexible code generators for Sinatra
664
+ powered in part by the excellent Thor gem (incidentally also used in the Rails 3 generators). These generators are intended
665
+ to allow for easy code generation both in creating new applications and building on existing ones. The generators have been
666
+ built to be as library-agnostic as possible, supporting a myriad of test frameworks, js libraries, mocking libraries, etc.
667
+
668
+ The usage for the generator is quite simple:
669
+
670
+ $ sinatra_gen <the_app_name> </path/to/create/app> --<component-name> <value>
671
+
672
+ The simplest possible command to generate a base application would be:
673
+
674
+ $ sinatra_gen demo_app .
675
+
676
+ This would construct a Sinatra application DemoApp (which extends from Sinatra::Application)
677
+ inside the folder 'demo_app' at our current path. Inside the application there would be configuration and
678
+ setup performed for the default components.
679
+
680
+ You can also define specific components to be used:
681
+
682
+ $ sinatra_gen demo_app . --test=rspec --renderer=haml --mock=rr --script=jquery --orm datamapper
683
+
684
+ There is also support for aliases for each component within the command:
685
+
686
+ $ sinatra_gen demo_app . -t=rspec -r=haml -m=rr -s=jquery -d=datamapper
687
+
688
+ The available components and their default options are listed below:
689
+
690
+ * test: <tt>bacon</tt> (default), <tt>shoulda</tt>, <tt>rspec</tt>, <tt>testspec</tt>, <tt>riot</tt>
691
+ * renderer: <tt>erb</tt> (default), <tt>haml</tt>
692
+ * mock: <tt>mocha</tt> (default), <tt>rr</tt>
693
+ * script: <tt>jquery</tt> (default), <tt>prototype</tt>, <tt>rightjs</tt>
694
+ * orm: <tt>datamapper</tt> (default), <tt>mongomapper</tt>, <tt>activerecord</tt>, <tt>sequel</tt>, <tt>couchrest</tt>
695
+
696
+ The generator uses the <tt>bundler</tt> gem to resolve any application dependencies when the application is newly created.
697
+ The necessary bundler command can be executed automatically through the generator with
698
+
699
+ $ sinatra_gen demo_app . --run_bundler # alias -b
700
+
701
+ or this can be done manually through executing command <tt>gem bundle</tt> in the terminal at the root of the generated application.
702
+ If not executed manually, the bundling will be performed automatically the first time the application attempts to boot.
703
+ Note that this command only has to be performed when the application is first generated or when the Gemfile is modified.
704
+
705
+ The generator framework within sinatra_more is extensible and additional components and tools can be added easily.
706
+ This would be achieved through forking our project and reading through the code in <tt>/generators/sinatra_generator.rb</tt> and
707
+ the setup instructions inside the relevant files within <tt>/generators/components/</tt>. We are happy to accept pull requests
708
+ for additional component types not originally included (although helping us maintain them would also be appreciated).
709
+
710
+ We are also planning to add generator actions such as:
711
+
712
+ * Model generation (working for any of the available orms listed)
713
+ * Routes generation
714
+ * Mailer generation
715
+ * Migrations generation
716
+
717
+ See the wiki article for additional information: <http://wiki.github.com/nesquena/sinatra_more/generator>
718
+
719
+ == Acknowledgements
720
+
721
+ * Thanks to kelredd (http://github.com/kelredd) for the <tt>sinatra_helpers</tt> code that helped me to create erb capture and concat methods.
722
+ * Thanks to sbfaulkner for the <tt>sinatra-helpers</tt> code that I browsed through many times while starting this library.
723
+ * Thanks to vestel for the excellent modified <tt>pony</tt> fork which in part powers the mailer_plugin (http://github.com/vestel/pony)
724
+ * Thanks to focat and sinatra-content-for library (http://github.com/focat/sinatra-content-for) for a good content_for starting point
725
+ * Thanks to bcarlso for the snap sinatra library (http://github.com/bcarlso/snap) which was a starting point for named routes
726
+ * Thanks to wycats and others for the awesome Thor gem which made creating the sinatra generator relatively painless
727
+ * Thanks to wycats and others for the bundler gem which made bundling the required gems for an application easy
728
+
729
+ == Contributors
730
+
731
+ * Nathan Esquenazi - Project creator and code maintainer
732
+ * Arthur Chiu - Forming the idea and various code contributions
733
+ * Rob Holland - Added couchrest ORM component to generator
734
+ * Bill Turner - Added several form builder and helper fixes
735
+
736
+ == Known Issues
737
+
738
+ * No serious issues known
739
+
740
+ == Links and Resources
741
+
742
+ * EnvyCasts - <http://railsenvy.com/2009/10/28/episode-098>
743
+ * Ruby5 - <http://ruby5.envylabs.com/episodes/24-episode-23-october-30-2009/stories/183-sinatra-more>
744
+
745
+ == Note on Patches/Pull Requests
746
+
747
+ * Fork the project.
748
+ * Make your feature addition or bug fix.
749
+ * Add tests for it. This is important so I don't break it in a
750
+ future version unintentionally.
751
+ * Commit, do not mess with rakefile, version, or history.
752
+ * Send me a pull request. Bonus points for topic branches.
753
+
754
+ == Copyright
755
+
756
+ Copyright (c) 2009 Nathan Esquenazi. See LICENSE for details.