dry_crud 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/MIT-LICENSE +19 -0
  2. data/README.rdoc +208 -0
  3. data/Rakefile +101 -0
  4. data/VERSION +1 -0
  5. data/rails_generators/dry_crud/USAGE +1 -0
  6. data/rails_generators/dry_crud/dry_crud_generator.rb +22 -0
  7. data/rails_generators/dry_crud/templates/INSTALL +13 -0
  8. data/rails_generators/dry_crud/templates/app/controllers/crud_controller.rb +182 -0
  9. data/rails_generators/dry_crud/templates/app/helpers/crud_helper.rb +45 -0
  10. data/rails_generators/dry_crud/templates/app/helpers/standard_helper.rb +209 -0
  11. data/rails_generators/dry_crud/templates/app/views/crud/_attrs.html.erb +1 -0
  12. data/rails_generators/dry_crud/templates/app/views/crud/_form.html.erb +1 -0
  13. data/rails_generators/dry_crud/templates/app/views/crud/_list.html.erb +1 -0
  14. data/rails_generators/dry_crud/templates/app/views/crud/edit.html.erb +8 -0
  15. data/rails_generators/dry_crud/templates/app/views/crud/index.html.erb +14 -0
  16. data/rails_generators/dry_crud/templates/app/views/crud/new.html.erb +7 -0
  17. data/rails_generators/dry_crud/templates/app/views/crud/show.html.erb +9 -0
  18. data/rails_generators/dry_crud/templates/app/views/layouts/crud.html.erb +20 -0
  19. data/rails_generators/dry_crud/templates/app/views/shared/_labeled.html.erb +5 -0
  20. data/rails_generators/dry_crud/templates/lib/crud_callbacks.rb +55 -0
  21. data/rails_generators/dry_crud/templates/lib/render_inheritable.rb +118 -0
  22. data/rails_generators/dry_crud/templates/lib/standard_form_builder.rb +161 -0
  23. data/rails_generators/dry_crud/templates/lib/standard_table_builder.rb +104 -0
  24. data/rails_generators/dry_crud/templates/public/stylesheets/crud.css +91 -0
  25. data/rails_generators/dry_crud/templates/test/crud_test_model.rb +124 -0
  26. data/rails_generators/dry_crud/templates/test/functional/crud_controller_test_helper.rb +172 -0
  27. data/rails_generators/dry_crud/templates/test/functional/crud_test_models_controller_test.rb +96 -0
  28. data/rails_generators/dry_crud/templates/test/unit/crud_helper_test.rb +57 -0
  29. data/rails_generators/dry_crud/templates/test/unit/render_inheritable_test.rb +161 -0
  30. data/rails_generators/dry_crud/templates/test/unit/standard_form_builder_test.rb +83 -0
  31. data/rails_generators/dry_crud/templates/test/unit/standard_helper_test.rb +183 -0
  32. data/rails_generators/dry_crud/templates/test/unit/standard_table_builder_test.rb +99 -0
  33. data/test/templates/app/controllers/ajax_controller.rb +7 -0
  34. data/test/templates/app/controllers/application_controller.rb +11 -0
  35. data/test/templates/app/controllers/cities_controller.rb +13 -0
  36. data/test/templates/app/controllers/people_controller.rb +7 -0
  37. data/test/templates/app/helpers/people_helper.rb +7 -0
  38. data/test/templates/app/models/city.rb +13 -0
  39. data/test/templates/app/models/person.rb +9 -0
  40. data/test/templates/app/views/ajax/_hello.html.erb +1 -0
  41. data/test/templates/app/views/ajax/ajax.js.rjs +1 -0
  42. data/test/templates/app/views/ajax/index.html.erb +9 -0
  43. data/test/templates/app/views/cities/_attrs.html.erb +1 -0
  44. data/test/templates/app/views/cities/_form.html.erb +4 -0
  45. data/test/templates/app/views/cities/_hello.html.erb +1 -0
  46. data/test/templates/app/views/cities/_list.html.erb +5 -0
  47. data/test/templates/config/routes.rb +11 -0
  48. data/test/templates/db/migrate/20100511174904_create_people_and_cities.rb +23 -0
  49. data/test/templates/test/fixtures/cities.yml +11 -0
  50. data/test/templates/test/fixtures/people.yml +14 -0
  51. data/test/templates/test/functional/cities_controller_test.rb +35 -0
  52. data/test/templates/test/functional/people_controller_test.rb +30 -0
  53. metadata +127 -0
