dry_crud 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/README.rdoc +24 -19
  2. data/Rakefile +12 -4
  3. data/VERSION +1 -1
  4. data/lib/generators/dry_crud/dry_crud_generator.rb +7 -7
  5. data/lib/generators/dry_crud/templates/app/assets/stylesheets/crud.scss +18 -7
  6. data/lib/generators/dry_crud/templates/app/assets/stylesheets/sample.scss +13 -18
  7. data/lib/generators/dry_crud/templates/app/controllers/crud_controller.rb +74 -87
  8. data/lib/generators/dry_crud/templates/app/controllers/list_controller.rb +95 -50
  9. data/lib/generators/dry_crud/templates/app/helpers/crud_helper.rb +11 -11
  10. data/lib/generators/dry_crud/templates/app/helpers/list_helper.rb +7 -7
  11. data/lib/generators/dry_crud/templates/app/helpers/standard_form_builder.rb +55 -27
  12. data/lib/generators/dry_crud/templates/app/helpers/standard_helper.rb +83 -30
  13. data/lib/generators/dry_crud/templates/app/helpers/standard_table_builder.rb +5 -13
  14. data/lib/generators/dry_crud/templates/app/views/layouts/_flash.html.erb +1 -4
  15. data/lib/generators/dry_crud/templates/app/views/layouts/_flash.html.haml +1 -3
  16. data/lib/generators/dry_crud/templates/app/views/layouts/_nav.html.erb +6 -6
  17. data/lib/generators/dry_crud/templates/app/views/layouts/_nav.html.haml +5 -3
  18. data/lib/generators/dry_crud/templates/app/views/layouts/crud.html.erb +13 -11
  19. data/lib/generators/dry_crud/templates/app/views/layouts/crud.html.haml +15 -15
  20. data/lib/generators/dry_crud/templates/app/views/shared/_error_messages.html.erb +10 -10
  21. data/lib/generators/dry_crud/templates/app/views/shared/_labeled.html.erb +2 -2
  22. data/lib/generators/dry_crud/templates/app/views/shared/_labeled.html.haml +2 -2
  23. data/lib/generators/dry_crud/templates/config/initializers/field_error_proc.rb +1 -0
  24. data/lib/generators/dry_crud/templates/config/locales/en_crud.yml +1 -0
  25. data/lib/generators/dry_crud/templates/test/crud_test_model.rb +72 -17
  26. data/lib/generators/dry_crud/templates/test/custom_assertions.rb +1 -1
  27. data/lib/generators/dry_crud/templates/test/functional/crud_controller_test_helper.rb +42 -26
  28. data/lib/generators/dry_crud/templates/test/functional/crud_test_models_controller_test.rb +135 -29
  29. data/lib/generators/dry_crud/templates/test/unit/custom_assertions_test.rb +4 -4
  30. data/lib/generators/dry_crud/templates/test/unit/helpers/crud_helper_test.rb +4 -2
  31. data/lib/generators/dry_crud/templates/test/unit/helpers/list_helper_test.rb +2 -0
  32. data/lib/generators/dry_crud/templates/test/unit/helpers/standard_form_builder_test.rb +94 -16
  33. data/lib/generators/dry_crud/templates/test/unit/helpers/standard_helper_test.rb +58 -18
  34. data/lib/generators/dry_crud/templates/test/unit/helpers/standard_table_builder_test.rb +4 -4
  35. data/test/templates/Gemfile +1 -0
  36. data/test/templates/app/controllers/admin/cities_controller.rb +0 -7
  37. data/test/templates/app/controllers/admin/countries_controller.rb +2 -3
  38. data/test/templates/app/controllers/ajax_controller.rb +2 -0
  39. data/test/templates/app/controllers/people_controller.rb +1 -1
  40. data/test/templates/app/models/city.rb +2 -0
  41. data/test/templates/app/models/country.rb +2 -0
  42. data/test/templates/app/models/person.rb +2 -0
  43. data/test/templates/app/views/admin/cities/_attrs.html.erb +1 -0
  44. data/test/templates/app/views/admin/cities/_attrs.html.haml +1 -0
  45. data/test/templates/app/views/admin/cities/_form.html.erb +7 -1
  46. data/test/templates/app/views/admin/cities/_form.html.haml +5 -1
  47. data/test/templates/app/views/admin/cities/_list.html.erb +1 -4
  48. data/test/templates/app/views/admin/cities/_list.html.haml +1 -3
  49. data/test/templates/app/views/ajax/_actions_show.html.erb +4 -0
  50. data/test/templates/app/views/ajax/_actions_show.html.haml +4 -0
  51. data/test/templates/app/views/ajax/_form.html.erb +2 -0
  52. data/test/templates/app/views/ajax/_form.html.haml +2 -0
  53. data/test/templates/app/views/ajax/edit.js.erb +1 -0
  54. data/test/templates/app/views/ajax/edit.js.haml +1 -0
  55. data/test/templates/app/views/ajax/show.js.erb +1 -0
  56. data/test/templates/app/views/ajax/show.js.haml +1 -0
  57. data/test/templates/app/views/ajax/update.js.erb +5 -0
  58. data/test/templates/app/views/ajax/update.js.haml +5 -0
  59. data/test/templates/app/views/layouts/_nav.html.erb +6 -0
  60. data/test/templates/app/views/layouts/_nav.html.haml +5 -0
  61. data/test/templates/app/views/layouts/bootstrap.html.erb +68 -0
  62. data/test/templates/app/views/layouts/bootstrap.html.haml +49 -0
  63. data/test/templates/app/views/people/_attrs.html.erb +2 -2
  64. data/test/templates/app/views/people/_attrs.html.haml +2 -2
  65. data/test/templates/config/routes.rb +5 -5
  66. data/test/templates/db/migrate/20100511174904_create_people_and_cities.rb +1 -1
  67. data/test/templates/db/seeds.rb +52 -52
  68. data/test/templates/test/functional/admin/cities_controller_test.rb +15 -15
  69. data/test/templates/test/functional/admin/countries_controller_test.rb +4 -5
  70. data/test/templates/test/functional/people_controller_test.rb +32 -4
  71. metadata +22 -9
  72. data/lib/generators/dry_crud/templates/app/views/layouts/_menu.html.erb +0 -3
  73. data/lib/generators/dry_crud/templates/app/views/layouts/_menu.html.haml +0 -3
  74. data/test/templates/app/views/layouts/_menu.html.erb +0 -3
  75. data/test/templates/app/views/layouts/_menu.html.haml +0 -3
