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 +148 -0
- data/lib/resourceful_views.rb +618 -0
- data/tasks/resourceful_views_tasks.rake +11 -0
- metadata +57 -0
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
|
+
|