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.
- data/MIT-LICENSE +19 -0
- data/README.rdoc +208 -0
- data/Rakefile +101 -0
- data/VERSION +1 -0
- data/rails_generators/dry_crud/USAGE +1 -0
- data/rails_generators/dry_crud/dry_crud_generator.rb +22 -0
- data/rails_generators/dry_crud/templates/INSTALL +13 -0
- data/rails_generators/dry_crud/templates/app/controllers/crud_controller.rb +182 -0
- data/rails_generators/dry_crud/templates/app/helpers/crud_helper.rb +45 -0
- data/rails_generators/dry_crud/templates/app/helpers/standard_helper.rb +209 -0
- data/rails_generators/dry_crud/templates/app/views/crud/_attrs.html.erb +1 -0
- data/rails_generators/dry_crud/templates/app/views/crud/_form.html.erb +1 -0
- data/rails_generators/dry_crud/templates/app/views/crud/_list.html.erb +1 -0
- data/rails_generators/dry_crud/templates/app/views/crud/edit.html.erb +8 -0
- data/rails_generators/dry_crud/templates/app/views/crud/index.html.erb +14 -0
- data/rails_generators/dry_crud/templates/app/views/crud/new.html.erb +7 -0
- data/rails_generators/dry_crud/templates/app/views/crud/show.html.erb +9 -0
- data/rails_generators/dry_crud/templates/app/views/layouts/crud.html.erb +20 -0
- data/rails_generators/dry_crud/templates/app/views/shared/_labeled.html.erb +5 -0
- data/rails_generators/dry_crud/templates/lib/crud_callbacks.rb +55 -0
- data/rails_generators/dry_crud/templates/lib/render_inheritable.rb +118 -0
- data/rails_generators/dry_crud/templates/lib/standard_form_builder.rb +161 -0
- data/rails_generators/dry_crud/templates/lib/standard_table_builder.rb +104 -0
- data/rails_generators/dry_crud/templates/public/stylesheets/crud.css +91 -0
- data/rails_generators/dry_crud/templates/test/crud_test_model.rb +124 -0
- data/rails_generators/dry_crud/templates/test/functional/crud_controller_test_helper.rb +172 -0
- data/rails_generators/dry_crud/templates/test/functional/crud_test_models_controller_test.rb +96 -0
- data/rails_generators/dry_crud/templates/test/unit/crud_helper_test.rb +57 -0
- data/rails_generators/dry_crud/templates/test/unit/render_inheritable_test.rb +161 -0
- data/rails_generators/dry_crud/templates/test/unit/standard_form_builder_test.rb +83 -0
- data/rails_generators/dry_crud/templates/test/unit/standard_helper_test.rb +183 -0
- data/rails_generators/dry_crud/templates/test/unit/standard_table_builder_test.rb +99 -0
- data/test/templates/app/controllers/ajax_controller.rb +7 -0
- data/test/templates/app/controllers/application_controller.rb +11 -0
- data/test/templates/app/controllers/cities_controller.rb +13 -0
- data/test/templates/app/controllers/people_controller.rb +7 -0
- data/test/templates/app/helpers/people_helper.rb +7 -0
- data/test/templates/app/models/city.rb +13 -0
- data/test/templates/app/models/person.rb +9 -0
- data/test/templates/app/views/ajax/_hello.html.erb +1 -0
- data/test/templates/app/views/ajax/ajax.js.rjs +1 -0
- data/test/templates/app/views/ajax/index.html.erb +9 -0
- data/test/templates/app/views/cities/_attrs.html.erb +1 -0
- data/test/templates/app/views/cities/_form.html.erb +4 -0
- data/test/templates/app/views/cities/_hello.html.erb +1 -0
- data/test/templates/app/views/cities/_list.html.erb +5 -0
- data/test/templates/config/routes.rb +11 -0
- data/test/templates/db/migrate/20100511174904_create_people_and_cities.rb +23 -0
- data/test/templates/test/fixtures/cities.yml +11 -0
- data/test/templates/test/fixtures/people.yml +14 -0
- data/test/templates/test/functional/cities_controller_test.rb +35 -0
- data/test/templates/test/functional/people_controller_test.rb +30 -0
- 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 @@
|
|
1
|
+
<% crud_form %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= crud_table %>
|
@@ -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
|