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
@@ -31,26 +31,33 @@ module ActionView
31
31
  # stylesheet_link_tag("application")
32
32
  # # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" />
33
33
  #
34
- # Browsers typically open at most two simultaneous connections to a single
35
- # host, which means your assets often have to wait for other assets to finish
36
- # downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the
37
- # +asset_host+. For example, "assets%d.example.com". If that wildcard is
38
- # present Rails distributes asset requests among the corresponding four hosts
39
- # "assets0.example.com", ..., "assets3.example.com". With this trick browsers
40
- # will open eight simultaneous connections rather than two.
34
+ # Browsers open a limited number of simultaneous connections to a single
35
+ # host. The exact number varies by browser and version. This limit may cause
36
+ # some asset downloads to wait for previous assets to finish before they can
37
+ # begin. You can use the <tt>%d</tt> wildcard in the +asset_host+ to
38
+ # distribute the requests over four hosts. For example,
39
+ # <tt>assets%d.example.com<tt> will spread the asset requests over
40
+ # "assets0.example.com", ..., "assets3.example.com".
41
41
  #
42
42
  # image_tag("rails.png")
43
43
  # # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" />
44
44
  # stylesheet_link_tag("application")
45
45
  # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
46
46
  #
47
- # To do this, you can either setup four actual hosts, or you can use wildcard
48
- # DNS to CNAME the wildcard to a single asset host. You can read more about
49
- # setting up your DNS CNAME records from your ISP.
47
+ # This may improve the asset loading performance of your application.
48
+ # It is also possible the combination of additional connection overhead
49
+ # (DNS, SSL) and the overall browser connection limits may result in this
50
+ # solution being slower. You should be sure to measure your actual
51
+ # performance across targeted browsers both before and after this change.
52
+ #
53
+ # To implement the corresponding hosts you can either setup four actual
54
+ # hosts or use wildcard DNS to CNAME the wildcard to a single asset host.
55
+ # You can read more about setting up your DNS CNAME records from your ISP.
50
56
  #
51
57
  # Note: This is purely a browser performance optimization and is not meant
52
58
  # for server load balancing. See http://www.die.net/musings/page_load_time/
53
- # for background.
59
+ # for background and http://www.browserscope.org/?category=network for
60
+ # connection limit data.
54
61
  #
55
62
  # Alternatively, you can exert more control over the asset host by setting
56
63
  # +asset_host+ to a proc like this:
@@ -121,11 +128,13 @@ module ActionView
121
128
  # asset_path "application", type: :stylesheet # => /assets/application.css
122
129
  # asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
123
130
  def asset_path(source, options = {})
131
+ raise ArgumentError, "nil is not a valid asset source" if source.nil?
132
+
124
133
  source = source.to_s
125
134
  return "" unless source.present?
126
135
  return source if source =~ URI_REGEXP
127
136
 
128
- tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '')
137
+ tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, ''.freeze)
129
138
 
130
139
  if extname = compute_asset_extname(source, options)
131
140
  source = "#{source}#{extname}"
@@ -248,6 +257,11 @@ module ActionView
248
257
 
249
258
  # Computes the full URL to a JavaScript asset in the public javascripts directory.
250
259
  # This will use +javascript_path+ internally, so most of their behaviors will be the same.
260
+ # Since +javascript_url+ is based on +asset_url+ method you can set :host options. If :host
261
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
262
+ #
263
+ # javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/dir/xmlhr.js
264
+ #
251
265
  def javascript_url(source, options = {})
252
266
  url_to_asset(source, {type: :javascript}.merge!(options))
253
267
  end
@@ -270,6 +284,11 @@ module ActionView
270
284
 
271
285
  # Computes the full URL to a stylesheet asset in the public stylesheets directory.
272
286
  # This will use +stylesheet_path+ internally, so most of their behaviors will be the same.
287
+ # Since +stylesheet_url+ is based on +asset_url+ method you can set :host options. If :host
288
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
289
+ #
290
+ # stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/css/style.css
291
+ #
273
292
  def stylesheet_url(source, options = {})
274
293
  url_to_asset(source, {type: :stylesheet}.merge!(options))
