actionview 4.2.11 → 5.0.7

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 (68) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +304 -184
  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 +51 -18
  8. data/lib/action_view/digestor.rb +83 -81
  9. data/lib/action_view/flows.rb +4 -5
  10. data/lib/action_view/gem_version.rb +3 -3
  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 +6 -5
  14. data/lib/action_view/helpers/cache_helper.rb +62 -21
  15. data/lib/action_view/helpers/capture_helper.rb +5 -4
  16. data/lib/action_view/helpers/controller_helper.rb +11 -2
  17. data/lib/action_view/helpers/date_helper.rb +59 -13
  18. data/lib/action_view/helpers/debug_helper.rb +1 -1
  19. data/lib/action_view/helpers/form_helper.rb +74 -72
  20. data/lib/action_view/helpers/form_options_helper.rb +79 -39
  21. data/lib/action_view/helpers/form_tag_helper.rb +74 -44
  22. data/lib/action_view/helpers/javascript_helper.rb +4 -4
  23. data/lib/action_view/helpers/number_helper.rb +28 -13
  24. data/lib/action_view/helpers/output_safety_helper.rb +32 -2
  25. data/lib/action_view/helpers/record_tag_helper.rb +12 -99
  26. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  27. data/lib/action_view/helpers/sanitize_helper.rb +1 -2
  28. data/lib/action_view/helpers/tag_helper.rb +19 -11
  29. data/lib/action_view/helpers/tags/base.rb +45 -29
  30. data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -28
  31. data/lib/action_view/helpers/tags/collection_helpers.rb +32 -0
  32. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -9
  33. data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
  34. data/lib/action_view/helpers/tags/label.rb +1 -1
  35. data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
  36. data/lib/action_view/helpers/tags/search_field.rb +12 -9
  37. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  38. data/lib/action_view/helpers/tags/translator.rb +1 -1
  39. data/lib/action_view/helpers/text_helper.rb +27 -11
  40. data/lib/action_view/helpers/translation_helper.rb +56 -26
  41. data/lib/action_view/helpers/url_helper.rb +108 -79
  42. data/lib/action_view/layouts.rb +11 -10
  43. data/lib/action_view/log_subscriber.rb +35 -1
  44. data/lib/action_view/lookup_context.rb +69 -48
  45. data/lib/action_view/model_naming.rb +1 -1
  46. data/lib/action_view/path_set.rb +9 -0
  47. data/lib/action_view/railtie.rb +18 -3
  48. data/lib/action_view/record_identifier.rb +45 -19
  49. data/lib/action_view/renderer/abstract_renderer.rb +7 -3
  50. data/lib/action_view/renderer/partial_renderer.rb +38 -37
  51. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +49 -0
  52. data/lib/action_view/renderer/renderer.rb +2 -6
  53. data/lib/action_view/renderer/streaming_template_renderer.rb +1 -1
  54. data/lib/action_view/renderer/template_renderer.rb +11 -10
  55. data/lib/action_view/rendering.rb +15 -7
  56. data/lib/action_view/routing_url_for.rb +18 -6
  57. data/lib/action_view/tasks/{dependencies.rake → cache_digests.rake} +2 -2
  58. data/lib/action_view/template.rb +36 -12
  59. data/lib/action_view/template/error.rb +20 -9
  60. data/lib/action_view/template/handlers.rb +6 -4
  61. data/lib/action_view/template/handlers/html.rb +9 -0
  62. data/lib/action_view/template/handlers/raw.rb +1 -3
  63. data/lib/action_view/template/resolver.rb +49 -42
  64. data/lib/action_view/template/types.rb +14 -16
  65. data/lib/action_view/test_case.rb +15 -9
  66. data/lib/action_view/testing/resolvers.rb +1 -2
  67. data/lib/action_view/view_paths.rb +6 -24
  68. metadata +16 -20
@@ -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
@@ -759,7 +765,7 @@ module ActionView
759
765
  # # => <label for="post_privacy_public">Public Post</label>
760
766
  #
761
767
  # label(:post, :terms) do