@@ -6,62 +6,71 @@
6
6
  # the user the same list as he left it.
7
7
  class ListController < ApplicationController
8
8
 
9
- helper_method :model_class, :models_label, :path_args
9
+ helper_method :model_class, :models_label, :entries, :path_args
10
10
 
11
11
  delegate :model_class, :models_label, :to => 'self.class'
12
12
 
13
13
  hide_action :model_class, :models_label, :inheritable_root_controller
14
14
 
15
+ respond_to :html, :json
15
16
 
16
17
  ############## ACTIONS ############################################
17
18
 
18
19
  # List all entries of this model.
19
20
  # GET /entries
20
21
  # GET /entries.json
21
- def index
22
- @entries = list_entries
23
- respond_with @entries
22
+ def index(&block)
23
+ respond_with(entries, &block)
24
24
  end
25
25
 
26
26
  protected
27
27
 
28
- # The entries to be displayed in the current index page.
28
+ # Helper method to access the entries to be displayed in the current index page in an uniform way.
29
+ def entries
30
+ get_model_ivar(true) || set_model_ivar(list_entries)
31
+ end
32
+
33
+ # The base relation used to filter the entries.
34
+ # This method may be adapted as long it returns an ActiveRecord::Relation.
29
35
  def list_entries
30
36
  model_scope
