actionview 4.2.11.3 → 5.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (65) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +136 -255
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/action_view.rb +1 -1
  6. data/lib/action_view/base.rb +14 -2
  7. data/lib/action_view/dependency_tracker.rb +46 -15
  8. data/lib/action_view/digestor.rb +13 -9
  9. data/lib/action_view/flows.rb +1 -1
  10. data/lib/action_view/gem_version.rb +4 -4
  11. data/lib/action_view/helpers/asset_tag_helper.rb +15 -5
  12. data/lib/action_view/helpers/asset_url_helper.rb +51 -12
  13. data/lib/action_view/helpers/atom_feed_helper.rb +5 -4
  14. data/lib/action_view/helpers/cache_helper.rb +75 -20
  15. data/lib/action_view/helpers/capture_helper.rb +3 -2
  16. data/lib/action_view/helpers/controller_helper.rb +1 -0
  17. data/lib/action_view/helpers/date_helper.rb +39 -10
  18. data/lib/action_view/helpers/debug_helper.rb +1 -1
  19. data/lib/action_view/helpers/form_helper.rb +81 -35
  20. data/lib/action_view/helpers/form_options_helper.rb +74 -35
  21. data/lib/action_view/helpers/form_tag_helper.rb +46 -19
  22. data/lib/action_view/helpers/javascript_helper.rb +4 -4
  23. data/lib/action_view/helpers/number_helper.rb +10 -12
  24. data/lib/action_view/helpers/record_tag_helper.rb +12 -99
  25. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  26. data/lib/action_view/helpers/sanitize_helper.rb +1 -2
  27. data/lib/action_view/helpers/tag_helper.rb +20 -13
  28. data/lib/action_view/helpers/tags/base.rb +33 -28
  29. data/lib/action_view/helpers/tags/collection_check_boxes.rb +2 -30
  30. data/lib/action_view/helpers/tags/collection_helpers.rb +28 -0
  31. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -9
  32. data/lib/action_view/helpers/tags/file_field.rb +15 -0
  33. data/lib/action_view/helpers/tags/label.rb +1 -1
  34. data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
  35. data/lib/action_view/helpers/tags/search_field.rb +12 -9
  36. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  37. data/lib/action_view/helpers/tags/translator.rb +1 -1
  38. data/lib/action_view/helpers/text_helper.rb +25 -9
  39. data/lib/action_view/helpers/translation_helper.rb +56 -26
  40. data/lib/action_view/helpers/url_helper.rb +40 -65
  41. data/lib/action_view/layouts.rb +11 -10
  42. data/lib/action_view/lookup_context.rb +14 -40
  43. data/lib/action_view/model_naming.rb +1 -1
  44. data/lib/action_view/path_set.rb +15 -18
  45. data/lib/action_view/railtie.rb +20 -3
  46. data/lib/action_view/record_identifier.rb +44 -19
  47. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  48. data/lib/action_view/renderer/partial_renderer.rb +27 -26
  49. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +70 -0
  50. data/lib/action_view/renderer/renderer.rb +2 -6
  51. data/lib/action_view/renderer/streaming_template_renderer.rb +1 -1
  52. data/lib/action_view/renderer/template_renderer.rb +12 -11
  53. data/lib/action_view/rendering.rb +8 -5
  54. data/lib/action_view/routing_url_for.rb +18 -6
  55. data/lib/action_view/template.rb +50 -13
  56. data/lib/action_view/template/error.rb +14 -7
  57. data/lib/action_view/template/handlers.rb +3 -3
  58. data/lib/action_view/template/handlers/erb.rb +25 -0
  59. data/lib/action_view/template/handlers/raw.rb +1 -1
  60. data/lib/action_view/template/resolver.rb +36 -58
  61. data/lib/action_view/template/types.rb +1 -1
  62. data/lib/action_view/test_case.rb +13 -8
  63. data/lib/action_view/testing/resolvers.rb +3 -4
  64. data/lib/action_view/view_paths.rb +6 -22
  65. metadata +17 -14