@@ -0,0 +1,209 @@
1
+ # A view helper to standartize often used functions like formatting,
2
+ # tables, forms or action links. This helper is ideally defined in the
3
+ # ApplicationController.
4
+ module StandardHelper
5
+
6
+ NO_LIST_ENTRIES_MESSAGE = "No entries available"
7
+ CONFIRM_DELETE_MESSAGE = 'Do you really want to delete this entry?'
8
+
9
+ FLOAT_FORMAT = "%.2f"
10
+ TIME_FORMAT = "%H:%M"
11
+ EMPTY_STRING = " " # non-breaking space asserts better css styling.
12
+
13
+ ################ FORMATTING HELPERS ##################################
14
+
15
+ # Define an array of associations symbols in your helper that should not get automatically linked.
16
+ #def no_assoc_links = [:city]
17
+
18
+ # Formats a single value
19
+ def f(value)
20
+ case value
21
+ when Fixnum then number_with_delimiter(value)
22
+ when Float then FLOAT_FORMAT % value
23
+ when Date then value.to_s
24
+ when Time then value.strftime(TIME_FORMAT)
25
+ when true then 'yes'
26
+ when false then 'no'
27
+ when nil then EMPTY_STRING
28
+ else
29
+ value.respond_to?(:label) ? h(value.label) : h(value.to_s)
30
+ end
31
+ end
32
+
33
+ # Formats an arbitrary attribute of the given ActiveRecord object.
34
+ # If no specific format_{attr} method is found, formats the value as follows:
35
+ # If the value is an associated model, renders the label of this object.
36
+ # Otherwise, calls format_type.
37
+ def format_attr(obj, attr)
38
+ format_attr_method = :"format_#{attr.to_s}"
39
+ if respond_to?(format_attr_method)
40
+ send(format_attr_method, obj)
41
+ elsif assoc = belongs_to_association(obj, attr)
42
+ format_assoc(obj, assoc)
43
+ else
44
+ format_type(obj, attr)
45
+ end
46
+ end
47
+
48
+ # Formats an active record association
49
+ def format_assoc(obj, assoc)
50
+ if assoc_val = obj.send(assoc.name)
51
+ link_to_unless(no_assoc_link?(assoc), h(assoc_val.label), assoc_val)
52
+ else
53
+ '(none)'
54
+ end
55
+ end
56
+
57
+ # Returns true if no link should be created when formatting the given association.
58
+ def no_assoc_link?(assoc)
59
+ (respond_to?(:no_assoc_links) && no_assoc_links.to_a.include?(assoc.name.to_sym)) ||
60
+ !respond_to?("#{assoc.klass.name.underscore}_path".to_sym)
61
+ end
62
+
63
+ # Formats an arbitrary attribute of the given object depending on its data type.
64
+ # For ActiveRecords, take the defined data type into account for special types
65
+ # that have no own object class.
66
+ def format_type(obj, attr)
67
+ val = obj.send(attr)
68
+ return EMPTY_STRING if val.nil?
69
+ case column_type(obj, attr)
70
+ when :time then val.strftime(TIME_FORMAT)
71
+ when :date then val.to_date.to_s
72
+ when :text then simple_format(h(val))
73
+ when :decimal then f(val.to_s.to_f)
74
+ else f(val)
75
+ end
76
+ end
77
+
78
+ # Returns the ActiveRecord column type or nil.
79
+ def column_type(obj, attr)
80
+ column_property(obj, attr, :type)
81
+ end
82
+
83
+ # Returns an ActiveRecord column property for the passed attr or nil
84
+ def column_property(obj, attr, property)
85
+ if obj.respond_to?(:column_for_attribute)
86
+ column = obj.column_for_attribute(attr)
87
+ column.try(property)
88
+ end
89
+ end
90
+
91
+ # Returns the :belongs_to association for the given attribute or nil if there is none.
92
+ def belongs_to_association(obj, attr)
93
+ if assoc = association(obj, attr)
94
+ assoc if assoc.macro == :belongs_to
95
+ end
96
+ end
97
+
98
+ # Returns the association proxy for the given attribute. The attr parameter
99
+ # may be the _id column or the association name. Returns nil if no association
100
+ # was found.
101
+ def association(obj, attr)
102
+ if obj.class.respond_to?(:reflect_on_association)
103
+ assoc = attr.to_s =~ /_id$/ ? attr.to_s[0..-4].to_sym : attr
104
+ obj.class.reflect_on_association(assoc)
105
+ end
106
+ end
107
+
108
+
109
+ ############## STANDARD HTML SECTIONS ############################
110
+
111
+
112
+ # Renders an arbitrary content with the given label. Used for uniform presentation.
113
+ # Without block, this may be used in the form <%= labeled(...) %>, with like <% labeled(..) do %>
114
+ def labeled(label, content = nil, &block)
115
+ content = capture(&block) if block_given?
116
+ html = render(:partial => 'shared/labeled', :locals => { :label => label, :content => content})
117
+ block_given? ? concat(html) : html
118
+ end
119
+
120
+ # Transform the given text into a form as used by labels or table headers.
121
+ def captionize(text, clazz = nil)
122
+ if clazz.respond_to?(:human_attribute_name)
123
+ clazz.human_attribute_name(text)
124
+ else
125
+ text.to_s.humanize.titleize
126
+ end
127
+ end
128
+
129
+ # Renders a list of attributes with label and value for a given object.
130
+ # Optionally surrounded with a div.
131
+ def render_attrs(obj, attrs, div = true)
132
+ html = attrs.collect do |a|
133
+ labeled(captionize(a, obj.class), format_attr(obj, a))
134
+ end.join
135
+
136
+ div ? content_tag(:div, html, :class => 'attributes') : html
137
+ end
138
+
139
+ # Renders a table for the given entries as defined by the following block.
140
+ # If entries are empty, an appropriate message is rendered.
141
+ def table(entries, &block)
142
+ if entries.present?
143
+ StandardTableBuilder.table(entries, self, &block)
144
+ else
145
+ content_tag(:div, NO_LIST_ENTRIES_MESSAGE, :class => 'list')
146
+ end
147
+ end
148
+
149
+ # Renders a generic form for all given attributes using StandardFormBuilder.
150
+ # Before the input fields, the error messages are rendered, if present.
151
+ # The form is rendered with a basic save button.
152
+ # If a block is given, custom input fields may be rendered and attrs is ignored.
153
+ #
154
+ # The form is always directly printed into the erb, so the call must
155
+ # go within a normal <% form(...) %> section, not in a <%= output section
156
+ def standard_form(object, attrs = [], options = {})
157
+ form_for(object, {:builder => StandardFormBuilder}.merge(options)) do |form|
158
+ concat form.error_messages
159
+
160
+ if block_given?
161
+ yield(form)
162
+ else
163
+ concat form.labeled_input_fields(*attrs)
164
+ end
165
+
166
+ concat labeled(EMPTY_STRING, form.submit("Save"))
167
+ end
168
+ end
169
+
170
+ # Alternate table row
171
+ def tr_alt(&block)
172
+ content_tag(:tr, :class => cycle("even", "odd", :name => "row_class"), &block)
173
+ end
174
+
175
+
176
+ ######## ACTION LINKS ###################################################### :nodoc:
177
+
178
+ # Standard link action to the show page of a given record.
179
+ def link_action_show(record)
180
+ link_action 'Show', record
181
+ end
182
+
183
+ # Standard link action to the edit page of a given record.
184
+ def link_action_edit(record)
185
+ link_action 'Edit', edit_polymorphic_path(record)
186
+ end
187
+
188
+ # Standard link action to the destroy action of a given record.
189
+ def link_action_destroy(record)
190
+ link_action 'Delete', record, :confirm => CONFIRM_DELETE_MESSAGE, :method => :delete
191
+ end
192
+
193
+ # Standard link action to the list page.
194
+ def link_action_index(url_options = {:action => 'index'})
195
+ link_action 'List', url_options
196
+ end
197
+
198
+ # Standard link action to the new page.
199
+ def link_action_add(url_options = {:action => 'new'})
200
+ link_action 'Add', url_options
201
+ end
202
+
203
+ # A generic helper method to create action links.
204
+ # These link may be styled to look like buttons, for example.
205
+ def link_action(label, options = {}, html_options = {})
206
+ link_to("[#{label}]", options, {:class => 'action'}.merge(html_options))
207
+ end
208
+
209
+ end
@@ -0,0 +1 @@
1
+ <%= render_attrs @entry, default_attrs %>
@@ -0,0 +1,8 @@
1
+ <% @title = "Edit #{full_entry_label}" -%>
2
+
3
+ <%= render_inheritable :partial => 'form' %>
4
+
5
+ <br/>
6
+
7
+ <%= link_action_show @entry %>
8
+ <%= link_action_destroy @entry %>
@@ -0,0 +1,14 @@
1
+ <% @title = "Listing #{models_label}" -%>
2
+
3
+ <%= render_inheritable :partial => 'list' %>
4
+
5
+ <br/>
6
+
7
+ <%= link_action_add %>
8
+
9
+
10
+
11
+
12
+
13
+
14
+
@@ -0,0 +1,7 @@
1
+ <% @title = "New #{models_label.singularize}" -%>
2
+
3
+ <%= render_inheritable :partial => 'form' %>
4
+
5
+ <br/>
6
+
7
+ <%= link_action_index %>
@@ -0,0 +1,9 @@
1
+ <% @title = full_entry_label -%>
2
+
3
+ <%= render_inheritable :partial => 'attrs' %>
4
+
5
+ <br/>
6
+
7
+ <%= link_action_index %>
8
+ <%= link_action_edit(@entry) %>
9
+ <%= link_action_destroy (@entry) %>
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+
4
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
+ <head>
6
+ <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
7
+ <title><%= @title %></title>
8
+ <%= stylesheet_link_tag 'crud' %>
9
+ <%= javascript_include_tag :all %>
10
+ </head>
11
+ <body>
12
+
13
+ <h1><%= @title %></h1>
14
+
15
+ <p id="flash_notice"><%= flash[:notice] %></p>
16
+
17
+ <%= yield %>
18
+
19
+ </body>
20
+ </html>
@@ -0,0 +1,5 @@
1
+ <div class="labeled">
2
+ <%-# presence is Rails 2.3.8 for label.present? ? label : StandardHelper::EMPTY_STRING -%>
3
+ <div class="caption"><%= label.presence || StandardHelper::EMPTY_STRING %></div>
4
+ <div class="value"><%= content.presence || StandardHelper::EMPTY_STRING %></div>
5
+ </div>
@@ -0,0 +1,55 @@
1
+ # Defines before and after callback hooks for render, create, update, save and destroy.
2
+ # When to execute the callbacks is in the responsibility of the clients of this module.
3
+ #
4
+ # The following callbacks may be defined:
5
+ # * before_create
6
+ # * after_create
7
+ # * before_update
8
+ # * after_update
9
+ # * before_save
10
+ # * after_save
11
+ # * before_destroy
12
+ # * after_destroy
13
+ # * before_render_index
14
+ # * before_render_show
15
+ # * before_render_new
16
+ # * before_render_edit
17
+ #
18
+ module CrudCallbacks
19
+
20
+ def self.included(base)
21
+ base.send :include, ActiveSupport::Callbacks
22
+
23
+ base.define_callbacks :before_create, :after_create,
24
+ :before_update, :after_update,
25
+ :before_save, :after_save,
26
+ :before_destroy, :after_destroy,
27
+ :before_render_index,
28
+ :before_render_show,
29
+ :before_render_new,
30
+ :before_render_edit
31
+ end
32
+
33
+ protected
34
+
35
+ # Helper method the run the given block in between the before and after
36
+ # callbacks of the given kind.
37
+ def with_callbacks(kind)
38
+ return false if callbacks("before_#{kind}".to_sym) == false
39
+ if result = yield
40
+ callbacks("after_#{kind}".to_sym)
41
+ end
42
+ result
43
+ end
44
+
45
+ def render_callbacks(action)
46
+ run_callbacks("before_render_#{action}".to_sym) do |result, object|
47
+ result == false || object.performed?
48
+ end
49
+ end
50
+
51
+ def callbacks(kind)
52
+ run_callbacks(kind) { |result, object| false == result }
53
+ end
54
+
55
+ end
@@ -0,0 +1,118 @@
1
+ # Allows one to render inheritable views and partials.
2
+ # If no view file is found for the current controller, the corresponding file
3
+ # is looked up in its superclass hierarchy. This module must only be
4
+ # included in the root controller of the desired lookup hierarchy.
5
+ #
6
+ # By default, this module only supports direct inheritance over one level. By overriding
7
+ # the method lookup_path, you may define a custom lookup path. By providing an object
8
+ # for the 'with' parameter, this path may even be dynamic.
9
+ module RenderInheritable
10
+
11
+ protected
12
+
13
+ # Add inheritable_root_path method to includer
14
+ def self.included(controller_class)
15
+ controller_class.send(:extend, ClassMethods)
16
+
17
+ controller_class.send(:class_variable_set, :@@inheritable_root_controller, controller_class)
18
+ controller_class.cattr_reader :inheritable_root_controller
19
+
20
+ controller_class.helper ViewHelper
21
+ controller_class.helper_method :inheritable_partial_options
22
+ end
23
+
24
+ # Method from ActionController::Base overriden, so render_inheritable will be
25
+ # called if the action does not call render explicitly.
26
+ def default_render
27
+ render_inheritable :action => action_name
28
+ end
29
+
30
+ # Renders an action or a partial considering the lookup path. Templates
31
+ # specified in the :action or :partial options are looked up and the most
32
+ # specific one found will get rendered. The options are directly passed to
33
+ # the original render method.
34
+ def render_inheritable(options)
35
+ if options[:action]
36
+ inheritable_template_options(options)
37
+ elsif options[:partial]
38
+ inheritable_partial_options(options)
39
+ end
40
+ render options
41
+ end
42
+
43
+ # Replaces the :template option with the file found in the lookup.
44
+ def inheritable_template_options(options)
45
+ file = options.delete(:action)
46
+ inheritable_file_options(options, :template, file)
47
+ end
48
+
49
+ # Replaces the :partial option with the file found in the lookup.
50
+ def inheritable_partial_options(options)
51
+ inheritable_file_options(options, :partial, options[:partial])
52
+ end
53
+
54
+ def inheritable_file_options(options, key, file) #:nodoc:
55
+ with = options.delete(:with)
56
+ filename = (key == :partial) ? "_#{file}" : file
57
+ folder = self.class.find_inheritable_file(filename, default_template_format, with)
58
+ options[key] = folder.present? ? "#{folder}/#{file}" : file
59
+ end
60
+
61
+ module ClassMethods
62
+ # Performs a lookup for the given filename and returns the most specific
63
+ # folder that contains the file.
64
+ def find_inheritable_file(filename, format = :html, with = nil)
65
+ inheritable_cache[format.to_sym][filename][with] ||= find_inheritable_artifact(with) do |folder|
66
+ view_paths.find_template("#{folder}/#{filename}", format).present? rescue false
67
+ end
68
+ end
69
+
70
+ # Performs a lookup for a controller and returns the name of the most specific one found.
71
+ # This method is primarly usefull when given a 'with' argument, that is used
72
+ # in a custom lookup_path.
73
+ def inheritable_controller(with = nil)
74
+ c = find_inheritable_artifact(with) do |folder|
75
+ ActionController::Routing.possible_controllers.any? { |c| c == folder }
76
+ end
77
+ c || inheritable_root_controller.controller_path
78
+ end
79
+
80
+ # Runs through the lookup path and yields each folder to the passed block.
81
+ # If the block returns true, this folder is returned and no further lookup
82
+ # happens. If no folder is found, the nil is returned.
83
+ def find_inheritable_artifact(with = nil)
84
+ lookup_path(with).each { |folder| return folder if yield(folder) }
85
+ nil
86
+ end
87
+
88
+ # An array of controller names / folders, ordered from most specific to most general.
89
+ # May be dynamic dependening on the passed 'with' argument.
90
+ # You may override this method in an own controller to customize the lookup path.
91
+ def lookup_path(with = nil)
92
+ inheritance_lookup_path
93
+ end
94
+
95
+ # The inheritance path of controllers that is used as default lookup path.
96
+ def inheritance_lookup_path
97
+ path = [self]
98
+ until path.last == inheritable_root_controller
99
+ path << path.last.superclass
100
+ end
101
+ path.collect(&:controller_path)
102
+ end
103
+
104
+ def inheritable_cache #:nodoc:
105
+ @inheritable_cache ||= Hash.new {|h, k| h[k] = Hash.new {|h, k| h[k] = Hash.new } }
106
+ end
107
+ end
108
+
109
+ module ViewHelper
110
+ # Because ActionView has a different :render method than ActionController,
111
+ # this method provides an entry point to use render_inheritable from views.
112
+ def render_inheritable(options)
113
+ inheritable_partial_options(options) if options[:partial]
114
+ render options
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1,161 @@
1
+ # A form builder that automatically selects the corresponding input field
2
+ # for ActiveRecord column types. Convenience methods for each column type allow
3
+ # one to customize the different fields.
4
+ # All field methods may be prefixed with 'labeled_' in order to render
5
+ # a standard label with them.
6
+ class StandardFormBuilder < ActionView::Helpers::FormBuilder
7
+
8
+ BLANK_SELECT_LABEL = 'Please select'
9
+
10
+ attr_reader :template
11
+
12
+ delegate :association, :belongs_to_association, :column_type, :column_property, :captionize,
13
+ :to => :template
14
+
15
+ # Render multiple input fields together with a label for the given attributes.
16
+ def labeled_input_fields(*attrs)
17
+ attrs.collect {|a| labeled_input_field(a) }.join("\n")
18
+ end
19
+
20
+ # Render a standartized label.
21
+ def label(attr, text = nil, options = {})
22
+ if attr.is_a?(Symbol) && text.nil? && options.blank?
23
+ super(attr, captionize(attr, @object.class))
24
+ else
25
+ super(attr, text, options)
26
+ end
27
+ end
28
+
29
+ # Render a corresponding input field for the given attribute.
30
+ # The input field is chosen based on the ActiveRecord column type.
31
+ # Use additional html_options for the input element.
32
+ def input_field(attr, html_options = {})
33
+ type = column_type(@object, attr)
34
+ if type == :text
35
+ text_area(attr, html_options)
36
+ elsif belongs_to_association?(attr, type)
37
+ belongs_to_field(attr, html_options)
38
+ else
39
+ custom_field_method = :"#{type}_field"
40
+ if respond_to?(custom_field_method)
41
+ send(custom_field_method, attr, html_options)
42
+ else
43
+ text_field(attr, html_options)
44
+ end
45
+ end
46
+ end
47
+
48
+ # Render a standard text field.
49
+ def text_field(attr, html_options = {})
50
+ super(attr, {:size => 30}.merge(html_options))
51
+ end
52
+
53
+ # Render a standard text area.
54
+ def text_area(attr, html_options = {})
55
+ super(attr, {:rows => 5, :cols => 30}.merge(html_options))
56
+ end
57
+
58
+ # Render a standard string field with column contraints.
59
+ def string_field(attr, html_options = {})
60
+ limit = column_property(@object, attr, :limit)
61
+ html_options = {:maxlength => limit}.merge(html_options) if limit
62
+ text_field(attr, html_options)
63
+ end
64
+
65
+ # Render a standard number field.
66
+ def number_field(attr, html_options = {})
67
+ text_field(attr, {:size => 15}.merge(html_options))
68
+ end
69
+
70
+ # Render an integer field.
71
+ def integer_field(attr, html_options = {})
72
+ number_field(attr, html_options)
73
+ end
74
+
75
+ # Render a float field.
76
+ def float_field(attr, html_options = {})
77
+ number_field(attr, html_options)
78
+ end
79
+
80
+ # Render a decimal field.
81
+ def decimal_field(attr, html_options = {})
82
+ number_field(attr, html_options)
83
+ end
84
+
85
+ # Render a boolean field.
86
+ def boolean_field(attr, html_options = {})
87
+ check_box(attr, html_options)
88
+ end
89
+
90
+ # Render a field to select a date. You might want to customize this.
91
+ def date_field(attr, html_options = {})
92
+ date_select(attr, {}, html_options)
93
+ end
94
+
95
+ # Render a select element for a :belongs_to association defined by attr.
96
+ # Use additional html_options for the select element.
97
+ # To pass a custom element list, specify the list with the :list key or
98
+ # define an instance variable with the pluralized name of the association.
99
+ def belongs_to_field(attr, html_options = {})
100
+ list = association_entries(attr, html_options)
101
+ if list.present?
102
+ collection_select(attr, list, :id, :label, { :prompt => BLANK_SELECT_LABEL }, html_options)
103
+ else
104
+ '(none available)'
105
+ end
106
+ end
107
+
108
+ # Render a label for the given attribute with the passed field html section.
109
+ def labeled(attr, field_html = nil, &block)
110
+ template.labeled(label(attr), field_html, &block)
111
+ end
112
+
113
+ # Dispatch methods starting with 'labeled_' to render a label and the corresponding
114
+ # input field. E.g. labeled_boolean_field(:checked, {:class => 'bold'})
115
+ def method_missing(name, *args)
116
+ if field_method = labeled_field_method?(name)
117
+ labeled(args.first, send(field_method, *args))
118
+ else
119
+ super(name, *args)
120
+ end
121
+ end
122
+
123
+ def respond_to?(name)
124
+ labeled_field_method?(name).present? || super(name)
125
+ end
126
+
127
+ protected
128
+
129
+ def labeled_field_method?(name)
130
+ prefix = 'labeled_'
131
+ if name.to_s.start_with?(prefix)
132
+ field_method = name.to_s[prefix.size..-1]
133
+ field_method if respond_to?(field_method)
134
+ end
135
+ end
136
+
137
+ def belongs_to_association?(attr, type)
138
+ if type == :integer || type == nil
139
+ assoc = belongs_to_association(@object, attr)
140
+ assoc.present? && assoc.options[:polymorphic].nil?
141
+ else
142
+ false
143
+ end
144
+ end
145
+
146
+ # Returns the list of association entries, either from options[:list],
147
+ # the instance variable with the pluralized association name or all
148
+ # entries of the association klass.
149
+ def association_entries(attr, options)
150
+ list = options.delete(:list)
151
+ unless list
152
+ assoc = association(@object, attr)
153
+ list = @template.send(:instance_variable_get, :"@#{assoc.name.to_s.pluralize}")
154
+ unless list
155
+ list = assoc.klass.find(:all, :conditions => assoc.options[:conditions],
156
+ :order => assoc.options[:order])
157
+ end
158
+ end
159
+ list
160
+ end
161
+ end