31
37
  end
32
-
38
+
33
39
  # The scope where model entries will be listed and created.
34
- # This is mainly used for nested models to provide the
40
+ # This is mainly used for nested models to provide the
35
41
  # required context.
36
42
  def model_scope
37
43
  model_class.scoped
38
44
  end
39
-
45
+
40
46
  # The path arguments to link to the given entry.
41
47
  # If the controller is nested, this provides the required context.
42
48
  def path_args(last)
43
49
  last
44
50
  end
45
51
 
46
- # Convenience method to respond to various formats with the given object.
47
- def respond_with(object)
48
- respond_to do |format|
49
- format.html { render_with_callback action_name }
50
- format.json { render :json => object }
51
- end
52
+ # Get the instance variable named after the model_class.
53
+ # If the collection variable is required, pass true as the second argument.
54
+ def get_model_ivar(plural = false)
55
+ name = model_class.name.underscore
56
+ name = name.pluralize if plural
57
+ instance_variable_get(:"@#{name}")
52
58
  end
53
59
 
54
- # Helper method to run before_render callbacks and render the action.
55
- # If a callback renders or redirects, the action is not rendered.
56
- def render_with_callback(action)
57
- run_callbacks(:"render_#{action}")
58
- render action unless performed?
60
+ # Sets an instance variable with the underscored class name if the given value.
61
+ # If the value is a collection, sets the plural name.
62
+ def set_model_ivar(value)
63
+ name = if value.respond_to?(:klass) # ActiveRecord::Relation
64
+ value.klass.name.pluralize
65
+ elsif value.respond_to?(:each) # Array
66
+ value.first.klass.name.pluralize
67
+ else
68
+ value.class.name
69
+ end
70
+ instance_variable_set(:"@#{name.underscore}", value)
59
71
  end
60
72
 
61
73
  class << self
62
- # Callbacks
63
- include ActiveModel::Callbacks
64
-
65
74
  # The ActiveRecord class of the model.
66
75
  def model_class
67
76
  @model_class ||= controller_name.classify.constantize
@@ -72,20 +81,56 @@ class ListController < ApplicationController
72
81
  opts = {:count => (plural ? 3 : 1)}
73
82
  opts[:default] = model_class.model_name.human.titleize
74
83
  opts[:default] = opts[:default].pluralize if plural
75
-
84
+
76
85
  model_class.model_name.human(opts)
77
86
  end
78
-
79
- # Defines before callbacks for the render actions.
80
- def define_render_callbacks(*actions)
81
- args = actions.collect {|a| :"render_#{a}" }
82
- args << {:only => :before,
83
- :terminator => "result == false || performed?"}
84
- define_model_callbacks *args
87
+
88
+ end
89
+
90
+ # Provide before_render callbacks.
91
+ module Callbacks
92
+
93
+ def self.included(controller)
94
+ controller.extend ActiveModel::Callbacks
95
+ controller.extend ClassMethods
96
+ controller.alias_method_chain :render, :callbacks
97
+
98
+ controller.define_render_callbacks :index
99
+ end
100
+
101
+ # Helper method to run before_render callbacks and render the action.
102
+ # If a callback renders or redirects, the action is not rendered.
103
+ def render_with_callbacks(*args, &block)
104
+ options = _normalize_render(*args, &block)
105
+ p options if options[:location] == :back
106
+ callback = "render_#{options[:template]}"
107
+ run_callbacks(callback) if respond_to?(:"_run_#{callback}_callbacks", true)
108
+
109
+ render_without_callbacks(*args, &block) unless performed?
110
+ end
111
+
112
+ protected
113
+
114
+ # Helper method the run the given block in between the before and after
115
+ # callbacks of the given kinds.
116
+ def with_callbacks(*kinds, &block)
117
+ kinds.reverse.inject(block) do |b, kind|
118
+ lambda { run_callbacks(kind, &b) }
119
+ end.call
120
+ end
121
+
122
+ module ClassMethods
123
+ # Defines before callbacks for the render actions.
124
+ def define_render_callbacks(*actions)
125
+ args = actions.collect {|a| :"render_#{a}" }
126
+ args << {:only => :before,
127
+ :terminator => "result == false || performed?"}
128
+ define_model_callbacks *args
129
+ end
85
130
  end
