dry_crud 1.2.5 → 1.2.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +17 -7
- data/Rakefile +15 -2
- data/VERSION +1 -1
- data/lib/generators/dry_crud/templates/INSTALL +1 -1
- data/lib/generators/dry_crud/templates/app/controllers/crud_controller.rb +41 -27
- data/lib/generators/dry_crud/templates/app/controllers/list_controller.rb +2 -3
- data/lib/generators/dry_crud/templates/app/helpers/crud_helper.rb +33 -14
- data/lib/generators/dry_crud/templates/app/helpers/list_helper.rb +1 -2
- data/lib/generators/dry_crud/templates/app/helpers/standard_form_builder.rb +32 -3
- data/lib/generators/dry_crud/templates/app/helpers/standard_helper.rb +38 -20
- data/lib/generators/dry_crud/templates/app/views/crud/_attrs.html.erb +1 -1
- data/lib/generators/dry_crud/templates/app/views/crud/edit.html.erb +3 -3
- data/lib/generators/dry_crud/templates/app/views/crud/new.html.erb +3 -3
- data/lib/generators/dry_crud/templates/app/views/crud/show.html.erb +4 -2
- data/lib/generators/dry_crud/templates/app/views/layouts/_menu.html.erb +0 -0
- data/lib/generators/dry_crud/templates/app/views/layouts/crud.html.erb +22 -9
- data/lib/generators/dry_crud/templates/app/views/list/_search.html.erb +1 -3
- data/lib/generators/dry_crud/templates/app/views/list/index.html.erb +4 -4
- data/lib/generators/dry_crud/templates/public/images/actions/add.png +0 -0
- data/lib/generators/dry_crud/templates/public/images/actions/delete.png +0 -0
- data/lib/generators/dry_crud/templates/public/images/actions/edit.png +0 -0
- data/lib/generators/dry_crud/templates/public/images/actions/list.png +0 -0
- data/lib/generators/dry_crud/templates/public/images/actions/show.png +0 -0
- data/lib/generators/dry_crud/templates/public/stylesheets/crud.css +136 -18
- data/lib/generators/dry_crud/templates/test/crud_test_model.rb +4 -3
- data/lib/generators/dry_crud/templates/test/custom_assertions.rb +1 -1
- data/lib/generators/dry_crud/templates/test/functional/crud_test_models_controller_test.rb +21 -1
- data/lib/generators/dry_crud/templates/test/unit/helpers/crud_helper_test.rb +0 -5
- data/lib/generators/dry_crud/templates/test/unit/helpers/standard_form_builder_test.rb +22 -0
- data/test/templates/app/models/city.rb +13 -1
- data/test/templates/app/models/person.rb +1 -1
- data/test/templates/app/views/ajax/_actions_index.html.erb +1 -1
- data/test/templates/app/views/cities/_form.html.erb +1 -1
- data/test/templates/app/views/cities/_list.html.erb +2 -2
- data/test/templates/app/views/layouts/_menu.html.erb +3 -0
- data/test/templates/app/views/people/_attrs.html.erb +5 -7
- data/test/templates/config/routes.rb +2 -0
- data/test/templates/db/seeds.rb +5 -1
- data/test/templates/test/functional/cities_controller_test.rb +10 -0
- metadata +11 -6
- data/test/templates/app/views/layouts/application.html.erb +0 -33
- data/test/templates/public/stylesheets/demo.css +0 -113
data/README.rdoc
CHANGED
@@ -20,16 +20,16 @@ To integrate DRY CRUD into your code, only a few additions are required:
|
|
20
20
|
* To use standard formatting, tables and forms throughout your application, add <tt>helper :standard</tt> to your +ApplicationController+ and benefit everywhere from these little helper methods.
|
21
21
|
* Add a <tt>:label</tt> method to your models for a human-friendly representation.
|
22
22
|
|
23
|
-
Version 1.
|
23
|
+
Version 1.0.0 and higher are built for Rails 3. If you need a version for Rails 2.3, please get version 0.6.0 of the gem or go to the rails-2.3 branch on Github.
|
24
24
|
|
25
25
|
== Overview
|
26
26
|
|
27
|
-
In most Rails applications, you have some models that require basic CRUD (create, read, update, delete) functionality. There are various possibilities like Rails scaffolding, {
|
27
|
+
In most Rails applications, you have some models that require basic CRUD (create, read, update, delete) functionality. There are various possibilities like Rails scaffolding, {Inherited Resources}[https://github.com/josevalim/inherited_resources] or {Dry Scaffold}[http://github.com/grimen/dry_scaffold]. Still, various parts in your application remain duplicated. While you might pull up common methods into a common superclass controller, most views still contain very similar code.
|
28
28
|
|
29
29
|
Enter DRY CRUD.
|
30
30
|
|
31
31
|
<b>
|
32
|
-
The main idea of DRY CRUD is to concentrate basic functionality of your application, like CRUD actions, uniform formatting, forms and tables into specifically extendable units. DRY CRUD generates various foundation classes that you may freely adapt to your application's needs. For each model, you may transparently customize arbitrary parts or just fallback to the general behavior. This applies not only for controllers, but also for view templates and helpers.
|
32
|
+
The main idea of DRY CRUD is to concentrate basic functionality of your application, like CRUD actions, uniform formatting, forms and tables into specifically extendable units. DRY CRUD generates various foundation classes that you may freely adapt to your application's needs. For each model, you may transparently customize arbitrary parts or just fallback to the general behavior. This applies not only for controllers, but also for view templates and helpers. There is no black box your code depends on. You lay the foundation that fits your application best.
|
33
33
|
</b>
|
34
34
|
|
35
35
|
A core element of DRY CRUD is the +RenderInheritable+ module. This gives you inheritable views and partials. In the default case, a template is searched in the current controller's view folder. If it is not found there, the template with the same name in the view folder of the superclass controller is used. This lookup path might be customized as well. RenderInheritable is also available as a stand-alone gem at http://github.com/codez/render_inheritable.
|
@@ -85,7 +85,7 @@ In <tt>app/views/list/index.html.erb</tt>, add the following line for the pagina
|
|
85
85
|
|
86
86
|
And we are done again. All our controllers inheriting from +ListController+, including above +PeopleController+, now have paginated index views. Because our customization for the people table is in the separate <tt>_list</tt> partial, no further modifications are required.
|
87
87
|
If the current page should be remembered while viewing or editing an entry, just add :page to the remembered_params in <tt>ListController::Memory</tt>:
|
88
|
-
|
88
|
+
|
89
89
|
controller.remember_params = [:q, :sort, :sort_dir, :page]
|
90
90
|
|
91
91
|
|
@@ -125,11 +125,19 @@ In <tt>app/controllers/people_controller.rb</tt>:
|
|
125
125
|
|
126
126
|
def delete_picture
|
127
127
|
if !perform_delete_picture(@entry.picture)
|
128
|
-
flash
|
128
|
+
flash.alert = 'Could not delete picture'
|
129
129
|
false
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
133
|
+
Beside these "action" callbacks, there is also a set of "before render" callbacks that are called whenever a certain view is rendered. They are available for the +index+, +show+, +new+, +edit+ and +form+ (= +new+ and +edit+) views. These callbacks are not only called for the corresponding action, but, for example, also when the +new+ view is going to be rendered from an unsuccessfull +create+ action. Say you need to prepare additional variables whenever the form is rendered:
|
134
|
+
|
135
|
+
In <tt>app/controllers/people_controller.rb</tt>:
|
136
|
+
before_render_form :set_hometowns
|
137
|
+
|
138
|
+
def set_hometowns
|
139
|
+
@hometowns = City.where(:country => @entry.country)
|
140
|
+
end
|
133
141
|
|
134
142
|
=== Standard Tables and Forms
|
135
143
|
|
@@ -170,9 +178,9 @@ Of course, custom input fields may be defined as well:
|
|
170
178
|
<% end %>
|
171
179
|
|
172
180
|
Even +belongs_to+ associations are automatically rendered with a select field. By default, all entries from the associated model are used as options. To customize this, either define an instance variable with the same name as the association in your controller, or pass a <tt>:list</tt> option:
|
173
|
-
<%= f.belongs_to_field :hometown, :list => City.where(
|
181
|
+
<%= f.belongs_to_field :hometown, :list => City.where(:country => @person.country) %>
|
174
182
|
|
175
|
-
Yes, it's bad practice to use finder logic in your views! Define the variable <tt>@hometowns</tt> in your controller instead, and you do not even have to specify the <tt>:list</tt> option.
|
183
|
+
Yes, it's bad practice to use finder logic in your views! Define the variable <tt>@hometowns</tt> in your controller instead (as shown in the example above), and you do not even have to specify the <tt>:list</tt> option.
|
176
184
|
|
177
185
|
== Generated Files
|
178
186
|
|
@@ -235,6 +243,8 @@ views/shared/_labeled.html.erb:: Partial to define the layout for an arbitrary c
|
|
235
243
|
|
236
244
|
views/layouts/crud.html.erb:: An example layout showing how to use the @title and +flash+. Most probably you want to merge this with your application.html.erb or adapt the main CRUD templates, so you wont need this file.
|
237
245
|
|
246
|
+
views/layouts/_menu.html.erb:: An empty file to put your menu items into. Included from +list.html.erb+.
|
247
|
+
|
238
248
|
public/stylesheets/crud.css:: A simple CSS with all the classes and ids used in the CRUD code.
|
239
249
|
|
240
250
|
|
data/Rakefile
CHANGED
@@ -25,6 +25,16 @@ task :test => ['test:app:init'] do
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
require 'rcov/rcovtask'
|
29
|
+
Rcov::RcovTask.new do |test|
|
30
|
+
test.libs << "test/test_app/test"
|
31
|
+
test.test_files = Dir[ "test/test_app/test/**/*_test.rb" ]
|
32
|
+
test.rcov_opts = ['--text-report',
|
33
|
+
'-i', '"test\/test_app\/app\/.*"',
|
34
|
+
'-x', '"\/Library\/Ruby\/.*"']
|
35
|
+
test.verbose = true
|
36
|
+
end
|
37
|
+
|
28
38
|
namespace :test do
|
29
39
|
namespace :app do
|
30
40
|
task :environment do
|
@@ -51,9 +61,12 @@ namespace :test do
|
|
51
61
|
desc "Initializes the test application with a couple of classes"
|
52
62
|
task :init => :generate_crud do
|
53
63
|
FileUtils.cp_r(File.join(File.dirname(__FILE__), 'test', 'templates', '.'), TEST_APP_ROOT)
|
54
|
-
FileUtils.rm_f(File.join(TEST_APP_ROOT, '
|
64
|
+
FileUtils.rm_f(File.join(TEST_APP_ROOT, 'public', 'index.html'))
|
65
|
+
FileUtils.mv(File.join(TEST_APP_ROOT, 'app', 'views', 'layouts', 'crud.html.erb'),
|
66
|
+
File.join(TEST_APP_ROOT, 'app', 'views', 'layouts', 'application.html.erb'),
|
67
|
+
:force => true)
|
55
68
|
FileUtils.cd(TEST_APP_ROOT) do
|
56
|
-
sh "rake db:migrate db:seed db:test:prepare"
|
69
|
+
sh "rake db:migrate db:seed db:test:prepare RAILS_ENV=development"
|
57
70
|
end
|
58
71
|
end
|
59
72
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.2.
|
1
|
+
1.2.6
|
@@ -8,6 +8,6 @@ required:
|
|
8
8
|
* To use standard formatting, tables and forms throughout your
|
9
9
|
application, add 'helper :standard' to your ApplicationController and
|
10
10
|
benefit everywhere from these little helper methods.
|
11
|
-
* Add a
|
11
|
+
* Add a #label method to your models for a human-friendly representation.
|
12
12
|
|
13
13
|
Enjoy dry_crud and stay DRY!
|
@@ -20,7 +20,8 @@ class CrudController < ListController
|
|
20
20
|
# Defines before and after callback hooks for create, update, save and destroy.
|
21
21
|
define_model_callbacks :create, :update, :save, :destroy
|
22
22
|
|
23
|
-
# Defines before callbacks for the render actions.
|
23
|
+
# Defines before callbacks for the render actions. A virtual callback
|
24
|
+
# unifiying render_new and render_edit, called render_form, is defined further down.
|
24
25
|
define_model_callbacks :render_show,
|
25
26
|
:render_new,
|
26
27
|
:render_edit,
|
@@ -49,26 +50,27 @@ class CrudController < ListController
|
|
49
50
|
# GET /entries/new
|
50
51
|
# GET /entries/new.xml
|
51
52
|
def new
|
53
|
+
@entry.attributes = params[model_identifier]
|
52
54
|
respond_with @entry
|
53
55
|
end
|
54
|
-
|
55
|
-
# Display a form to edit an exisiting entry of this model.
|
56
|
-
# GET /entries/1/edit
|
57
|
-
def edit
|
58
|
-
render_with_callback :edit
|
59
|
-
end
|
60
|
-
|
56
|
+
|
61
57
|
# Create a new entry of this model from the passed params.
|
62
58
|
# POST /entries
|
63
59
|
# POST /entries.xml
|
64
60
|
def create
|
61
|
+
@entry.attributes = params[model_identifier]
|
65
62
|
created = with_callbacks(:create) { save_entry }
|
66
63
|
|
67
64
|
respond_processed(created, 'created', 'new') do |format|
|
68
|
-
format.
|
69
|
-
format.xml { render :xml => @entry, :status => :created, :location => @entry }
|
65
|
+
format.xml { render :xml => @entry, :status => :created, :location => @entry } if created
|
70
66
|
end
|
71
67
|
end
|
68
|
+
|
69
|
+
# Display a form to edit an exisiting entry of this model.
|
70
|
+
# GET /entries/1/edit
|
71
|
+
def edit
|
72
|
+
render_with_callback 'edit'
|
73
|
+
end
|
72
74
|
|
73
75
|
# Update an existing entry of this model from the passed params.
|
74
76
|
# PUT /entries/1
|
@@ -77,10 +79,7 @@ class CrudController < ListController
|
|
77
79
|
@entry.attributes = params[model_identifier]
|
78
80
|
updated = with_callbacks(:update) { save_entry }
|
79
81
|
|
80
|
-
respond_processed(updated, 'updated', 'edit')
|
81
|
-
format.html { redirect_to_show }
|
82
|
-
format.xml { head :ok }
|
83
|
-
end
|
82
|
+
respond_processed(updated, 'updated', 'edit')
|
84
83
|
end
|
85
84
|
|
86
85
|
# Destroy an existing entry of this model.
|
@@ -88,27 +87,37 @@ class CrudController < ListController
|
|
88
87
|
# DELETE /entries/1.xml
|
89
88
|
def destroy
|
90
89
|
destroyed = with_callbacks(:destroy) { @entry.destroy }
|
91
|
-
|
92
|
-
respond_processed(destroyed, 'destroyed'
|
93
|
-
format.html
|
94
|
-
|
90
|
+
|
91
|
+
respond_processed(destroyed, 'destroyed') do |format|
|
92
|
+
format.html do
|
93
|
+
if destroyed
|
94
|
+
redirect_to_index
|
95
|
+
else
|
96
|
+
flash.alert = @entry.errors.full_messages.join('<br/>').html_safe
|
97
|
+
request.env["HTTP_REFERER"].present? ? redirect_to(:back) : redirect_to_show
|
98
|
+
end
|
99
|
+
end
|
95
100
|
end
|
96
101
|
end
|
97
102
|
|
98
103
|
protected
|
99
104
|
|
100
105
|
############# CUSTOMIZABLE HELPER METHODS ##############################
|
101
|
-
|
102
106
|
|
103
107
|
# Convenience method to respond to various formats if the performed
|
104
|
-
# action may succeed or fail.
|
105
|
-
#
|
106
|
-
#
|
107
|
-
def respond_processed(success, operation, failed_action)
|
108
|
+
# action may succeed or fail. It is possible to pass a block and respond
|
109
|
+
# in custom ways for certain cases. If no response is performed in the
|
110
|
+
# given block, the default responses in this method are executed.
|
111
|
+
def respond_processed(success, operation, failed_action = 'show')
|
108
112
|
respond_to do |format|
|
113
|
+
flash.notice = "#{full_entry_label} was successfully #{operation}." if success
|
114
|
+
yield format if block_given?
|
115
|
+
return if performed?
|
116
|
+
|
117
|
+
# fallback responders if nothing was performed in the block
|
109
118
|
if success
|
110
|
-
|
111
|
-
|
119
|
+
format.html { redirect_to_show }
|
120
|
+
format.xml { head :ok }
|
112
121
|
else
|
113
122
|
format.html { render_with_callback failed_action }
|
114
123
|
format.xml { render :xml => @entry.errors, :status => :unprocessable_entity }
|
@@ -116,9 +125,9 @@ class CrudController < ListController
|
|
116
125
|
end
|
117
126
|
end
|
118
127
|
|
119
|
-
# Creates a new model entry
|
128
|
+
# Creates a new model entry.
|
120
129
|
def build_entry
|
121
|
-
@entry = model_class.new
|
130
|
+
@entry = model_class.new
|
122
131
|
end
|
123
132
|
|
124
133
|
# Sets an existing model entry from the given id.
|
@@ -159,6 +168,11 @@ class CrudController < ListController
|
|
159
168
|
@model_identifier ||= model_class.name.underscore.to_sym
|
160
169
|
end
|
161
170
|
|
171
|
+
# Convenience callback to apply a callback on both form actions (new and edit).
|
172
|
+
def before_render_form(*methods)
|
173
|
+
before_render_new *methods
|
174
|
+
before_render_edit *methods
|
175
|
+
end
|
162
176
|
end
|
163
177
|
|
164
178
|
end
|
@@ -53,7 +53,6 @@ class ListController < ApplicationController
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
|
57
56
|
# Helper method to run before_render callbacks and render the action.
|
58
57
|
# If a callback renders or redirects, the action is not rendered.
|
59
58
|
def render_with_callback(action)
|
@@ -115,7 +114,7 @@ class ListController < ApplicationController
|
|
115
114
|
|
116
115
|
# Sort functionality for the index table.
|
117
116
|
# Extracted into an own module for convenience.
|
118
|
-
module
|
117
|
+
module Sort
|
119
118
|
# Adds a :sort_mappings class attribute.
|
120
119
|
def self.included(controller)
|
121
120
|
# Define a map of (virtual) attributes to SQL order expressions.
|
@@ -160,7 +159,7 @@ class ListController < ApplicationController
|
|
160
159
|
end
|
161
160
|
end
|
162
161
|
|
163
|
-
include
|
162
|
+
include Sort
|
164
163
|
|
165
164
|
# Remembers certain params of the index action in order to return
|
166
165
|
# to the same list after an entry was viewed or edited.
|
@@ -2,6 +2,14 @@
|
|
2
2
|
# attributes for the current model to be used in tables and forms. This helper
|
3
3
|
# is included in CrudController.
|
4
4
|
module CrudHelper
|
5
|
+
|
6
|
+
# Renders a generic form for the current entry with :default_attrs or the
|
7
|
+
# given attribute array, using the StandardFormBuilder.
|
8
|
+
# If a block is given, a custom form may be rendered and attrs is ignored.
|
9
|
+
def crud_form(attrs = nil, options = {}, &block)
|
10
|
+
attrs = default_attrs - [:created_at, :updated_at] unless attrs
|
11
|
+
standard_form(@entry, attrs, &block)
|
12
|
+
end
|
5
13
|
|
6
14
|
# Create a table of the @entries variable with the default or
|
7
15
|
# the passed attributes in its columns.
|
@@ -11,27 +19,38 @@ module CrudHelper
|
|
11
19
|
else
|
12
20
|
attrs = default_attrs if attrs.blank?
|
13
21
|
list_table(*attrs) do |t|
|
14
|
-
|
22
|
+
add_table_actions(t)
|
15
23
|
end
|
16
24
|
end
|
17
25
|
end
|
18
26
|
|
19
27
|
# Adds a set of standard action link column (show, edit, destroy) to the given table.
|
20
|
-
def
|
21
|
-
table
|
22
|
-
table
|
23
|
-
table
|
28
|
+
def add_table_actions(table)
|
29
|
+
action_col(table) { |e| link_table_action_show(e) }
|
30
|
+
action_col(table) { |e| link_table_action_edit(e) }
|
31
|
+
action_col(table) { |e| link_table_action_destroy(e) }
|
24
32
|
end
|
25
33
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
def link_table_action_show(record)
|
35
|
+
link_table_action('show', record)
|
36
|
+
end
|
37
|
+
|
38
|
+
def link_table_action_edit(record)
|
39
|
+
link_table_action('edit', edit_polymorphic_path(record))
|
40
|
+
end
|
41
|
+
|
42
|
+
def link_table_action_destroy(record)
|
43
|
+
link_table_action('delete', record,
|
44
|
+
:confirm => StandardHelper::CONFIRM_DELETE_MESSAGE,
|
45
|
+
:method => :delete)
|
46
|
+
end
|
47
|
+
|
48
|
+
def link_table_action(image, url, html_options = {})
|
49
|
+
link_to(action_icon(image), url, html_options)
|
50
|
+
end
|
51
|
+
|
52
|
+
def action_col(table, &block)
|
53
|
+
table.col('', :class => 'center', &block)
|
35
54
|
end
|
36
55
|
|
37
56
|
end
|
@@ -18,8 +18,7 @@ module ListHelper
|
|
18
18
|
# These are all defined attributes except certain special ones like 'id' or 'position'.
|
19
19
|
def default_attrs
|
20
20
|
attrs = model_class.column_names.collect(&:to_sym)
|
21
|
-
[:id, :position]
|
22
|
-
attrs
|
21
|
+
attrs - [:id, :position, :password]
|
23
22
|
end
|
24
23
|
|
25
24
|
end
|
@@ -6,6 +6,7 @@
|
|
6
6
|
class StandardFormBuilder < ActionView::Helpers::FormBuilder
|
7
7
|
|
8
8
|
BLANK_SELECT_LABEL = 'Please select'
|
9
|
+
REQUIRED_MARK = '<span class="required">*</span>'.html_safe
|
9
10
|
|
10
11
|
attr_reader :template
|
11
12
|
|
@@ -35,6 +36,8 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
|
|
35
36
|
text_area(attr, html_options)
|
36
37
|
elsif belongs_to_association?(attr, type)
|
37
38
|
belongs_to_field(attr, html_options)
|
39
|
+
elsif attr.to_s.include?('password')
|
40
|
+
password_field(attr, html_options)
|
38
41
|
else
|
39
42
|
custom_field_method = :"#{type}_field"
|
40
43
|
if respond_to?(custom_field_method)
|
@@ -50,6 +53,11 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
|
|
50
53
|
super(attr, {:size => 30}.merge(html_options))
|
51
54
|
end
|
52
55
|
|
56
|
+
# Render a standard password field.
|
57
|
+
def password_field(attr, html_options = {})
|
58
|
+
super(attr, {:size => 30}.merge(html_options))
|
59
|
+
end
|
60
|
+
|
53
61
|
# Render a standard text area.
|
54
62
|
def text_area(attr, html_options = {})
|
55
63
|
super(attr, {:rows => 5, :cols => 30}.merge(html_options))
|
@@ -99,12 +107,17 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
|
|
99
107
|
def belongs_to_field(attr, html_options = {})
|
100
108
|
list = association_entries(attr, html_options)
|
101
109
|
if list.present?
|
102
|
-
collection_select(attr, list, :id, :label,
|
110
|
+
collection_select(attr, list, :id, :label, select_options(attr), html_options)
|
103
111
|
else
|
104
112
|
'(none available)'
|
105
113
|
end
|
106
114
|
end
|
107
115
|
|
116
|
+
# Renders a marker if the given attr has to be present.
|
117
|
+
def required_mark(attr)
|
118
|
+
REQUIRED_MARK if required?(attr)
|
119
|
+
end
|
120
|
+
|
108
121
|
# Render a label for the given attribute with the passed field html section.
|
109
122
|
def labeled(attr, field_html = nil, &block)
|
110
123
|
template.labeled(label(attr), field_html, &block)
|
@@ -114,7 +127,7 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
|
|
114
127
|
# input field. E.g. labeled_boolean_field(:checked, {:class => 'bold'})
|
115
128
|
def method_missing(name, *args)
|
116
129
|
if field_method = labeled_field_method?(name)
|
117
|
-
labeled(args.first, send(field_method, *args))
|
130
|
+
labeled(args.first, send(field_method, *args) + required_mark(args.first))
|
118
131
|
else
|
119
132
|
super(name, *args)
|
120
133
|
end
|
@@ -129,7 +142,7 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
|
|
129
142
|
# Returns true if attr is a non-polymorphic belongs_to association,
|
130
143
|
# for which an input field may be automatically rendered.
|
131
144
|
def belongs_to_association?(attr, type)
|
132
|
-
if type == :integer || type
|
145
|
+
if type == :integer || type.nil?
|
133
146
|
assoc = association(@object, attr, :belongs_to)
|
134
147
|
assoc.present? && assoc.options[:polymorphic].nil?
|
135
148
|
else
|
@@ -153,6 +166,22 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
|
|
153
166
|
list
|
154
167
|
end
|
155
168
|
|
169
|
+
# Returns true if the given attribute must be present.
|
170
|
+
def required?(attr)
|
171
|
+
attr = attr.to_s
|
172
|
+
attr, attr_id = attr.end_with?('_id') ? [attr[0..-4], attr] : [attr, "#{attr}_id"]
|
173
|
+
validators = @object.class.validators_on(attr) +
|
174
|
+
@object.class.validators_on(attr_id)
|
175
|
+
validators.any? {|v| v.kind == :presence }
|
176
|
+
end
|
177
|
+
|
178
|
+
# Depending if the given attribute must be present, return
|
179
|
+
# only an initial selection prompt or a blank option, respectively.
|
180
|
+
def select_options(attr)
|
181
|
+
required?(attr) ? { :prompt => BLANK_SELECT_LABEL } :
|
182
|
+
{ :include_blank => StandardHelper::NO_ENTRY }
|
183
|
+
end
|
184
|
+
|
156
185
|
private
|
157
186
|
|
158
187
|
def labeled_field_method?(name)
|
@@ -4,6 +4,7 @@
|
|
4
4
|
module StandardHelper
|
5
5
|
|
6
6
|
NO_LIST_ENTRIES_MESSAGE = "No entries found"
|
7
|
+
NO_ENTRY = '(none)'
|
7
8
|
CONFIRM_DELETE_MESSAGE = 'Do you really want to delete this entry?'
|
8
9
|
|
9
10
|
FLOAT_FORMAT = "%.2f"
|
@@ -20,7 +21,7 @@ module StandardHelper
|
|
20
21
|
case value
|
21
22
|
when Fixnum then number_with_delimiter(value)
|
22
23
|
when Float then FLOAT_FORMAT % value
|
23
|
-
when Date
|
24
|
+
when Date then value.to_s
|
24
25
|
when Time then value.strftime(TIME_FORMAT)
|
25
26
|
when true then 'yes'
|
26
27
|
when false then 'no'
|
@@ -52,7 +53,8 @@ module StandardHelper
|
|
52
53
|
# Renders an arbitrary content with the given label. Used for uniform presentation.
|
53
54
|
def labeled(label, content = nil, &block)
|
54
55
|
content = capture(&block) if block_given?
|
55
|
-
render(:partial => 'shared/labeled',
|
56
|
+
render(:partial => 'shared/labeled',
|
57
|
+
:locals => { :label => label, :content => content})
|
56
58
|
end
|
57
59
|
|
58
60
|
# Transform the given text into a form as used by labels or table headers.
|
@@ -66,17 +68,15 @@ module StandardHelper
|
|
66
68
|
|
67
69
|
# Renders a list of attributes with label and value for a given object.
|
68
70
|
# Optionally surrounded with a div.
|
69
|
-
def render_attrs(obj, attrs
|
70
|
-
|
71
|
+
def render_attrs(obj, *attrs)
|
72
|
+
attrs.collect do |a|
|
71
73
|
labeled_attr(obj, a)
|
72
74
|
end.join("\n").html_safe
|
73
|
-
|
74
|
-
div ? content_tag(:div, html, :class => 'attributes') : html
|
75
75
|
end
|
76
76
|
|
77
77
|
# Renders the formatted content of the given attribute with a label.
|
78
78
|
def labeled_attr(obj, attr)
|
79
|
-
|
79
|
+
labeled(captionize(attr, obj.class), format_attr(obj, attr))
|
80
80
|
end
|
81
81
|
|
82
82
|
# Renders a table for the given entries. One column is rendered for each attribute passed.
|
@@ -99,8 +99,8 @@ module StandardHelper
|
|
99
99
|
# If a block is given, custom input fields may be rendered and attrs is ignored.
|
100
100
|
def standard_form(object, attrs = [], options = {}, &block)
|
101
101
|
form_for(object, {:builder => StandardFormBuilder}.merge(options)) do |form|
|
102
|
-
content =
|
103
|
-
|
102
|
+
content = render(:partial => 'shared/error_messages',
|
103
|
+
:locals => {:errors => object.errors})
|
104
104
|
|
105
105
|
content << if block_given?
|
106
106
|
capture(form, &block)
|
@@ -108,14 +108,22 @@ module StandardHelper
|
|
108
108
|
form.labeled_input_fields(*attrs)
|
109
109
|
end
|
110
110
|
|
111
|
-
content << labeled(nil, form.submit("Save"))
|
111
|
+
content << labeled(nil, form.submit("Save") + cancel_link(object))
|
112
112
|
content.html_safe
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
+
def cancel_link(object)
|
117
|
+
link_to("Cancel", polymorphic_path(object), :class => 'cancel')
|
118
|
+
end
|
119
|
+
|
116
120
|
# Alternate table row
|
117
|
-
def tr_alt(&block)
|
118
|
-
content_tag(:tr, :class => cycle("even", "odd", :name =>
|
121
|
+
def tr_alt(cycle_name = 'row_class', &block)
|
122
|
+
content_tag(:tr, :class => cycle("even", "odd", :name => cycle_name), &block)
|
123
|
+
end
|
124
|
+
|
125
|
+
def clear
|
126
|
+
content_tag(:div, '', :class => 'clear')
|
119
127
|
end
|
120
128
|
|
121
129
|
|
@@ -123,33 +131,43 @@ module StandardHelper
|
|
123
131
|
|
124
132
|
# Standard link action to the show page of a given record.
|
125
133
|
def link_action_show(record)
|
126
|
-
link_action 'Show', record
|
134
|
+
link_action 'Show', 'show', record
|
127
135
|
end
|
128
136
|
|
129
137
|
# Standard link action to the edit page of a given record.
|
130
138
|
def link_action_edit(record)
|
131
|
-
link_action 'Edit', edit_polymorphic_path(record)
|
139
|
+
link_action 'Edit', 'edit', edit_polymorphic_path(record)
|
132
140
|
end
|
133
141
|
|
134
142
|
# Standard link action to the destroy action of a given record.
|
135
143
|
def link_action_destroy(record)
|
136
|
-
link_action 'Delete',
|
144
|
+
link_action 'Delete', 'delete', record,
|
145
|
+
:confirm => CONFIRM_DELETE_MESSAGE,
|
146
|
+
:method => :delete
|
137
147
|
end
|
138
148
|
|
139
149
|
# Standard link action to the list page.
|
140
150
|
def link_action_index(url_options = {:action => 'index', :returning => true})
|
141
|
-
link_action 'List', url_options
|
151
|
+
link_action 'List', 'list', url_options
|
142
152
|
end
|
143
153
|
|
144
154
|
# Standard link action to the new page.
|
145
155
|
def link_action_add(url_options = {:action => 'new'})
|
146
|
-
link_action 'Add', url_options
|
156
|
+
link_action 'Add', 'add', url_options
|
147
157
|
end
|
148
158
|
|
149
159
|
# A generic helper method to create action links.
|
150
160
|
# These link could be styled to look like buttons, for example.
|
151
|
-
def link_action(label,
|
152
|
-
link_to(
|
161
|
+
def link_action(label, icon = nil, url = {}, html_options = {})
|
162
|
+
link_to(icon ? action_icon(icon, label) : label,
|
163
|
+
url,
|
164
|
+
{:class => 'action'}.merge(html_options))
|
165
|
+
end
|
166
|
+
|
167
|
+
def action_icon(icon, label = nil)
|
168
|
+
html = image_tag("actions/#{icon}.png", :size => '16x16')
|
169
|
+
html << ' ' << label if label
|
170
|
+
html
|
153
171
|
end
|
154
172
|
|
155
173
|
protected
|
@@ -161,7 +179,7 @@ module StandardHelper
|
|
161
179
|
if assoc_val = obj.send(assoc.name)
|
162
180
|
link_to_unless(no_assoc_link?(assoc, assoc_val), assoc_val.label, assoc_val)
|
163
181
|
else
|
164
|
-
|
182
|
+
NO_ENTRY
|
165
183
|
end
|
166
184
|
end
|
167
185
|
|
@@ -1 +1 @@
|
|
1
|
-
<%= render_attrs @entry, default_attrs %>
|
1
|
+
<%= render_attrs @entry, *default_attrs %>
|
File without changes
|