275
294
  end
@@ -295,6 +314,11 @@ module ActionView
295
314
 
296
315
  # Computes the full URL to an image asset.
297
316
  # This will use +image_path+ internally, so most of their behaviors will be the same.
317
+ # Since +image_url+ is based on +asset_url+ method you can set :host options. If :host
318
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
319
+ #
320
+ # image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/edit.png
321
+ #
298
322
  def image_url(source, options = {})
299
323
  url_to_asset(source, {type: :image}.merge!(options))
300
324
  end
@@ -316,6 +340,11 @@ module ActionView
316
340
 
317
341
  # Computes the full URL to a video asset in the public videos directory.
318
342
  # This will use +video_path+ internally, so most of their behaviors will be the same.
343
+ # Since +video_url+ is based on +asset_url+ method you can set :host options. If :host
344
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
345
+ #
346
+ # video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/hd.avi
347
+ #
319
348
  def video_url(source, options = {})
320
349
  url_to_asset(source, {type: :video}.merge!(options))
321
350
  end
@@ -337,6 +366,11 @@ module ActionView
337
366
 
338
367
  # Computes the full URL to an audio asset in the public audios directory.
339
368
  # This will use +audio_path+ internally, so most of their behaviors will be the same.
369
+ # Since +audio_url+ is based on +asset_url+ method you can set :host options. If :host
370
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
371
+ #
372
+ # audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/horse.wav
373
+ #
340
374
  def audio_url(source, options = {})
341
375
  url_to_asset(source, {type: :audio}.merge!(options))
342
376
  end
@@ -357,6 +391,11 @@ module ActionView
357
391
 
358
392
  # Computes the full URL to a font asset.
359
393
  # This will use +font_path+ internally, so most of their behaviors will be the same.
394
+ # Since +font_url+ is based on +asset_url+ method you can set :host options. If :host
395
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
396
+ #
397
+ # font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/font.ttf
398
+ #
360
399
  def font_url(source, options = {})
361
400
  url_to_asset(source, {type: :font}.merge!(options))
362
401
  end
@@ -16,7 +16,7 @@ module ActionView
16
16
  # end
17
17
  #
18
18
  # app/controllers/posts_controller.rb:
19
- # class PostsController < ApplicationController::Base
19
+ # class PostsController < ApplicationController
20
20
  # # GET /posts.html
21
21
  # # GET /posts.atom
22
22
  # def index
@@ -51,7 +51,7 @@ module ActionView
51
51
  # * <tt>:language</tt>: Defaults to "en-US".
52
52
  # * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
53
53
  # * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
54
- # * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}"
54
+ # * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case.
55
55
  # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
56
56
  # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
57
57
  # 2005 is used (as an "I don't care" value).
@@ -132,7 +132,7 @@ module ActionView
132
132
  end
133
133
 
134
134
  private
135
- # Delegate to xml builder, first wrapping the element in a xhtml
135
+ # Delegate to xml builder, first wrapping the element in an xhtml
136
136
  # namespaced div element if the method and arguments indicate
137
137
  # that an xhtml_block? is desired.
138
138
  def method_missing(method, *arguments, &block)
@@ -174,7 +174,7 @@ module ActionView
174
174
  #
175
175
  # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
176
176
  # * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
177
- # * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
177
+ # * <tt>:url</tt>: The URL for this entry or false or nil for not having a link tag. Defaults to the polymorphic_url for the record.
178
178
  # * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
179
179
  # * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
180
180
  def entry(record, options = {})
@@ -191,7 +191,8 @@ module ActionView
191
191
 
192
192
  type = options.fetch(:type, 'text/html')
193
193
 
194
- @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record))
194
+ url = options.fetch(:url) { @view.polymorphic_url(record) }
195
+ @xml.link(:rel => 'alternate', :type => type, :href => url) if url
195
196
 
196
197
  yield AtomBuilder.new(@xml)
197
198
  end
@@ -39,7 +39,7 @@ module ActionView
39
39
  # This will include both records as part of the cache key and updating either of them will
40
40
  # expire the cache.
41
41
  #