762
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
768
+ # raw('Accept <a href="/terms">Terms</a>.')
763
769
  # end
764
770
  # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
765
771
  def label(object_name, method, content_or_options = nil, options = nil, &block)
@@ -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]" />
@@ -1014,7 +1020,7 @@ module ActionView
1014
1020
  # date_field("user", "born_on")
1015
1021
  # # => <input id="user_born_on" name="user[born_on]" type="date" />
1016
1022
  #
1017
- # The default value is generated by trying to call "to_date"
1023
+ # The default value is generated by trying to call +strftime+ with "%Y-%m-%d"
1018
1024
  # on the object's value, which makes it behave as expected for instances
1019
1025
  # of DateTime and ActiveSupport::TimeWithZone. You can still override that
1020
1026
  # by passing the "value" option explicitly, e.g.
@@ -1068,38 +1074,9 @@ module ActionView
1068
1074
  Tags::TimeField.new(object_name, method, self, options).render
1069
1075
  end
1070
1076
 
1071
- # Returns a text_field of type "datetime".
1072
- #
1073
- # datetime_field("user", "born_on")
1074
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" />
1075
- #
1076
- # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z"
1077
- # on the object's value, which makes it behave as expected for instances
1078
- # of DateTime and ActiveSupport::TimeWithZone.
1079
- #
1080
- # @user.born_on = Date.new(1984, 1, 12)
1081
- # datetime_field("user", "born_on")
1082
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" />
1083
- #
1084
- # You can create values for the "min" and "max" attributes by passing
1085
- # instances of Date or Time to the options hash.
1086
- #
1087
- # datetime_field("user", "born_on", min: Date.today)
1088
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
1089
- #
1090
- # Alternatively, you can pass a String formatted as an ISO8601 datetime
1091
- # with UTC offset as the values for "min" and "max."
1092
- #
1093
- # datetime_field("user", "born_on", min: "2014-05-20T00:00:00+0000")
1094
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
1095
- #
1096
- def datetime_field(object_name, method, options = {})
1097
- Tags::DatetimeField.new(object_name, method, self, options).render
1098
- end
1099
-
1100
1077
  # Returns a text_field of type "datetime-local".
1101
1078
  #
1102
- # datetime_local_field("user", "born_on")
1079
+ # datetime_field("user", "born_on")
1103
1080
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
1104
1081
  #
1105
1082
  # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
@@ -1107,25 +1084,27 @@ module ActionView
1107
1084
  # of DateTime and ActiveSupport::TimeWithZone.
1108
1085
  #
1109
1086
  # @user.born_on = Date.new(1984, 1, 12)
1110
- # datetime_local_field("user", "born_on")
1087
+ # datetime_field("user", "born_on")
1111
1088
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
1112
1089
  #
1113
1090
  # You can create values for the "min" and "max" attributes by passing
1114
1091
  # instances of Date or Time to the options hash.
1115
1092
  #
1116
- # datetime_local_field("user", "born_on", min: Date.today)
1093
+ # datetime_field("user", "born_on", min: Date.today)
1117
1094
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
1118
1095
  #
1119
1096
  # Alternatively, you can pass a String formatted as an ISO8601 datetime as
1120
1097
  # the values for "min" and "max."
1121
1098
  #
1122
- # datetime_local_field("user", "born_on", min: "2014-05-20T00:00:00")
1099
+ # datetime_field("user", "born_on", min: "2014-05-20T00:00:00")
1123
1100
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
1124
1101
  #
1125
- def datetime_local_field(object_name, method, options = {})
1102
+ def datetime_field(object_name, method, options = {})
1126
1103
  Tags::DatetimeLocalField.new(object_name, method, self, options).render
1127
1104
  end
1128
1105
 
1106
+ alias datetime_local_field datetime_field
1107
+
1129
1108
  # Returns a text_field of type "month".
1130
1109
  #
1131
1110
  # month_field("user", "born_on")
@@ -1211,7 +1190,7 @@ module ActionView
1211
1190
  end
1212
1191
 
1213
1192
  def default_form_builder_class