86
131
  end
87
-
88
- define_render_callbacks :index
132
+
133
+ include Callbacks
89
134
 
90
135
  # The search functionality for the index table.
91
136
  # Extracted into an own module for convenience.
@@ -111,12 +156,12 @@ class ListController < ApplicationController
111
156
  def search_condition
112
157
  if search_support? && params[:q].present?
113
158
  terms = params[:q].split(/\s+/).collect { |t| "%#{t}%" }
114
- clause = search_columns.collect do |f|
159
+ clause = search_columns.collect do |f|
115
160
  col = f.to_s.include?('.') ? f : "#{model_class.table_name}.#{f}"
116
161
  "#{col} LIKE ?"
117
162
  end.join(" OR ")
118
163
  clause = terms.collect {|t| "(#{clause})" }.join(" AND ")
119
-
164
+
120
165
  ["(#{clause})"] + terms.collect {|t| [t] * search_columns.size }.flatten
121
166
  end
122
167
  end
@@ -228,7 +273,7 @@ class ListController < ApplicationController
228
273
  session[:list_params] ||= {}
229
274
  session[:list_params][remember_key] ||= {}
230
275
  end
231
-
276
+
232
277
  # Params are stored by request path to play nice when a controller
233
278
  # is used in different routes.
234
279
  def remember_key
@@ -245,27 +290,27 @@ class ListController < ApplicationController
245
290
  # namespace, may define this attribute as follows:
246
291
  # self.nesting = :admin, Country
247
292
  module Nesting
248
-
293
+
249
294
  # Adds the :nesting class attribute and parent helper methods
250
295
  # to the including controller.
251
296
  def self.included(controller)
252
297
  controller.class_attribute :nesting
253
-
298
+
254
299
  controller.helper_method :parent, :parents
255
-
300
+
256
301
  controller.alias_method_chain :model_scope, :nesting
257
302
  controller.alias_method_chain :path_args, :nesting
258
303
  end
259
-
304
+
260
305
  protected
261
-
306
+
262
307
  # Returns the direct parent ActiveRecord of the current request, if any.
263
308
  def parent
264
309
  parents.select {|p| p.is_a?(ActiveRecord::Base) }.last
265
310
  end
266
-
311
+
267
312
  # Returns the parent entries of the current request, if any.
268
- # These are ActiveRecords or namespace symbols, corresponding
313
+ # These are ActiveRecords or namespace symbols, corresponding
269
314
  # to the defined nesting attribute.
270
315
  def parents
271
316
  @parents ||= Array(nesting).collect do |p|
@@ -276,18 +321,18 @@ class ListController < ApplicationController
276
321
  end
277
322
  end
278
323
  end
279
-
324
+
280
325
  # Loads the parent entry for the given ActiveRecord class.
281
326
  # By default, performs a find with the class_name_id param.
282
327
  def parent_entry(clazz)
283
- clazz.find(params["#{clazz.name.underscore}_id"])
328
+ set_model_ivar(clazz.find(params["#{clazz.name.underscore}_id"]))
284
329
  end
285
-
330
+
286
331
  # An array of objects used in url_for and related functions.
287
332
  def path_args_with_nesting(last)
288
333
  parents + [last]
289
334
  end
290
-
335
+
291
336
  # Uses the parent entry (if any) to constrain the model scope.
292
337
  def model_scope_with_nesting
293
338
  if parent.present?
@@ -296,13 +341,13 @@ class ListController < ApplicationController
296
341
  model_scope_without_nesting
297
342
  end
298
343
  end
299
-
344
+
300
345
  # The model scope for the current parent resource.
301
346
  def parent_scope