42
- # ==== Template digest
42
+ # ==== \Template digest
43
43
  #
44
44
  # The template digest that's added to the cache key is computed by taking an md5 of the
45
45
  # contents of the entire template file. This ensures that your caches will automatically
@@ -75,7 +75,8 @@ module ActionView
75
75
  # render(topics) => render("topics/topic")
76
76
  # render(message.topics) => render("topics/topic")
77
77
  #
78
- # It's not possible to derive all render calls like that, though. Here are a few examples of things that can't be derived:
78
+ # It's not possible to derive all render calls like that, though.
79
+ # Here are a few examples of things that can't be derived:
79
80
  #
80
81
  # render group_of_attachments
81
82
  # render @project.documents.where(published: true).order('created_at')
@@ -97,22 +98,61 @@ module ActionView
97
98
  # <%# Template Dependency: todolists/todolist %>
98
99
  # <%= render_sortable_todolists @project.todolists %>
99
100
  #
100
- # The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so.
101
+ # In some cases, like a single table inheritance setup, you might have
102
+ # a bunch of explicit dependencies. Instead of writing every template out,
103
+ # you can use a wildcard to match any template in a directory:
104
+ #
105
+ # <%# Template Dependency: events/* %>
106
+ # <%= render_categorizable_events @person.events %>
107
+ #
108
+ # This marks every template in the directory as a dependency. To find those
109
+ # templates, the wildcard path must be absolutely defined from app/views or paths
110
+ # otherwise added with +prepend_view_path+ or +append_view_path+.
111
+ # This way the wildcard for `app/views/recordings/events` would be `recordings/events/*` etc.
112
+ #
113
+ # The pattern used to match explicit dependencies is <tt>/# Template Dependency: (\S+)/</tt>,
114
+ # so it's important that you type it out just so.
101
115
  # You can only declare one template dependency per line.
102
116
  #
103
117
  # === External dependencies
104
118
  #
105
- # If you use a helper method, for example, inside of a cached block and you then update that helper,
106
- # you'll have to bump the cache as well. It doesn't really matter how you do it, but the md5 of the template file
119
+ # If you use a helper method, for example, inside a cached block and
120
+ # you then update that helper, you'll have to bump the cache as well.
121
+ # It doesn't really matter how you do it, but the md5 of the template file
107
122
  # must change. One recommendation is to simply be explicit in a comment, like:
108
123
  #
109
124
  # <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
110
125
  # <%= some_helper_method(person) %>
111
126
  #
112
- # Now all you'll have to do is change that timestamp when the helper method changes.
113
- def cache(name = {}, options = nil, &block)
127
+ # Now all you have to do is change that timestamp when the helper method changes.
128
+ #
129
+ # === Collection Caching
130
+ #
131
+ # When rendering a collection of objects that each use the same partial, a `cached`
132
+ # option can be passed.
133
+ # For collections rendered such:
134
+ #
135
+ # <%= render partial: 'notifications/notification', collection: @notifications, cached: true %>
136
+ #
137
+ # The `cached: true` will make Action View's rendering read several templates
138
+ # from cache at once instead of one call per template.
139
+ #
140
+ # Templates in the collection not already cached are written to cache.
141
+ #
142
+ # Works great alongside individual template fragment caching.
143
+ # For instance if the template the collection renders is cached like:
144
+ #
145
+ # # notifications/_notification.html.erb
146
+ # <% cache notification do %>
147
+ # <%# ... %>
148
+ # <% end %>
149
+ #
150
+ # Any collection renders will find those cached templates when attempting
151
+ # to read multiple templates at once.
152
+ def cache(name = {}, options = {}, &block)
114
153
  if controller.respond_to?(:perform_caching) && controller.perform_caching
115
- safe_concat(fragment_for(cache_fragment_name(name, options), options, &block))
154
+ name_options = options.slice(:skip_digest, :virtual_path)
155
+ safe_concat(fragment_for(cache_fragment_name(name, name_options), options, &block))
116
156
  else
117
157
  yield
118
158
  end
@@ -126,7 +166,7 @@ module ActionView
126
166
  # <b>All the topics on this project</b>