1214
- builder = ActionView::Base.default_form_builder
1193
+ builder = default_form_builder || ActionView::Base.default_form_builder
1215
1194
  builder.respond_to?(:constantize) ? builder.constantize : builder
1216
1195
  end
1217
1196
  end
@@ -1586,14 +1565,22 @@ module ActionView
1586
1565
  record_name = model_name_from_record_or_class(record_object).param_key
1587
1566
  end
1588
1567
 
1568
+ object_name = @object_name
1589
1569
  index = if options.has_key?(:index)
1590
1570
  options[:index]
1591
1571
  elsif defined?(@auto_index)
1592
- self.object_name = @object_name.to_s.sub(/\[\]$/,"")
1572
+ object_name = object_name.to_s.sub(/\[\]$/, "")
1593
1573
  @auto_index
1594
1574
  end
1595
1575
 
1596
- record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
1576
+ record_name = if index
1577
+ "#{object_name}[#{index}][#{record_name}]"
1578
+ elsif record_name.to_s.end_with?('[]')
1579
+ record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
1580
+ "#{object_name}#{record_name}"
1581
+ else
1582
+ "#{object_name}[#{record_name}]"
1583
+ end
1597
1584
  fields_options[:child_index] = index
1598
1585
 
1599
1586
  @template.fields_for(record_name, record_object, fields_options, &block)
@@ -1607,7 +1594,7 @@ module ActionView
1607
1594
  # target labels for radio_button tags (where the value is used in the ID of the input tag).
1608
1595
  #
1609
1596
  # ==== Examples
1610
- # label(:post, :title)
1597
+ # label(:title)
1611
1598
  # # => <label for="post_title">Title</label>
1612
1599
  #
1613
1600
  # You can localize your labels based on model and attribute names.
@@ -1620,7 +1607,7 @@ module ActionView
1620
1607
  #
1621
1608
  # Which then will result in
1622
1609
  #
1623
- # label(:post, :body)
1610
+ # label(:body)
1624
1611
  # # => <label for="post_body">Write your entire text here</label>
1625
1612
  #
1626
1613
  # Localization can also be based purely on the translation of the attribute-name
@@ -1631,21 +1618,22 @@ module ActionView
1631
1618
  # post:
1632
1619
  # cost: "Total cost"
1633
1620
  #
1634
- # label(:post, :cost)
1621
+ # label(:cost)
1635
1622
  # # => <label for="post_cost">Total cost</label>
1636
1623
  #
1637
- # label(:post, :title, "A short title")
1624
+ # label(:title, "A short title")
1638
1625
  # # => <label for="post_title">A short title</label>
1639
1626
  #
1640
- # label(:post, :title, "A short title", class: "title_label")
1627
+ # label(:title, "A short title", class: "title_label")
1641
1628
  # # => <label for="post_title" class="title_label">A short title</label>
1642
1629
  #
1643
- # label(:post, :privacy, "Public Post", value: "public")
1630
+ # label(:privacy, "Public Post", value: "public")
1644
1631
  # # => <label for="post_privacy_public">Public Post</label>
1645
1632
  #
1646
- # label(:post, :terms) do
1647
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
1633
+ # label(:terms) do
1634
+ # raw('Accept <a href="/terms">Terms</a>.')
1648
1635
  # end
1636
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
1649
1637
  def label(method, text = nil, options = {}, &block)
1650
1638
  @template.label(@object_name, method, text, objectify_options(options), &block)
1651
1639
  end
@@ -1694,16 +1682,17 @@ module ActionView
1694
1682
  # hashes instead of arrays.
1695
1683
  #
1696
1684
  # # Let's say that @post.validated? is 1:
1697
- # check_box("post", "validated")
1685
+ # check_box("validated")
1698
1686
  # # => <input name="post[validated]" type="hidden" value="0" />
1699
1687
  # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
1700
1688
  #
1701
1689
  # # Let's say that @puppy.gooddog is "no":
1702
- # check_box("puppy", "gooddog", {}, "yes", "no")
1690
+ # check_box("gooddog", {}, "yes", "no")
1703
1691
  # # => <input name="puppy[gooddog]" type="hidden" value="no" />
