actionpack 3.0.20 → 3.1.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 actionpack might be problematic. Click here for more details.

Files changed (161) hide show
  1. data/CHANGELOG +88 -142
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +5 -6
  4. data/lib/abstract_controller.rb +1 -0
  5. data/lib/abstract_controller/asset_paths.rb +2 -2
  6. data/lib/abstract_controller/base.rb +24 -19
  7. data/lib/abstract_controller/callbacks.rb +19 -19
  8. data/lib/abstract_controller/helpers.rb +11 -13
  9. data/lib/abstract_controller/layouts.rb +4 -5
  10. data/lib/abstract_controller/railties/routes_helpers.rb +18 -0
  11. data/lib/abstract_controller/rendering.rb +34 -31
  12. data/lib/abstract_controller/url_for.rb +27 -0
  13. data/lib/abstract_controller/view_paths.rb +31 -6
  14. data/lib/action_controller.rb +5 -3
  15. data/lib/action_controller/base.rb +15 -16
  16. data/lib/action_controller/caching.rb +2 -2
  17. data/lib/action_controller/caching/actions.rb +11 -12
  18. data/lib/action_controller/caching/fragments.rb +41 -19
  19. data/lib/action_controller/caching/pages.rb +3 -9
  20. data/lib/action_controller/caching/sweeping.rb +0 -1
  21. data/lib/action_controller/deprecated.rb +1 -1
  22. data/lib/action_controller/log_subscriber.rb +1 -1
  23. data/lib/action_controller/metal.rb +78 -20
  24. data/lib/action_controller/metal/compatibility.rb +0 -9
  25. data/lib/action_controller/metal/conditional_get.rb +9 -9
  26. data/lib/action_controller/metal/data_streaming.rb +145 -0
  27. data/lib/action_controller/metal/force_ssl.rb +35 -0
  28. data/lib/action_controller/metal/head.rb +1 -1
  29. data/lib/action_controller/metal/helpers.rb +37 -44
  30. data/lib/action_controller/metal/hide_actions.rb +2 -3
  31. data/lib/action_controller/metal/http_authentication.rb +41 -38
  32. data/lib/action_controller/metal/implicit_render.rb +13 -13
  33. data/lib/action_controller/metal/instrumentation.rb +2 -2
  34. data/lib/action_controller/metal/mime_responds.rb +25 -19
  35. data/lib/action_controller/metal/params_wrapper.rb +224 -0
  36. data/lib/action_controller/metal/redirecting.rb +6 -2
  37. data/lib/action_controller/metal/renderers.rb +50 -36
  38. data/lib/action_controller/metal/rendering.rb +34 -25
  39. data/lib/action_controller/metal/request_forgery_protection.rb +18 -36
  40. data/lib/action_controller/metal/responder.rb +47 -12
  41. data/lib/action_controller/metal/streaming.rb +244 -138
  42. data/lib/action_controller/metal/testing.rb +0 -9
  43. data/lib/action_controller/metal/url_for.rb +12 -14
  44. data/lib/action_controller/railtie.rb +19 -37
  45. data/lib/action_controller/railties/paths.rb +24 -0
  46. data/lib/action_controller/record_identifier.rb +4 -10
  47. data/lib/action_controller/test_case.rb +36 -19
  48. data/lib/action_controller/vendor/html-scanner/html/node.rb +5 -5
  49. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +3 -3
  50. data/lib/action_controller/vendor/html-scanner/html/selector.rb +2 -0
  51. data/lib/action_dispatch.rb +4 -1
  52. data/lib/action_dispatch/http/cache.rb +5 -32
  53. data/lib/action_dispatch/http/filter_parameters.rb +3 -1
  54. data/lib/action_dispatch/http/mime_negotiation.rb +22 -3
  55. data/lib/action_dispatch/http/mime_type.rb +45 -5
  56. data/lib/action_dispatch/http/rack_cache.rb +58 -0
  57. data/lib/action_dispatch/http/request.rb +27 -41
  58. data/lib/action_dispatch/http/response.rb +56 -54
  59. data/lib/action_dispatch/http/upload.rb +1 -11
  60. data/lib/action_dispatch/http/url.rb +102 -42
  61. data/lib/action_dispatch/middleware/callbacks.rb +8 -25
  62. data/lib/action_dispatch/middleware/closed_error.rb +7 -0
  63. data/lib/action_dispatch/middleware/cookies.rb +37 -15
  64. data/lib/action_dispatch/middleware/flash.rb +80 -11
  65. data/lib/action_dispatch/middleware/params_parser.rb +2 -2
  66. data/lib/action_dispatch/middleware/reloader.rb +76 -0
  67. data/lib/action_dispatch/middleware/session/abstract_store.rb +56 -226
  68. data/lib/action_dispatch/middleware/session/cookie_store.rb +20 -44
  69. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -46
  70. data/lib/action_dispatch/middleware/show_exceptions.rb +15 -2
  71. data/lib/action_dispatch/middleware/stack.rb +50 -17
  72. data/lib/action_dispatch/middleware/static.rb +41 -29
  73. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +3 -3
  74. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +3 -3
  75. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +3 -3
  76. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +4 -2
  77. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +2 -6
  78. data/lib/action_dispatch/railtie.rb +8 -0
  79. data/lib/action_dispatch/routing.rb +13 -1
  80. data/lib/action_dispatch/routing/mapper.rb +345 -227
  81. data/lib/action_dispatch/routing/polymorphic_routes.rb +33 -13
  82. data/lib/action_dispatch/routing/redirection.rb +110 -0
  83. data/lib/action_dispatch/routing/route.rb +15 -13
  84. data/lib/action_dispatch/routing/route_set.rb +116 -90
  85. data/lib/action_dispatch/routing/routes_proxy.rb +35 -0
  86. data/lib/action_dispatch/routing/url_for.rb +25 -1
  87. data/lib/action_dispatch/testing/assertions/response.rb +8 -10
  88. data/lib/action_dispatch/testing/assertions/routing.rb +15 -15
  89. data/lib/action_dispatch/testing/assertions/selector.rb +13 -220
  90. data/lib/action_dispatch/testing/integration.rb +37 -28
  91. data/lib/action_dispatch/testing/performance_test.rb +1 -3
  92. data/lib/action_dispatch/testing/test_process.rb +1 -1
  93. data/lib/action_dispatch/testing/test_request.rb +9 -3
  94. data/lib/action_dispatch/testing/test_response.rb +4 -111
  95. data/lib/action_pack.rb +1 -1
  96. data/lib/action_pack/version.rb +3 -3
  97. data/lib/action_view.rb +39 -24
  98. data/lib/action_view/base.rb +61 -86
  99. data/lib/action_view/buffers.rb +43 -0
  100. data/lib/action_view/context.rb +21 -24
  101. data/lib/action_view/flows.rb +79 -0
  102. data/lib/action_view/helpers.rb +8 -6
  103. data/lib/action_view/helpers/active_model_helper.rb +0 -23
  104. data/lib/action_view/helpers/asset_paths.rb +79 -0
  105. data/lib/action_view/helpers/asset_tag_helper.rb +30 -500
  106. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +147 -0
  107. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +101 -0
  108. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +200 -0
  109. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +152 -0
  110. data/lib/action_view/helpers/atom_feed_helper.rb +2 -2
  111. data/lib/action_view/helpers/cache_helper.rb +11 -19
  112. data/lib/action_view/helpers/capture_helper.rb +19 -8
  113. data/lib/action_view/helpers/controller_helper.rb +21 -0
  114. data/lib/action_view/helpers/csrf_helper.rb +22 -4
  115. data/lib/action_view/helpers/date_helper.rb +36 -22
  116. data/lib/action_view/helpers/form_helper.rb +199 -113
  117. data/lib/action_view/helpers/form_options_helper.rb +10 -11
  118. data/lib/action_view/helpers/form_tag_helper.rb +94 -22
  119. data/lib/action_view/helpers/javascript_helper.rb +24 -107
  120. data/lib/action_view/helpers/number_helper.rb +36 -33
  121. data/lib/action_view/helpers/output_safety_helper.rb +38 -0
  122. data/lib/action_view/helpers/record_tag_helper.rb +6 -6
  123. data/lib/action_view/helpers/rendering_helper.rb +90 -0
  124. data/lib/action_view/helpers/sanitize_helper.rb +2 -2
  125. data/lib/action_view/helpers/sprockets_helper.rb +69 -0
  126. data/lib/action_view/helpers/tag_helper.rb +34 -12
  127. data/lib/action_view/helpers/text_helper.rb +30 -145
  128. data/lib/action_view/helpers/translation_helper.rb +10 -17
  129. data/lib/action_view/helpers/url_helper.rb +70 -67
  130. data/lib/action_view/locale/en.yml +1 -1
  131. data/lib/action_view/lookup_context.rb +36 -14
  132. data/lib/action_view/{paths.rb → path_set.rb} +9 -8
  133. data/lib/action_view/railtie.rb +12 -4
  134. data/lib/action_view/renderer/abstract_renderer.rb +36 -0
  135. data/lib/action_view/{render/partials.rb → renderer/partial_renderer.rb} +147 -146
  136. data/lib/action_view/renderer/renderer.rb +54 -0
  137. data/lib/action_view/renderer/streaming_template_renderer.rb +106 -0
  138. data/lib/action_view/renderer/template_renderer.rb +74 -0
  139. data/lib/action_view/template.rb +91 -54
  140. data/lib/action_view/template/error.rb +11 -8
  141. data/lib/action_view/template/handler.rb +9 -1
  142. data/lib/action_view/template/handlers.rb +9 -9
  143. data/lib/action_view/template/handlers/builder.rb +4 -4
  144. data/lib/action_view/template/handlers/erb.rb +21 -41
  145. data/lib/action_view/template/resolver.rb +171 -57
  146. data/lib/action_view/template/text.rb +0 -4
  147. data/lib/action_view/test_case.rb +32 -16
  148. data/lib/action_view/testing/resolvers.rb +16 -10
  149. data/lib/sprockets/railtie.rb +100 -0
  150. metadata +162 -140
  151. checksums.yaml +0 -7
  152. data/lib/action_controller/deprecated/base.rb +0 -143
  153. data/lib/action_controller/deprecated/dispatcher.rb +0 -28
  154. data/lib/action_controller/deprecated/url_writer.rb +0 -14
  155. data/lib/action_dispatch/routing/deprecated_mapper.rb +0 -525
  156. data/lib/action_view/helpers/prototype_helper.rb +0 -851
  157. data/lib/action_view/helpers/raw_output_helper.rb +0 -18
  158. data/lib/action_view/helpers/scriptaculous_helper.rb +0 -263
  159. data/lib/action_view/render/layouts.rb +0 -83
  160. data/lib/action_view/render/rendering.rb +0 -67
  161. data/lib/action_view/template/handlers/rjs.rb +0 -17
