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.
Files changed (42) hide show
  1. data/README.rdoc +17 -7
  2. data/Rakefile +15 -2
  3. data/VERSION +1 -1
  4. data/lib/generators/dry_crud/templates/INSTALL +1 -1
  5. data/lib/generators/dry_crud/templates/app/controllers/crud_controller.rb +41 -27
  6. data/lib/generators/dry_crud/templates/app/controllers/list_controller.rb +2 -3
  7. data/lib/generators/dry_crud/templates/app/helpers/crud_helper.rb +33 -14
  8. data/lib/generators/dry_crud/templates/app/helpers/list_helper.rb +1 -2
  9. data/lib/generators/dry_crud/templates/app/helpers/standard_form_builder.rb +32 -3
  10. data/lib/generators/dry_crud/templates/app/helpers/standard_helper.rb +38 -20
  11. data/lib/generators/dry_crud/templates/app/views/crud/_attrs.html.erb +1 -1
  12. data/lib/generators/dry_crud/templates/app/views/crud/edit.html.erb +3 -3
  13. data/lib/generators/dry_crud/templates/app/views/crud/new.html.erb +3 -3
  14. data/lib/generators/dry_crud/templates/app/views/crud/show.html.erb +4 -2
  15. data/lib/generators/dry_crud/templates/app/views/layouts/_menu.html.erb +0 -0
  16. data/lib/generators/dry_crud/templates/app/views/layouts/crud.html.erb +22 -9
  17. data/lib/generators/dry_crud/templates/app/views/list/_search.html.erb +1 -3
  18. data/lib/generators/dry_crud/templates/app/views/list/index.html.erb +4 -4
  19. data/lib/generators/dry_crud/templates/public/images/actions/add.png +0 -0
  20. data/lib/generators/dry_crud/templates/public/images/actions/delete.png +0 -0
  21. data/lib/generators/dry_crud/templates/public/images/actions/edit.png +0 -0
  22. data/lib/generators/dry_crud/templates/public/images/actions/list.png +0 -0
  23. data/lib/generators/dry_crud/templates/public/images/actions/show.png +0 -0
  24. data/lib/generators/dry_crud/templates/public/stylesheets/crud.css +136 -18
  25. data/lib/generators/dry_crud/templates/test/crud_test_model.rb +4 -3
  26. data/lib/generators/dry_crud/templates/test/custom_assertions.rb +1 -1
  27. data/lib/generators/dry_crud/templates/test/functional/crud_test_models_controller_test.rb +21 -1
  28. data/lib/generators/dry_crud/templates/test/unit/helpers/crud_helper_test.rb +0 -5
  29. data/lib/generators/dry_crud/templates/test/unit/helpers/standard_form_builder_test.rb +22 -0
  30. data/test/templates/app/models/city.rb +13 -1
  31. data/test/templates/app/models/person.rb +1 -1
  32. data/test/templates/app/views/ajax/_actions_index.html.erb +1 -1
  33. data/test/templates/app/views/cities/_form.html.erb +1 -1
  34. data/test/templates/app/views/cities/_list.html.erb +2 -2
  35. data/test/templates/app/views/layouts/_menu.html.erb +3 -0
  36. data/test/templates/app/views/people/_attrs.html.erb +5 -7
  37. data/test/templates/config/routes.rb +2 -0
  38. data/test/templates/db/seeds.rb +5 -1
  39. data/test/templates/test/functional/cities_controller_test.rb +10 -0
  40. metadata +11 -6
  41. data/test/templates/app/views/layouts/application.html.erb +0 -33
  42. 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.2.0 is 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.
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, {Active Scaffold}[http://activescaffold.com/] 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.
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[:alert] = 'Could not delete picture'
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('country = ?', @person.country) %>
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, 'app', 'views', 'layouts', 'crud.html.erb'))
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.5
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 :label method to your models for a human-friendly representation.
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.html { redirect_to_show }
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') do |format|
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', 'show') do |format|
93
- format.html { redirect_to_index }
94
- format.xml { head :ok }
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. In case of failure, a standard response
105
- # is given and the failed_action template is rendered. In case of success,
106
- # the flash[:notice] is set and control is passed to the given block.
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
- flash[:notice] = "#{full_entry_label} was successfully #{operation}."
111
- yield format
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 from the given params.
128
+ # Creates a new model entry.
120
129
  def build_entry
121
- @entry = model_class.new(params[model_identifier])
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 Sorting
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 Sorting
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
- add_list_actions(t)
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 add_list_actions(table)
21
- table.col('', :class => 'center') { |e| link_action_show(e) }
22
- table.col('', :class => 'center') { |e| link_action_edit(e) }
23
- table.col('', :class => 'center') { |e| link_action_destroy(e) }
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
- # Renders a generic form for the current entry with :default_attrs or the
27
- # given attribute array, using the StandardFormBuilder.
28
- # If a block is given, a custom form may be rendered and attrs is ignored.
29
- def crud_form(attrs = nil, options = {}, &block)
30
- unless attrs
31
- attrs = default_attrs
32
- [:created_at, :updated_at].each {|a| attrs.delete(a) }
33
- end
34
- standard_form(@entry, attrs, &block)
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].each {|a| attrs.delete(a) }
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, { :prompt => BLANK_SELECT_LABEL }, html_options)
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 == nil
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 then value.to_s
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', :locals => { :label => label, :content => content})
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, div = true)
70
- html = attrs.collect do |a|
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
- labeled(captionize(attr, obj.class), format_attr(obj, attr))
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
- content << render(:partial => 'shared/error_messages', :locals => {:errors => object.errors})
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 => "row_class"), &block)
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', record, :confirm => CONFIRM_DELETE_MESSAGE, :method => :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, options = {}, html_options = {})
152
- link_to("[#{label}]", options, {:class => 'action'}.merge(html_options))
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
- '(none)'
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 %>
@@ -1,7 +1,7 @@
1
1
  <% @title ||= "Edit #{full_entry_label}" -%>
2
2
 
3
- <%= render :partial => 'form' %>
3
+ <% content_for(:actions, render(:partial => 'actions_edit')) %>
4
4
 
5
- <br/>
5
+ <%= clear %>
6
6
 
7
- <%= render :partial => 'actions_edit' %>
7
+ <%= render :partial => 'form' %>
@@ -1,7 +1,7 @@
1
1
  <% @title ||= "New #{models_label.singularize}" -%>
2
2
 
3
- <%= render :partial => 'form' %>
3
+ <% content_for(:actions, link_action_index) %>
4
4
 
5
- <br/>
5
+ <%= clear %>
6
6
 
7
- <%= link_action_index %>
7
+ <%= render :partial => 'form' %>
@@ -1,7 +1,9 @@
1
1
  <% @title ||= full_entry_label -%>
2
2
 
3
+ <% content_for(:actions, render(:partial => 'actions_show')) %>
4
+
5
+ <%= clear %>
6
+
3
7
  <%= render :partial => 'attrs' %>
4
8
 
5
- <br/>
6
9
 
7
- <%= render :partial => 'actions_show' %>