@@ -16,7 +16,7 @@ module ActionView
16
16
  # end
17
17
  #
18
18
  # app/controllers/posts_controller.rb:
19
- # class PostsController < ApplicationController::Base
19
+ # class PostsController < ApplicationController
20
20
  # # GET /posts.html
21
21
  # # GET /posts.atom
22
22
  # def index
@@ -51,7 +51,7 @@ module ActionView
51
51
  # * <tt>:language</tt>: Defaults to "en-US".
52
52
  # * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
53
53
  # * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
54
- # * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}"
54
+ # * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case.
55
55
  # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
56
56
  # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
57
57
  # 2005 is used (as an "I don't care" value).
@@ -174,7 +174,7 @@ module ActionView
174
174
  #
175
175
  # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
176
176
  # * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
177
- # * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
177
+ # * <tt>:url</tt>: The URL for this entry or false or nil for not having a link tag. Defaults to the polymorphic_url for the record.
178
178
  # * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
179
179
  # * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
180
180
  def entry(record, options = {})
@@ -191,7 +191,8 @@ module ActionView
191
191
 
192
192
  type = options.fetch(:type, 'text/html')
193
193
 
194
- @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record))
194
+ url = options.fetch(:url) { @view.polymorphic_url(record) }
195
+ @xml.link(:rel => 'alternate', :type => type, :href => url) if url
195
196
 
196
197
  yield AtomBuilder.new(@xml)
197
198
  end
@@ -39,7 +39,7 @@ module ActionView
39
39
  # This will include both records as part of the cache key and updating either of them will
40
40
  # expire the cache.
41
41
  #
42
- # ==== Template digest
42
+ # ==== \Template digest
43
43
  #
44
44
  # The template digest that's added to the cache key is computed by taking an md5 of the
45
45
  # contents of the entire template file. This ensures that your caches will automatically
@@ -75,7 +75,8 @@ module ActionView
75
75
  # render(topics) => render("topics/topic")
76
76
  # render(message.topics) => render("topics/topic")
77
77
  #
78
- # It's not possible to derive all render calls like that, though. Here are a few examples of things that can't be derived:
78
+ # It's not possible to derive all render calls like that, though.
79
+ # Here are a few examples of things that can't be derived:
79
80
  #
80
81
  # render group_of_attachments
81
82
  # render @project.documents.where(published: true).order('created_at')
@@ -97,20 +98,73 @@ module ActionView
97
98
  # <%# Template Dependency: todolists/todolist %>
98
99
  # <%= render_sortable_todolists @project.todolists %>
99
100
  #
100
- # The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so.
101
+ # In some cases, like a single table inheritance setup, you might have
102
+ # a bunch of explicit dependencies. Instead of writing every template out,
103
+ # you can use a wildcard to match any template in a directory:
104
+ #
105
+ # <%# Template Dependency: events/* %>
106
+ # <%= render_categorizable_events @person.events %>
107
+ #
108
+ # This marks every template in the directory as a dependency. To find those
109
+ # templates, the wildcard path must be absolutely defined from app/views or paths
110
+ # otherwise added with +prepend_view_path+ or +append_view_path+.
111
+ # This way the wildcard for `app/views/recordings/events` would be `recordings/events/*` etc.
112
+ #
113
+ # The pattern used to match explicit dependencies is <tt>/# Template Dependency: (\S+)/</tt>,
114
+ # so it's important that you type it out just so.
101
115
  # You can only declare one template dependency per line.
102
116
  #
103
117
  # === External dependencies
104
118
  #
105
- # If you use a helper method, for example, inside of a cached block and you then update that helper,
106
- # you'll have to bump the cache as well. It doesn't really matter how you do it, but the md5 of the template file
119
+ # If you use a helper method, for example, inside a cached block and
120
+ # you then update that helper, you'll have to bump the cache as well.
121
+ # It doesn't really matter how you do it, but the md5 of the template file
107
122
  # must change. One recommendation is to simply be explicit in a comment, like:
108
123
  #
109
124
  # <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
110
125
  # <%= some_helper_method(person) %>
111
126
  #