@@ -4,7 +4,7 @@ module ActionView
4
4
  # = Action View Atom Feed Helpers
5
5
  module Helpers #:nodoc:
6
6
  module AtomFeedHelper
7
- # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other
7
+ # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
8
8
  # template languages).
9
9
  #
10
10
  # Full usage example:
@@ -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.request_uri.split(".")[0]}"
54
+ # * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}"
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).
@@ -2,31 +2,29 @@ module ActionView
2
2
  # = Action View Cache Helper
3
3
  module Helpers
4
4
  module CacheHelper
5
- # This helper to exposes a method for caching of view fragments.
6
- # See ActionController::Caching::Fragments for usage instructions.
5
+ # This helper exposes a method for caching fragments of a view
6
+ # rather than an entire action or page. This technique is useful
7
+ # caching pieces like menus, lists of newstopics, static HTML
8
+ # fragments, and so on. This method takes a block that contains
9
+ # the content you wish to cache.
7
10
  #
8
- # A method for caching fragments of a view rather than an entire
9
- # action or page. This technique is useful caching pieces like
10
- # menus, lists of news topics, static HTML fragments, and so on.
11
- # This method takes a block that contains the content you wish
12
- # to cache. See ActionController::Caching::Fragments for more
13
- # information.
11
+ # See ActionController::Caching::Fragments for usage instructions.
14
12
  #
15
13
  # ==== Examples
16
- # If you wanted to cache a navigation menu, you could do the
17
- # following.
14
+ # If you want to cache a navigation menu, you can do following:
18
15
  #
19
16
  # <% cache do %>
20
17
  # <%= render :partial => "menu" %>
21
18
  # <% end %>
22
19
  #
23
- # You can also cache static content...
20
+ # You can also cache static content:
24
21
  #
25
22
  # <% cache do %>
26
23
  # <p>Hello users! Welcome to our website!</p>
27
24
  # <% end %>
28
25
  #
29
- # ...and static content mixed with RHTML content.
26
+ # Static content with embedded ruby content can be cached as
27
+ # well:
30
28
  #
