omg-actionview 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/app/assets/javascripts/rails-ujs.esm.js +686 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +316 -0
  8. data/lib/action_view/buffers.rb +165 -0
  9. data/lib/action_view/cache_expiry.rb +69 -0
  10. data/lib/action_view/context.rb +32 -0
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
  12. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  13. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  14. data/lib/action_view/dependency_tracker.rb +41 -0
  15. data/lib/action_view/deprecator.rb +7 -0
  16. data/lib/action_view/digestor.rb +130 -0
  17. data/lib/action_view/flows.rb +75 -0
  18. data/lib/action_view/gem_version.rb +17 -0
  19. data/lib/action_view/helpers/active_model_helper.rb +54 -0
  20. data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
  21. data/lib/action_view/helpers/asset_url_helper.rb +473 -0
  22. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  23. data/lib/action_view/helpers/cache_helper.rb +315 -0
  24. data/lib/action_view/helpers/capture_helper.rb +236 -0
  25. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  26. data/lib/action_view/helpers/controller_helper.rb +42 -0
  27. data/lib/action_view/helpers/csp_helper.rb +26 -0
  28. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  29. data/lib/action_view/helpers/date_helper.rb +1266 -0
  30. data/lib/action_view/helpers/debug_helper.rb +38 -0
  31. data/lib/action_view/helpers/form_helper.rb +2765 -0
  32. data/lib/action_view/helpers/form_options_helper.rb +927 -0
  33. data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
  34. data/lib/action_view/helpers/javascript_helper.rb +96 -0
  35. data/lib/action_view/helpers/number_helper.rb +165 -0
  36. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  37. data/lib/action_view/helpers/rendering_helper.rb +218 -0
  38. data/lib/action_view/helpers/sanitize_helper.rb +201 -0
  39. data/lib/action_view/helpers/tag_helper.rb +621 -0
  40. data/lib/action_view/helpers/tags/base.rb +138 -0
  41. data/lib/action_view/helpers/tags/check_box.rb +65 -0
  42. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  43. data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
  44. data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
  45. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  46. data/lib/action_view/helpers/tags/collection_select.rb +33 -0
  47. data/lib/action_view/helpers/tags/color_field.rb +26 -0
  48. data/lib/action_view/helpers/tags/date_field.rb +14 -0
  49. data/lib/action_view/helpers/tags/date_select.rb +75 -0
  50. data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
  51. data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
  52. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  53. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  54. data/lib/action_view/helpers/tags/file_field.rb +26 -0
  55. data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
  56. data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/label.rb +84 -0
  58. data/lib/action_view/helpers/tags/month_field.rb +14 -0
  59. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  60. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  61. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  62. data/lib/action_view/helpers/tags/radio_button.rb +32 -0
  63. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  64. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  65. data/lib/action_view/helpers/tags/select.rb +45 -0
  66. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  67. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  68. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  69. data/lib/action_view/helpers/tags/text_field.rb +33 -0
  70. data/lib/action_view/helpers/tags/time_field.rb +23 -0
  71. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  72. data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
  73. data/lib/action_view/helpers/tags/translator.rb +39 -0
  74. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  75. data/lib/action_view/helpers/tags/week_field.rb +14 -0
  76. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  77. data/lib/action_view/helpers/tags.rb +47 -0
  78. data/lib/action_view/helpers/text_helper.rb +568 -0
  79. data/lib/action_view/helpers/translation_helper.rb +161 -0
  80. data/lib/action_view/helpers/url_helper.rb +812 -0
  81. data/lib/action_view/helpers.rb +68 -0
  82. data/lib/action_view/layouts.rb +434 -0
  83. data/lib/action_view/locale/en.yml +56 -0
  84. data/lib/action_view/log_subscriber.rb +132 -0
  85. data/lib/action_view/lookup_context.rb +299 -0
  86. data/lib/action_view/model_naming.rb +14 -0
  87. data/lib/action_view/path_registry.rb +57 -0
  88. data/lib/action_view/path_set.rb +84 -0
  89. data/lib/action_view/railtie.rb +132 -0
  90. data/lib/action_view/record_identifier.rb +118 -0
  91. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  92. data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
  93. data/lib/action_view/render_parser.rb +40 -0
  94. data/lib/action_view/renderer/abstract_renderer.rb +186 -0
  95. data/lib/action_view/renderer/collection_renderer.rb +204 -0
  96. data/lib/action_view/renderer/object_renderer.rb +34 -0
  97. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
  98. data/lib/action_view/renderer/partial_renderer.rb +267 -0
  99. data/lib/action_view/renderer/renderer.rb +107 -0
  100. data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
  101. data/lib/action_view/renderer/template_renderer.rb +115 -0
  102. data/lib/action_view/rendering.rb +190 -0
  103. data/lib/action_view/routing_url_for.rb +149 -0
  104. data/lib/action_view/tasks/cache_digests.rake +25 -0
  105. data/lib/action_view/template/error.rb +264 -0
  106. data/lib/action_view/template/handlers/builder.rb +25 -0
  107. data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
  108. data/lib/action_view/template/handlers/erb.rb +157 -0
  109. data/lib/action_view/template/handlers/html.rb +11 -0
  110. data/lib/action_view/template/handlers/raw.rb +11 -0
  111. data/lib/action_view/template/handlers.rb +66 -0
  112. data/lib/action_view/template/html.rb +33 -0
  113. data/lib/action_view/template/inline.rb +22 -0
  114. data/lib/action_view/template/raw_file.rb +25 -0
  115. data/lib/action_view/template/renderable.rb +30 -0
  116. data/lib/action_view/template/resolver.rb +212 -0
  117. data/lib/action_view/template/sources/file.rb +17 -0
  118. data/lib/action_view/template/sources.rb +13 -0
  119. data/lib/action_view/template/text.rb +32 -0
  120. data/lib/action_view/template/types.rb +50 -0
  121. data/lib/action_view/template.rb +580 -0
  122. data/lib/action_view/template_details.rb +66 -0
  123. data/lib/action_view/template_path.rb +66 -0
  124. data/lib/action_view/test_case.rb +449 -0
  125. data/lib/action_view/testing/resolvers.rb +44 -0
  126. data/lib/action_view/unbound_template.rb +67 -0
  127. data/lib/action_view/version.rb +10 -0
  128. data/lib/action_view/view_paths.rb +117 -0
  129. data/lib/action_view.rb +104 -0
  130. metadata +275 -0