1704
1692
  # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
1705
1693
  #
1706
- # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
1694
+ # # Let's say that @eula.accepted is "no":
1695
+ # check_box("accepted", { class: 'eula_check' }, "yes", "no")
1707
1696
  # # => <input name="eula[accepted]" type="hidden" value="no" />
1708
1697
  # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
1709
1698
  def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
@@ -1718,13 +1707,14 @@ module ActionView
1718
1707
  # +options+ hash. You may pass HTML options there as well.
1719
1708
  #
1720
1709
  # # Let's say that @post.category returns "rails":
1721
- # radio_button("post", "category", "rails")
1722
- # radio_button("post", "category", "java")
1710
+ # radio_button("category", "rails")
1711
+ # radio_button("category", "java")
1723
1712
  # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
1724
1713
  # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
1725
1714
  #
1726
- # radio_button("user", "receive_newsletter", "yes")
1727
- # radio_button("user", "receive_newsletter", "no")
1715
+ # # Let's say that @user.category returns "no":
1716
+ # radio_button("receive_newsletter", "yes")
1717
+ # radio_button("receive_newsletter", "no")
1728
1718
  # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
1729
1719
  # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
1730
1720
  def radio_button(method, tag_value, options = {})
@@ -1737,14 +1727,17 @@ module ActionView
1737
1727
  # shown.
1738
1728
  #
1739
1729
  # ==== Examples
1740
- # hidden_field(:signup, :pass_confirm)
1741
- # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
1730
+ # # Let's say that @signup.pass_confirm returns true:
1731
+ # hidden_field(:pass_confirm)
1732
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="true" />
1742
1733
  #
1743
- # hidden_field(:post, :tag_list)
1744
- # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
1734
+ # # Let's say that @post.tag_list returns "blog, ruby":
1735
+ # hidden_field(:tag_list)
1736
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="blog, ruby" />
1745
1737
  #
1746
- # hidden_field(:user, :token)
1747
- # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
1738
+ # # Let's say that @user.token returns "abcde":
1739
+ # hidden_field(:token)
1740
+ # # => <input type="hidden" id="user_token" name="user[token]" value="abcde" />
1748
1741
  #
1749
1742
  def hidden_field(method, options = {})
1750
1743
  @emitted_hidden_id = true if method == :id
@@ -1765,19 +1758,24 @@ module ActionView
1765
1758
  # * <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
1759
  #
1767
1760
  # ==== Examples
1768
- # file_field(:user, :avatar)
1761
+ # # Let's say that @user has avatar:
1762
+ # file_field(:avatar)
1769
1763
  # # => <input type="file" id="user_avatar" name="user[avatar]" />
1770
1764
  #
1771
- # file_field(:post, :image, :multiple => true)
1772
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
1765
+ # # Let's say that @post has image:
1766
+ # file_field(:image, :multiple => true)
1767
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
1773
1768
  #
1774
- # file_field(:post, :attached, accept: 'text/html')
1769
+ # # Let's say that @post has attached:
1770
+ # file_field(:attached, accept: 'text/html')
1775
1771
  # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
1776
1772
  #
1777
- # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg')
1773
+ # # Let's say that @post has image:
1774
+ # file_field(:image, accept: 'image/png,image/gif,image/jpeg')
1778
1775
  # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
1779
1776
  #
1780
- # file_field(:attachment, :file, class: 'file_input')
1777
+ # # Let's say that @attachment has file:
1778
+ # file_field(:file, class: 'file_input')
1781
1779
  # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
1782
1780
  def file_field(method, options = {})
1783
1781
  self.multipart = true
@@ -1845,7 +1843,7 @@ module ActionView
1845
1843
  # create: "Add %{model}"
1846
1844
  #
1847
1845
  # ==== Examples
1848
- # button("Create a post")
1846
+ # button("Create post")
1849
1847
  # # => <button name='button' type='submit'>Create post</button>
1850
1848
  #
1851
1849
  # button do
