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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +136 -255
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/action_view.rb +1 -1
- data/lib/action_view/base.rb +14 -2
- data/lib/action_view/dependency_tracker.rb +46 -15
- data/lib/action_view/digestor.rb +13 -9
- data/lib/action_view/flows.rb +1 -1
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/asset_tag_helper.rb +15 -5
- data/lib/action_view/helpers/asset_url_helper.rb +51 -12
- data/lib/action_view/helpers/atom_feed_helper.rb +5 -4
- data/lib/action_view/helpers/cache_helper.rb +75 -20
- data/lib/action_view/helpers/capture_helper.rb +3 -2
- data/lib/action_view/helpers/controller_helper.rb +1 -0
- data/lib/action_view/helpers/date_helper.rb +39 -10
- data/lib/action_view/helpers/debug_helper.rb +1 -1
- data/lib/action_view/helpers/form_helper.rb +81 -35
- data/lib/action_view/helpers/form_options_helper.rb +74 -35
- data/lib/action_view/helpers/form_tag_helper.rb +46 -19
- data/lib/action_view/helpers/javascript_helper.rb +4 -4
- data/lib/action_view/helpers/number_helper.rb +10 -12
- data/lib/action_view/helpers/record_tag_helper.rb +12 -99
- data/lib/action_view/helpers/rendering_helper.rb +2 -2
- data/lib/action_view/helpers/sanitize_helper.rb +1 -2
- data/lib/action_view/helpers/tag_helper.rb +20 -13
- data/lib/action_view/helpers/tags/base.rb +33 -28
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +2 -30
- data/lib/action_view/helpers/tags/collection_helpers.rb +28 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -9
- data/lib/action_view/helpers/tags/file_field.rb +15 -0
- data/lib/action_view/helpers/tags/label.rb +1 -1
- data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
- data/lib/action_view/helpers/tags/search_field.rb +12 -9
- data/lib/action_view/helpers/tags/text_field.rb +0 -1
- data/lib/action_view/helpers/tags/translator.rb +1 -1
- data/lib/action_view/helpers/text_helper.rb +25 -9
- data/lib/action_view/helpers/translation_helper.rb +56 -26
- data/lib/action_view/helpers/url_helper.rb +40 -65
- data/lib/action_view/layouts.rb +11 -10
- data/lib/action_view/lookup_context.rb +14 -40
- data/lib/action_view/model_naming.rb +1 -1
- data/lib/action_view/path_set.rb +15 -18
- data/lib/action_view/railtie.rb +20 -3
- data/lib/action_view/record_identifier.rb +44 -19
- data/lib/action_view/renderer/abstract_renderer.rb +1 -1
- data/lib/action_view/renderer/partial_renderer.rb +27 -26
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +70 -0
- data/lib/action_view/renderer/renderer.rb +2 -6
- data/lib/action_view/renderer/streaming_template_renderer.rb +1 -1
- data/lib/action_view/renderer/template_renderer.rb +12 -11
- data/lib/action_view/rendering.rb +8 -5
- data/lib/action_view/routing_url_for.rb +18 -6
- data/lib/action_view/template.rb +50 -13
- data/lib/action_view/template/error.rb +14 -7
- data/lib/action_view/template/handlers.rb +3 -3
- data/lib/action_view/template/handlers/erb.rb +25 -0
- data/lib/action_view/template/handlers/raw.rb +1 -1
- data/lib/action_view/template/resolver.rb +36 -58
- data/lib/action_view/template/types.rb +1 -1
- data/lib/action_view/test_case.rb +13 -8
- data/lib/action_view/testing/resolvers.rb +3 -4
- data/lib/action_view/view_paths.rb +6 -22
- 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
|
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
|
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
|
-
|
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.
|
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
|
-
#
|
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
|
106
|
-
# you'll have to bump the cache as well.
|
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
|
113
|
-
|
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 =
|
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 =
|
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
|
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
|
-
|
155
|
-
|
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
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
[
|
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
|
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
|
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.
|
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 +
|
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(
|
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(
|
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
|
-
|
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
|
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(
|
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(
|
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(
|
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(
|
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
|
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.
|
70
|
+
# set in the form. That hash is ready to be passed to <tt>Person.new</tt>:
|
70
71
|
#
|
71
|
-
#
|
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, :
|
847
|
-
# # => <input type="file" id="post_image" name="post[image]" multiple="
|
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 "
|
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 =
|
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(:
|
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(:
|
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(:
|
1665
|
+
# label(:cost)
|
1635
1666
|
# # => <label for="post_cost">Total cost</label>
|
1636
1667
|
#
|
1637
|
-
# label(:
|
1668
|
+
# label(:title, "A short title")
|
1638
1669
|
# # => <label for="post_title">A short title</label>
|
1639
1670
|
#
|
1640
|
-
# 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(:
|
1674
|
+
# label(:privacy, "Public Post", value: "public")
|
1644
1675
|
# # => <label for="post_privacy_public">Public Post</label>
|
1645
1676
|
#
|
1646
|
-
# label(:
|
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("
|
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("
|
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
|
-
#
|
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("
|
1722
|
-
# radio_button("
|
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
|
-
#
|
1727
|
-
# radio_button("
|
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
|
-
#
|
1741
|
-
#
|
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
|
-
#
|
1744
|
-
#
|
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
|
-
#
|
1747
|
-
#
|
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
|
-
#
|
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
|
-
#
|
1772
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
-
|
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
|