@@ -0,0 +1,927 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+ require "erb"
5
+ require "active_support/core_ext/string/output_safety"
6
+ require "active_support/core_ext/array/extract_options"
7
+ require "active_support/core_ext/array/wrap"
8
+ require "action_view/helpers/text_helper"
9
+
10
+ module ActionView
11
+ module Helpers # :nodoc:
12
+ # = Action View Form Option \Helpers
13
+ #
14
+ # Provides a number of methods for turning different kinds of containers into a set of option tags.
15
+ #
16
+ # The <tt>collection_select</tt>, <tt>select</tt> and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter, a hash:
17
+ #
18
+ # * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
19
+ #
20
+ # select(:post, :category, Post::CATEGORIES, { include_blank: true })
21
+ #
22
+ # could become:
23
+ #
24
+ # <select name="post[category]" id="post_category">
25
+ # <option value="" label=" "></option>
26
+ # <option value="joke">joke</option>
27
+ # <option value="poem">poem</option>
28
+ # </select>
29
+ #
30
+ # Another common case is a select tag for a <tt>belongs_to</tt>-associated object.
31
+ #
32
+ # Example with <tt>@post.person_id => 2</tt>:
33
+ #
34
+ # select(:post, :person_id, Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: "None" })
35
+ #
36
+ # could become:
37
+ #
38
+ # <select name="post[person_id]" id="post_person_id">
39
+ # <option value="">None</option>
40
+ # <option value="1">David</option>
41
+ # <option value="2" selected="selected">Eileen</option>
42
+ # <option value="3">Rafael</option>
43
+ # </select>
44
+ #
45
+ # * <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.
46
+ #
47
+ # select(:post, :person_id, Person.all.collect { |p| [ p.name, p.id ] }, { prompt: "Select Person" })
48
+ #
49
+ # could become:
50
+ #
51
+ # <select name="post[person_id]" id="post_person_id">
52
+ # <option value="">Select Person</option>
53
+ # <option value="1">David</option>
54
+ # <option value="2">Eileen</option>
55
+ # <option value="3">Rafael</option>
56
+ # </select>
57
+ #
58
+ # * <tt>:index</tt> - like the other form helpers, <tt>select</tt> can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, <tt>select</tt> expects this
59
+ # option to be in the +html_options+ parameter.
60
+ #
61
+ # select("album[]", :genre, %w[ rap rock country ], {}, { index: nil })
62
+ #
63
+ # becomes:
64
+ #
65
+ # <select name="album[][genre]" id="album__genre">
66
+ # <option value="rap">rap</option>
67
+ # <option value="rock">rock</option>
68
+ # <option value="country">country</option>
69
+ # </select>
70
+ #
71
+ # * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
72
+ #
73
+ # select(:post, :category, Post::CATEGORIES, { disabled: "restricted" })
74
+ #
75
+ # could become:
76
+ #
77
+ # <select name="post[category]" id="post_category">
78
+ # <option value="joke">joke</option>
79
+ # <option value="poem">poem</option>
80
+ # <option disabled="disabled" value="restricted">restricted</option>
81
+ # </select>
82
+ #
83
+ # 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.
84
+ #
85
+ # collection_select(:post, :category_id, Category.all, :id, :name, { disabled: -> (category) { category.archived? } })
86
+ #
87
+ # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
88
+ # <select name="post[category_id]" id="post_category_id">
89
+ # <option value="1" disabled="disabled">2008 stuff</option>
90
+ # <option value="2" disabled="disabled">Christmas</option>
91
+ # <option value="3">Jokes</option>
92
+ # <option value="4">Poems</option>
93
+ # </select>
94
+ module FormOptionsHelper
95
+ # ERB::Util can mask some helpers like textilize. Make sure to include them.
96
+ include TextHelper
97
+
98
+ # Create a select tag and a series of contained option tags for the provided object and method.
99
+ # The option currently held by the object will be selected, provided that the object is available.
100
+ #
101
+ # There are two possible formats for the +choices+ parameter, corresponding to other helpers' output:
102
+ #
103
+ # * A flat collection (see <tt>options_for_select</tt>).
104
+ # * A nested collection (see <tt>grouped_options_for_select</tt>).
105
+ #
106
+ # Example with <tt>@post.person_id => 2</tt>:
107
+ #
108
+ # select :post, :person_id, Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: true })
109
+ #
110
+ # would become:
111
+ #
112
+ # <select name="post[person_id]" id="post_person_id">
113
+ # <option value="" label=" "></option>
114
+ # <option value="1">David</option>
115
+ # <option value="2" selected="selected">Eileen</option>
116
+ # <option value="3">Rafael</option>
117
+ # </select>
118
+ #
119
+ # This can be used to provide a default set of options in the standard way: before rendering the create form, a
120
+ # new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
121
+ # to the database. Instead, a second model object is created when the create request is received.
122
+ # This allows the user to submit a form page more than once with the expected results of creating multiple records.
123
+ # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
124
+ #
125
+ # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>selected: value</tt> to use a different selection
126
+ # or <tt>selected: nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
127
+ # tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
128
+ #
129
+ # A block can be passed to +select+ to customize how the options tags will be rendered. This
130
+ # is useful when the options tag has complex attributes.
131
+ #
132
+ # select(report, :campaign_ids) do
133
+ # available_campaigns.each do |c|
134
+ # tag.option(c.name, value: c.id, data: { tags: c.tags.to_json })
135
+ # end
136
+ # end
137
+ #
138
+ # ==== Gotcha
139
+ #
140
+ # The HTML specification says when +multiple+ parameter passed to select and all options got deselected
141
+ # web browsers do not send any value to server. Unfortunately this introduces a gotcha:
142
+ # if a +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
143
+ # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
144
+ # any mass-assignment idiom like
145
+ #
146
+ # @user.update(params[:user])
147
+ #
148
+ # wouldn't update roles.
149
+ #
150
+ # To prevent this the helper generates an auxiliary hidden field before
151
+ # every multiple select. The hidden field has the same name as multiple select and blank value.
152
+ #
153
+ # <b>Note:</b> The client either sends only the hidden field (representing
154
+ # the deselected multiple select box), or both fields. This means that the resulting array
155
+ # always contains a blank string.
156
+ #
157
+ # In case if you don't want the helper to generate this hidden field you can specify
158
+ # <tt>include_hidden: false</tt> option.
159
+ def select(object, method, choices = nil, options = {}, html_options = {}, &block)
160
+ Tags::Select.new(object, method, self, choices, options, html_options, &block).render
161
+ end
162
+
163
+ # Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
164
+ # +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
165
+ # be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
166
+ # or <tt>:include_blank</tt> in the +options+ hash.
167
+ #
168
+ # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
169
+ # of +collection+. The return values are used as the +value+ attribute and contents of each
170
+ # <tt><option></tt> tag, respectively. They can also be any object that responds to +call+, such
171
+ # as a +proc+, that will be called for each member of the +collection+ to
172
+ # retrieve the value/text.
173
+ #
174
+ # Example object structure for use with this method:
175
+ #
176
+ # class Post < ActiveRecord::Base
177
+ # belongs_to :author
178
+ # end
179
+ #
180
+ # class Author < ActiveRecord::Base
181
+ # has_many :posts
182
+ #
183
+ # def name_with_initial
184
+ # "#{first_name.first}. #{last_name}"
185
+ # end
186
+ # end
187
+ #
188
+ # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
189
+ #
190
+ # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, prompt: true)
191
+ #
192
+ # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
193
+ # <select name="post[author_id]" id="post_author_id">
194
+ # <option value="">Please select</option>
195
+ # <option value="1" selected="selected">D. Heinemeier Hansson</option>
196
+ # <option value="2">D. Thomas</option>
197
+ # <option value="3">M. Clark</option>
198
+ # </select>
199
+ def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
200
+ Tags::CollectionSelect.new(object, method, self, collection, value_method, text_method, options, html_options).render
201
+ end
202
+
203
+ # Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of
204
+ # +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
205
+ # be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
206
+ # or <tt>:include_blank</tt> in the +options+ hash.
207
+ #
208
+ # Parameters:
209
+ # * +object+ - The instance of the class to be used for the select tag
210
+ # * +method+ - The attribute of +object+ corresponding to the select tag
211
+ # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
212
+ # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
213
+ # array of child objects representing the <tt><option></tt> tags. It can also be any object that responds
214
+ # to +call+, such as a +proc+, that will be called for each member of the +collection+ to retrieve the
215
+ # value.
216
+ # * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
217
+ # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag. It can also be any object
218
+ # that responds to +call+, such as a +proc+, that will be called for each member of the +collection+ to
219
+ # retrieve the label.
220
+ # * +option_key_method+ - The name of a method which, when called on a child object of a member of
221
+ # +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
222
+ # * +option_value_method+ - The name of a method which, when called on a child object of a member of
223
+ # +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
224
+ #
225
+ # Example object structure for use with this method:
226
+ #
227
+ # # attributes: id, name
228
+ # class Continent < ActiveRecord::Base
229
+ # has_many :countries
230
+ # end
231
+ #
232
+ # # attributes: id, name, continent_id
233
+ # class Country < ActiveRecord::Base
234
+ # belongs_to :continent
235
+ # end
236
+ #
237
+ # # attributes: id, name, country_id
238
+ # class City < ActiveRecord::Base
239
+ # belongs_to :country
240
+ # end
241
+ #
242
+ # Sample usage:
243
+ #
244
+ # grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name)
245
+ #
246
+ # Possible output:
247
+ #
248
+ # <select name="city[country_id]" id="city_country_id">
249
+ # <optgroup label="Africa">
250
+ # <option value="1">South Africa</option>
251
+ # <option value="3">Somalia</option>
252
+ # </optgroup>
253
+ # <optgroup label="Europe">
254
+ # <option value="7" selected="selected">Denmark</option>
255
+ # <option value="2">Ireland</option>
256
+ # </optgroup>
257
+ # </select>
258
+ def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
259
+ Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render
260
+ end
261
+
262
+ # Returns select and option tags for the given object and method, using
263
+ # #time_zone_options_for_select to generate the list of option tags.
264
+ #
265
+ # In addition to the <tt>:include_blank</tt> option documented above,
266
+ # this method also supports a <tt>:model</tt> option, which defaults
267
+ # to ActiveSupport::TimeZone. This may be used by users to specify a
268
+ # different time zone model object. (See +time_zone_options_for_select+
269
+ # for more information.)
270
+ #
271
+ # You can also supply an array of ActiveSupport::TimeZone objects
272
+ # as +priority_zones+ so that they will be listed above the rest of the
273
+ # (long) list. You can use ActiveSupport::TimeZone.us_zones for a list
274
+ # of US time zones, ActiveSupport::TimeZone.country_zones(country_code)
275
+ # for another country's time zones, or a Regexp to select the zones of
276
+ # your choice.
277
+ #
278
+ # Finally, this method supports a <tt>:default</tt> option, which selects
279
+ # a default ActiveSupport::TimeZone if the object's time zone is +nil+.
280
+ #
281
+ # time_zone_select(:user, :time_zone, nil, include_blank: true)
282
+ #
283
+ # time_zone_select(:user, :time_zone, nil, default: "Pacific Time (US & Canada)")
284
+ #
285
+ # time_zone_select(:user, :time_zone, ActiveSupport::TimeZone.us_zones, default: "Pacific Time (US & Canada)")
286
+ #
287
+ # time_zone_select(:user, :time_zone, [ ActiveSupport::TimeZone["Alaska"], ActiveSupport::TimeZone["Hawaii"] ])
288
+ #
289
+ # time_zone_select(:user, :time_zone, /Australia/)
290
+ #
291
+ # time_zone_select(:user, :time_zone, ActiveSupport::TimeZone.all.sort, model: ActiveSupport::TimeZone)
292
+ def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
293
+ Tags::TimeZoneSelect.new(object, method, self, priority_zones, options, html_options).render
294
+ end
295
+
296
+ # Returns select and option tags for the given object and method, using
297
+ # <tt>weekday_options_for_select</tt> to generate the list of option tags.
298
+ def weekday_select(object, method, options = {}, html_options = {}, &block)
299
+ Tags::WeekdaySelect.new(object, method, self, options, html_options, &block).render
300
+ end
301
+
302
+ # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
303
+ # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
304
+ # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
305
+ # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+
306
+ # may also be an array of values to be selected when using a multiple select.
307
+ #
308
+ # options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
309
+ # # => <option value="$">Dollar</option>
310
+ # # => <option value="DKK">Kroner</option>
311
+ #
312
+ # options_for_select([ "VISA", "MasterCard" ], "MasterCard")
313
+ # # => <option value="VISA">VISA</option>
314
+ # # => <option selected="selected" value="MasterCard">MasterCard</option>
315
+ #
316
+ # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
317
+ # # => <option value="$20">Basic</option>
318
+ # # => <option value="$40" selected="selected">Plus</option>
319
+ #
320
+ # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
321
+ # # => <option selected="selected" value="VISA">VISA</option>
322
+ # # => <option value="MasterCard">MasterCard</option>
323
+ # # => <option selected="selected" value="Discover">Discover</option>
324
+ #
325
+ # You can optionally provide HTML attributes as the last element of the array.
326
+ #
327
+ # options_for_select([ "Denmark", ["USA", { class: 'bold' }], "Sweden" ], ["USA", "Sweden"])
328
+ # # => <option value="Denmark">Denmark</option>
329
+ # # => <option value="USA" class="bold" selected="selected">USA</option>
330
+ # # => <option value="Sweden" selected="selected">Sweden</option>
331
+ #
332
+ # options_for_select([["Dollar", "$", { class: "bold" }], ["Kroner", "DKK", { onclick: "alert('HI');" }]])
333
+ # # => <option value="$" class="bold">Dollar</option>
334
+ # # => <option value="DKK" onclick="alert('HI');">Kroner</option>
335
+ #
336
+ # If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
337
+ # or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
338
+ #
339
+ # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: "Super Platinum")
340
+ # # => <option value="Free">Free</option>
341
+ # # => <option value="Basic">Basic</option>
342
+ # # => <option value="Advanced">Advanced</option>
343
+ # # => <option value="Super Platinum" disabled="disabled">Super Platinum</option>
344
+ #
345
+ # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: ["Advanced", "Super Platinum"])
346
+ # # => <option value="Free">Free</option>
347
+ # # => <option value="Basic">Basic</option>
348
+ # # => <option value="Advanced" disabled="disabled">Advanced</option>
349
+ # # => <option value="Super Platinum" disabled="disabled">Super Platinum</option>
350
+ #
351
+ # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], selected: "Free", disabled: "Super Platinum")
352
+ # # => <option value="Free" selected="selected">Free</option>
353
+ # # => <option value="Basic">Basic</option>
354
+ # # => <option value="Advanced">Advanced</option>
355
+ # # => <option value="Super Platinum" disabled="disabled">Super Platinum</option>
356
+ #
357
+ # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
358
+ def options_for_select(container, selected = nil)
359
+ return container if String === container
360
+
361
+ selected, disabled = extract_selected_and_disabled(selected).map do |r|
362
+ Array(r).map(&:to_s)
363
+ end
364
+
365
+ container.map do |element|
366
+ html_attributes = option_html_attributes(element)
367
+ text, value = option_text_and_value(element).map(&:to_s)
368
+
369
+ html_attributes[:selected] ||= option_value_selected?(value, selected)
370
+ html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled)
371
+ html_attributes[:value] = value
372
+
373
+ tag_builder.content_tag_string(:option, text, html_attributes)
374
+ end.join("\n").html_safe
375
+ end
376
+
377
+ # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning
378
+ # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
379
+ #
380
+ # options_from_collection_for_select(@people, 'id', 'name')
381
+ # # => <option value="#{person.id}">#{person.name}</option>
382
+ #
383
+ # This is more often than not used inside a #select_tag like this example:
384
+ #
385
+ # select_tag 'person', options_from_collection_for_select(@people, 'id', 'name')
386
+ #
387
+ # If +selected+ is specified as a value or array of values, the element(s) returning a match on +value_method+
388
+ # will be selected option tag(s).
389
+ #
390
+ # If +selected+ is specified as a Proc, those members of the collection that return true for the anonymous
391
+ # function are the selected values.
392
+ #
393
+ # +selected+ can also be a hash, specifying both <tt>:selected</tt> and/or <tt>:disabled</tt> values as required.
394
+ #
395
+ # Be sure to specify the same class as the +value_method+ when specifying selected or disabled options.
396
+ # Failure to do this will produce undesired results. Example:
397
+ # options_from_collection_for_select(@people, 'id', 'name', '1')
398
+ # Will not select a person with the id of 1 because 1 (an Integer) is not the same as '1' (a string)
399
+ # options_from_collection_for_select(@people, 'id', 'name', 1)
400
+ # should produce the desired results.
401
+ def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
402
+ options = collection.map do |element|
403
+ [value_for_collection(element, text_method), value_for_collection(element, value_method), option_html_attributes(element)]
404
+ end
405
+ selected, disabled = extract_selected_and_disabled(selected)
406
+ select_deselect = {
407
+ selected: extract_values_from_collection(collection, value_method, selected),
408
+ disabled: extract_values_from_collection(collection, value_method, disabled)
409
+ }
410
+
411
+ options_for_select(options, select_deselect)
412
+ end
413
+
414
+ # Returns a string of <tt><option></tt> tags, like <tt>options_from_collection_for_select</tt>, but
415
+ # groups them by <tt><optgroup></tt> tags based on the object relationships of the arguments.
416
+ #
417
+ # Parameters:
418
+ # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
419
+ # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
420
+ # array of child objects representing the <tt><option></tt> tags.
421
+ # * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
422
+ # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
423
+ # * +option_key_method+ - The name of a method which, when called on a child object of a member of
424
+ # +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
425
+ # * +option_value_method+ - The name of a method which, when called on a child object of a member of
426
+ # +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
427
+ # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
428
+ # which will have the +selected+ attribute set. Corresponds to the return value of one of the calls
429
+ # to +option_key_method+. If +nil+, no selection is made. Can also be a hash if disabled values are
430
+ # to be specified.
431
+ #
432
+ # Example object structure for use with this method:
433
+ #
434
+ # class Continent < ActiveRecord::Base
435
+ # has_many :countries
436
+ # # attribs: id, name
437
+ # end
438
+ #
439
+ # class Country < ActiveRecord::Base
440
+ # belongs_to :continent
441
+ # # attribs: id, name, continent_id
442
+ # end
443
+ #
444
+ # Sample usage:
445
+ # option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3)
446
+ #
447
+ # Possible output:
448
+ # <optgroup label="Africa">
449
+ # <option value="1">Egypt</option>
450
+ # <option value="4">Rwanda</option>
451
+ # ...
452
+ # </optgroup>
453
+ # <optgroup label="Asia">
454
+ # <option value="3" selected="selected">China</option>
455
+ # <option value="12">India</option>
456
+ # <option value="5">Japan</option>
457
+ # ...
458
+ # </optgroup>
459
+ #
460
+ # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
461
+ # wrap the output in an appropriate <tt><select></tt> tag.
462
+ def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
463
+ collection.map do |group|
464
+ option_tags = options_from_collection_for_select(
465
+ value_for_collection(group, group_method), option_key_method, option_value_method, selected_key)
466
+
467
+ content_tag("optgroup", option_tags, label: value_for_collection(group, group_label_method))
468
+ end.join.html_safe
469
+ end
470
+
471
+ # Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
472
+ # wraps them with <tt><optgroup></tt> tags:
473
+ #
474
+ # grouped_options = [
475
+ # ['North America',
476
+ # [['United States','US'],'Canada']],
477
+ # ['Europe',
478
+ # ['Denmark','Germany','France']]
479
+ # ]
480
+ # grouped_options_for_select(grouped_options)
481
+ #
482
+ # grouped_options = {
483
+ # 'North America' => [['United States','US'], 'Canada'],
484
+ # 'Europe' => ['Denmark','Germany','France']
485
+ # }
486
+ # grouped_options_for_select(grouped_options)
487
+ #
488
+ # Possible output:
489
+ # <optgroup label="North America">
490
+ # <option value="US">United States</option>
491
+ # <option value="Canada">Canada</option>
492
+ # </optgroup>
493
+ # <optgroup label="Europe">
494
+ # <option value="Denmark">Denmark</option>
495
+ # <option value="Germany">Germany</option>
496
+ # <option value="France">France</option>
497
+ # </optgroup>
498
+ #
499
+ # Parameters:
500
+ # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
501
+ # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
502
+ # nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
503
+ # Ex. ["North America",[["United States","US"],["Canada","CA"]]]
504
+ # An optional third value can be provided as HTML attributes for the <tt>optgroup</tt>.
505
+ # Ex. ["North America",[["United States","US"],["Canada","CA"]], { disabled: "disabled" }]
506
+ # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
507
+ # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
508
+ # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
509
+ #
510
+ # Options:
511
+ # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
512
+ # prepends an option with a generic prompt - "Please select" - or the given prompt string.
513
+ # * <tt>:divider</tt> - the divider for the options groups.
514
+ #
515
+ # grouped_options = [
516
+ # [['United States','US'], 'Canada'],
517
+ # ['Denmark','Germany','France']
518
+ # ]
519
+ # grouped_options_for_select(grouped_options, nil, divider: '---------')
520
+ #
521
+ # Possible output:
522
+ # <optgroup label="---------">
523
+ # <option value="US">United States</option>
524
+ # <option value="Canada">Canada</option>
525
+ # </optgroup>
526
+ # <optgroup label="---------">
527
+ # <option value="Denmark">Denmark</option>
528
+ # <option value="Germany">Germany</option>
529
+ # <option value="France">France</option>
530
+ # </optgroup>
531
+ #
532
+ # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
533
+ # wrap the output in an appropriate <tt><select></tt> tag.
534
+ def grouped_options_for_select(grouped_options, selected_key = nil, options = {})
535
+ prompt = options[:prompt]
536
+ divider = options[:divider]
537
+
538
+ body = "".html_safe
539
+
540
+ if prompt
541
+ body.safe_concat content_tag("option", prompt_text(prompt), value: "")
542
+ end
543
+
544
+ grouped_options.each do |container|
545
+ html_attributes = option_html_attributes(container)
546
+
547
+ if divider
548
+ label = divider
549
+ else
550
+ label, container = container
551
+ end
552
+
553
+ html_attributes = { label: label }.merge!(html_attributes)
554
+ body.safe_concat content_tag("optgroup", options_for_select(container, selected_key), html_attributes)
555
+ end
556
+
557
+ body
558
+ end
559
+
560
+ # Returns a string of option tags for pretty much any time zone in the
561
+ # world. Supply an ActiveSupport::TimeZone name as +selected+ to have it
562
+ # marked as the selected option tag. You can also supply an array of
563
+ # ActiveSupport::TimeZone objects as +priority_zones+, so that they will
564
+ # be listed above the rest of the (long) list. (You can use
565
+ # ActiveSupport::TimeZone.us_zones as a convenience for obtaining a list
566
+ # of the US time zones, or a Regexp to select the zones of your choice)
567
+ #
568
+ # The +selected+ parameter must be either +nil+, or a string that names
569
+ # an ActiveSupport::TimeZone.
570
+ #
571
+ # By default, +model+ is the ActiveSupport::TimeZone constant (which can
572
+ # be obtained in Active Record as a value object). The +model+ parameter
573
+ # must respond to +all+ and return an array of objects that represent time
574
+ # zones; each object must respond to +name+. If a Regexp is given it will
575
+ # attempt to match the zones using <code>match?</code> method.
576
+ #
577
+ # NOTE: Only the option tags are returned, you have to wrap this call in
578
+ # a regular HTML select tag.
579
+ def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
580
+ zone_options = "".html_safe
581
+
582
+ zones = model.all
583
+ convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
584
+
585
+ if priority_zones
586
+ if priority_zones.is_a?(Regexp)
587
+ priority_zones = zones.select { |z| z.match?(priority_zones) }
588
+ end
589
+
590
+ zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
591
+ zone_options.safe_concat content_tag("option", "-------------", value: "", disabled: true)
592
+ zone_options.safe_concat "\n"
593
+
594
+ zones = zones - priority_zones
595
+ end
596
+
597
+ zone_options.safe_concat options_for_select(convert_zones[zones], selected)
598
+ end
599
+
600
+ # Returns a string of option tags for the days of the week.
601
+ #
602
+ # Options:
603
+ # * <tt>:index_as_value</tt> - Defaults to false, set to true to use the indexes from
604
+ # <tt>I18n.translate("date.day_names")</tt> as the values. By default, Sunday is always 0.
605
+ # * <tt>:day_format</tt> - The I18n key of the array to use for the weekday options.
606
+ # Defaults to +:day_names+, set to +:abbr_day_names+ for abbreviations.
607
+ # * <tt>:beginning_of_week</tt> - Defaults to Date.beginning_of_week.
608
+ #
609
+ # NOTE: Only the option tags are returned, you have to wrap this call in
610
+ # a regular HTML select tag.
611
+ def weekday_options_for_select(selected = nil, index_as_value: false, day_format: :day_names, beginning_of_week: Date.beginning_of_week)
612
+ day_names = I18n.translate("date.#{day_format}")
613
+ day_names = day_names.map.with_index.to_a if index_as_value
614
+ day_names = day_names.rotate(Date::DAYS_INTO_WEEK.fetch(beginning_of_week))
615
+
616
+ options_for_select(day_names, selected)
617
+ end
618
+
619
+ # Returns radio button tags for the collection of existing return values
620
+ # of +method+ for +object+'s class. The value returned from calling
621
+ # +method+ on the instance +object+ will be selected. If calling +method+
622
+ # returns +nil+, no selection is made.
623
+ #
624
+ # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
625
+ # methods to be called on each member of +collection+. The return values
626
+ # are used as the +value+ attribute and contents of each radio button tag,
627
+ # respectively. They can also be any object that responds to +call+, such
628
+ # as a +proc+, that will be called for each member of the +collection+ to
629
+ # retrieve the value/text.
630
+ #
631
+ # Example object structure for use with this method:
632
+ #
633
+ # class Post < ActiveRecord::Base
634
+ # belongs_to :author
635
+ # end
636
+ #
637
+ # class Author < ActiveRecord::Base
638
+ # has_many :posts
639
+ #
640
+ # def name_with_initial
641
+ # "#{first_name.first}. #{last_name}"
642
+ # end
643
+ # end
644
+ #
645
+ # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
646
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial)
647
+ #
648
+ # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
649
+ # <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" />
650
+ # <label for="post_author_id_1">D. Heinemeier Hansson</label>
651
+ # <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
652
+ # <label for="post_author_id_2">D. Thomas</label>
653
+ # <input id="post_author_id_3" name="post[author_id]" type="radio" value="3" />
654
+ # <label for="post_author_id_3">M. Clark</label>
655
+ #
656
+ # It is also possible to customize the way the elements will be shown by
657
+ # giving a block to the method:
658
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
659
+ # b.label { b.radio_button }
660
+ # end
661
+ #
662
+ # The argument passed to the block is a special kind of builder for this
663
+ # collection, which has the ability to generate the label and radio button
664
+ # for the current item in the collection, with proper text and value.
665
+ # Using it, you can change the label and radio button display order or
666
+ # even use the label as wrapper, as in the example above.
667
+ #
668
+ # The builder methods <tt>label</tt> and <tt>radio_button</tt> also accept
669
+ # extra HTML options:
670
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
671
+ # b.label(class: "radio_button") { b.radio_button(class: "radio_button") }
672
+ # end
673
+ #
674
+ # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
675
+ # <tt>value</tt>, which are the current item being rendered, its text and value methods,
676
+ # respectively. You can use them like this:
677
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
678
+ # b.label(:"data-value" => b.value) { b.radio_button + b.text }
679
+ # end
680
+ #
681
+ # ==== Gotcha
682
+ #
683
+ # The HTML specification says when nothing is selected on a collection of radio buttons
684
+ # web browsers do not send any value to server.
685
+ # Unfortunately this introduces a gotcha:
686
+ # if a +User+ model has a +category_id+ field and in the form no category is selected, no +category_id+ parameter is sent. So,
687
+ # any strong parameters idiom like:
688
+ #
689
+ # params.expect(user: [...])
690
+ #
691
+ # will raise an error since no <tt>{user: ...}</tt> will be present.
692
+ #
693
+ # To prevent this the helper generates an auxiliary hidden field before
694
+ # every collection of radio buttons. The hidden field has the same name as collection radio button and blank value.
695
+ #
696
+ # In case if you don't want the helper to generate this hidden field you can specify
697
+ # <tt>include_hidden: false</tt> option.
698
+ def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
699
+ Tags::CollectionRadioButtons.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
700
+ end
701
+
702
+ # Returns check box tags for the collection of existing return values of
703
+ # +method+ for +object+'s class. The value returned from calling +method+
704
+ # on the instance +object+ will be selected. If calling +method+ returns
705
+ # +nil+, no selection is made.
706
+ #
707
+ # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
708
+ # methods to be called on each member of +collection+. The return values
709
+ # are used as the +value+ attribute and contents of each check box tag,
710
+ # respectively. They can also be any object that responds to +call+, such
711
+ # as a +proc+, that will be called for each member of the +collection+ to
712
+ # retrieve the value/text.
713
+ #
714
+ # Example object structure for use with this method:
715
+ # class Post < ActiveRecord::Base
716
+ # has_and_belongs_to_many :authors
717
+ # end
718
+ # class Author < ActiveRecord::Base
719
+ # has_and_belongs_to_many :posts
720
+ # def name_with_initial
721
+ # "#{first_name.first}. #{last_name}"
722
+ # end
723
+ # end
724
+ #
725
+ # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
726
+ # collection_checkboxes(:post, :author_ids, Author.all, :id, :name_with_initial)
727
+ #
728
+ # If <tt>@post.author_ids</tt> is already <tt>[1]</tt>, this would return:
729
+ # <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" />
730
+ # <label for="post_author_ids_1">D. Heinemeier Hansson</label>
731
+ # <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
732
+ # <label for="post_author_ids_2">D. Thomas</label>
733
+ # <input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" />
734
+ # <label for="post_author_ids_3">M. Clark</label>
735
+ # <input name="post[author_ids][]" type="hidden" value="" />
736
+ #
737
+ # It is also possible to customize the way the elements will be shown by
738
+ # giving a block to the method:
739
+ # collection_checkboxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
740
+ # b.label { b.checkbox }
741
+ # end
742
+ #
743
+ # The argument passed to the block is a special kind of builder for this
744
+ # collection, which has the ability to generate the label and check box
745
+ # for the current item in the collection, with proper text and value.
746
+ # Using it, you can change the label and check box display order or even
747
+ # use the label as wrapper, as in the example above.
748
+ #
749
+ # The builder methods <tt>label</tt> and <tt>checkbox</tt> also accept
750
+ # extra HTML options:
751
+ # collection_checkboxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
752
+ # b.label(class: "checkbox") { b.checkbox(class: "checkbox") }
753
+ # end
754
+ #
755
+ # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
756
+ # <tt>value</tt>, which are the current item being rendered, its text and value methods,
757
+ # respectively. You can use them like this:
758
+ # collection_checkboxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
759
+ # b.label(:"data-value" => b.value) { b.checkbox + b.text }
760
+ # end
761
+ #
762
+ # ==== Gotcha
763
+ #
764
+ # When no selection is made for a collection of checkboxes most
765
+ # web browsers will not send any value.
766
+ #
767
+ # For example, if we have a +User+ model with +category_ids+ field and we
768
+ # have the following code in our update action:
769
+ #
770
+ # @user.update(params[:user])
771
+ #
772
+ # If no +category_ids+ are selected then we can safely assume this field
773
+ # will not be updated.
774
+ #
775
+ # This is possible thanks to a hidden field generated by the helper method
776
+ # for every collection of checkboxes.
777
+ # This hidden field is given the same field name as the checkboxes with a
778
+ # blank value.
779
+ #
780
+ # In the rare case you don't want this hidden field, you can pass the
781
+ # <tt>include_hidden: false</tt> option to the helper method.
782
+ def collection_checkboxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
783
+ Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
784
+ end
785
+ alias_method :collection_check_boxes, :collection_checkboxes
786
+
787
+ private
788
+ def option_html_attributes(element)
789
+ if Array === element
790
+ element.select { |e| Hash === e }.reduce({}, :merge!)
791
+ else
792
+ {}
793
+ end
794
+ end
795
+
796
+ def option_text_and_value(option)
797
+ # Options are [text, value] pairs or strings used for both.
798
+ if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
799
+ option = option.reject { |e| Hash === e } if Array === option
800
+ [option.first, option.last]
801
+ else
802
+ [option, option]
803
+ end
804
+ end
805
+
806
+ def option_value_selected?(value, selected)
807
+ Array(selected).include? value
808
+ end
809
+
810
+ def extract_selected_and_disabled(selected)
811
+ if selected.is_a?(Proc)
812
+ [selected, nil]
813
+ else
814
+ selected = Array.wrap(selected)
815
+ options = selected.extract_options!.symbolize_keys
816
+ selected_items = options.fetch(:selected, selected)
817
+ [selected_items, options[:disabled]]
818
+ end
819
+ end
820
+
821
+ def extract_values_from_collection(collection, value_method, selected)
822
+ if selected.is_a?(Proc)
823
+ collection.filter_map do |element|
824
+ element.public_send(value_method) if selected.call(element)
825
+ end
826
+ else
827
+ selected
828
+ end
829
+ end
830
+
831
+ def value_for_collection(item, value)
832
+ value.respond_to?(:call) ? value.call(item) : item.public_send(value)
833
+ end
834
+
835
+ def prompt_text(prompt)
836
+ prompt.kind_of?(String) ? prompt : I18n.translate("helpers.select.prompt", default: "Please select")
837
+ end
838
+ end
839
+
840
+ class FormBuilder
841
+ # Wraps ActionView::Helpers::FormOptionsHelper#select for form builders:
842
+ #
843
+ # <%= form_for @post do |f| %>
844
+ # <%= f.select :person_id, Person.all.collect { |p| [ p.name, p.id ] }, include_blank: true %>
845
+ # <%= f.submit %>
846
+ # <% end %>
847
+ #
848
+ # Please refer to the documentation of the base helper for details.
849
+ def select(method, choices = nil, options = {}, html_options = {}, &block)
850
+ @template.select(@object_name, method, choices, objectify_options(options), @default_html_options.merge(html_options), &block)
851
+ end
852
+
853
+ # Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders:
854
+ #
855
+ # <%= form_for @post do |f| %>
856
+ # <%= f.collection_select :person_id, Author.all, :id, :name_with_initial, prompt: true %>
857
+ # <%= f.submit %>
858
+ # <% end %>
859
+ #
860
+ # Please refer to the documentation of the base helper for details.
861
+ def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
862
+ @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options))
863
+ end
864
+
865
+ # Wraps ActionView::Helpers::FormOptionsHelper#grouped_collection_select for form builders:
866
+ #
867
+ # <%= form_for @city do |f| %>
868
+ # <%= f.grouped_collection_select :country_id, @continents, :countries, :name, :id, :name %>
869
+ # <%= f.submit %>
870
+ # <% end %>
871
+ #
872
+ # Please refer to the documentation of the base helper for details.
873
+ def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
874
+ @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_html_options.merge(html_options))
875
+ end
876
+
877
+ # Wraps ActionView::Helpers::FormOptionsHelper#time_zone_select for form builders:
878
+ #
879
+ # <%= form_for @user do |f| %>
880
+ # <%= f.time_zone_select :time_zone, nil, include_blank: true %>
881
+ # <%= f.submit %>
882
+ # <% end %>
883
+ #
884
+ # Please refer to the documentation of the base helper for details.
885
+ def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
886
+ @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_html_options.merge(html_options))
887
+ end
888
+
889
+ # Wraps ActionView::Helpers::FormOptionsHelper#weekday_select for form builders:
890
+ #
891
+ # <%= form_for @user do |f| %>
892
+ # <%= f.weekday_select :weekday, include_blank: true %>
893
+ # <%= f.submit %>
894
+ # <% end %>
895
+ #
896
+ # Please refer to the documentation of the base helper for details.
897
+ def weekday_select(method, options = {}, html_options = {})
898
+ @template.weekday_select(@object_name, method, objectify_options(options), @default_html_options.merge(html_options))
899
+ end
900
+
901
+ # Wraps ActionView::Helpers::FormOptionsHelper#collection_checkboxes for form builders:
902
+ #
903
+ # <%= form_for @post do |f| %>
904
+ # <%= f.collection_checkboxes :author_ids, Author.all, :id, :name_with_initial %>
905
+ # <%= f.submit %>
906
+ # <% end %>
907
+ #
908
+ # Please refer to the documentation of the base helper for details.
909
+ def collection_checkboxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
910
+ @template.collection_checkboxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block)
911
+ end
912
+ alias_method :collection_check_boxes, :collection_checkboxes
913
+
914
+ # Wraps ActionView::Helpers::FormOptionsHelper#collection_radio_buttons for form builders:
915
+ #
916
+ # <%= form_for @post do |f| %>
917
+ # <%= f.collection_radio_buttons :author_id, Author.all, :id, :name_with_initial %>
918
+ # <%= f.submit %>
919
+ # <% end %>
920
+ #
921
+ # Please refer to the documentation of the base helper for details.
922
+ def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
923
+ @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block)
924
+ end
925
+ end
926
+ end
927
+ end