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.
- data/CHANGELOG +88 -142
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -6
- data/lib/abstract_controller.rb +1 -0
- data/lib/abstract_controller/asset_paths.rb +2 -2
- data/lib/abstract_controller/base.rb +24 -19
- data/lib/abstract_controller/callbacks.rb +19 -19
- data/lib/abstract_controller/helpers.rb +11 -13
- data/lib/abstract_controller/layouts.rb +4 -5
- data/lib/abstract_controller/railties/routes_helpers.rb +18 -0
- data/lib/abstract_controller/rendering.rb +34 -31
- data/lib/abstract_controller/url_for.rb +27 -0
- data/lib/abstract_controller/view_paths.rb +31 -6
- data/lib/action_controller.rb +5 -3
- data/lib/action_controller/base.rb +15 -16
- data/lib/action_controller/caching.rb +2 -2
- data/lib/action_controller/caching/actions.rb +11 -12
- data/lib/action_controller/caching/fragments.rb +41 -19
- data/lib/action_controller/caching/pages.rb +3 -9
- data/lib/action_controller/caching/sweeping.rb +0 -1
- data/lib/action_controller/deprecated.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +1 -1
- data/lib/action_controller/metal.rb +78 -20
- data/lib/action_controller/metal/compatibility.rb +0 -9
- data/lib/action_controller/metal/conditional_get.rb +9 -9
- data/lib/action_controller/metal/data_streaming.rb +145 -0
- data/lib/action_controller/metal/force_ssl.rb +35 -0
- data/lib/action_controller/metal/head.rb +1 -1
- data/lib/action_controller/metal/helpers.rb +37 -44
- data/lib/action_controller/metal/hide_actions.rb +2 -3
- data/lib/action_controller/metal/http_authentication.rb +41 -38
- data/lib/action_controller/metal/implicit_render.rb +13 -13
- data/lib/action_controller/metal/instrumentation.rb +2 -2
- data/lib/action_controller/metal/mime_responds.rb +25 -19
- data/lib/action_controller/metal/params_wrapper.rb +224 -0
- data/lib/action_controller/metal/redirecting.rb +6 -2
- data/lib/action_controller/metal/renderers.rb +50 -36
- data/lib/action_controller/metal/rendering.rb +34 -25
- data/lib/action_controller/metal/request_forgery_protection.rb +18 -36
- data/lib/action_controller/metal/responder.rb +47 -12
- data/lib/action_controller/metal/streaming.rb +244 -138
- data/lib/action_controller/metal/testing.rb +0 -9
- data/lib/action_controller/metal/url_for.rb +12 -14
- data/lib/action_controller/railtie.rb +19 -37
- data/lib/action_controller/railties/paths.rb +24 -0
- data/lib/action_controller/record_identifier.rb +4 -10
- data/lib/action_controller/test_case.rb +36 -19
- data/lib/action_controller/vendor/html-scanner/html/node.rb +5 -5
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +3 -3
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +2 -0
- data/lib/action_dispatch.rb +4 -1
- data/lib/action_dispatch/http/cache.rb +5 -32
- data/lib/action_dispatch/http/filter_parameters.rb +3 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +22 -3
- data/lib/action_dispatch/http/mime_type.rb +45 -5
- data/lib/action_dispatch/http/rack_cache.rb +58 -0
- data/lib/action_dispatch/http/request.rb +27 -41
- data/lib/action_dispatch/http/response.rb +56 -54
- data/lib/action_dispatch/http/upload.rb +1 -11
- data/lib/action_dispatch/http/url.rb +102 -42
- data/lib/action_dispatch/middleware/callbacks.rb +8 -25
- data/lib/action_dispatch/middleware/closed_error.rb +7 -0
- data/lib/action_dispatch/middleware/cookies.rb +37 -15
- data/lib/action_dispatch/middleware/flash.rb +80 -11
- data/lib/action_dispatch/middleware/params_parser.rb +2 -2
- data/lib/action_dispatch/middleware/reloader.rb +76 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +56 -226
- data/lib/action_dispatch/middleware/session/cookie_store.rb +20 -44
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -46
- data/lib/action_dispatch/middleware/show_exceptions.rb +15 -2
- data/lib/action_dispatch/middleware/stack.rb +50 -17
- data/lib/action_dispatch/middleware/static.rb +41 -29
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +4 -2
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +2 -6
- data/lib/action_dispatch/railtie.rb +8 -0
- data/lib/action_dispatch/routing.rb +13 -1
- data/lib/action_dispatch/routing/mapper.rb +345 -227
- data/lib/action_dispatch/routing/polymorphic_routes.rb +33 -13
- data/lib/action_dispatch/routing/redirection.rb +110 -0
- data/lib/action_dispatch/routing/route.rb +15 -13
- data/lib/action_dispatch/routing/route_set.rb +116 -90
- data/lib/action_dispatch/routing/routes_proxy.rb +35 -0
- data/lib/action_dispatch/routing/url_for.rb +25 -1
- data/lib/action_dispatch/testing/assertions/response.rb +8 -10
- data/lib/action_dispatch/testing/assertions/routing.rb +15 -15
- data/lib/action_dispatch/testing/assertions/selector.rb +13 -220
- data/lib/action_dispatch/testing/integration.rb +37 -28
- data/lib/action_dispatch/testing/performance_test.rb +1 -3
- data/lib/action_dispatch/testing/test_process.rb +1 -1
- data/lib/action_dispatch/testing/test_request.rb +9 -3
- data/lib/action_dispatch/testing/test_response.rb +4 -111
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/version.rb +3 -3
- data/lib/action_view.rb +39 -24
- data/lib/action_view/base.rb +61 -86
- data/lib/action_view/buffers.rb +43 -0
- data/lib/action_view/context.rb +21 -24
- data/lib/action_view/flows.rb +79 -0
- data/lib/action_view/helpers.rb +8 -6
- data/lib/action_view/helpers/active_model_helper.rb +0 -23
- data/lib/action_view/helpers/asset_paths.rb +79 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +30 -500
- data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +147 -0
- data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +101 -0
- data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +200 -0
- data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +152 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +2 -2
- data/lib/action_view/helpers/cache_helper.rb +11 -19
- data/lib/action_view/helpers/capture_helper.rb +19 -8
- data/lib/action_view/helpers/controller_helper.rb +21 -0
- data/lib/action_view/helpers/csrf_helper.rb +22 -4
- data/lib/action_view/helpers/date_helper.rb +36 -22
- data/lib/action_view/helpers/form_helper.rb +199 -113
- data/lib/action_view/helpers/form_options_helper.rb +10 -11
- data/lib/action_view/helpers/form_tag_helper.rb +94 -22
- data/lib/action_view/helpers/javascript_helper.rb +24 -107
- data/lib/action_view/helpers/number_helper.rb +36 -33
- data/lib/action_view/helpers/output_safety_helper.rb +38 -0
- data/lib/action_view/helpers/record_tag_helper.rb +6 -6
- data/lib/action_view/helpers/rendering_helper.rb +90 -0
- data/lib/action_view/helpers/sanitize_helper.rb +2 -2
- data/lib/action_view/helpers/sprockets_helper.rb +69 -0
- data/lib/action_view/helpers/tag_helper.rb +34 -12
- data/lib/action_view/helpers/text_helper.rb +30 -145
- data/lib/action_view/helpers/translation_helper.rb +10 -17
- data/lib/action_view/helpers/url_helper.rb +70 -67
- data/lib/action_view/locale/en.yml +1 -1
- data/lib/action_view/lookup_context.rb +36 -14
- data/lib/action_view/{paths.rb → path_set.rb} +9 -8
- data/lib/action_view/railtie.rb +12 -4
- data/lib/action_view/renderer/abstract_renderer.rb +36 -0
- data/lib/action_view/{render/partials.rb → renderer/partial_renderer.rb} +147 -146
- data/lib/action_view/renderer/renderer.rb +54 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +106 -0
- data/lib/action_view/renderer/template_renderer.rb +74 -0
- data/lib/action_view/template.rb +91 -54
- data/lib/action_view/template/error.rb +11 -8
- data/lib/action_view/template/handler.rb +9 -1
- data/lib/action_view/template/handlers.rb +9 -9
- data/lib/action_view/template/handlers/builder.rb +4 -4
- data/lib/action_view/template/handlers/erb.rb +21 -41
- data/lib/action_view/template/resolver.rb +171 -57
- data/lib/action_view/template/text.rb +0 -4
- data/lib/action_view/test_case.rb +32 -16
- data/lib/action_view/testing/resolvers.rb +16 -10
- data/lib/sprockets/railtie.rb +100 -0
- metadata +162 -140
- checksums.yaml +0 -7
- data/lib/action_controller/deprecated/base.rb +0 -143
- data/lib/action_controller/deprecated/dispatcher.rb +0 -28
- data/lib/action_controller/deprecated/url_writer.rb +0 -14
- data/lib/action_dispatch/routing/deprecated_mapper.rb +0 -525
- data/lib/action_view/helpers/prototype_helper.rb +0 -851
- data/lib/action_view/helpers/raw_output_helper.rb +0 -18
- data/lib/action_view/helpers/scriptaculous_helper.rb +0 -263
- data/lib/action_view/render/layouts.rb +0 -83
- data/lib/action_view/render/rendering.rb +0 -67
- 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
|
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.
|
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
|
6
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
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
|
-
|
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
|
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
|
-
|
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
|
111
|
-
#
|
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
|
-
@
|
139
|
-
|
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
|
-
@
|
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
|
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
|
6
|
-
#
|
7
|
-
|
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
|
-
|
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
|
-
# 23 hrs, 59 mins,
|
30
|
-
#
|
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) # =>
|
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..
|
92
|
-
when
|
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
|
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
|
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
|
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/
|
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), :
|
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
|
232
|
-
#
|
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 :
|
272
|
-
# <%=
|
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
|
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
|
-
|
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
|
355
|
+
options[:html] ||= {}
|
300
356
|
|
301
|
-
case
|
357
|
+
case record
|
302
358
|
when String, Symbol
|
303
|
-
|
304
|
-
|
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
|
312
|
-
object_name = options[:as] || ActiveModel::Naming.
|
313
|
-
apply_form_for_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
|
-
|
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
|
-
|
320
|
-
|
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
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
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[:
|
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(
|
531
|
-
|
532
|
-
|
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
|
-
#
|
618
|
+
# Localization can also be based purely on the translation of the attribute-name
|
619
|
+
# (if you are using ActiveRecord):
|
575
620
|
#
|
576
|
-
#
|
577
|
-
#
|
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"
|
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]"
|
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"
|
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
|
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
|
-
|
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 }
|
860
|
-
DEFAULT_RADIO_OPTIONS = { }
|
861
|
-
DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
|
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
|
-
@
|
867
|
-
|
868
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
1086
|
+
class << self
|
1014
1087
|
def value(object, method_name)
|
1015
|
-
object.send method_name
|
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
|
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
|
-
|
1106
|
-
self.field_helpers =
|
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
|
-
|
1154
|
-
|
1155
|
-
|
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 =
|
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
|
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
|
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
|
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
|
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
|
1364
|
+
object = convert_to_model(object)
|
1285
1365
|
|
1286
|
-
|
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
|
|