ingoweiss-resourceful_views 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,148 @@
1
+ = ResourcefulViews 0.2
2
+
3
+ ResourcefulViews aims to take RESTful conventions beyond controllers and into views by extending the 'map.resources' method to install a comprehensive vocabulary of resource-oriented view helpers
4
+
5
+
6
+ == Why?
7
+
8
+ Rails, being it's opinionated self, establishes strong RESTful conventions for routes and controllers. In comparison, Rails appears to have few opinions about how views should be written, let alone CSS sheets or JavaScripts. I think this is some wasted RESTful momentum worth recouping, and ResourcefulViews is here to recoup it.
9
+
10
+ The vision is that once an application's resources are defined, developers and designers will have a rich, shared vocabulary of view helpers and CSS classes readily available, greatly reducing the need to come up with their own, potentially conflicting vocabularies, and freeing them to focus on higher level problems instead.
11
+
12
+
13
+ == How?
14
+
15
+ For every resource defined in routes.rb via 'resources' or 'resource', ResourcefulViews defines seven helper methods (plus some extra ones). For each of the seven 'CRUD' actions one helper is installed which:
16
+
17
+ * is named '[CRUD action]_[route name prefix][resource name]': 'create_table', 'destroy_table_leg', 'new_table_top', etc.
18
+ * renders either a link (index new show edit) or a form (create update delete), that, when clicked/submitted, will trigger an HTTP request which gets routed to the helper's CRUD action on the resource's controller
19
+ * equips the link/form with a set of standard class attributes composed of the resource name, the CRUD action name, plus the two joined with '_' to help IE ('create table create_table')
20
+
21
+
22
+ == Basic example:
23
+
24
+ Say you have an app for furnishing your dream aparmtment, with a 'tables' resource. With ResourcefulViews installed, <code>map.resources :tables</code> will not only install the standard named route helpers, but also the following ones:
25
+
26
+ index_tables, new_table, create_table, show_table, edit_table, update_table, destroy_table
27
+
28
+ Now let's look at a vertical slice through an application, with the deletion of a 'table' resource as an example:
29
+
30
+ ==== Routes
31
+
32
+ map.resources :tables
33
+
34
+ ==== Controller
35
+
36
+ class TablesController < ActiveRecord::Base
37
+ def destroy
38
+ @table = Table.find(params[:table])
39
+ @table.destroy
40
+ redirect_to :action => 'index'
41
+ end
42
+ end
43
+
44
+ ==== View
45
+
46
+ So far so familar. However, here is where Rails drops the RESTful ball. With ResourcefulViews, however, you can write in your views:
47
+
48
+ <% destroy_table(@table) %>
49
+
50
+ # this will render:
51
+ <form action="/tables/1" method="post" class="destroy table destroy_table">
52
+ <input type="hidden" name="_method" value="delete" />
53
+ <button type="submit">Delete</button>
54
+ </form>
55
+
56
+ Note the auto-generated class names, allowing you to use 'resourceful' naming conventions beyond the view, for example in CSS sheets and JavaScript scripts:
57
+
58
+ ==== CSS
59
+
60
+ form.destroy_table button { background-image: url(/images/buttons/remove.png) }
61
+
62
+ ==== JavaScript
63
+
64
+ # LowPro behavior:
65
+ Event.addBehavior({
66
+ 'form.destroy': ConfirmDestroyResource()
67
+ });
68
+
69
+ ==== Tests
70
+
71
+ # rspec story step
72
+ Then(/the page should have a(n?) ([a-z_]+) ([a-z_]+) (form|link)/) do |_, crud_action, resource_name, tag|
73
+ response.should have_tag("#{tag}.#{crud_action}_#{resource_name}")
74
+ end
75
+
76
+ # rspec text story
77
+ Then the page should have a create comment form
78
+
79
+
80
+ Nice and consistent, isn't it? And compact, too:
81
+
82
+ <% create_table :with => {:type => 'DiningTable'} %>
83
+
84
+ # will render:
85
+ <form action="/tables" method="post" class="create table create_table">
86
+ <input type="text" name="table[type]" value="DiningTable" />
87
+ <button type="submit">Add</button>
88
+ </form>
89
+
90
+ <% search_tables :parameters => {:order => 'name'} %>
91
+
92
+ # will render:
93
+ <form action="/tables" method="get" class="index tables index_tables search search_tables">
94
+ <input type="hidden" name="order" value="name">
95
+ <input type="text" name="query" />
96
+ <button type="submit">Search</button>
97
+ </form>
98
+
99
+ This is just to give you a taste of what is possible. For more examples please refer to the rdoc for the individual 'build_[CRUD action name]_helper' methods (<code>rake doc:plugins PLUGIN=resourceful_views</code>)
100
+
101
+ == Common options
102
+
103
+ * <code>:label</code> specifies the content of the link tag in link helpers, and of the button tag in form helpers. If <code>:label</code> is omitted, the helpers will use a default label
104
+ * <code>:with</code> allows for specifying resource attributes to send with the request (using bracket syntax), via url query parameters in link helpers, and via hidden fields in form helpers (the old form of the option - <code>:attributes</code> - will continue to work for a while)
105
+ * <code>:sending</code> allows for specifying additional parameters to send with the request, via url query parameters in link helpers, and via hidden fields in form helpers (the old form of the option - <code>:parameters</code> - will continue to work for a while)
106
+ * All other options(such as <code>:id</code> and <code>:title</code>) are applied to the form in form helpers and to the link in link helpers
107
+ * To apply options to the button tag instead of the form tag in form helpers, make the options hash the value of the <code>:button</code> option (<code>:button => {:id => 'logout_button'}</code>)
108
+ * both the <code>create_resource</code> and the <code>update_resource</code> helpers support passing in a 'template' model as the last argument that is passed on to the 'fields_for' helper
109
+
110
+
111
+ == How about nested resources?
112
+
113
+ Not a problem. In fact, here is where it gets interesting:
114
+
115
+ <% edit_table_top(@table) %>
116
+
117
+ # will render:
118
+ <a href="/tables/1/top/edit" class="edit top edit_top">Edit</a>
119
+
120
+
121
+ == Helper name suffixes
122
+
123
+ If the helpers are crowding your namespace, or if you just don't like the short helper names, you can tell ResourcefulViews to use suffixes for naming the helpers. Just put this in an initializer:
124
+
125
+ # This will append '_link' to all link helpers, like 'edit_table_top_link'
126
+ ResourcefulViews.link_helpers_suffix = '_link'
127
+
128
+ # This will append '_form' to all link helpers, like 'search_tables_form'
129
+ ResourcefulViews.form_helpers_suffix = '_form'
130
+
131
+
132
+ == Tests
133
+
134
+ ResourcefulViews comes with en extensive rspec test suite: <code>rake spec:plugins PLUGIN=resourceful_views</code>. The test suite might also be a good place to look for more usage examples
135
+
136
+
137
+ == Feedback
138
+
139
+ Please report bugs and suggest improvements at http://resourceful_views.lighthouseapp.com
140
+
141
+
142
+ == Known problems
143
+
144
+ * ActiveMerchant seems to run 'routes.rb' before ResourcefulViews loads, resulting in ResourcefulViews helpers not being defined. To fix this, make sure ResourcefulViews loads before active_merchant by putting this in your environment file: <code>config.plugins = [ :resourceful_views, :all ]</code>
145
+
146
+
147
+ == License
148
+ Copyright (c) 2008 Ingo Weiss, released under the MIT license
@@ -0,0 +1,618 @@
1
+ class ResourcefulViews
2
+
3
+ cattr_accessor :form_helpers_suffix, :link_helpers_suffix
4
+ cattr_accessor :helpers
5
+
6
+ def initialize # :nodoc:
7
+ @module ||= Module.new
8
+ yield self
9
+ end
10
+
11
+ # generate a string of space-separated standardized CSS classnames
12
+ def self.resourceful_classnames(primary_classname, *secondary_classnames)
13
+ classnames = []
14
+ classnames << primary_classname
15
+ secondary_classnames.each do |classname|
16
+ classnames << classname
17
+ classnames << [classname, primary_classname].join('_') # a little help for IE
18
+ end
19
+ classnames.join(' ')
20
+ end
21
+
22
+ # Build resourceful helpers for a plural resource and install them into ActionView::Base
23
+ def build_and_install_helpers_for_resource(resource) # :nodoc:
24
+ build_index_helper(resource)
25
+ build_search_helper(resource)
26
+ build_show_helper(resource)
27
+ build_new_helper(resource)
28
+ build_edit_helper(resource)
29
+ build_destroy_helper(resource)
30
+ build_list_helpers(resource)
31
+ build_table_helpers(resource)
32
+ build_create_helper(resource)
33
+ build_update_helper(resource)
34
+ memorize_helpers(resource)
35
+ install_helpers
36
+ end
37
+
38
+ # Build resourceful helpers for a singular resource and install them into ActionView::Base
39
+ def build_and_install_helpers_for_singular_resource(resource) # :nodoc:
40
+ build_show_helper(resource)
41
+ build_new_helper(resource)
42
+ build_edit_helper(resource)
43
+ build_destroy_helper(resource)
44
+ build_create_helper(resource)
45
+ build_update_helper(resource)
46
+ memorize_helpers(resource)
47
+ install_helpers
48
+ end
49
+
50
+ def self.deprecation_warning(msg) # :nodoc:
51
+ RAILS_DEFAULT_LOGGER.warn('ResourcefulViews deprecation warning: ' + msg)
52
+ end
53
+
54
+ def memorize_helpers(resource) # :nodoc:
55
+ @@helpers ||= {}
56
+ @@helpers["#{resource.name_prefix}#{resource.plural} at #{resource.path}"] = @module.instance_methods
57
+ end
58
+
59
+
60
+ protected
61
+
62
+ # Build the 'index_[resource]' helper
63
+ #
64
+ # === Examples
65
+ #
66
+ # <% index_tables %>
67
+ #
68
+ # renders:
69
+ #
70
+ # <a href="/tables" class="index tables index_tables">Index</a>
71
+ #
72
+ # <% index_table_legs(@table, :id => 'back_button', :label => 'Back') %>
73
+ #
74
+ # renders:
75
+ #
76
+ # <a href="/tables/1/legs" id="back_button" class="index legs index_legs">Back</a>
77
+ #
78
+ def build_index_helper(resource)
79
+ helper_name = "index_#{resource.name_prefix}#{resource.plural}#{@@link_helpers_suffix}"
80
+ return if already_defined?(helper_name)
81
+ @module.module_eval <<-end_eval
82
+ def #{helper_name}(*args)
83
+ opts = args.extract_options!
84
+ label = opts.delete(:label) || 'Index'
85
+ custom_classes = opts.delete(:class) || ''
86
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.plural}', 'index', *custom_classes.split)
87
+ opts[:sending] = opts.delete(:parameters) and ResourcefulViews.deprecation_warning('Please use :sending instead of :parameters') if opts[:parameters]
88
+ args << opts.delete(:sending) if opts[:sending]
89
+ link_to(label, #{resource.name_prefix}#{resource.plural}_path(*args), opts)
90
+ end
91
+ end_eval
92
+ end
93
+
94
+
95
+
96
+ # Build the 'search_[resource]' helper
97
+ #
98
+ # === Examples
99
+ #
100
+ # <% search_tables %>
101
+ #
102
+ # renders:
103
+ #
104
+ # <form action="/tables" method="get" class="index tables index_tables search search_tables">
105
+ # <input type="text" name="query" />
106
+ # <button type="submit">Search</button>
107
+ # </form>
108
+ #
109
+ # <%= search_tables(:label => 'Find', :sending => {:order => 'material'}) %>
110
+ #
111
+ # renders:
112
+ #
113
+ # <form action="/tables" method="get" class="index tables index_tables search search_tables">
114
+ # <input type="text" name="query" />
115
+ # <input type="hidden" name="order" value="material" />
116
+ # <button type="submit">Find</button>
117
+ # </form>
118
+ #
119
+ # <% search_table_legs(table, :sending => {:order => 'price'}) do %>
120
+ # <%= select_tag 'filter', '<option>wood</option><option>metal</option>', :id => false %>
121
+ # <%= submit_button 'Search' %>
122
+ # <% end %>
123
+ #
124
+ # renders:
125
+ #
126
+ # <form action="/tables/1/legs" method="get" class="index tables index_tables search search_tables">
127
+ # <input type="text" name="query" />
128
+ # <input type="hidden" name="order" value="price" />
129
+ # <select name="filter"><option>wood</option><option>glass</option></select>
130
+ # <button type="submit">Search</button>
131
+ # </form>
132
+ #
133
+ def build_search_helper(resource)
134
+ helper_name = "search_#{resource.name_prefix}#{resource.plural}#{@@form_helpers_suffix}"
135
+ return if already_defined?(helper_name)
136
+ @module.module_eval <<-end_eval
137
+ def #{helper_name}(*args, &block)
138
+ opts = args.extract_options!
139
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.plural}', 'search', *(opts.delete(:class) || '').split)
140
+ opts[:method] = :get
141
+ opts[:sending] = opts.delete(:parameters) and ResourcefulViews.deprecation_warning('Please use :sending instead of :parameters') if opts[:parameters]
142
+ parameters = opts.delete(:sending) || {}
143
+ if block_given?
144
+ concat(form_tag(#{resource.name_prefix}#{resource.plural}_path(*args), opts), block.binding)
145
+ parameters.collect{ |key, value|
146
+ concat(hidden_field_tag(key.to_s, value, :id => nil), block.binding)
147
+ }
148
+ yield
149
+ concat('</form>', block.binding)
150
+ else
151
+ opts_for_button = opts.delete(:button) || {}
152
+ opts_for_button.merge!(:type => 'submit')
153
+ label = opts.delete(:label) || 'Search'
154
+ opts[:action] = #{resource.name_prefix}#{resource.plural}_path(*args)
155
+ content_tag('form', opts) do
156
+ text_field_tag(:query, @query, :id => nil) +
157
+ parameters.collect{ |key, value|
158
+ hidden_field_tag(key.to_s, value, :id => nil)
159
+ }.join +
160
+ content_tag(:button, label, opts_for_button)
161
+ end
162
+ end
163
+ end
164
+ end_eval
165
+ end
166
+
167
+
168
+
169
+ # Build the 'show_[resource]' helper
170
+ #
171
+ # === Examples
172
+ #
173
+ # <% show_table_top(@table, @top) %>
174
+ #
175
+ # renders:
176
+ #
177
+ # <a href="/tables/1/top" class="show top show_top">Show</a>
178
+ #
179
+ # <% show_table(@table, :label => @top.material) %>
180
+ #
181
+ # renders:
182
+ #
183
+ # <a href="/tables/1" class="show table show_table">Linoleum</a>
184
+ #
185
+ def build_show_helper(resource)
186
+ helper_name = "show_#{resource.name_prefix}#{resource.singular}#{@@link_helpers_suffix}"
187
+ return if already_defined?(helper_name)
188
+ @module.module_eval <<-end_eval
189
+ def #{helper_name}(*args)
190
+ opts = args.extract_options!
191
+ label = opts.delete(:label) || #{resource.kind_of?(ActionController::Resources::SingletonResource) ? "'Show'" : 'args.last.to_s'}
192
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.singular}', 'show', *(opts.delete(:class) || '').split)
193
+ opts[:sending] = opts.delete(:parameters) and ResourcefulViews.deprecation_warning('Please use :sending instead of :parameters') if opts[:parameters]
194
+ args << opts.delete(:sending) if opts[:sending]
195
+ link_to(label, #{resource.name_prefix}#{resource.singular}_path(*args), opts)
196
+ end
197
+ end_eval
198
+ end
199
+
200
+
201
+
202
+ # Build the 'new_[resource]' helper
203
+ #
204
+ # === Examples
205
+ #
206
+ # <%= new_table %>
207
+ #
208
+ # renders:
209
+ #
210
+ # <a href="/tables/new" class="new table new_table">New</a>
211
+ #
212
+ # <%= new_table_top(@table, :label => 'Add a top', :id => 'add_button') %>
213
+ #
214
+ # renders:
215
+ #
216
+ # <a href="/tables/1/top/new" id="add_button" class="new top new_top">Add a top</a>
217
+ #
218
+ # <%- new_table :with => {:name => 'Ingo'} do |f| %>
219
+ # <%= f.text_area :description %>
220
+ # <%= submit_button 'Save' %>
221
+ # <%- end %>
222
+ #
223
+ # renders:
224
+ #
225
+ # <form action="/tables/new" method="get" class="new table new_table">
226
+ # <input type="hidden" name="table[name]" value="Ingo" />
227
+ # <textarea name="table[description]" value="" />
228
+ # <button type="submit">Save</button>
229
+ # </form>
230
+ #
231
+ def build_new_helper(resource)
232
+ helper_name = "new_#{resource.name_prefix}#{resource.singular}#{@@link_helpers_suffix}"
233
+ return if already_defined?(helper_name)
234
+ @module.module_eval <<-end_eval
235
+ def #{helper_name}(*args, &block)
236
+ opts = args.extract_options!
237
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.singular}', 'new', *(opts.delete(:class) || '').split)
238
+ opts[:sending] = opts.delete(:parameters) and ResourcefulViews.deprecation_warning('Please use :sending instead of :parameters') if opts[:parameters]
239
+ parameters = opts.delete(:sending) || {}
240
+ opts[:with] = opts.delete(:attributes) and ResourcefulViews.deprecation_warning('Please use :with instead of :attributes') if opts[:attributes]
241
+ resource_attributes = opts.delete(:with) || {}
242
+ parameters.merge!(resource_attributes.inject({}){|attributes, (key, value)| attributes['#{resource.singular}[' + key.to_s + ']'] = value; attributes}) if resource_attributes
243
+ if block_given?
244
+ opts[:method] = :get
245
+ args_for_fields_for = ['#{resource.singular}']
246
+ concat(form_tag(new_#{resource.name_prefix}#{resource.singular}_path(*args), opts), block.binding)
247
+ concat(parameters.collect{|key, value| hidden_field_tag(key.to_s, value, :id => nil)}.join, block.binding) unless parameters.empty?
248
+ fields_for(*args_for_fields_for, &block)
249
+ concat('</form>', block.binding)
250
+ else
251
+ label = opts.delete(:label) || 'New'
252
+ args << parameters unless parameters.empty?
253
+ link_to(label, new_#{resource.name_prefix}#{resource.singular}_path(*args), opts)
254
+ end
255
+ end
256
+ end_eval
257
+ end
258
+
259
+
260
+
261
+ # Build the 'edit_[resource]' helper
262
+ #
263
+ # === Examples
264
+ #
265
+ # <% edit_table_top(@table) %>
266
+ #
267
+ # renders:
268
+ #
269
+ # <a href="/tables/1/top/edit" class="edit top edit_top">Edit</a>
270
+ #
271
+ # <% edit_table_top(@table, :label => 'Change top') %>
272
+ #
273
+ # renders:
274
+ #
275
+ # <a href="/tables/1/top/edit" class="edit top edit_top">Change top</a>
276
+ #
277
+ def build_edit_helper(resource)
278
+ helper_name = "edit_#{resource.name_prefix}#{resource.singular}#{@@link_helpers_suffix}"
279
+ return if already_defined?(helper_name)
280
+ @module.module_eval <<-end_eval
281
+ def #{helper_name}(*args)
282
+ opts = args.extract_options!
283
+ label = opts.delete(:label) || 'Edit'
284
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.singular}', 'edit', *(opts.delete(:class) || '').split)
285
+ opts[:sending] = opts.delete(:parameters) and ResourcefulViews.deprecation_warning('Please use :sending instead of :parameters') if opts[:parameters]
286
+ args << opts.delete(:sending) if opts[:sending]
287
+ link_to(label, edit_#{resource.name_prefix}#{resource.singular}_path(*args), opts)
288
+ end
289
+ end_eval
290
+ end
291
+
292
+
293
+
294
+ # Build the 'destroy_[resource]' helper
295
+ #
296
+ # === Examples
297
+ #
298
+ # <% destroy_table_leg(@table, @leg) %>
299
+ #
300
+ # renders:
301
+ #
302
+ # <form action="/tables/1/legs/1" class="leg destroy destroy_leg" method="post">
303
+ # <input name="_method" type="hidden" value="delete" />
304
+ # <button type="submit">Delete</button>
305
+ # </form>
306
+ #
307
+ # <% destroy_table_leg(@table, @leg, :button => {:title => 'Click to remove'}) %>
308
+ #
309
+ # renders:
310
+ #
311
+ # <form action="/tables/1/legs/1" class="leg destroy destroy_leg" method="post">
312
+ # <input name="_method" type="hidden" value="delete" />
313
+ # <button type="submit" title="Click to remove">Delete</button>
314
+ # </form>
315
+ #
316
+ def build_destroy_helper(resource)
317
+ helper_name = "destroy_#{resource.name_prefix}#{resource.singular}#{@@form_helpers_suffix}"
318
+ return if already_defined?(helper_name)
319
+ @module.module_eval <<-end_eval
320
+ def #{helper_name}(*args)
321
+ opts = args.extract_options!
322
+ opts_for_button = opts.delete(:button) || {}
323
+ opts_for_button.merge!(:type => 'submit')
324
+ label = opts.delete(:label) || 'Delete'
325
+ opts_for_button[:title] = opts.delete(:title) if opts[:title]
326
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.singular}', 'destroy', *(opts.delete(:class) || '').split)
327
+ opts[:method] = :post
328
+ opts[:action] = #{resource.name_prefix}#{resource.singular}_path(*args)
329
+ opts[:sending] = opts.delete(:parameters) and ResourcefulViews.deprecation_warning('Please use :sending instead of :parameters') if opts[:parameters]
330
+ parameters = opts.delete(:sending) || {}
331
+ content_tag('form', opts) do
332
+ hidden_field_tag(:_method, :delete, :id => nil) +
333
+ parameters.collect{|key, value|
334
+ hidden_field_tag(key, value, :id => nil)
335
+ }.join +
336
+ token_tag.to_s +
337
+ content_tag(:button, label, opts_for_button)
338
+ end
339
+ end
340
+ end_eval
341
+ end
342
+
343
+
344
+
345
+ # Build the list helpers
346
+ #
347
+ # === Examples
348
+ #
349
+ # <% table_list do %>
350
+ # ...
351
+ # <%- end -%>
352
+ #
353
+ # renders:
354
+ #
355
+ # <ul class="table_list">
356
+ # ...
357
+ # </ul>
358
+ #
359
+ # <% table_list :ordered => true do %>
360
+ # ...
361
+ # <% end %>
362
+ #
363
+ # renders:
364
+ #
365
+ # <ol class="table_list">
366
+ # ...
367
+ # </ol>
368
+ #
369
+ def build_list_helpers(resource)
370
+ @module.module_eval <<-end_eval
371
+ def #{resource.singular}_list(opts={}, &block)
372
+ content = capture(&block)
373
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.singular}_list', *(opts.delete(:class) || '').split)
374
+ concat(content_tag((opts[:ordered] ? :ol : :ul), content, opts), block.binding)
375
+ end
376
+ def #{resource.singular}_item(*args, &block)
377
+ opts = args.extract_options!
378
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.singular}', *(opts.delete(:class) || '').split)
379
+ opts[:id] = '#{resource.singular}_' + args.first.id.to_s unless args.empty?
380
+ content = capture(&block)
381
+ concat(content_tag(:li, content, opts), block.binding)
382
+ end
383
+ end_eval
384
+ end
385
+
386
+
387
+
388
+ # Build the table helpers
389
+ #
390
+ # === Examples
391
+ #
392
+ # <% user_table do %>
393
+ # ...
394
+ # <%- end -%>
395
+ #
396
+ # renders:
397
+ #
398
+ # <table class="user_table">
399
+ # ...
400
+ # </table>
401
+ #
402
+ def build_table_helpers(resource)
403
+ @module.module_eval <<-end_eval
404
+ def #{resource.singular}_table(opts={}, &block)
405
+ content = capture(&block)
406
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.singular}_table', *(opts.delete(:class) || '').split)
407
+ concat(content_tag(:table, content, opts), block.binding)
408
+ end
409
+ def #{resource.singular}_row(*args, &block)
410
+ opts = args.extract_options!
411
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.singular}', *(opts.delete(:class) || '').split)
412
+ opts[:id] = '#{resource.singular}_' + args.first.id.to_s unless args.empty?
413
+ content = capture(&block)
414
+ concat(content_tag(:tr, content, opts), block.binding)
415
+ end
416
+ end_eval
417
+ end
418
+
419
+
420
+
421
+ # Build the 'create_[resource]' helper
422
+ #
423
+ # === Examples without block
424
+ #
425
+ # <% create_table_top(@table, :with => {:material => 'Mahogany'}) %>
426
+ #
427
+ # renders:
428
+ #
429
+ # <form action="/tables/1/top" class="top create create_top" method="post">
430
+ # <input type="hidden" type="top[material]" value="Mahogany" />
431
+ # <button type="submit">Save</button>
432
+ # </form>
433
+ #
434
+ # <% create_table(:label => 'Add table') %>
435
+ #
436
+ # renders:
437
+ #
438
+ # <form action="/tables" class="table create create_table" method="post">
439
+ # <button type="submit">Add table</button>
440
+ # </form>
441
+ #
442
+ # === Examples with block
443
+ #
444
+ # <% create_table do |form| %>
445
+ # <%= form.text_field :title %>
446
+ # <%= submit_button 'Save' %>
447
+ # <% end %>
448
+ #
449
+ # renders:
450
+ #
451
+ # <form action="/tables" class="table create create_table" method="post">
452
+ # <input type="text" type="table[title]" value="My title" />
453
+ # <button type="submit">Save</button>
454
+ # </form>
455
+ #
456
+ def build_create_helper(resource)
457
+ helper_name = "create_#{resource.name_prefix}#{resource.singular}#{@@form_helpers_suffix}"
458
+ return if already_defined?(helper_name)
459
+ number_of_expected_args = number_of_args_expected_by_named_route_helper([resource.name_prefix, resource.plural].join)
460
+ @module.module_eval <<-end_eval
461
+ def #{helper_name}(*args, &block)
462
+ opts = args.extract_options!
463
+ opts[:with] = opts.delete(:attributes) and ResourcefulViews.deprecation_warning('Please use :with instead of :attributes') if opts[:attributes]
464
+ resource_attributes = opts.delete(:with) || {}
465
+ opts[:sending] = opts.delete(:parameters) and ResourcefulViews.deprecation_warning('Please use :sending instead of :parameters') if opts[:parameters]
466
+ parameters = opts.delete(:sending) || {}
467
+ if block_given?
468
+ args_for_fields_for = ['#{resource.singular}']
469
+ args_for_fields_for.push(args.pop) if args.length > #{number_of_expected_args}
470
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.singular}', 'create', *(opts.delete(:class) || '').split)
471
+ concat(form_tag(#{resource.name_prefix}#{resource.plural}_path(*args), opts), block.binding)
472
+ concat(resource_attributes.collect{|key, value| hidden_field_tag('#{resource.singular}[' + key.to_s + ']', value, :id => nil)}.join, block.binding)
473
+ concat(parameters.collect{|key, value| hidden_field_tag(key, value, :id => nil)}.join, block.binding)
474
+ fields_for(*args_for_fields_for, &block)
475
+ concat('</form>', block.binding)
476
+ else
477
+ label = opts.delete(:label) || 'Add'
478
+ opts_for_button = opts.delete(:button) || {}
479
+ opts_for_button.merge!(:type => 'submit')
480
+ opts[:method] = :post
481
+ opts[:action] = #{resource.name_prefix}#{resource.plural}_path(*args)
482
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.singular}', 'create', *(opts.delete(:class) || '').split)
483
+ content_tag('form', opts) do
484
+ token_tag.to_s +
485
+ parameters.collect{|key, value|
486
+ hidden_field_tag(key, value, :id => nil)
487
+ }.join +
488
+ resource_attributes.collect{ |key, value|
489
+ hidden_field_tag('#{resource.singular}[' + key.to_s + ']', value, :id => nil)
490
+ }.join +
491
+ content_tag(:button, label, opts_for_button)
492
+ end
493
+ end
494
+ end
495
+ end_eval
496
+ end
497
+
498
+
499
+
500
+ # Build the 'update_[resource](resource)' helper
501
+ #
502
+ # === Examples without block
503
+ #
504
+ # <% update_table(@table, :with => {:name => 'Ingo'}) %>
505
+ #
506
+ # renders:
507
+ #
508
+ # <form action="/tables/1" class="table update update_table" method="post">
509
+ # <input type="hidden" name="_method" value="put" />
510
+ # <input type="hidden" name="table[name]" value="Ingo" />
511
+ # <button type="submit">Save</button>
512
+ # </form>
513
+ #
514
+ # === Examples with block
515
+ #
516
+ # <% update_table(@table) do |form| %>
517
+ # <%= form.text_field :name %>
518
+ # <%= submit_button 'Save' %>
519
+ # <% end %>
520
+ #
521
+ # <form action="/tables/1" class="table update update_table" method="post">
522
+ # <input type="hidden" name="_method" value="put" />
523
+ # <input type="text" name="table[name]" value="Ingo" />
524
+ # <button type="submit">Save</button>
525
+ # </form>
526
+ #
527
+ def build_update_helper(resource)
528
+ helper_name = "update_#{resource.name_prefix}#{resource.singular}#{@@form_helpers_suffix}"
529
+ return if already_defined?(helper_name)
530
+ number_of_expected_args = number_of_args_expected_by_named_route_helper([resource.name_prefix, resource.singular].join)
531
+ resource_is_singular = resource.is_a?(ActionController::Resources::SingletonResource)
532
+ resource_is_plural = !resource_is_singular
533
+ @module.module_eval <<-end_eval
534
+ def #{helper_name}(*args, &block)
535
+ if block_given?
536
+ opts = args.extract_options!
537
+ args_for_fields_for = ['#{resource.singular}']
538
+ #{'args_for_fields_for.push(args.pop) if args.length > ' + number_of_expected_args.to_s if resource_is_singular}
539
+ #{'args_for_fields_for.push(args.last)' if resource_is_plural}
540
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.singular}', 'update', *(opts.delete(:class) || '').split)
541
+ opts[:method] = :put
542
+ concat(form_tag(#{resource.name_prefix}#{resource.singular}_path(*args), opts), block.binding)
543
+ fields_for(*args_for_fields_for, &block)
544
+ concat('</form>', block.binding)
545
+ else
546
+ opts = args.extract_options!
547
+ label = opts.delete(:label) || 'Save'
548
+ opts[:with] = opts.delete(:attributes) and ResourcefulViews.deprecation_warning('Please use :with instead of :attributes') if opts[:attributes]
549
+ resource_attributes = opts.delete(:with) || {}
550
+ opts_for_button = opts.delete(:button) || {}
551
+ opts_for_button.merge!(:type => 'submit')
552
+ opts[:class] = ResourcefulViews.resourceful_classnames('#{resource.singular}', 'update', *(opts.delete(:class) || '').split)
553
+ opts[:method] = :post
554
+ opts[:action] = #{resource.name_prefix}#{resource.singular}_path(*args)
555
+ content_tag('form', opts) do
556
+ token_tag.to_s +
557
+ resource_attributes.collect{ |key, value|
558
+ hidden_field_tag('#{resource.singular}[' + key.to_s + ']', value, :id => nil)
559
+ }.join <<
560
+ hidden_field_tag('_method', 'put', :id => nil) <<
561
+ content_tag(:button, label, opts_for_button)
562
+ end
563
+ end
564
+ end
565
+ end_eval
566
+ end
567
+
568
+
569
+ # include the module (loaded with helper methods) into ActionView::Base
570
+ def install_helpers # :nodoc:
571
+ ActionView::Base.send! :include, @module
572
+ end
573
+
574
+ protected
575
+
576
+ # Check whether method is already defined
577
+ def already_defined?(helper_name)
578
+ ActionView::Base.instance_methods.include?(helper_name) or @module.methods.include?(helper_name)
579
+ end
580
+
581
+ # determine how many arguments a specific named route helper expects
582
+ def number_of_args_expected_by_named_route_helper(helper_name)
583
+ ActionController::Routing::Routes.named_routes.routes[helper_name.to_sym].segment_keys.size
584
+ end
585
+
586
+ end
587
+
588
+
589
+ ActionController::Routing::RouteSet::Mapper.class_eval do
590
+
591
+ def resources_with_resourceful_view_helpers(*entities, &block)
592
+ resources_without_resourceful_view_helpers(*entities, &block)
593
+ options = entities.extract_options!
594
+ ResourcefulViews.new do |resourceful_views|
595
+ entities.each do |entity|
596
+ resource = ActionController::Resources::Resource.new(entity, options)
597
+ resourceful_views.build_and_install_helpers_for_resource(resource)
598
+ end
599
+ end
600
+ end
601
+
602
+ alias_method_chain :resources, :resourceful_view_helpers
603
+
604
+ def resource_with_resourceful_view_helpers(*entities, &block)
605
+ resource_without_resourceful_view_helpers(*entities, &block)
606
+ options = entities.extract_options!
607
+ ResourcefulViews.new do |resourceful_views|
608
+ entities.each do |entity|
609
+ resource = ActionController::Resources::SingletonResource.new(entity, options)
610
+ resourceful_views.build_and_install_helpers_for_singular_resource(resource)
611
+ end
612
+ end
613
+ end
614
+
615
+ alias_method_chain :resource, :resourceful_view_helpers
616
+
617
+ end
618
+
@@ -0,0 +1,11 @@
1
+ namespace :resourceful_views do
2
+ desc 'List view helper methods introduced by ResourcefulViews'
3
+ task :helpers => :environment do
4
+ ResourcefulViews.helpers.each do |resource, helper_names|
5
+ puts resource
6
+ puts '-' * resource.length
7
+ puts helper_names
8
+ puts
9
+ end
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ingoweiss-resourceful_views
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Ingo Weiss
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-15 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: ResourcefulViews aims to take RESTful conventions beyond controllers and into views by extending the 'map.resources' method to install a comprehensive vocabulary of resource-oriented view helpers
17
+ email:
18
+ - ingo@ingoweiss.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - README
27
+ - lib/resourceful_views.rb
28
+ - tasks/resourceful_views_tasks.rake
29
+ has_rdoc: true
30
+ homepage: http://github.com/ingoweiss/resourceful_views
31
+ post_install_message:
32
+ rdoc_options:
33
+ - --main
34
+ - README
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: "0"
42
+ version:
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ requirements: []
50
+
51
+ rubyforge_project:
52
+ rubygems_version: 1.2.0
53
+ signing_key:
54
+ specification_version: 2
55
+ summary: Resource-oriented helpers for rendering forms and links in Rails
56
+ test_files: []
57
+