31
29
  # <% cache do %>
32
30
  # Topics:
@@ -53,13 +51,7 @@ module ActionView
53
51
  # This dance is needed because Builder can't use capture
54
52
  pos = output_buffer.length
55
53
  yield
56
- if output_buffer.html_safe?
57
- safe_output_buffer = output_buffer.to_str
58
- fragment = safe_output_buffer.slice!(pos..-1)
59
- self.output_buffer = output_buffer.class.new(safe_output_buffer)
60
- else
61
- fragment = output_buffer.slice!(pos..-1)
62
- end
54
+ fragment = output_buffer.slice!(pos..-1)
63
55
  controller.write_fragment(name, fragment, options)
64
56
  end
65
57
  end
@@ -14,7 +14,7 @@ module ActionView
14
14
  # variable. You can then use this variable anywhere in your templates or layout.
15
15
  #
16
16
  # ==== Examples
17
- # The capture method can be used in ERb templates...
17
+ # The capture method can be used in ERB templates...
18
18
  #
19
19
  # <% @greeting = capture do %>
20
20
  # Welcome to my shiny new web page! The date and time is
@@ -39,7 +39,7 @@ module ActionView
39
39
  value = nil
40
40
  buffer = with_output_buffer { value = yield(*args) }
41
41
  if string = buffer.presence || value and string.is_a?(String)
42
- NonConcattingString.new(ERB::Util.html_escape(string))
42
+ ERB::Util.html_escape string
43
43
  end
44
44
  end
45
45
 
@@ -107,8 +107,8 @@ module ActionView
107
107
  # <%= javascript_include_tag :defaults %>
108
108
  # <% end %>
109
109
  #
110
- # That will place <tt>script</tt> tags for Prototype, Scriptaculous, and application.js (if it exists)
111
- # on the page; this technique is useful if you'll only be using these scripts in a few views.
110
+ # That will place +script+ tags for your default set of JavaScript files on the page;
111
+ # this technique is useful if you'll only be using these scripts in a few views.
112
112
  #
113
113
  # Note that content_for concatenates the blocks it is given for a particular
114
114
  # identifier in order. For example:
@@ -135,8 +135,19 @@ module ActionView
135
135
  # for elements that will be fragment cached.
136
136
  def content_for(name, content = nil, &block)
137
137
  content = capture(&block) if block_given?
138
- @_content_for[name] << content if content
139
- @_content_for[name] unless content
138
+ result = @view_flow.append(name, content) if content
139
+ result unless content
140
+ end
141
+
142
+ # The same as +content_for+ but when used with streaming flushes
143
+ # straight back to the layout. In other words, if you want to
144
+ # concatenate several times to the same buffer when rendering a given
145
+ # template, you should use +content_for+, if not, use +provide+ to tell
146
+ # the layout to stop looking for more contents.
147
+ def provide(name, content = nil, &block)
148
+ content = capture(&block) if block_given?
149
+ result = @view_flow.append!(name, content) if content
150
+ result unless content
140
151
  end
141
152
 
142
153
  # content_for? simply checks whether any content has been captured yet using content_for
@@ -158,7 +169,7 @@ module ActionView
158
169
  # </body>
159
170
  # </html>
160
171
  def content_for?(name)
161
- @_content_for[name].present?
172
+ @view_flow.get(name).present?
162
173
  end
163
174
 
164
175
  # Use an alternate output buffer for the duration of the block.
@@ -179,7 +190,7 @@ module ActionView
179
190
  def flush_output_buffer #:nodoc:
180
191
  if output_buffer && !output_buffer.empty?
181
192
  response.body_parts << output_buffer
182
- self.output_buffer = output_buffer.respond_to?(:clone_empty) ? output_buffer.clone_empty : output_buffer[0, 0]
193
+ self.output_buffer = output_buffer[0,0]
183
194
  nil
184
195
  end
185
196
  end
@@ -0,0 +1,21 @@
1
+ module ActionView
2
+ module Helpers
3
+ # This module keeps all methods and behavior in ActionView
4
+ # that simply delegates to the controller.
5
+ module ControllerHelper #:nodoc:
6
+ attr_internal :controller, :request
7
+
8
+ delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,
9
+ :flash, :action_name, :controller_name, :controller_path, :to => :controller
10
+
11
+ delegate :logger, :to => :controller, :allow_nil => true
12
+
13
+ def assign_controller(controller)
14
+ if @_controller = controller
15
+ @_request = controller.request if controller.respond_to?(:request)
16
+ @_config = controller.config.inheritable_copy if controller.respond_to?(:config)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,14 +1,32 @@
1
+ require 'active_support/core_ext/string/strip'
2
+
1
3
  module ActionView
2
4
  # = Action View CSRF Helper
3
5
  module Helpers
4
6
  module CsrfHelper
5
- # Returns a meta tag with the cross-site request forgery protection token
6
- # for forms to use. Place this in your head.
7
- def csrf_meta_tag
7
+ # Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site
8
+ # request forgery protection parameter and token, respectively.
9
+ #
10
+ # <head>
11
+ # <%= csrf_meta_tags %>
12
+ # </head>
13
+ #
14
+ # These are used to generate the dynamic forms that implement non-remote links with
15
+ # <tt>:method</tt>.
16
+ #
17
+ # Note that regular forms generate hidden fields, and that Ajax calls are whitelisted,
18
+ # so they do not use these tags.
19
+ def csrf_meta_tags
8
20
  if protect_against_forgery?
9
- %(<meta name="csrf-param" content="#{Rack::Utils.escape_html(request_forgery_protection_token)}"/>\n<meta name="csrf-token" content="#{Rack::Utils.escape_html(form_authenticity_token)}"/>).html_safe
21
+ [
22
+ tag('meta', :name => 'csrf-param', :content => request_forgery_protection_token),
23
+ tag('meta', :name => 'csrf-token', :content => form_authenticity_token)
24
+ ].join("\n").html_safe
10
25
  end
11
26
  end
27
+
28
+ # For backwards compatibility.
29
+ alias csrf_meta_tag csrf_meta_tags
12
30
  end
13
31
  end
14
32
  end
@@ -1,5 +1,6 @@
1
1
  require 'date'
2
2
  require 'action_view/helpers/tag_helper'
3
+ require 'active_support/core_ext/date/conversions'
3
4
  require 'active_support/core_ext/hash/slice'
4
5
  require 'active_support/core_ext/object/with_options'
5
6
 