@@ -1906,7 +1904,11 @@ module ActionView
1906
1904
  explicit_child_index = options[:child_index]
1907
1905
  output = ActiveSupport::SafeBuffer.new
1908
1906
  association.each do |child|
1909
- options[:child_index] = nested_child_index(name) unless explicit_child_index
1907
+ if explicit_child_index
1908
+ options[:child_index] = explicit_child_index.call if explicit_child_index.respond_to?(:call)
1909
+ else
1910
+ options[:child_index] = nested_child_index(name)
1911
+ end
1910
1912
  output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
1911
1913
  end
1912
1914
  output
@@ -18,10 +18,10 @@ module ActionView
18
18
  #
19
19
  # could become:
20
20
  #
21
- # <select name="post[category]">
22
- # <option></option>
23
- # <option>joke</option>
24
- # <option>poem</option>
21
+ # <select name="post[category]" id="post_category">
22
+ # <option value=""></option>
23
+ # <option value="joke">joke</option>
24
+ # <option value="poem">poem</option>
25
25
  # </select>
26
26
  #
27
27
  # Another common case is a select tag for a <tt>belongs_to</tt>-associated object.
@@ -32,11 +32,11 @@ module ActionView
32
32
  #
33
33
  # could become:
34
34
  #
35
- # <select name="post[person_id]">
35
+ # <select name="post[person_id]" id="post_person_id">
36
36
  # <option value="">None</option>
37
37
  # <option value="1">David</option>
38
- # <option value="2" selected="selected">Sam</option>
39
- # <option value="3">Tobias</option>
38
+ # <option value="2" selected="selected">Eileen</option>
39
+ # <option value="3">Rafael</option>
40
40
  # </select>
41
41
  #
42
42
  # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
@@ -45,11 +45,11 @@ module ActionView
45
45
  #
46
46
  # could become:
47
47
  #
48
- # <select name="post[person_id]">
48
+ # <select name="post[person_id]" id="post_person_id">
49
49
  # <option value="">Select Person</option>
50
50
  # <option value="1">David</option>
51
- # <option value="2">Sam</option>
52
- # <option value="3">Tobias</option>
51
+ # <option value="2">Eileen</option>
52
+ # <option value="3">Rafael</option>
53
53
  # </select>
54
54
  #
55
55
  # * <tt>:index</tt> - like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
@@ -71,19 +71,19 @@ module ActionView
71
71
  #
72
72
  # could become:
73
73
  #
74
- # <select name="post[category]">
75
- # <option></option>
76
- # <option>joke</option>
77
- # <option>poem</option>
78
- # <option disabled="disabled">restricted</option>
74
+ # <select name="post[category]" id="post_category">
75
+ # <option value=""></option>
76
+ # <option value="joke">joke</option>
77
+ # <option value="poem">poem</option>
78
+ # <option disabled="disabled" value="restricted">restricted</option>
79
79
  # </select>
80
80
  #
81
81
  # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
82
82
  #
83
- # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
83
+ # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: -> (category) { category.archived? }})
84
84
  #
85
85
  # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
86
- # <select name="post[category_id]">
86
+ # <select name="post[category_id]" id="post_category_id">
87
87
  # <option value="1" disabled="disabled">2008 stuff</option>
88
88
  # <option value="2" disabled="disabled">Christmas</option>
89
89
  # <option value="3">Jokes</option>
@@ -109,11 +109,11 @@ module ActionView
109
109
  #
110
110
  # would become:
111
111
  #
112
- # <select name="post[person_id]">
112
+ # <select name="post[person_id]" id="post_person_id">
113
113
  # <option value=""></option>
114
114
  # <option value="1" selected="selected">David</option>
115
- # <option value="2">Sam</option>
116
- # <option value="3">Tobias</option>
115
+ # <option value="2">Eileen</option>
116
+ # <option value="3">Rafael</option>
117
117
  # </select>
118
118
  #
119
119
  # assuming the associated person has ID 1.
@@ -192,7 +192,7 @@ module ActionView
192
192
  # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, prompt: true)
193
193
  #
194
194
  # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