112
- # Now all you'll have to do is change that timestamp when the helper method changes.
113
- def cache(name = {}, options = nil, &block)
127
+ # Now all you have to do is change that timestamp when the helper method changes.
128
+ #
129
+ # === Automatic Collection Caching
130
+ #
131
+ # When rendering collections such as:
132
+ #
133
+ # <%= render @notifications %>
134
+ # <%= render partial: 'notifications/notification', collection: @notifications %>
135
+ #
136
+ # If the notifications/_notification partial starts with a cache call as:
137
+ #
138
+ # <% cache notification do %>
139
+ # <%= notification.name %>
140
+ # <% end %>
141
+ #
142
+ # The collection can then automatically use any cached renders for that
143
+ # template by reading them at once instead of one by one.
144
+ #
145
+ # See ActionView::Template::Handlers::ERB.resource_cache_call_pattern for
146
+ # more information on what cache calls make a template eligible for this
147
+ # collection caching.
148
+ #
149
+ # The automatic cache multi read can be turned off like so:
150
+ #
151
+ # <%= render @notifications, cache: false %>
152
+ #
153
+ # === Explicit Collection Caching
154
+ #
155
+ # If the partial template doesn't start with a clean cache call as
156
+ # mentioned above, you can still benefit from collection caching by
157
+ # adding a special comment format anywhere in the template, like:
158
+ #
159
+ # <%# Template Collection: notification %>
160
+ # <% my_helper_that_calls_cache(some_arg, notification) do %>
161
+ # <%= notification.name %>
162
+ # <% end %>
163
+ #
164
+ # The pattern used to match these is <tt>/# Template Collection: (\S+)/</tt>,
165
+ # so it's important that you type it out just so.
166
+ # You can only declare one collection in a partial template file.
167
+ def cache(name = {}, options = {}, &block)
114
168
  if controller.respond_to?(:perform_caching) && controller.perform_caching
115
169
  safe_concat(fragment_for(cache_fragment_name(name, options), options, &block))
116
170
  else
@@ -126,7 +180,7 @@ module ActionView
126
180
  # <b>All the topics on this project</b>
127
181
  # <%= render project.topics %>
128
182
  # <% end %>
129
- def cache_if(condition, name = {}, options = nil, &block)
183
+ def cache_if(condition, name = {}, options = {}, &block)
130
184
  if condition
131
185
  cache(name, options, &block)
132
186
  else
@@ -142,33 +196,34 @@ module ActionView
142
196
  # <b>All the topics on this project</b>
143
197
  # <%= render project.topics %>
144
198
  # <% end %>
145
- def cache_unless(condition, name = {}, options = nil, &block)
199
+ def cache_unless(condition, name = {}, options = {}, &block)
146
200
  cache_if !condition, name, options, &block
147
201
  end
148
202
 
149
203
  # This helper returns the name of a cache key for a given fragment cache
150
- # call. By supplying skip_digest: true to cache, the digestion of cache
204
+ # call. By supplying +skip_digest:+ true to cache, the digestion of cache
151
205
  # fragments can be manually bypassed. This is useful when cache fragments
152
206
  # cannot be manually expired unless you know the exact key which is the
153
207
  # case when using memcached.
154
- def cache_fragment_name(name = {}, options = nil)
155
- skip_digest = options && options[:skip_digest]
156
-
208
+ #
209
+ # The digest will be generated using +virtual_path:+ if it is provided.
210
+ #
211
+ def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil)
157
212
  if skip_digest
158
213
  name
159
214
  else
160
- fragment_name_with_digest(name)
215
+ fragment_name_with_digest(name, virtual_path)
161
216
  end
162
217
  end
163
218
 
164
219
  private
165
220
 
166
- def fragment_name_with_digest(name) #:nodoc:
167
- if @virtual_path
168
- names = Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name)
169
- digest = Digestor.digest name: @virtual_path, finder: lookup_context, dependencies: view_cache_dependencies
170
-
171
- [ *names, digest ]
221
+ def fragment_name_with_digest(name, virtual_path) #:nodoc:
222
+ virtual_path ||= @virtual_path
223
+ if virtual_path
224
+ name = controller.url_for(name).split("://").last if name.is_a?(Hash)
225
+ digest = Digestor.digest name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies
226
+ [ name, digest ]
172
227
  else