@@ -25,9 +26,9 @@ module ActionView
25
26
  # 30 secs <-> 1 min, 29 secs # => 1 minute
26
27
  # 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
27
28
  # 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
28
- # 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
29
- # 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day
30
- # 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
29
+ # 89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
30
+ # 23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs # => 1 day
31
+ # 41 hrs, 59 mins, 30 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
31
32
  # 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
32
33
  # 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
33
34
  # 1 yr <-> 1 yr, 3 months # => about 1 year
@@ -50,7 +51,7 @@ module ActionView
50
51
  # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
51
52
  # distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
52
53
  # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years
53
- # distance_of_time_in_words(from_time, from_time + 60.hours) # => about 3 days
54
+ # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days
54
55
  # distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute
55
56
  # distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
56
57
  # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
@@ -88,8 +89,8 @@ module ActionView
88
89
  when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
89
90
  when 45..89 then locale.t :about_x_hours, :count => 1
90
91
  when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
91
- when 1440..2529 then locale.t :x_days, :count => 1
92
- when 2530..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
92
+ when 1440..2519 then locale.t :x_days, :count => 1
93
+ when 2520..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
93
94
  when 43200..86399 then locale.t :about_x_months, :count => 1
94
95
  when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
95
96
  else
@@ -111,10 +112,12 @@ module ActionView
111
112
  #
112
113
  # ==== Examples
113
114
  # time_ago_in_words(3.minutes.from_now) # => 3 minutes
114
- # time_ago_in_words(Time.now - 15.hours) # => 15 hours
115
+ # time_ago_in_words(Time.now - 15.hours) # => about 15 hours
115
116
  # time_ago_in_words(Time.now) # => less than a minute
116
117
  #
117
- # from_time = Time.now - 3.days - 14.minutes - 25.seconds # => 3 days
118
+ # from_time = Time.now - 3.days - 14.minutes - 25.seconds
119
+ # time_ago_in_words(from_time) # => 3 days
120
+ #
118
121
  def time_ago_in_words(from_time, include_seconds = false)
119
122
  distance_of_time_in_words(from_time, Time.now, include_seconds)
120
123
  end
@@ -215,21 +218,10 @@ module ActionView
215
218
  # # Creates a time select tag that, when POSTed, will be stored in the post variable in the sunrise attribute
216
219
  # time_select("post", "sunrise")
217
220
  #
218
- # # Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted
219
- # # attribute
220
- # time_select("order", "submitted")
221
- #
222
- # # Creates a time select tag that, when POSTed, will be stored in the mail variable in the sent_at attribute
223
- # time_select("mail", "sent_at")
224
- #
225
221
  # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in
226
222
  # # the sunrise attribute.
227
223
  # time_select("post", "start_time", :include_seconds => true)
228
224
  #
229
- # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in
230
- # # the submission_time attribute.
231
- # time_select("entry", "submission_time", :include_seconds => true)
232
- #
233
225
  # # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45.
234
226
  # time_select 'game', 'game_time', {:minute_step => 15}
235
227
  #
@@ -474,7 +466,7 @@ module ActionView
474
466
  #
475
467
  # # Generates a select field for hours with a custom prompt. Use :prompt => true for a
476
468
  # # generic prompt.
477
- # select_hour(13, :prompt =>'Choose hour')
469
+ # select_hour(13, :prompt => 'Choose hour')
478
470
  #
479
471
  def select_hour(datetime, options = {}, html_options = {})
480
472
  DateTimeSelector.new(datetime, options, html_options).select_hour
@@ -577,6 +569,27 @@ module ActionView
577
569
  def select_year(date, options = {}, html_options = {})
578
570
  DateTimeSelector.new(date, options, html_options).select_year
579
571
  end
572
+
573
+ # Returns an html time tag for the given date or time.
574
+ #
575
+ # ==== Examples
576
+ # time_tag Date.today # =>
577
+ # <time datetime="2010-11-04">November 04, 2010</time>
578
+ # time_tag Time.now # =>
579
+ # <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
580
+ # time_tag Date.yesterday, 'Yesterday' # =>
581
+ # <time datetime="2010-11-03">Yesterday</time>
582
+ # time_tag Date.today, :pubdate => true # =>
583
+ # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
584
+ #
585
+ def time_tag(date_or_time, *args)
586
+ options = args.extract_options!
587
+ format = options.delete(:format) || :long
588
+ content = args.first || I18n.l(date_or_time, :format => format)
589
+ datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339
590
+
591
+ content_tag(:time, content, options.reverse_merge(:datetime => datetime))
592
+ end
580
593
  end
581
594
 
582
595
  class DateTimeSelector #:nodoc:
@@ -606,7 +619,7 @@ module ActionView
606
619
  @options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute]
607
620
 
608
621
  # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
609
- # valid (otherwise it could be 31 and february wouldn't be a valid date)
622
+ # valid (otherwise it could be 31 and February wouldn't be a valid date)
610
623
  if @datetime && @options[:discard_day] && !@options[:discard_month]
611
624
  @datetime = @datetime.change(:day => 1)
612
625
  end
@@ -633,7 +646,7 @@ module ActionView
633
646
  @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
634
647
 
635
648
  # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
636
- # valid (otherwise it could be 31 and february wouldn't be a valid date)
649
+ # valid (otherwise it could be 31 and February wouldn't be a valid date)
637
650
  if @datetime && @options[:discard_day] && !@options[:discard_month]
638
651
  @datetime = @datetime.change(:day => 1)
639
652
  end
@@ -925,6 +938,7 @@ module ActionView
925
938
  private
926
939
  def datetime_selector(options, html_options)
927
940
  datetime = value(object) || default_datetime(options)
941
+ @auto_index ||= nil
928
942
 
929
943
  options = options.dup
930
944
  options[:field_name] = @method_name
@@ -2,9 +2,11 @@ require 'cgi'
2
2
  require 'action_view/helpers/date_helper'
3
3
  require 'action_view/helpers/tag_helper'
4
4
  require 'action_view/helpers/form_tag_helper'
5
- require 'active_support/core_ext/class/inheritable_attributes'
5
+ require 'active_support/core_ext/class/attribute'
6
6
  require 'active_support/core_ext/hash/slice'
7
7
  require 'active_support/core_ext/object/blank'
8
+ require 'active_support/core_ext/string/output_safety'
9
+ require 'active_support/core_ext/array/extract_options'
8
10
 
9
11
  module ActionView
10
12
  # = Action View Form Helpers
@@ -100,6 +102,11 @@ module ActionView
100
102
  include FormTagHelper