302
347
  parent.send(model_class.name.underscore.pluralize)
303
348
  end
304
349
  end
305
-
350
+
306
351
  include Nesting
307
-
308
- end
352
+
353
+ end
@@ -4,7 +4,7 @@
4
4
  module CrudHelper
5
5
 
6
6
  # Renders a generic form for the current entry with :default_attrs or the
7
- # given attribute array, using the StandardFormBuilder. An options hash
7
+ # given attribute array, using the StandardFormBuilder. An options hash
8
8
  # may be given as the last argument.
9
9
  # If a block is given, a custom form may be rendered and attrs is ignored.
10
10
  def crud_form(*attrs, &block)
@@ -12,7 +12,7 @@ module CrudHelper
12
12
  standard_form(path_args(entry), *attrs, &block)
13
13
  end
14
14
 
15
- # Create a table of the @entries variable with the default or
15
+ # Create a table of the entries with the default or
16
16
  # the passed attributes in its columns. An options hash may be given
17
17
  # as the last argument.
18
18
  def crud_table(*attrs, &block)
@@ -42,7 +42,7 @@ module CrudHelper
42
42
  # Action link to edit inside a table.
43
43
  # A block may be given to define the link path for the row entry.
44
44
  def action_col_edit(table, &block)
45
- action_col(table) do |e|
45
+ action_col(table) do |e|
46
46
  path = action_path(e, &block)
47
47
  link_table_action('pencil', path.is_a?(String) ? path : edit_polymorphic_path(path))
48
48
  end
@@ -61,14 +61,14 @@ module CrudHelper
61
61
  # Generic action link inside a table.
62
62
  def link_table_action(icon, url, html_options = {})
63
63
  add_css_class html_options, "icon-#{icon}"
64
- link_to('', url, html_options)
64
+ link_to('', url, html_options)
65
65
  end
66
66
 
67
67
  # Defines a column with an action link.
68
68
  def action_col(table, &block)
69
- table.col('', :class => 'action', &block)
69
+ table.col('', :class => 'action', &block)
70
70
  end
71
-
71
+
72
72
  ######## ACTION LINKS ###################################################### :nodoc:
73
73
 
74
74
  # Standard link action to the show page of a given record.
@@ -107,7 +107,7 @@ module CrudHelper
107
107
  path ||= path_args(model_class)
108
108
  link_action ti(:"link.add"), 'plus', path.is_a?(String) ? path : new_polymorphic_path(path, url_options)
109
109
  end
110
-
110
+
111
111
  private
112
112
 
113
113
  # If a block is given, call it to get the path for the current row entry.
@@ -115,12 +115,12 @@ module CrudHelper
115
115
  def action_path(e, &block)
116
116
  block_given? ? yield(e) : path_args(e)
117
117
  end
118
-
118
+
119
119
  # Returns default attrs for a crud table if no others are passed.
120
120
  def attrs_or_default(attrs)
121
- options = attrs.extract_options!
122
- attrs = yield if attrs.blank?
123
- attrs << options
121
+ options = attrs.extract_options!
122
+ attrs = yield if attrs.blank?
123
+ attrs << options
124
124
  end
125
125
 
126
126
  end
@@ -3,25 +3,25 @@
3
3
  # is included in CrudController.
4
4
  module ListHelper
5
5
 
6
- # Create a table of the @entries variable with the default or
6
+ # Create a table of the entries with the default or
7
7
  # the passed attributes in its columns. An options hash may be given
8
8
  # as the last argument.
9
9
  def list_table(*attrs, &block)
10
10
  options = attrs.extract_options!
11
11
  # only use default attrs if no attrs and no block are given
12
12
  attributes = (block_given? || attrs.present?) ? attrs : default_attrs
13
- table(@entries, options) do |t|
14
- t.sortable_attrs(*attributes)
15
- yield t if block_given?
13
+ table(entries, options) do |t|
14
+ t.sortable_attrs(*attributes)
15
+ yield t if block_given?
16
16
  end