173
228
  name
174
229
  end
@@ -31,7 +31,8 @@ module ActionView
31
31
  # <head><title><%= @greeting %></title></head>
32
32
  # <body>
33
33
  # <b><%= @greeting %></b>
34
- # </body></html>
34
+ # </body>
35
+ # </html>
35
36
  #
36
37
  def capture(*args)
37
38
  value = nil
@@ -114,7 +115,7 @@ module ActionView
114
115
  # <li><%= link_to 'Home', action: 'index' %></li>
115
116
  # <% end %>
116
117
  #
117
- # And in other place:
118
+ # And in another place:
118
119
  #
119
120
  # <% content_for :navigation do %>
120
121
  # <li><%= link_to 'Login', action: 'login' %></li>
@@ -14,6 +14,7 @@ module ActionView
14
14
  if @_controller = controller
15
15
  @_request = controller.request if controller.respond_to?(:request)
16
16
  @_config = controller.config.inheritable_copy if controller.respond_to?(:config)
17
+ @_default_form_builder = controller.default_form_builder if controller.respond_to?(:default_form_builder)
17
18
  end
18
19
  end
19
20
 
@@ -68,6 +68,27 @@ module ActionView
68
68
  # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years
69
69
  # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years
70
70
  # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
71
+ #
72
+ # With the <tt>scope</tt> option, you can define a custom scope for Rails
73
+ # to look up the translation.
74
+ #
75
+ # For example you can define the following in your locale (e.g. en.yml).
76
+ #
77
+ # datetime:
78
+ # distance_in_words:
79
+ # short:
80
+ # about_x_hours:
81
+ # one: 'an hour'
82
+ # other: '%{count} hours'
83
+ #
84
+ # See https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en.yml
85
+ # for more examples.
86
+ #
87
+ # Which will then result in the following:
88
+ #
89
+ # from_time = Time.now
90
+ # distance_of_time_in_words(from_time, from_time + 50.minutes, scope: 'datetime.distance_in_words.short') # => "an hour"
91
+ # distance_of_time_in_words(from_time, from_time + 3.hours, scope: 'datetime.distance_in_words.short') # => "3 hours"
71
92
  def distance_of_time_in_words(from_time, to_time = 0, options = {})
72
93
  options = {
73
94
  scope: :'datetime.distance_in_words'
@@ -177,6 +198,8 @@ module ActionView
177
198
  # and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example.
178
199
  # See <tt>Kernel.sprintf</tt> for documentation on format sequences.
179
200
  # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
201
+ # * <tt>:time_separator</tt> - Specifies a string to separate the time fields. Default is "" (i.e. nothing).
202
+ # * <tt>:datetime_separator</tt>- Specifies a string to separate the date and time fields. Default is "" (i.e. nothing).
180
203
  # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt> if
181
204
  # you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
182
205
  # the current selected year minus 5.
@@ -205,6 +228,7 @@ module ActionView
205
228
  # or the given prompt string.
206
229
  # * <tt>:with_css_classes</tt> - Set to true if you want assign different styles for 'select' tags. This option
207
230
  # automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second' for your 'select' tags.
231
+ # * <tt>:use_hidden</tt> - Set to true if you only want to generate hidden input tags.
208
232
  #
209
233
  # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
210
234
  #
@@ -462,7 +486,7 @@ module ActionView
462
486
  # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
463
487
  # Override the field name using the <tt>:field_name</tt> option, 'second' by default.
464
488
  #
465
- # my_time = Time.now + 16.minutes
489
+ # my_time = Time.now + 16.seconds
466
490
  #
467
491
  # # Generates a select field for seconds that defaults to the seconds for the time in my_time.
468
492
  # select_second(my_time)
@@ -486,7 +510,7 @@ module ActionView
486
510
  # selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
487
511
  # Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
488
512
  #
489
- # my_time = Time.now + 6.hours
513
+ # my_time = Time.now + 10.minutes
490
514
  #
491
515
  # # Generates a select field for minutes that defaults to the minutes for the time in my_time.
492
516
  # select_minute(my_time)
@@ -658,7 +682,7 @@ module ActionView
658
682
  content = args.first || I18n.l(date_or_time, :format => format)
659
683
  datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601
660
684
 
661
- content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block)
685
+ content_tag("time".freeze, content, options.reverse_merge(:datetime => datetime), &block)
662
686
  end