101
103
  include UrlHelper
102
104
 
105
+ # Converts the given object to an ActiveModel compliant one.
106
+ def convert_to_model(object)
107
+ object.respond_to?(:to_model) ? object.to_model : object
108
+ end
109
+
103
110
  # Creates a form and a scope around a specific model object that is used
104
111
  # as a base for questioning about values for the fields.
105
112
  #
@@ -111,6 +118,7 @@ module ActionView
111
118
  # <%= f.text_field :version %><br />
112
119
  # <%= f.label :author, 'Author' %>:
113
120
  # <%= f.text_field :author %><br />
121
+ # <%= f.submit %>
114
122
  # <% end %>
115
123
  #
116
124
  # There, +form_for+ is able to generate the rest of RESTful form
@@ -128,6 +136,7 @@ module ActionView
128
136
  # Last name : <%= f.text_field :last_name %><br />
129
137
  # Biography : <%= f.text_area :biography %><br />
130
138
  # Admin? : <%= f.check_box :admin %><br />
139
+ # <%= f.submit %>
131
140
  # <% end %>
132
141
  #
133
142
  # There, the argument is a symbol or string with the name of the
@@ -159,6 +168,7 @@ module ActionView
159
168
  # Last name : <%= f.text_field :last_name %>
160
169
  # Biography : <%= text_area :person, :biography %>
161
170
  # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
171
+ # <%= f.submit %>
162
172
  # <% end %>
163
173
  #
164
174
  # This also works for the methods in FormOptionHelper and DateHelper that
@@ -180,7 +190,7 @@ module ActionView
180
190
  #
181
191
  # is equivalent to something like:
182
192
  #
183
- # <%= form_for @post, :as => :post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
193
+ # <%= form_for @post, :as => :post, :url => post_path(@post), :method => :put, :html => { :class => "edit_post", :id => "edit_post_45" } do |f| %>
184
194
  # ...
185
195
  # <% end %>
186
196
  #
@@ -211,7 +221,7 @@ module ActionView
211
221
  # If you have an object that needs to be represented as a different
212
222
  # parameter, like a Client that acts as a Person:
213
223
  #
214
- # <%= form_for(@post, :as => :client do |f| %>
224
+ # <%= form_for(@post, :as => :client) do |f| %>
215
225
  # ...
216
226
  # <% end %>
217
227
  #
@@ -228,8 +238,18 @@ module ActionView
228
238
  # ...
229
239
  # <% end %>
230
240
  #
231
- # Where +@document = Document.find(params[:id])+ and
232
- # +@comment = Comment.new+.
241
+ # Where <tt>@document = Document.find(params[:id])</tt> and
242
+ # <tt>@comment = Comment.new</tt>.
243
+ #
244
+ # === Setting the method
245
+ #
246
+ # You can force the form to use the full array of HTTP verbs by setting
247
+ #
248
+ # :method => (:get|:post|:put|:delete)
249
+ #
250
+ # in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the
251
+ # form will be set to POST and a hidden input called _method will carry the intended verb for the server
252
+ # to interpret.
233
253
  #
234
254
  # === Unobtrusive JavaScript
235
255
  #
@@ -258,6 +278,24 @@ module ActionView
258
278
  # ...
259
279
  # </form>
260
280
  #
281
+ # === Removing hidden model id's
282
+ #
283
+ # The form_for method automatically includes the model id as a hidden field in the form.
284
+ # This is used to maintain the correlation between the form data and its associated model.
285
+ # Some ORM systems do not use IDs on nested models so in this case you want to be able
286
+ # to disable the hidden id.
287
+ #
288
+ # In the following example the Post model has many Comments stored within it in a NoSQL database,
289
+ # thus there is no primary key for comments.
290
+ #
291
+ # Example:
292
+ #
293
+ # <%= form(@post) do |f| %>
294
+ # <% f.fields_for(:comments, :include_id => false) do |cf| %>
295
+ # ...
296
+ # <% end %>
297
+ # <% end %>
298
+ #
261
299
  # === Customized form builders
262
300
  #
263
301
  # You can also build forms using a customized FormBuilder class. Subclass
@@ -268,13 +306,14 @@ module ActionView
268
306
  # <%= form_for @person, :url => { :action => "create" }, :builder => LabellingFormBuilder do |f| %>
269
307
  # <%= f.text_field :first_name %>
270
308
  # <%= f.text_field :last_name %>
271
- # <%= text_area :person, :biography %>
272
- # <%= check_box_tag "person[admin]", @person.company.admin? %>
309
+ # <%= f.text_area :biography %>
310
+ # <%= f.check_box :admin %>
311
+ # <%= f.submit %>
273
312
  # <% end %>
274
313
  #
275
314
  # In this case, if you use this:
276
315
  #
277
- # <%= render :partial => f %>
316
+ # <%= render f %>
278
317
  #
279
318
  # The rendered template is <tt>people/_labelling_form</tt> and the local
280
319
  # variable referencing the form builder is called
@@ -293,31 +332,47 @@ module ActionView
293
332
  #
294
333
  # If you don't need to attach a form to a model instance, then check out
295
334
  # FormTagHelper#form_tag.
296
- def form_for(record_or_name_or_array, *args, &proc)
335
+ #
336
+ # === Form to external resources
337
+ #
338
+ # When you build forms to external resources sometimes you need to set an authenticity token or just render a form
339
+ # without it, for example when you submit data to a payment gateway number and types of fields could be limited.
340
+ #
341
+ # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
342
+ #
343
+ # <%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f|
344
+ # ...
345
+ # <% end %>
346
+ #
347
+ # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
348
+ #
349
+ # <%= form_for @invoice, :url => external_url, :authenticity_token => false do |f|
350
+ # ...
351
+ # <% end %>
352
+ def form_for(record, options = {}, &proc)
297
353
  raise ArgumentError, "Missing block" unless block_given?
298
354
 
299
- options = args.extract_options!
355
+ options[:html] ||= {}
300
356
 
301
- case record_or_name_or_array
357
+ case record
302
358
  when String, Symbol
303
- ActiveSupport::Deprecation.warn("Using form_for(:name, @resource) is deprecated. Please use form_for(@resource, :as => :name) instead.", caller) unless args.empty?
304
- object_name = record_or_name_or_array
305
- when Array
306
- object = record_or_name_or_array.last
307
- object_name = options[:as] || ActiveModel::Naming.singular(object)
308
- apply_form_for_options!(record_or_name_or_array, options)
309
- args.unshift object
359
+ object_name = record
360
+ object = nil
310
361
  else