127
167
  # <%= render project.topics %>
128
168
  # <% end %>
129
- def cache_if(condition, name = {}, options = nil, &block)
169
+ def cache_if(condition, name = {}, options = {}, &block)
130
170
  if condition
131
171
  cache(name, options, &block)
132
172
  else
@@ -142,33 +182,34 @@ module ActionView
142
182
  # <b>All the topics on this project</b>
143
183
  # <%= render project.topics %>
144
184
  # <% end %>
145
- def cache_unless(condition, name = {}, options = nil, &block)
185
+ def cache_unless(condition, name = {}, options = {}, &block)
146
186
  cache_if !condition, name, options, &block
147
187
  end
148
188
 
149
189
  # This helper returns the name of a cache key for a given fragment cache
150
- # call. By supplying skip_digest: true to cache, the digestion of cache
190
+ # call. By supplying +skip_digest:+ true to cache, the digestion of cache
151
191
  # fragments can be manually bypassed. This is useful when cache fragments
152
192
  # cannot be manually expired unless you know the exact key which is the
153
193
  # case when using memcached.
154
- def cache_fragment_name(name = {}, options = nil)
155
- skip_digest = options && options[:skip_digest]
156
-
194
+ #
195
+ # The digest will be generated using +virtual_path:+ if it is provided.
196
+ #
197
+ def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil)
157
198
  if skip_digest
158
199
  name
159
200
  else
160
- fragment_name_with_digest(name)
201
+ fragment_name_with_digest(name, virtual_path)
161
202
  end
162
203
  end
163
204
 
164
205
  private
165
206
 
166
- def fragment_name_with_digest(name) #:nodoc:
167
- if @virtual_path
168
- names = Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name)
169
- digest = Digestor.digest name: @virtual_path, finder: lookup_context, dependencies: view_cache_dependencies
170
-
171
- [ *names, digest ]
207
+ def fragment_name_with_digest(name, virtual_path) #:nodoc:
208
+ virtual_path ||= @virtual_path
209
+ if virtual_path
210
+ name = controller.url_for(name).split("://").last if name.is_a?(Hash)
211
+ digest = Digestor.digest name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies
212
+ [ name, digest ]
172
213
  else
173
214
  name
174
215
  end
@@ -9,8 +9,8 @@ module ActionView
9
9
  # It provides a method to capture blocks into variables through capture and
10
10
  # a way to capture a block of markup for use in a layout through content_for.
11
11
  module CaptureHelper
12
- # The capture method allows you to extract part of a template into a
13
- # variable. You can then use this variable anywhere in your templates or layout.
12
+ # The capture method extracts part of a template as a String object.
13
+ # You can then use this object anywhere in your templates, layout, or helpers.
14
14
  #
15
15
  # The capture method can be used in ERB templates...
16
16
  #
@@ -31,7 +31,8 @@ module ActionView
31
31
  # <head><title><%= @greeting %></title></head>
32
32
  # <body>
33
33
  # <b><%= @greeting %></b>
34
- # </body></html>
34
+ # </body>
35
+ # </html>
35
36
  #
36
37
  def capture(*args)
37
38
  value = nil
@@ -114,7 +115,7 @@ module ActionView
114
115
  # <li><%= link_to 'Home', action: 'index' %></li>
115
116
  # <% end %>
116
117
  #
117
- # And in other place:
118
+ # And in another place:
118
119
  #
119
120
  # <% content_for :navigation do %>
120
121
  # <li><%= link_to 'Login', action: 'login' %></li>
@@ -7,19 +7,28 @@ module ActionView
7
7
  module ControllerHelper #:nodoc:
8
8
  attr_internal :controller, :request
9
9
 
10
- delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,
11
- :flash, :action_name, :controller_name, :controller_path, :to => :controller
10
+ CONTROLLER_DELEGATES = [:request_forgery_protection_token, :params,
11
+ :session, :cookies, :response, :headers, :flash, :action_name,
12
+ :controller_name, :controller_path]
13
+
14
+ delegate *CONTROLLER_DELEGATES, to: :controller
12
15
 
13
16
  def assign_controller(controller)