663
687
  end
664
688
 
@@ -786,7 +810,7 @@ module ActionView
786
810
  1.upto(12) do |month_number|
787
811
  options = { :value => month_number }
788
812
  options[:selected] = "selected" if month == month_number
789
- month_options << content_tag(:option, month_name(month_number), options) + "\n"
813
+ month_options << content_tag("option".freeze, month_name(month_number), options) + "\n"
790
814
  end
791
815
  build_select(:month, month_options.join)
792
816
  end
@@ -821,7 +845,12 @@ module ActionView
821
845
  private
822
846
  %w( sec min hour day month year ).each do |method|
823
847
  define_method(method) do
824
- @datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime
848
+ case @datetime
849
+ when Hash then @datetime[method.to_sym]
850
+ when Numeric then @datetime
851
+ when nil then nil
852
+ else @datetime.send(method)
853
+ end
825
854
  end
826
855
  end
827
856
 
@@ -898,7 +927,7 @@ module ActionView
898
927
 
899
928
  def translated_date_order
900
929
  date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => [])
901
- date_order = date_order.map { |element| element.to_sym }
930
+ date_order = date_order.map(&:to_sym)
902
931
 
903
932
  forbidden_elements = date_order - [:year, :month, :day]
904
933
  if forbidden_elements.any?
@@ -948,7 +977,7 @@ module ActionView
948
977
  tag_options[:selected] = "selected" if selected == i
949
978
  text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
950
979
  text = options[:ampm] ? AMPM_TRANSLATION[i] : text
951
- select_options << content_tag(:option, text, tag_options)
980
+ select_options << content_tag("option".freeze, text, tag_options)
952
981
  end
953
982
 
954
983
  (select_options.join("\n") + "\n").html_safe
@@ -968,11 +997,11 @@ module ActionView
968
997
  select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes]
969
998
 
970
999
  select_html = "\n"
971
- select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
1000
+ select_html << content_tag("option".freeze, '', :value => '') + "\n" if @options[:include_blank]
972
1001
  select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
973
1002
  select_html << select_options_as_html
974
1003
 
975
- (content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe
1004
+ (content_tag("select".freeze, select_html.html_safe, select_options) + "\n").html_safe
976
1005
  end
977
1006
 
978
1007
  # Builds a prompt option tag with supplied options or from default options.
@@ -989,7 +1018,7 @@ module ActionView
989
1018
  I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale])
990
1019
  end
991
1020
 
992
- prompt ? content_tag(:option, prompt, :value => '') : ''
1021
+ prompt ? content_tag("option".freeze, prompt, :value => '') : ''
993
1022
  end
994
1023
 
995
1024
  # Builds hidden input tag for date part and value.
@@ -26,7 +26,7 @@ module ActionView
26
26
  Marshal::dump(object)
27
27
  object = ERB::Util.html_escape(object.to_yaml)
28
28
  content_tag(:pre, object, :class => "debug_dump")
29
- rescue Exception # errors from Marshal or YAML
29
+ rescue # errors from Marshal or YAML
30
30
  # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
31
31
  content_tag(:code, object.inspect, :class => "debug_dump")
32
32
  end
@@ -4,6 +4,7 @@ require 'action_view/helpers/tag_helper'
4
4
  require 'action_view/helpers/form_tag_helper'
5
5
  require 'action_view/helpers/active_model_helper'
6
6
  require 'action_view/model_naming'