311
- object = record_or_name_or_array
312
- object_name = options[:as] || ActiveModel::Naming.singular(object)
313
- apply_form_for_options!([object], options)
314
- args.unshift object
362
+ object = record.is_a?(Array) ? record.last : record
363
+ object_name = options[:as] || ActiveModel::Naming.param_key(object)
364
+ apply_form_for_options!(record, options)
315
365
  end
316
366
 
317
- (options[:html] ||= {})[:remote] = true if options.delete(:remote)
367
+ options[:html][:remote] = options.delete(:remote)
368
+ options[:html][:method] = options.delete(:method) if options.has_key?(:method)
369
+ options[:html][:authenticity_token] = options.delete(:authenticity_token)
318
370
 
319
- output = form_tag(options.delete(:url) || {}, options.delete(:html) || {})
320
- output << fields_for(object_name, *(args << options), &proc)
371
+ builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc)
372
+ fields_for = fields_for(object_name, object, options, &proc)
373
+ default_options = builder.multipart? ? { :multipart => true } : {}
374
+ output = form_tag(options.delete(:url) || {}, default_options.merge!(options.delete(:html)))
375
+ output << fields_for
321
376
  output.safe_concat('</form>')
322
377
  end
323
378
 
@@ -325,23 +380,17 @@ module ActionView
325
380
  object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
326
381
  object = convert_to_model(object)
327
382
 
328
- html_options =
329
- if object.respond_to?(:persisted?) && object.persisted?
330
- { :class => options[:as] ? "#{options[:as]}_edit" : dom_class(object, :edit),
331
- :id => options[:as] ? "#{options[:as]}_edit" : dom_id(object, :edit),
332
- :method => :put }
333
- else
334
- { :class => options[:as] ? "#{options[:as]}_new" : dom_class(object, :new),
335
- :id => options[:as] ? "#{options[:as]}_new" : dom_id(object),
336
- :method => :post }
337
- end
383
+ as = options[:as]
384
+ action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
385
+ options[:html].reverse_merge!(
386
+ :class => as ? "#{as}_#{action}" : dom_class(object, action),
387
+ :id => as ? "#{as}_#{action}" : dom_id(object, action),
388
+ :method => method
389
+ )
338
390
 
339
- options[:html] ||= {}
340
- options[:html].reverse_merge!(html_options)
341
- options[:url] ||= options[:format] ? \
342
- polymorphic_path(object_or_array, :format => options.delete(:format)) : \
343
- polymorphic_path(object_or_array)
391
+ options[:url] ||= polymorphic_path(object_or_array, :format => options.delete(:format))
344
392
  end
393
+ private :apply_form_for_options!
345
394
 
346
395
  # Creates a scope around a specific model object like form_for, but
347
396
  # doesn't create the form tags themselves. This makes fields_for suitable
@@ -356,6 +405,8 @@ module ActionView
356
405
  # <%= fields_for @person.permission do |permission_fields| %>
357
406
  # Admin? : <%= permission_fields.check_box :admin %>
358
407
  # <% end %>
408
+ #
409
+ # <%= f.submit %>
359
410
  # <% end %>
360
411
  #
361
412
  # ...or if you have an object that needs to be represented as a different
@@ -417,6 +468,7 @@ module ActionView
417
468
  # Street : <%= address_fields.text_field :street %>
418
469
  # Zip code: <%= address_fields.text_field :zip_code %>
419
470
  # <% end %>
471
+ # ...
420
472
  # <% end %>
421
473
  #
422
474
  # When address is already an association on a Person you can use
@@ -446,6 +498,7 @@ module ActionView
446
498
  # ...
447
499
  # Delete: <%= address_fields.check_box :_destroy %>
448
500
  # <% end %>
501
+ # ...
449
502
  # <% end %>
450
503
  #
451
504
  # ==== One-to-many
@@ -475,6 +528,7 @@ module ActionView
475
528
  # Name: <%= project_fields.text_field :name %>
476
529
  # <% end %>
477
530
  # <% end %>
531
+ # ...
478
532
  # <% end %>
479
533
  #
480
534
  # It's also possible to specify the instance to be used:
@@ -488,6 +542,7 @@ module ActionView
488
542
  # <% end %>
489
543
  # <% end %>
490
544
  # <% end %>
545
+ # ...
491
546
  # <% end %>
492
547
  #
493
548
  # Or a collection to be used:
@@ -497,6 +552,7 @@ module ActionView
497
552
  # <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
498
553
  # Name: <%= project_fields.text_field :name %>
499
554
  # <% end %>
555
+ # ...
500
556
  # <% end %>
501
557
  #
502
558
  # When projects is already an association on Person you can use
@@ -526,23 +582,11 @@ module ActionView
526
582
  # <%= person_form.fields_for :projects do |project_fields| %>
527
583
  # Delete: <%= project_fields.check_box :_destroy %>
528
584
  # <% end %>
585
+ # ...
529
586
  # <% end %>
530
- def fields_for(record_or_name_or_array, *args, &block)
531
- raise ArgumentError, "Missing block" unless block_given?
532
- options = args.extract_options!
533
-
534
- case record_or_name_or_array
535
- when String, Symbol
536
- object_name = record_or_name_or_array
537
- object = args.first
538
- else
539
- object = record_or_name_or_array
540
- object_name = ActiveModel::Naming.singular(object)
541
- end
542
-
543
- builder = options[:builder] || ActionView::Base.default_form_builder
544
- builder = builder.new(object_name, object, self, options, block)
545
- output = capture(builder, &block)
587
+ def fields_for(record, record_object = nil, options = {}, &block)
588
+ builder = instantiate_builder(record, record_object, options, &block)
589
+ output = capture(builder, &block)
546
590
  output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
547
591
  output
548
592
  end
@@ -571,10 +615,11 @@ module ActionView
571
615
  # label(:post, :body)
572
616
  # # => <label for="post_body">Write your entire text here</label>
573
617
  #
574
- # Localization can also be based purely on the translation of the attribute-name like this:
618
+ # Localization can also be based purely on the translation of the attribute-name
619
+ # (if you are using ActiveRecord):
575
620
  #
576
- # activemodel:
577
- # attribute:
621
+ # activerecord:
622
+ # attributes:
578
623
  # post:
579
624
  # cost: "Total cost"
580
625
  #
@@ -635,19 +680,19 @@ module ActionView
635
680
  #
636
681
  # ==== Examples
