darkhelmet-sinatra_more 0.3.33

Sign up to get free protection for your applications and to get access to all the features.
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.