7
+ require 'action_view/record_identifier'
7
8
  require 'active_support/core_ext/module/attribute_accessors'
8
9
  require 'active_support/core_ext/hash/slice'
9
10
  require 'active_support/core_ext/string/output_safety'
@@ -66,9 +67,10 @@ module ActionView
66
67
  #
67
68
  # In particular, thanks to the conventions followed in the generated field names, the
68
69
  # controller gets a nested hash <tt>params[:person]</tt> with the person attributes
69
- # set in the form. That hash is ready to be passed to <tt>Person.create</tt>:
70
+ # set in the form. That hash is ready to be passed to <tt>Person.new</tt>:
70
71
  #
71
- # if @person = Person.create(params[:person])
72
+ # @person = Person.new(params[:person])
73
+ # if @person.save
72
74
  # # success
73
75
  # else
74
76
  # # error handling
@@ -110,6 +112,9 @@ module ActionView
110
112
  include FormTagHelper
111
113
  include UrlHelper
112
114
  include ModelNaming
115
+ include RecordIdentifier
116
+
117
+ attr_internal :default_form_builder
113
118
 
114
119
  # Creates a form that allows the user to create or update the attributes
115
120
  # of a specific model object.
@@ -138,6 +143,7 @@ module ActionView
138
143
  # will get expanded to
139
144
  #
140
145
  # <%= text_field :person, :first_name %>
146
+ #
141
147
  # which results in an HTML <tt><input></tt> tag whose +name+ attribute is
142
148
  # <tt>person[first_name]</tt>. This means that when the form is submitted,
143
149
  # the value entered by the user will be available in the controller as
@@ -843,8 +849,8 @@ module ActionView
843
849
  # file_field(:user, :avatar)
844
850
  # # => <input type="file" id="user_avatar" name="user[avatar]" />
845
851
  #
846
- # file_field(:post, :image, :multiple => true)
847
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
852
+ # file_field(:post, :image, multiple: true)
853
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
848
854
  #
849
855
  # file_field(:post, :attached, accept: 'text/html')
850
856
  # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
@@ -854,6 +860,24 @@ module ActionView
854
860
  #
855
861
  # file_field(:attachment, :file, class: 'file_input')
856
862
  # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
863
+ #
864
+ # ==== Gotcha
865
+ #
866
+ # The HTML specification says that when a file field is empty, web browsers
867
+ # do not send any value to the server. Unfortunately this introduces a
868
+ # gotcha: if a +User+ model has an +avatar+ field, and no file is selected,
869
+ # then the +avatar+ parameter is empty. Thus, any mass-assignment idiom like
870
+ #
871
+ # @user.update(params[:user])
872
+ #
873
+ # wouldn't update the +avatar+ field.
874
+ #
875
+ # To prevent this, the helper generates an auxiliary hidden field before
876
+ # every file field. The hidden field has the same name as the file one and
877
+ # a blank value.
878
+ #
879
+ # In case you don't want the helper to generate this hidden field you can
880
+ # specify the <tt>include_hidden: false</tt> option.
857
881
  def file_field(object_name, method, options = {})
858
882
  Tags::FileField.new(object_name, method, self, options).render
859
883
  end
@@ -1014,7 +1038,7 @@ module ActionView
1014
1038
  # date_field("user", "born_on")
1015
1039
  # # => <input id="user_born_on" name="user[born_on]" type="date" />
1016
1040
  #
1017
- # The default value is generated by trying to call "to_date"
1041
+ # The default value is generated by trying to call +strftime+ with "%Y-%m-%d"
1018
1042
  # on the object's value, which makes it behave as expected for instances
1019
1043
  # of DateTime and ActiveSupport::TimeWithZone. You can still override that
1020
1044
  # by passing the "value" option explicitly, e.g.
@@ -1211,7 +1235,7 @@ module ActionView
1211
1235
  end
1212
1236
 
1213
1237
  def default_form_builder_class
1214
- builder = ActionView::Base.default_form_builder
1238
+ builder = default_form_builder || ActionView::Base.default_form_builder
1215
1239
  builder.respond_to?(:constantize) ? builder.constantize : builder