637
682
  # password_field(:login, :pass, :size => 20)
638
- # # => <input type="password" id="login_pass" name="login[pass]" size="20" value="#{@login.pass}" />
683
+ # # => <input type="password" id="login_pass" name="login[pass]" size="20" />
639
684
  #
640
- # password_field(:account, :secret, :class => "form_input")
685
+ # password_field(:account, :secret, :class => "form_input", :value => @account.secret)
641
686
  # # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
642
687
  #
643
688
  # password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
644
- # # => <input type="password" id="user_password" name="user[password]" value="#{@user.password}" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
689
+ # # => <input type="password" id="user_password" name="user[password]" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
645
690
  #
646
691
  # password_field(:account, :pin, :size => 20, :class => 'form_input')
647
- # # => <input type="password" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
692
+ # # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
648
693
  #
649
694
  def password_field(object_name, method, options = {})
650
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", options)
695
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", { :value => nil }.merge!(options))
651
696
  end
652
697
 
653
698
  # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -668,11 +713,13 @@ module ActionView
668
713
  InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
669
714
  end
670
715
 
671
- # Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
716
+ # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
672
717
  # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
673
718
  # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
674
719
  # shown.
675
720
  #
721
+ # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
722
+ #
676
723
  # ==== Examples
677
724
  # file_field(:user, :avatar)
678
725
  # # => <input type="file" id="user_avatar" name="user[avatar]" />
@@ -848,29 +895,42 @@ module ActionView
848
895
  def range_field(object_name, method, options = {})
849
896
  InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("range", options)
850
897
  end
898
+
899
+ private
900
+
901
+ def instantiate_builder(record, *args, &block)
902
+ options = args.extract_options!
903
+ record_object = args.shift
904
+
905
+ case record
906
+ when String, Symbol
907
+ object = record_object
908
+ object_name = record
909
+ else
910
+ object = record
911
+ object_name = ActiveModel::Naming.param_key(object)
912
+ end
913
+
914
+ builder = options[:builder] || ActionView::Base.default_form_builder
915
+ builder.new(object_name, object, self, options, block)
916
+ end
851
917
  end
852
918
 
853
- module InstanceTagMethods #:nodoc:
854
- extend ActiveSupport::Concern
919
+ class InstanceTag
855
920
  include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper
856
921
 
857
- attr_reader :method_name, :object_name
922
+ attr_reader :object, :method_name, :object_name
858
923
 
859
- DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze
860
- DEFAULT_RADIO_OPTIONS = { }.freeze
861
- DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze
924
+ DEFAULT_FIELD_OPTIONS = { "size" => 30 }
925
+ DEFAULT_RADIO_OPTIONS = { }
926
+ DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
862
927
 
863
928
  def initialize(object_name, method_name, template_object, object = nil)
864
929
  @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
865
930
  @template_object = template_object
866
- @object = object
867
- if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
868
- if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
869
- @auto_index = object.to_param
870
- else
871
- raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
872
- end
873
- end
931
+ @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
932
+ @object = retrieve_object(object)
933
+ @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
874
934
  end
875
935
 
876
936
  def to_label_tag(text = nil, options = {}, &block)
@@ -892,7 +952,8 @@ module ActionView
892
952
  label_tag(name_and_id["id"], options, &block)
893
953
  else
894
954
  content = if text.blank?
895
- I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence
955
+ method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
956
+ I18n.t("helpers.label.#{object_name}.#{method_and_value}", :default => "").presence
896
957
  else
897
958
  text.to_s
898
959
  end
@@ -916,7 +977,7 @@ module ActionView
916
977
  end
917
978
  options["type"] ||= field_type
918
979
  options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
919
- options["value"] &&= html_escape(options["value"])
980
+ options["value"] &&= ERB::Util.html_escape(options["value"])
920
981
  add_default_name_and_id(options)
921
982
  tag("input", options)
922
983
  end
@@ -952,7 +1013,7 @@ module ActionView
952
1013
  options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
953
1014
  end
954
1015
 
955
- content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
1016
+ content_tag("textarea", ERB::Util.html_escape(options.delete('value') || value_before_type_cast(object)), options)
956
1017
  end
957
1018
 
958
1019
  def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
@@ -994,14 +1055,26 @@ module ActionView
994
1055
  content_tag(tag_name, value(object), options)
995
1056
  end
996
1057
 
997
- def object
998
- @object || @template_object.instance_variable_get("@#{@object_name}")
1058
+ def retrieve_object(object)
1059
+ if object
1060
+ object
1061
+ elsif @template_object.instance_variable_defined?("@#{@object_name}")
1062
+ @template_object.instance_variable_get("@#{@object_name}")
1063
+ end
999
1064
  rescue NameError
1000
- # As @object_name may contain the nested syntax (item[subobject]) we
1001
- # need to fallback to nil.
1065
+ # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
1002
1066
  nil
1003
1067
  end
1004
1068
 
1069
+ def retrieve_autoindex(pre_match)
1070
+ object = self.object || @template_object.instance_variable_get("@#{pre_match}")
1071
+ if object && object.respond_to?(:to_param)
1072
+ object.to_param
1073
+ else
1074
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
1075
+ end
1076
+ end
1077
+
1005
1078
  def value(object)
1006
1079
  self.class.value(object, @method_name)
1007
1080
  end
@@ -1010,9 +1083,9 @@ module ActionView
1010
1083
  self.class.value_before_type_cast(object, @method_name)
1011
1084
  end
1012
1085
 
1013
- module ClassMethods
1086
+ class << self
1014
1087
  def value(object, method_name)
1015
- object.send method_name unless object.nil?
1088
+ object.send method_name if object
1016
1089
  end
1017
1090
 
1018
1091
  def value_before_type_cast(object, method_name)
@@ -1066,7 +1139,7 @@ module ActionView
1066
1139
  options["name"] ||= tag_name_with_index(@auto_index)
1067
1140
  options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
1068
1141
  else
1069
- options["name"] ||= tag_name + (options['multiple'] ? '[]' : '')
1142
+ options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
1070
1143
  options["id"] = options.fetch("id"){ tag_id }
1071
1144
  end
1072
1145
  end
@@ -1096,17 +1169,21 @@ module ActionView
1096
1169
  end
1097
1170
  end
1098
1171
 
1099
- class InstanceTag
1100
- include InstanceTagMethods
1101
- end
1102
-
1103
1172
  class FormBuilder
1104
1173
  # The methods which wrap a form helper call.