14
17
  if @_controller = controller
15
18
  @_request = controller.request if controller.respond_to?(:request)
16
19
  @_config = controller.config.inheritable_copy if controller.respond_to?(:config)
20
+ @_default_form_builder = controller.default_form_builder if controller.respond_to?(:default_form_builder)
17
21
  end
18
22
  end
19
23
 
20
24
  def logger
21
25
  controller.logger if controller.respond_to?(:logger)
22
26
  end
27
+
28
+ def respond_to?(method_name, include_private = false)
29
+ return controller.respond_to?(method_name) if CONTROLLER_DELEGATES.include?(method_name.to_sym)
30
+ super
31
+ end
23
32
  end
24
33
  end
25
34
  end
@@ -3,6 +3,7 @@ require 'action_view/helpers/tag_helper'
3
3
  require 'active_support/core_ext/array/extract_options'
4
4
  require 'active_support/core_ext/date/conversions'
5
5
  require 'active_support/core_ext/hash/slice'
6
+ require 'active_support/core_ext/object/acts_like'
6
7
  require 'active_support/core_ext/object/with_options'
7
8
 
8
9
  module ActionView
@@ -68,6 +69,27 @@ module ActionView
68
69
  # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years
69
70
  # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years
70
71
  # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
72
+ #
73
+ # With the <tt>scope</tt> option, you can define a custom scope for Rails
74
+ # to look up the translation.
75
+ #
76
+ # For example you can define the following in your locale (e.g. en.yml).
77
+ #
78
+ # datetime:
79
+ # distance_in_words:
80
+ # short:
81
+ # about_x_hours:
82
+ # one: 'an hour'
83
+ # other: '%{count} hours'
84
+ #
85
+ # See https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en.yml
86
+ # for more examples.
87
+ #
88
+ # Which will then result in the following:
89
+ #
90
+ # from_time = Time.now
91
+ # distance_of_time_in_words(from_time, from_time + 50.minutes, scope: 'datetime.distance_in_words.short') # => "an hour"
92
+ # distance_of_time_in_words(from_time, from_time + 3.hours, scope: 'datetime.distance_in_words.short') # => "3 hours"
71
93
  def distance_of_time_in_words(from_time, to_time = 0, options = {})
72
94
  options = {
73
95
  scope: :'datetime.distance_in_words'
@@ -177,6 +199,8 @@ module ActionView
177
199
  # and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example.
178
200
  # See <tt>Kernel.sprintf</tt> for documentation on format sequences.
179
201
  # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
202
+ # * <tt>:time_separator</tt> - Specifies a string to separate the time fields. Default is "" (i.e. nothing).
203
+ # * <tt>:datetime_separator</tt>- Specifies a string to separate the date and time fields. Default is "" (i.e. nothing).
180
204
  # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt> if
181
205
  # you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
182
206
  # the current selected year minus 5.
@@ -203,8 +227,11 @@ module ActionView
203
227
  # for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>.
204
228
  # Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds)
205
229
  # or the given prompt string.
206
- # * <tt>:with_css_classes</tt> - Set to true if you want assign different styles for 'select' tags. This option
207
- # automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second' for your 'select' tags.
230
+ # * <tt>:with_css_classes</tt> - Set to true or a hash of strings. Use true if you want to assign generic styles for
231
+ # select tags. This automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second'. A hash of
232
+ # strings for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, <tt>:second</tt>
233
+ # will extend the select type with the given value. Use +html_options+ to modify every select tag in the set.
234
+ # * <tt>:use_hidden</tt> - Set to true if you only want to generate hidden input tags.
208
235
  #
209
236
  # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
210
237
  #
@@ -462,7 +489,7 @@ module ActionView
462
489
  # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
463
490
  # Override the field name using the <tt>:field_name</tt> option, 'second' by default.
464
491
  #
465
- # my_time = Time.now + 16.minutes
492
+ # my_time = Time.now + 16.seconds
466
493
  #
467
494
  # # Generates a select field for seconds that defaults to the seconds for the time in my_time.
468
495
  # select_second(my_time)
@@ -486,7 +513,7 @@ module ActionView
486
513
  # selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
487
514
  # Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
488
515
  #
489
- # my_time = Time.now + 6.hours
516
+ # my_time = Time.now + 10.minutes
490
517
  #
491
518
  # # Generates a select field for minutes that defaults to the minutes for the time in my_time.
492
519
  # select_minute(my_time)
@@ -658,7 +685,7 @@ module ActionView
658
685
  content = args.first || I18n.l(date_or_time, :format => format)
659
686
  datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601
660
687
 
661
- content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block)
688
+ content_tag("time".freeze, content, options.reverse_merge(:datetime => datetime), &block)
662
689
  end