195
- # <select name="post[author_id]">
195
+ # <select name="post[author_id]" id="post_author_id">
196
196
  # <option value="">Please select</option>
197
197
  # <option value="1" selected="selected">D. Heinemeier Hansson</option>
198
198
  # <option value="2">D. Thomas</option>
@@ -243,7 +243,7 @@ module ActionView
243
243
  #
244
244
  # Possible output:
245
245
  #
246
- # <select name="city[country_id]">
246
+ # <select name="city[country_id]" id="city_country_id">
247
247
  # <optgroup label="Africa">
248
248
  # <option value="1">South Africa</option>
249
249
  # <option value="3">Somalia</option>
@@ -268,10 +268,11 @@ module ActionView
268
268
  # for more information.)
269
269
  #
270
270
  # You can also supply an array of ActiveSupport::TimeZone objects
271
- # as +priority_zones+, so that they will be listed above the rest of the
272
- # (long) list. (You can use ActiveSupport::TimeZone.us_zones as a convenience
273
- # for obtaining a list of the US time zones, or a Regexp to select the zones
274
- # of your choice)
271
+ # as +priority_zones+ so that they will be listed above the rest of the
272
+ # (long) list. You can use ActiveSupport::TimeZone.us_zones for a list
273
+ # of US time zones, ActiveSupport::TimeZone.country_zones(country_code)
274
+ # for another country's time zones, or a Regexp to select the zones of
275
+ # your choice.
275
276
  #
276
277
  # Finally, this method supports a <tt>:default</tt> option, which selects
277
278
  # a default ActiveSupport::TimeZone if the object's time zone is +nil+.
@@ -302,17 +303,17 @@ module ActionView
302
303
  # # => <option value="DKK">Kroner</option>
303
304
  #
304
305
  # options_for_select([ "VISA", "MasterCard" ], "MasterCard")
305
- # # => <option>VISA</option>
306
- # # => <option selected="selected">MasterCard</option>
306
+ # # => <option value="VISA">VISA</option>
307
+ # # => <option selected="selected" value="MasterCard">MasterCard</option>
307
308
  #
308
309
  # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
309
310
  # # => <option value="$20">Basic</option>
310
311
  # # => <option value="$40" selected="selected">Plus</option>
311
312
  #
312
313
  # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
313
- # # => <option selected="selected">VISA</option>
314
- # # => <option>MasterCard</option>
315
- # # => <option selected="selected">Discover</option>
314
+ # # => <option selected="selected" value="VISA">VISA</option>
315
+ # # => <option value="MasterCard">MasterCard</option>
316
+ # # => <option selected="selected" value="Discover">Discover</option>
316
317
  #
317
318
  # You can optionally provide HTML attributes as the last element of the array.
318
319
  #
@@ -351,12 +352,12 @@ module ActionView
351
352
  return container if String === container
352
353
 
353
354
  selected, disabled = extract_selected_and_disabled(selected).map do |r|
354
- Array(r).map { |item| item.to_s }
355
+ Array(r).map(&:to_s)
355
356
  end
356
357
 
357
358
  container.map do |element|
358
359
  html_attributes = option_html_attributes(element)
359
- text, value = option_text_and_value(element).map { |item| item.to_s }
360
+ text, value = option_text_and_value(element).map(&:to_s)
360
361
 
361
362
  html_attributes[:selected] ||= option_value_selected?(value, selected)
362
363
  html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled)
@@ -456,7 +457,7 @@ module ActionView
456
457
  option_tags = options_from_collection_for_select(
457
458
  group.send(group_method), option_key_method, option_value_method, selected_key)
458
459
 
459
- content_tag(:optgroup, option_tags, label: group.send(group_label_method))
460
+ content_tag("optgroup".freeze, option_tags, label: group.send(group_label_method))
460
461
  end.join.html_safe
461
462
  end
462
463
 
@@ -528,7 +529,7 @@ module ActionView
528
529
  body = "".html_safe
529
530
 
530
531
  if prompt
531
- body.safe_concat content_tag(:option, prompt_text(prompt), value: "")
532
+ body.safe_concat content_tag("option".freeze, prompt_text(prompt), value: "")
532
533
  end