17
17
  end
18
18
 
19
19
  # The default attributes to use in attrs, list and form partials.
20
20
  # These are all defined attributes except certain special ones like 'id' or 'position'.
21
- def default_attrs
21
+ def default_attrs
22
22
  attrs = model_class.column_names.collect(&:to_sym)
23
23
  attrs - [:id, :position, :password]
24
24
  end
25
-
26
-
25
+
26
+
27
27
  end
@@ -9,15 +9,14 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
9
9
 
10
10
  attr_reader :template
11
11
 
12
- delegate :association, :column_type, :column_property, :captionize,
13
- :content_tag, :ta, :add_css_class, :to => :template
12
+ delegate :association, :column_type, :column_property, :captionize, :ta,
13
+ :content_tag, :safe_join, :capture, :add_css_class, :assoc_and_id_attr,
14
+ :to => :template
14
15
 
15
16
  # Render multiple input fields together with a label for the given attributes.
16
17
  def labeled_input_fields(*attrs)
17
18
  options = attrs.extract_options!
18
- attrs.collect do |a|
19
- labeled_input_field(a, options.clone)
20
- end.join("\n").html_safe
19
+ safe_join(attrs) { |a| labeled_input_field(a, options.clone) }
21
20
  end
22
21
 
23
22
  # Render a corresponding input field for the given attribute.
@@ -27,8 +26,10 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
27
26
  type = column_type(@object, attr)
28
27
  if type == :text
29
28
  text_area(attr, html_options)
30
- elsif belongs_to_association?(attr, type)
29
+ elsif association_kind?(attr, type, :belongs_to)
31
30
  belongs_to_field(attr, html_options)
31
+ elsif association_kind?(attr, type, :has_and_belongs_to_many, :has_many)
32
+ has_many_field(attr, html_options)
32
33
  elsif attr.to_s.include?('password')
33
34
  password_field(attr, html_options)
34
35
  else
@@ -43,11 +44,10 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
43
44
 
44
45
  # Render a number field.
45
46
  def number_field(attr, html_options = {})
46
- add_css_class html_options, 'span1'
47
47
  html_options[:size] ||= 10
48
48
  super(attr, html_options)
49
49
  end
50
-
50
+
51
51
  # Render a standard string field with column contraints.
52
52
  def string_field(attr, html_options = {})
53
53
  html_options[:maxlength] ||= column_property(@object, attr, :limit)
@@ -80,22 +80,19 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
80
80
 
81
81
  # Render a field to select a date. You might want to customize this.
82
82
  def date_field(attr, html_options = {})
83
- add_css_class html_options, 'span1'
84
83
  date_select(attr, {}, html_options)
85
84
  end
86
85
 
87
86
  # Render a field to enter a time. You might want to customize this.
88
87
  def time_field(attr, html_options = {})
89
- add_css_class html_options, 'span1'
90
88
  time_select(attr, {}, html_options)
91
89
  end
92
-
90
+
93
91
  # Render a field to enter a date and time. You might want to customize this.
94
92
  def datetime_field(attr, html_options = {})
95
- add_css_class html_options, 'span1'
96
93
  datetime_select(attr, {}, html_options)
97
94
  end
98
-
95
+
99
96
  # Render a select element for a :belongs_to association defined by attr.
100
97
  # Use additional html_options for the select element.
101
98
  # To pass a custom element list, specify the list with the :list key or
@@ -103,24 +100,52 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
103
100
  def belongs_to_field(attr, html_options = {})
104
101
  list = association_entries(attr, html_options)
105
102
  if list.present?
106
- collection_select(attr, list, :id, :to_s, select_options(attr), html_options)
103
+ collection_select(attr,
104
+ list,
105
+ :id,
106
+ :to_s,
107
+ html_options[:multiple] ? {} : select_options(attr),
108
+ html_options)
107
109
  else
108
110
  ta(:none_available, association(@object, attr))