663
690
  end
664
691
 
@@ -786,7 +813,7 @@ module ActionView
786
813
  1.upto(12) do |month_number|
787
814
  options = { :value => month_number }
788
815
  options[:selected] = "selected" if month == month_number
789
- month_options << content_tag(:option, month_name(month_number), options) + "\n"
816
+ month_options << content_tag("option".freeze, month_name(month_number), options) + "\n"
790
817
  end
791
818
  build_select(:month, month_options.join)
792
819
  end
@@ -821,7 +848,12 @@ module ActionView
821
848
  private
822
849
  %w( sec min hour day month year ).each do |method|
823
850
  define_method(method) do
824
- @datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime
851
+ case @datetime
852
+ when Hash then @datetime[method.to_sym]
853
+ when Numeric then @datetime
854
+ when nil then nil
855
+ else @datetime.send(method)
856
+ end
825
857
  end
826
858
  end
827
859
 
@@ -898,7 +930,7 @@ module ActionView
898
930
 
899
931
  def translated_date_order
900
932
  date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => [])
901
- date_order = date_order.map { |element| element.to_sym }
933
+ date_order = date_order.map(&:to_sym)
902
934
 
903
935
  forbidden_elements = date_order - [:year, :month, :day]
904
936
  if forbidden_elements.any?
@@ -948,7 +980,7 @@ module ActionView
948
980
  tag_options[:selected] = "selected" if selected == i
949
981
  text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
950
982
  text = options[:ampm] ? AMPM_TRANSLATION[i] : text
951
- select_options << content_tag(:option, text, tag_options)
983
+ select_options << content_tag("option".freeze, text, tag_options)
952
984
  end
953
985
 
954
986
  (select_options.join("\n") + "\n").html_safe
@@ -965,14 +997,28 @@ module ActionView
965
997
  :name => input_name_from_type(type)
966
998
  }.merge!(@html_options)
967
999
  select_options[:disabled] = 'disabled' if @options[:disabled]
968
- select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes]
1000
+ select_options[:class] = css_class_attribute(type, select_options[:class], @options[:with_css_classes]) if @options[:with_css_classes]
969
1001
 
970
1002
  select_html = "\n"
971
- select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
1003
+ select_html << content_tag("option".freeze, '', :value => '') + "\n" if @options[:include_blank]
972
1004
  select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
973
1005
  select_html << select_options_as_html
974
1006
 
975
- (content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe
1007
+ (content_tag("select".freeze, select_html.html_safe, select_options) + "\n").html_safe
1008
+ end
1009
+
1010
+ # Builds the css class value for the select element
1011
+ # css_class_attribute(:year, 'date optional', { year: 'my-year' })
1012
+ # => "date optional my-year"
1013
+ def css_class_attribute(type, html_options_class, options) # :nodoc:
1014
+ css_class = case options
1015
+ when Hash
1016
+ options[type.to_sym]
1017
+ else
1018
+ type
1019
+ end
1020
+
1021
+ [html_options_class, css_class].compact.join(' ')
976
1022
  end
977
1023
 
978
1024
  # Builds a prompt option tag with supplied options or from default options.
@@ -989,7 +1035,7 @@ module ActionView
989
1035
  I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale])
990
1036
  end
991
1037
 
992
- prompt ? content_tag(:option, prompt, :value => '') : ''
1038
+ prompt ? content_tag("option".freeze, prompt, :value => '') : ''
993
1039
  end
994
1040
 
995
1041
  # Builds hidden input tag for date part and value.