1105
- class_inheritable_accessor :field_helpers
1106
- self.field_helpers = (FormHelper.instance_method_names - ['form_for'])
1174
+ class_attribute :field_helpers
1175
+ self.field_helpers = FormHelper.instance_method_names - %w(form_for convert_to_model)
1107
1176
 
1108
1177
  attr_accessor :object_name, :object, :options
1109
1178
 
1179
+ attr_reader :multipart, :parent_builder
1180
+ alias :multipart? :multipart
1181
+
1182
+ def multipart=(multipart)
1183
+ @multipart = multipart
1184
+ parent_builder.multipart = multipart if parent_builder
1185
+ end
1186
+
1110
1187
  def self.model_name
1111
1188
  @model_name ||= Struct.new(:partial_path).new(name.demodulize.underscore.sub!(/_builder$/, ''))
1112
1189
  end
@@ -1118,6 +1195,7 @@ module ActionView
1118
1195
  def initialize(object_name, object, template, options, proc)
1119
1196
  @nested_child_index = {}
1120
1197
  @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
1198
+ @parent_builder = options[:parent_builder]
1121
1199
  @default_options = @options ? @options.slice(:index) : {}
1122
1200
  if @object_name.to_s.match(/\[\]$/)
1123
1201
  if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
@@ -1126,9 +1204,10 @@ module ActionView
1126
1204
  raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
1127
1205
  end
1128
1206
  end
1207
+ @multipart = nil
1129
1208
  end
1130
1209
 
1131
- (field_helpers - %w(label check_box radio_button fields_for hidden_field)).each do |selector|
1210
+ (field_helpers - %w(label check_box radio_button fields_for hidden_field file_field)).each do |selector|
1132
1211
  class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1133
1212
  def #{selector}(method, options = {}) # def text_field(method, options = {})
1134
1213
  @template.send( # @template.send(
@@ -1150,27 +1229,23 @@ module ActionView
1150
1229
  index = ""
1151
1230
  end
1152
1231
 
1153
- if options[:builder]
1154
- args << {} unless args.last.is_a?(Hash)
1155
- args.last[:builder] ||= options[:builder]
1156
- end
1232
+ args << {} unless args.last.is_a?(Hash)
1233
+ args.last[:builder] ||= options[:builder]
1234
+ args.last[:parent_builder] = self
1157
1235
 
1158
1236
  case record_or_name_or_array
1159
1237
  when String, Symbol
1160
1238
  if nested_attributes_association?(record_or_name_or_array)
1161
1239
  return fields_for_with_nested_attributes(record_or_name_or_array, args, block)
1162
1240
  else
1163
- name = "#{object_name}#{index}[#{record_or_name_or_array}]"
1241
+ name = record_or_name_or_array
1164
1242
  end
1165
- when Array
1166
- object = record_or_name_or_array.last
1167
- name = "#{object_name}#{index}[#{ActiveModel::Naming.singular(object)}]"
1168
- args.unshift(object)
1169
1243
  else
1170
- object = record_or_name_or_array
1171
- name = "#{object_name}#{index}[#{ActiveModel::Naming.singular(object)}]"
1244
+ object = record_or_name_or_array.is_a?(Array) ? record_or_name_or_array.last : record_or_name_or_array
1245
+ name = ActiveModel::Naming.param_key(object)
1172
1246
  args.unshift(object)
1173
1247
  end
1248
+ name = "#{object_name}#{index}[#{name}]"
1174
1249
 
1175
1250
  @template.fields_for(name, *args, &block)
1176
1251
  end
@@ -1192,6 +1267,11 @@ module ActionView
1192
1267
  @template.hidden_field(@object_name, method, objectify_options(options))
1193
1268
  end
1194
1269
 
1270
+ def file_field(method, options = {})
1271
+ self.multipart = true
1272
+ @template.file_field(@object_name, method, objectify_options(options))
1273
+ end
1274
+
1195
1275
  # Add the submit button for the given form. When no value is given, it checks
1196
1276
  # if the object is a new resource or not to create the proper label:
1197
1277
  #
@@ -1222,11 +1302,11 @@ module ActionView
1222
1302
  def submit(value=nil, options={})
1223
1303
  value, options = nil, value if value.is_a?(Hash)
1224
1304
  value ||= submit_default_value
1225
- @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
1305
+ @template.submit_tag(value, options)
1226
1306
  end
1227
1307
 
1228
1308
  def emitted_hidden_id?
1229
- @emitted_hidden_id
1309
+ @emitted_hidden_id ||= nil
1230
1310
  end
1231
1311
 
1232
1312
  private
@@ -1235,7 +1315,7 @@ module ActionView
1235
1315
  end
1236
1316
 
1237
1317
  def submit_default_value
1238
- object = @object.respond_to?(:to_model) ? @object.to_model : @object
1318
+ object = convert_to_model(@object)
1239
1319
  key = object ? (object.persisted? ? :update : :create) : :submit
1240
1320
 
1241
1321
  model = if object.class.respond_to?(:model_name)
@@ -1260,7 +1340,7 @@ module ActionView
1260
1340
  name = "#{object_name}[#{association_name}_attributes]"
1261
1341
  options = args.extract_options!
1262
1342
  association = args.shift
1263
- association = association.to_model if association.respond_to?(:to_model)
1343
+ association = convert_to_model(association)
1264
1344
 
1265
1345
  if association.respond_to?(:persisted?)
1266
1346
  association = [association] if @object.send(association_name).is_a?(Array)
@@ -1281,9 +1361,11 @@ module ActionView
1281
1361
  end
1282
1362
 
1283
1363
  def fields_for_nested_model(name, object, options, block)
1284
- object = object.to_model if object.respond_to?(:to_model)
1364
+ object = convert_to_model(object)
1285
1365
 
1286
- options[:hidden_field_id] = object.persisted?
1366
+ parent_include_id = self.options.fetch(:include_id, true)
1367
+ include_id = options.fetch(:include_id, parent_include_id)
1368
+ options[:hidden_field_id] = object.persisted? && include_id
1287
1369
  @template.fields_for(name, object, options, &block)
1288
1370
  end
1289
1371
 
@@ -1291,6 +1373,10 @@ module ActionView
1291
1373
  @nested_child_index[name] ||= -1
1292
1374
  @nested_child_index[name] += 1
1293
1375
  end
1376
+
1377
+ def convert_to_model(object)
1378
+ object.respond_to?(:to_model) ? object.to_model : object
1379
+ end
1294
1380
  end
1295
1381
  end
1296
1382