533
534
 
534
535
  grouped_options.each do |container|
@@ -541,14 +542,14 @@ module ActionView
541
542
  end
542
543
 
543
544
  html_attributes = { label: label }.merge!(html_attributes)
544
- body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), html_attributes)
545
+ body.safe_concat content_tag("optgroup".freeze, options_for_select(container, selected_key), html_attributes)
545
546
  end
546
547
 
547
548
  body
548
549
  end
549
550
 
550
551
  # Returns a string of option tags for pretty much any time zone in the
551
- # world. Supply a ActiveSupport::TimeZone name as +selected+ to have it
552
+ # world. Supply an ActiveSupport::TimeZone name as +selected+ to have it
552
553
  # marked as the selected option tag. You can also supply an array of
553
554
  # ActiveSupport::TimeZone objects as +priority_zones+, so that they will
554
555
  # be listed above the rest of the (long) list. (You can use
@@ -556,7 +557,7 @@ module ActionView
556
557
  # of the US time zones, or a Regexp to select the zones of your choice)
557
558
  #
558
559
  # The +selected+ parameter must be either +nil+, or a string that names
559
- # a ActiveSupport::TimeZone.
560
+ # an ActiveSupport::TimeZone.
560
561
  #
561
562
  # By default, +model+ is the ActiveSupport::TimeZone constant (which can
562
563
  # be obtained in Active Record as a value object). The only requirement
@@ -577,7 +578,7 @@ module ActionView
577
578
  end
578
579
 
579
580
  zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
580
- zone_options.safe_concat content_tag(:option, '-------------', value: '', disabled: true)
581
+ zone_options.safe_concat content_tag("option".freeze, '-------------', value: '', disabled: true)
581
582
  zone_options.safe_concat "\n"
582
583
 
583
584
  zones = zones - priority_zones
@@ -644,6 +645,24 @@ module ActionView
644
645
  # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
645
646
  # b.label(:"data-value" => b.value) { b.radio_button + b.text }
646
647
  # end
648
+ #
649
+ # ==== Gotcha
650
+ #
651
+ # The HTML specification says when nothing is select on a collection of radio buttons
652
+ # web browsers do not send any value to server.
653
+ # Unfortunately this introduces a gotcha:
654
+ # if a +User+ model has a +category_id+ field, and in the form none category is selected no +category_id+ parameter is sent. So,
655
+ # any strong parameters idiom like
656
+ #
657
+ # params.require(:user).permit(...)
658
+ #
659
+ # will raise an error since no +{user: ...}+ will be present.
660
+ #
661
+ # To prevent this the helper generates an auxiliary hidden field before
662
+ # every collection of radio buttons. The hidden field has the same name as collection radio button and blank value.
663
+ #
664
+ # In case if you don't want the helper to generate this hidden field you can specify
665
+ # <tt>include_hidden: false</tt> option.
647
666
  def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
648
667
  Tags::CollectionRadioButtons.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
649
668
  end
@@ -707,6 +726,27 @@ module ActionView
707
726
  # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
708
727
  # b.label(:"data-value" => b.value) { b.check_box + b.text }
709
728
  # end
729
+ #
730
+ # ==== Gotcha
731
+ #
732
+ # When no selection is made for a collection of checkboxes most
733
+ # web browsers will not send any value.
734
+ #
735
+ # For example, if we have a +User+ model with +category_ids+ field and we
736
+ # have the following code in our update action:
737
+ #
738
+ # @user.update(params[:user])
739
+ #
740
+ # If no +category_ids+ are selected then we can safely assume this field
741
+ # will not be updated.
742
+ #
743
+ # This is possible thanks to a hidden field generated by the helper method
744
+ # for every collection of checkboxes.
745
+ # This hidden field is given the same field name as the checkboxes with a
746
+ # blank value.
747
+ #
748
+ # In the rare case you don't want this hidden field, you can pass the
749
+ # <tt>include_hidden: false</tt> option to the helper method.
710
750
  def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
711
751
  Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
712
752
  end