1216
1240
  end
1217
1241
  end
@@ -1593,7 +1617,14 @@ module ActionView
1593
1617
  @auto_index
1594
1618
  end
1595
1619
 
1596
- record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
1620
+ record_name = if index
1621
+ "#{object_name}[#{index}][#{record_name}]"
1622
+ elsif record_name.to_s.end_with?('[]')
1623
+ record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
1624
+ "#{object_name}#{record_name}"
1625
+ else
1626
+ "#{object_name}[#{record_name}]"
1627
+ end
1597
1628
  fields_options[:child_index] = index
1598
1629
 
1599
1630
  @template.fields_for(record_name, record_object, fields_options, &block)
@@ -1607,7 +1638,7 @@ module ActionView
1607
1638
  # target labels for radio_button tags (where the value is used in the ID of the input tag).
1608
1639
  #
1609
1640
  # ==== Examples
1610
- # label(:post, :title)
1641
+ # label(:title)
1611
1642
  # # => <label for="post_title">Title</label>
1612
1643
  #
1613
1644
  # You can localize your labels based on model and attribute names.
@@ -1620,7 +1651,7 @@ module ActionView
1620
1651
  #
1621
1652
  # Which then will result in
1622
1653
  #
1623
- # label(:post, :body)
1654
+ # label(:body)
1624
1655
  # # => <label for="post_body">Write your entire text here</label>
1625
1656
  #
1626
1657
  # Localization can also be based purely on the translation of the attribute-name
@@ -1631,21 +1662,22 @@ module ActionView
1631
1662
  # post:
1632
1663
  # cost: "Total cost"
1633
1664
  #
1634
- # label(:post, :cost)
1665
+ # label(:cost)
1635
1666
  # # => <label for="post_cost">Total cost</label>
1636
1667
  #
1637
- # label(:post, :title, "A short title")
1668
+ # label(:title, "A short title")
1638
1669
  # # => <label for="post_title">A short title</label>
1639
1670
  #
1640
- # label(:post, :title, "A short title", class: "title_label")
1671
+ # label(:title, "A short title", class: "title_label")
1641
1672
  # # => <label for="post_title" class="title_label">A short title</label>
1642
1673
  #
1643
- # label(:post, :privacy, "Public Post", value: "public")
1674
+ # label(:privacy, "Public Post", value: "public")
1644
1675
  # # => <label for="post_privacy_public">Public Post</label>
1645
1676
  #
1646
- # label(:post, :terms) do
1677
+ # label(:terms) do
1647
1678
  # 'Accept <a href="/terms">Terms</a>.'.html_safe
1648
1679
  # end
1680
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
1649
1681
  def label(method, text = nil, options = {}, &block)
1650
1682
  @template.label(@object_name, method, text, objectify_options(options), &block)
1651
1683
  end
@@ -1694,16 +1726,17 @@ module ActionView
1694
1726
  # hashes instead of arrays.
1695
1727
  #
1696
1728
  # # Let's say that @post.validated? is 1:
1697
- # check_box("post", "validated")
1729
+ # check_box("validated")
1698
1730
  # # => <input name="post[validated]" type="hidden" value="0" />
1699
1731
  # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
1700
1732
  #
1701
1733
  # # Let's say that @puppy.gooddog is "no":
1702
- # check_box("puppy", "gooddog", {}, "yes", "no")
1734
+ # check_box("gooddog", {}, "yes", "no")
1703
1735
  # # => <input name="puppy[gooddog]" type="hidden" value="no" />
1704
1736
  # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
1705
1737
  #
1706
- # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
1738
+ # # Let's say that @eula.accepted is "no":
1739
+ # check_box("accepted", { class: 'eula_check' }, "yes", "no")
1707
1740
  # # => <input name="eula[accepted]" type="hidden" value="no" />
1708
1741
  # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
1709
1742
  def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
@@ -1718,13 +1751,14 @@ module ActionView
1718
1751
  # +options+ hash. You may pass HTML options there as well.
1719
1752
  #