109
111
  end
110
112
  end
111
113
 
114
+ # Render a multi select element for a :has_many or :has_and_belongs_to_many
115
+ # association defined by attr.
116
+ # Use additional html_options for the select element.
117
+ # To pass a custom element list, specify the list with the :list key or
118
+ # define an instance variable with the pluralized name of the association.
119
+ def has_many_field(attr, html_options = {})
120
+ html_options[:multiple] = true
121
+ add_css_class(html_options, 'multiselect')
122
+ belongs_to_field(attr, html_options)
123
+ end
124
+
112
125
  # Renders a marker if the given attr has to be present.
113
126
  def required_mark(attr)
114
127
  required?(attr) ? REQUIRED_MARK : ''
115
128
  end
116
129
 
117
130
  # Render a label for the given attribute with the passed field html section.
118
- def labeled(attr, field_html = nil, &block)
119
- field_html = capture(&block) if block_given?
120
- content_tag(:div,
121
- label(attr, :class => 'control-label') +
122
- content_tag(:div, field_html, :class => 'controls'),
123
- :class => 'control-group')
131
+ # The following parameters may be specified:
132
+ # labeled(:attr) { #content }
133
+ # labeled(:attr, content)
134
+ # labeled(:attr, 'Caption') { #content }
135
+ # labeled(:attr, 'Caption', content)
136
+ def labeled(attr, caption_or_content = nil, content = nil, &block)
137
+ if block_given?
138
+ content = capture(&block)
139
+ elsif content.nil?
140
+ content = caption_or_content
141
+ caption_or_content = nil
142
+ end
143
+ caption_or_content ||= captionize(attr, @object.class)
144
+
145
+ content_tag(:div, :class => "control-group#{' error' if @object.errors.has_key?(attr)}") do
146
+ label(attr, caption_or_content, :class => 'control-label') +
147
+ content_tag(:div, content, :class => 'controls')
148
+ end
124
149
  end
125
150
 
126
151
  # Depending if the given attribute must be present, return
@@ -130,7 +155,7 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
130
155
  required?(attr) ? { :prompt => ta(:please_select, assoc) } :
131
156
  { :include_blank => ta(:no_entry, assoc) }
132
157
  end
133
-
158
+
134
159
  # Dispatch methods starting with 'labeled_' to render a label and the corresponding
135
160
  # input field. E.g. labeled_boolean_field(:checked, :class => 'bold')
136
161
  def method_missing(name, *args)
@@ -148,11 +173,11 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
148
173
 
149
174
  protected
150
175
 
151
- # Returns true if attr is a non-polymorphic belongs_to association,
152
- # for which an input field may be automatically rendered.
153
- def belongs_to_association?(attr, type)
176
+ # Returns true if attr is a non-polymorphic association.
177
+ # If one or more macros are given, the association must be of this kind.
178
+ def association_kind?(attr, type, *macros)
154
179
  if type == :integer || type.nil?
155
- assoc = association(@object, attr, :belongs_to)
180
+ assoc = association(@object, attr, *macros)
156
181
  assoc.present? && assoc.options[:polymorphic].nil?
157
182
  else
158
183
  false
@@ -177,10 +202,13 @@ class StandardFormBuilder < ActionView::Helpers::FormBuilder
177
202
  # Returns true if the given attribute must be present.
178
203
  def required?(attr)
179
204
  attr = attr.to_s
180
- attr, attr_id = attr.end_with?('_id') ? [attr[0..-4], attr] : [attr, "#{attr}_id"]
205
+ attr, attr_id = assoc_and_id_attr(attr)
181
206
  validators = @object.class.validators_on(attr) +
182
207
  @object.class.validators_on(attr_id)
183
- validators.any? {|v| v.kind == :presence }
208
+ validators.any? do |v|
209
+ v.kind == :presence &&
210
+ !v.options.key?(:if) && !v.options.key?(:unless)
211
+ end
184
212
  end
185
213
 
186
214
  private