1720
1753
  # # Let's say that @post.category returns "rails":
1721
- # radio_button("post", "category", "rails")
1722
- # radio_button("post", "category", "java")
1754
+ # radio_button("category", "rails")
1755
+ # radio_button("category", "java")
1723
1756
  # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
1724
1757
  # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
1725
1758
  #
1726
- # radio_button("user", "receive_newsletter", "yes")
1727
- # radio_button("user", "receive_newsletter", "no")
1759
+ # # Let's say that @user.category returns "no":
1760
+ # radio_button("receive_newsletter", "yes")
1761
+ # radio_button("receive_newsletter", "no")
1728
1762
  # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
1729
1763
  # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
1730
1764
  def radio_button(method, tag_value, options = {})
@@ -1737,14 +1771,17 @@ module ActionView
1737
1771
  # shown.
1738
1772
  #
1739
1773
  # ==== Examples
1740
- # hidden_field(:signup, :pass_confirm)
1741
- # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
1774
+ # # Let's say that @signup.pass_confirm returns true:
1775
+ # hidden_field(:pass_confirm)
1776
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="true" />
1742
1777
  #
1743
- # hidden_field(:post, :tag_list)
1744
- # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
1778
+ # # Let's say that @post.tag_list returns "blog, ruby":
1779
+ # hidden_field(:tag_list)
1780
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="blog, ruby" />
1745
1781
  #
1746
- # hidden_field(:user, :token)
1747
- # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
1782
+ # # Let's say that @user.token returns "abcde":
1783
+ # hidden_field(:token)
1784
+ # # => <input type="hidden" id="user_token" name="user[token]" value="abcde" />
1748
1785
  #
1749
1786
  def hidden_field(method, options = {})
1750
1787
  @emitted_hidden_id = true if method == :id
@@ -1765,19 +1802,24 @@ module ActionView
1765
1802
  # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
1766
1803
  #
1767
1804
  # ==== Examples
1768
- # file_field(:user, :avatar)
1805
+ # # Let's say that @user has avatar:
1806
+ # file_field(:avatar)
1769
1807
  # # => <input type="file" id="user_avatar" name="user[avatar]" />
1770
1808
  #
1771
- # file_field(:post, :image, :multiple => true)
1772
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
1809
+ # # Let's say that @post has image:
1810
+ # file_field(:image, :multiple => true)
1811
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
1773
1812
  #
1774
- # file_field(:post, :attached, accept: 'text/html')
1813
+ # # Let's say that @post has attached:
1814
+ # file_field(:attached, accept: 'text/html')
1775
1815
  # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
1776
1816
  #
1777
- # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg')
1817
+ # # Let's say that @post has image:
1818
+ # file_field(:image, accept: 'image/png,image/gif,image/jpeg')
1778
1819
  # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
1779
1820
  #
1780
- # file_field(:attachment, :file, class: 'file_input')
1821
+ # # Let's say that @attachment has file:
1822
+ # file_field(:file, class: 'file_input')
1781
1823
  # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
1782
1824
  def file_field(method, options = {})
1783
1825
  self.multipart = true
@@ -1845,7 +1887,7 @@ module ActionView
1845
1887
  # create: "Add %{model}"
1846
1888
  #
1847
1889
  # ==== Examples
1848
- # button("Create a post")
1890
+ # button("Create post")
1849
1891
  # # => <button name='button' type='submit'>Create post</button>
1850
1892
  #
1851
1893
  # button do
@@ -1906,7 +1948,11 @@ module ActionView
1906
1948
  explicit_child_index = options[:child_index]
1907
1949
  output = ActiveSupport::SafeBuffer.new
1908
1950
  association.each do |child|
1909
- options[:child_index] = nested_child_index(name) unless explicit_child_index
1951
+ if explicit_child_index
1952
+ options[:child_index] = explicit_child_index.call if explicit_child_index.respond_to?(:call)
1953
+ else
1954
+ options[:child_index] = nested_child_index(name)
1955
+ end
1910
1956
  output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
1911
1957
  end
1912
1958
  output