halorgium-actionpack 3.0.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. data/CHANGELOG +5179 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +409 -0
  4. data/lib/abstract_controller.rb +16 -0
  5. data/lib/abstract_controller/base.rb +158 -0
  6. data/lib/abstract_controller/callbacks.rb +113 -0
  7. data/lib/abstract_controller/exceptions.rb +12 -0
  8. data/lib/abstract_controller/helpers.rb +151 -0
  9. data/lib/abstract_controller/layouts.rb +250 -0
  10. data/lib/abstract_controller/localized_cache.rb +49 -0
  11. data/lib/abstract_controller/logger.rb +61 -0
  12. data/lib/abstract_controller/rendering_controller.rb +188 -0
  13. data/lib/action_controller.rb +72 -0
  14. data/lib/action_controller/base.rb +168 -0
  15. data/lib/action_controller/caching.rb +80 -0
  16. data/lib/action_controller/caching/actions.rb +163 -0
  17. data/lib/action_controller/caching/fragments.rb +116 -0
  18. data/lib/action_controller/caching/pages.rb +154 -0
  19. data/lib/action_controller/caching/sweeping.rb +97 -0
  20. data/lib/action_controller/deprecated.rb +4 -0
  21. data/lib/action_controller/deprecated/integration_test.rb +2 -0
  22. data/lib/action_controller/deprecated/performance_test.rb +1 -0
  23. data/lib/action_controller/dispatch/dispatcher.rb +57 -0
  24. data/lib/action_controller/metal.rb +129 -0
  25. data/lib/action_controller/metal/benchmarking.rb +73 -0
  26. data/lib/action_controller/metal/compatibility.rb +145 -0
  27. data/lib/action_controller/metal/conditional_get.rb +86 -0
  28. data/lib/action_controller/metal/configuration.rb +28 -0
  29. data/lib/action_controller/metal/cookies.rb +105 -0
  30. data/lib/action_controller/metal/exceptions.rb +55 -0
  31. data/lib/action_controller/metal/filter_parameter_logging.rb +77 -0
  32. data/lib/action_controller/metal/flash.rb +162 -0
  33. data/lib/action_controller/metal/head.rb +27 -0
  34. data/lib/action_controller/metal/helpers.rb +115 -0
  35. data/lib/action_controller/metal/hide_actions.rb +47 -0
  36. data/lib/action_controller/metal/http_authentication.rb +312 -0
  37. data/lib/action_controller/metal/layouts.rb +171 -0
  38. data/lib/action_controller/metal/mime_responds.rb +317 -0
  39. data/lib/action_controller/metal/rack_convenience.rb +27 -0
  40. data/lib/action_controller/metal/redirector.rb +22 -0
  41. data/lib/action_controller/metal/render_options.rb +103 -0
  42. data/lib/action_controller/metal/rendering_controller.rb +57 -0
  43. data/lib/action_controller/metal/request_forgery_protection.rb +108 -0
  44. data/lib/action_controller/metal/rescuable.rb +13 -0
  45. data/lib/action_controller/metal/responder.rb +200 -0
  46. data/lib/action_controller/metal/session.rb +15 -0
  47. data/lib/action_controller/metal/session_management.rb +45 -0
  48. data/lib/action_controller/metal/streaming.rb +188 -0
  49. data/lib/action_controller/metal/testing.rb +39 -0
  50. data/lib/action_controller/metal/url_for.rb +41 -0
  51. data/lib/action_controller/metal/verification.rb +130 -0
  52. data/lib/action_controller/middleware.rb +38 -0
  53. data/lib/action_controller/notifications.rb +10 -0
  54. data/lib/action_controller/polymorphic_routes.rb +183 -0
  55. data/lib/action_controller/record_identifier.rb +91 -0
  56. data/lib/action_controller/testing/process.rb +111 -0
  57. data/lib/action_controller/testing/test_case.rb +345 -0
  58. data/lib/action_controller/translation.rb +13 -0
  59. data/lib/action_controller/url_rewriter.rb +204 -0
  60. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  61. data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
  62. data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
  63. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +176 -0
  64. data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
  65. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
  66. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  67. data/lib/action_dispatch.rb +70 -0
  68. data/lib/action_dispatch/http/headers.rb +33 -0
  69. data/lib/action_dispatch/http/mime_type.rb +231 -0
  70. data/lib/action_dispatch/http/mime_types.rb +23 -0
  71. data/lib/action_dispatch/http/request.rb +539 -0
  72. data/lib/action_dispatch/http/response.rb +290 -0
  73. data/lib/action_dispatch/http/status_codes.rb +42 -0
  74. data/lib/action_dispatch/http/utils.rb +20 -0
  75. data/lib/action_dispatch/middleware/callbacks.rb +50 -0
  76. data/lib/action_dispatch/middleware/params_parser.rb +79 -0
  77. data/lib/action_dispatch/middleware/rescue.rb +26 -0
  78. data/lib/action_dispatch/middleware/session/abstract_store.rb +208 -0
  79. data/lib/action_dispatch/middleware/session/cookie_store.rb +235 -0
  80. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +47 -0
  81. data/lib/action_dispatch/middleware/show_exceptions.rb +143 -0
  82. data/lib/action_dispatch/middleware/stack.rb +116 -0
  83. data/lib/action_dispatch/middleware/static.rb +44 -0
  84. data/lib/action_dispatch/middleware/string_coercion.rb +29 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +24 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +26 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +10 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +29 -0
  89. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +2 -0
  90. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +10 -0
  91. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +21 -0
  92. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +2 -0
  93. data/lib/action_dispatch/routing.rb +381 -0
  94. data/lib/action_dispatch/routing/deprecated_mapper.rb +878 -0
  95. data/lib/action_dispatch/routing/mapper.rb +327 -0
  96. data/lib/action_dispatch/routing/route.rb +49 -0
  97. data/lib/action_dispatch/routing/route_set.rb +497 -0
  98. data/lib/action_dispatch/testing/assertions.rb +8 -0
  99. data/lib/action_dispatch/testing/assertions/dom.rb +35 -0
  100. data/lib/action_dispatch/testing/assertions/model.rb +19 -0
  101. data/lib/action_dispatch/testing/assertions/response.rb +145 -0
  102. data/lib/action_dispatch/testing/assertions/routing.rb +144 -0
  103. data/lib/action_dispatch/testing/assertions/selector.rb +639 -0
  104. data/lib/action_dispatch/testing/assertions/tag.rb +123 -0
  105. data/lib/action_dispatch/testing/integration.rb +504 -0
  106. data/lib/action_dispatch/testing/performance_test.rb +15 -0
  107. data/lib/action_dispatch/testing/test_request.rb +83 -0
  108. data/lib/action_dispatch/testing/test_response.rb +131 -0
  109. data/lib/action_pack.rb +24 -0
  110. data/lib/action_pack/version.rb +9 -0
  111. data/lib/action_view.rb +58 -0
  112. data/lib/action_view/base.rb +308 -0
  113. data/lib/action_view/context.rb +44 -0
  114. data/lib/action_view/erb/util.rb +48 -0
  115. data/lib/action_view/helpers.rb +62 -0
  116. data/lib/action_view/helpers/active_model_helper.rb +306 -0
  117. data/lib/action_view/helpers/ajax_helper.rb +68 -0
  118. data/lib/action_view/helpers/asset_tag_helper.rb +830 -0
  119. data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
  120. data/lib/action_view/helpers/cache_helper.rb +39 -0
  121. data/lib/action_view/helpers/capture_helper.rb +168 -0
  122. data/lib/action_view/helpers/date_helper.rb +988 -0
  123. data/lib/action_view/helpers/debug_helper.rb +38 -0
  124. data/lib/action_view/helpers/form_helper.rb +1102 -0
  125. data/lib/action_view/helpers/form_options_helper.rb +600 -0
  126. data/lib/action_view/helpers/form_tag_helper.rb +495 -0
  127. data/lib/action_view/helpers/javascript_helper.rb +208 -0
  128. data/lib/action_view/helpers/number_helper.rb +311 -0
  129. data/lib/action_view/helpers/prototype_helper.rb +1309 -0
  130. data/lib/action_view/helpers/raw_output_helper.rb +9 -0
  131. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  132. data/lib/action_view/helpers/record_tag_helper.rb +58 -0
  133. data/lib/action_view/helpers/sanitize_helper.rb +259 -0
  134. data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
  135. data/lib/action_view/helpers/tag_helper.rb +151 -0
  136. data/lib/action_view/helpers/text_helper.rb +594 -0
  137. data/lib/action_view/helpers/translation_helper.rb +39 -0
  138. data/lib/action_view/helpers/url_helper.rb +639 -0
  139. data/lib/action_view/locale/en.yml +117 -0
  140. data/lib/action_view/paths.rb +80 -0
  141. data/lib/action_view/render/partials.rb +342 -0
  142. data/lib/action_view/render/rendering.rb +134 -0
  143. data/lib/action_view/safe_buffer.rb +28 -0
  144. data/lib/action_view/template/error.rb +101 -0
  145. data/lib/action_view/template/handler.rb +36 -0
  146. data/lib/action_view/template/handlers.rb +52 -0
  147. data/lib/action_view/template/handlers/builder.rb +17 -0
  148. data/lib/action_view/template/handlers/erb.rb +53 -0
  149. data/lib/action_view/template/handlers/rjs.rb +18 -0
  150. data/lib/action_view/template/resolver.rb +165 -0
  151. data/lib/action_view/template/template.rb +131 -0
  152. data/lib/action_view/template/text.rb +38 -0
  153. data/lib/action_view/test_case.rb +163 -0
  154. metadata +236 -0
@@ -0,0 +1,38 @@
1
+ module ActionView
2
+ module Helpers
3
+ # Provides a set of methods for making it easier to debug Rails objects.
4
+ module DebugHelper
5
+ # Returns a YAML representation of +object+ wrapped with <pre> and </pre>.
6
+ # If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
7
+ # Useful for inspecting an object at the time of rendering.
8
+ #
9
+ # ==== Example
10
+ #
11
+ # @user = User.new({ :username => 'testing', :password => 'xyz', :age => 42}) %>
12
+ # debug(@user)
13
+ # # =>
14
+ # <pre class='debug_dump'>--- !ruby/object:User
15
+ # attributes:
16
+ # &nbsp; updated_at:
17
+ # &nbsp; username: testing
18
+ #
19
+ # &nbsp; age: 42
20
+ # &nbsp; password: xyz
21
+ # &nbsp; created_at:
22
+ # attributes_cache: {}
23
+ #
24
+ # new_record: true
25
+ # </pre>
26
+
27
+ def debug(object)
28
+ begin
29
+ Marshal::dump(object)
30
+ "<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", "&nbsp; ")}</pre>"
31
+ rescue Exception => e # errors from Marshal or YAML
32
+ # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
33
+ "<code class='debug_dump'>#{h(object.inspect)}</code>"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,1102 @@
1
+ require 'cgi'
2
+ require 'action_view/helpers/date_helper'
3
+ require 'action_view/helpers/tag_helper'
4
+ require 'action_view/helpers/form_tag_helper'
5
+ require 'active_support/core_ext/class/inheritable_attributes'
6
+ require 'active_support/core_ext/hash/slice'
7
+
8
+ module ActionView
9
+ module Helpers
10
+ # Form helpers are designed to make working with models much easier
11
+ # compared to using just standard HTML elements by providing a set of
12
+ # methods for creating forms based on your models. This helper generates
13
+ # the HTML for forms, providing a method for each sort of input
14
+ # (e.g., text, password, select, and so on). When the form is submitted
15
+ # (i.e., when the user hits the submit button or <tt>form.submit</tt> is
16
+ # called via JavaScript), the form inputs will be bundled into the
17
+ # <tt>params</tt> object and passed back to the controller.
18
+ #
19
+ # There are two types of form helpers: those that specifically work with
20
+ # model attributes and those that don't. This helper deals with those that
21
+ # work with model attributes; to see an example of form helpers that don't
22
+ # work with model attributes, check the ActionView::Helpers::FormTagHelper
23
+ # documentation.
24
+ #
25
+ # The core method of this helper, form_for, gives you the ability to create
26
+ # a form for a model instance; for example, let's say that you have a model
27
+ # <tt>Person</tt> and want to create a new instance of it:
28
+ #
29
+ # # Note: a @person variable will have been created in the controller.
30
+ # # For example: @person = Person.new
31
+ # <% form_for :person, @person, :url => { :action => "create" } do |f| %>
32
+ # <%= f.text_field :first_name %>
33
+ # <%= f.text_field :last_name %>
34
+ # <%= submit_tag 'Create' %>
35
+ # <% end %>
36
+ #
37
+ # The HTML generated for this would be:
38
+ #
39
+ # <form action="/persons/create" method="post">
40
+ # <input id="person_first_name" name="person[first_name]" size="30" type="text" />
41
+ # <input id="person_last_name" name="person[last_name]" size="30" type="text" />
42
+ # <input name="commit" type="submit" value="Create" />
43
+ # </form>
44
+ #
45
+ # If you are using a partial for your form fields, you can use this shortcut:
46
+ #
47
+ # <% form_for :person, @person, :url => { :action => "create" } do |f| %>
48
+ # <%= render :partial => f %>
49
+ # <%= submit_tag 'Create' %>
50
+ # <% end %>
51
+ #
52
+ # This example will render the <tt>people/_form</tt> partial, setting a
53
+ # local variable called <tt>form</tt> which references the yielded
54
+ # FormBuilder. The <tt>params</tt> object created when this form is
55
+ # submitted would look like:
56
+ #
57
+ # {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
58
+ #
59
+ # The params hash has a nested <tt>person</tt> value, which can therefore
60
+ # be accessed with <tt>params[:person]</tt> in the controller. If were
61
+ # editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than
62
+ # <tt>Person.new</tt> in the controller), the objects attribute values are
63
+ # filled into the form (e.g., the <tt>person_first_name</tt> field would
64
+ # have that person's first name in it).
65
+ #
66
+ # If the object name contains square brackets the id for the object will be
67
+ # inserted. For example:
68
+ #
69
+ # <%= text_field "person[]", "name" %>
70
+ #
71
+ # ...will generate the following ERb.
72
+ #
73
+ # <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
74
+ #
75
+ # If the helper is being used to generate a repetitive sequence of similar
76
+ # form elements, for example in a partial used by
77
+ # <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may
78
+ # come in handy. Example:
79
+ #
80
+ # <%= text_field "person", "name", "index" => 1 %>
81
+ #
82
+ # ...becomes...
83
+ #
84
+ # <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
85
+ #
86
+ # An <tt>index</tt> option may also be passed to <tt>form_for</tt> and
87
+ # <tt>fields_for</tt>. This automatically applies the <tt>index</tt> to
88
+ # all the nested fields.
89
+ #
90
+ # There are also methods for helping to build form tags in
91
+ # link:classes/ActionView/Helpers/FormOptionsHelper.html,
92
+ # link:classes/ActionView/Helpers/DateHelper.html, and
93
+ # link:classes/ActionView/Helpers/ActiveRecordHelper.html
94
+ module FormHelper
95
+ # Creates a form and a scope around a specific model object that is used
96
+ # as a base for questioning about values for the fields.
97
+ #
98
+ # Rails provides succinct resource-oriented form generation with +form_for+
99
+ # like this:
100
+ #
101
+ # <% form_for @offer do |f| %>
102
+ # <%= f.label :version, 'Version' %>:
103
+ # <%= f.text_field :version %><br />
104
+ # <%= f.label :author, 'Author' %>:
105
+ # <%= f.text_field :author %><br />
106
+ # <% end %>
107
+ #
108
+ # There, +form_for+ is able to generate the rest of RESTful form
109
+ # parameters based on introspection on the record, but to understand what
110
+ # it does we need to dig first into the alternative generic usage it is
111
+ # based upon.
112
+ #
113
+ # === Generic form_for
114
+ #
115
+ # The generic way to call +form_for+ yields a form builder around a
116
+ # model:
117
+ #
118
+ # <% form_for :person, :url => { :action => "update" } do |f| %>
119
+ # <%= f.error_messages %>
120
+ # First name: <%= f.text_field :first_name %><br />
121
+ # Last name : <%= f.text_field :last_name %><br />
122
+ # Biography : <%= f.text_area :biography %><br />
123
+ # Admin? : <%= f.check_box :admin %><br />
124
+ # <% end %>
125
+ #
126
+ # There, the first argument is a symbol or string with the name of the
127
+ # object the form is about, and also the name of the instance variable
128
+ # the object is stored in.
129
+ #
130
+ # The form builder acts as a regular form helper that somehow carries the
131
+ # model. Thus, the idea is that
132
+ #
133
+ # <%= f.text_field :first_name %>
134
+ #
135
+ # gets expanded to
136
+ #
137
+ # <%= text_field :person, :first_name %>
138
+ #
139
+ # If the instance variable is not <tt>@person</tt> you can pass the actual
140
+ # record as the second argument:
141
+ #
142
+ # <% form_for :person, person, :url => { :action => "update" } do |f| %>
143
+ # ...
144
+ # <% end %>
145
+ #
146
+ # In that case you can think
147
+ #
148
+ # <%= f.text_field :first_name %>
149
+ #
150
+ # gets expanded to
151
+ #
152
+ # <%= text_field :person, :first_name, :object => person %>
153
+ #
154
+ # You can even display error messages of the wrapped model this way:
155
+ #
156
+ # <%= f.error_messages %>
157
+ #
158
+ # In any of its variants, the rightmost argument to +form_for+ is an
159
+ # optional hash of options:
160
+ #
161
+ # * <tt>:url</tt> - The URL the form is submitted to. It takes the same
162
+ # fields you pass to +url_for+ or +link_to+. In particular you may pass
163
+ # here a named route directly as well. Defaults to the current action.
164
+ # * <tt>:html</tt> - Optional HTML attributes for the form tag.
165
+ #
166
+ # Worth noting is that the +form_for+ tag is called in a ERb evaluation
167
+ # block, not an ERb output block. So that's <tt><% %></tt>, not
168
+ # <tt><%= %></tt>.
169
+ #
170
+ # Also note that +form_for+ doesn't create an exclusive scope. It's still
171
+ # possible to use both the stand-alone FormHelper methods and methods
172
+ # from FormTagHelper. For example:
173
+ #
174
+ # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
175
+ # First name: <%= f.text_field :first_name %>
176
+ # Last name : <%= f.text_field :last_name %>
177
+ # Biography : <%= text_area :person, :biography %>
178
+ # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
179
+ # <% end %>
180
+ #
181
+ # This also works for the methods in FormOptionHelper and DateHelper that
182
+ # are designed to work with an object as base, like
183
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
184
+ #
185
+ # === Resource-oriented style
186
+ #
187
+ # As we said above, in addition to manually configuring the +form_for+
188
+ # call, you can rely on automated resource identification, which will use
189
+ # the conventions and named routes of that approach. This is the
190
+ # preferred way to use +form_for+ nowadays.
191
+ #
192
+ # For example, if <tt>@post</tt> is an existing record you want to edit
193
+ #
194
+ # <% form_for @post do |f| %>
195
+ # ...
196
+ # <% end %>
197
+ #
198
+ # is equivalent to something like:
199
+ #
200
+ # <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
201
+ # ...
202
+ # <% end %>
203
+ #
204
+ # And for new records
205
+ #
206
+ # <% form_for(Post.new) do |f| %>
207
+ # ...
208
+ # <% end %>
209
+ #
210
+ # expands to
211
+ #
212
+ # <% form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
213
+ # ...
214
+ # <% end %>
215
+ #
216
+ # You can also overwrite the individual conventions, like this:
217
+ #
218
+ # <% form_for(@post, :url => super_post_path(@post)) do |f| %>
219
+ # ...
220
+ # <% end %>
221
+ #
222
+ # And for namespaced routes, like +admin_post_url+:
223
+ #
224
+ # <% form_for([:admin, @post]) do |f| %>
225
+ # ...
226
+ # <% end %>
227
+ #
228
+ # === Customized form builders
229
+ #
230
+ # You can also build forms using a customized FormBuilder class. Subclass
231
+ # FormBuilder and override or define some more helpers, then use your
232
+ # custom builder. For example, let's say you made a helper to
233
+ # automatically add labels to form inputs.
234
+ #
235
+ # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
236
+ # <%= f.text_field :first_name %>
237
+ # <%= f.text_field :last_name %>
238
+ # <%= text_area :person, :biography %>
239
+ # <%= check_box_tag "person[admin]", @person.company.admin? %>
240
+ # <% end %>
241
+ #
242
+ # In this case, if you use this:
243
+ #
244
+ # <%= render :partial => f %>
245
+ #
246
+ # The rendered template is <tt>people/_labelling_form</tt> and the local
247
+ # variable referencing the form builder is called
248
+ # <tt>labelling_form</tt>.
249
+ #
250
+ # The custom FormBuilder class is automatically merged with the options
251
+ # of a nested fields_for call, unless it's explicitely set.
252
+ #
253
+ # In many cases you will want to wrap the above in another helper, so you
254
+ # could do something like the following:
255
+ #
256
+ # def labelled_form_for(record_or_name_or_array, *args, &proc)
257
+ # options = args.extract_options!
258
+ # form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc)
259
+ # end
260
+ #
261
+ # If you don't need to attach a form to a model instance, then check out
262
+ # FormTagHelper#form_tag.
263
+ def form_for(record_or_name_or_array, *args, &proc)
264
+ raise ArgumentError, "Missing block" unless block_given?
265
+
266
+ options = args.extract_options!
267
+
268
+ case record_or_name_or_array
269
+ when String, Symbol
270
+ object_name = record_or_name_or_array
271
+ when Array
272
+ object = record_or_name_or_array.last
273
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
274
+ apply_form_for_options!(record_or_name_or_array, options)
275
+ args.unshift object
276
+ else
277
+ object = record_or_name_or_array
278
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
279
+ apply_form_for_options!([object], options)
280
+ args.unshift object
281
+ end
282
+
283
+ concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}))
284
+ fields_for(object_name, *(args << options), &proc)
285
+ concat('</form>'.html_safe!)
286
+ end
287
+
288
+ def apply_form_for_options!(object_or_array, options) #:nodoc:
289
+ object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
290
+
291
+ object = convert_to_model(object)
292
+
293
+ html_options =
294
+ if object.respond_to?(:new_record?) && object.new_record?
295
+ { :class => dom_class(object, :new), :id => dom_id(object), :method => :post }
296
+ else
297
+ { :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put }
298
+ end
299
+
300
+ options[:html] ||= {}
301
+ options[:html].reverse_merge!(html_options)
302
+ options[:url] ||= polymorphic_path(object_or_array)
303
+ end
304
+
305
+ # Creates a scope around a specific model object like form_for, but
306
+ # doesn't create the form tags themselves. This makes fields_for suitable
307
+ # for specifying additional model objects in the same form.
308
+ #
309
+ # === Generic Examples
310
+ #
311
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
312
+ # First name: <%= person_form.text_field :first_name %>
313
+ # Last name : <%= person_form.text_field :last_name %>
314
+ #
315
+ # <% fields_for @person.permission do |permission_fields| %>
316
+ # Admin? : <%= permission_fields.check_box :admin %>
317
+ # <% end %>
318
+ # <% end %>
319
+ #
320
+ # ...or if you have an object that needs to be represented as a different
321
+ # parameter, like a Client that acts as a Person:
322
+ #
323
+ # <% fields_for :person, @client do |permission_fields| %>
324
+ # Admin?: <%= permission_fields.check_box :admin %>
325
+ # <% end %>
326
+ #
327
+ # ...or if you don't have an object, just a name of the parameter:
328
+ #
329
+ # <% fields_for :person do |permission_fields| %>
330
+ # Admin?: <%= permission_fields.check_box :admin %>
331
+ # <% end %>
332
+ #
333
+ # Note: This also works for the methods in FormOptionHelper and
334
+ # DateHelper that are designed to work with an object as base, like
335
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
336
+ #
337
+ # === Nested Attributes Examples
338
+ #
339
+ # When the object belonging to the current scope has a nested attribute
340
+ # writer for a certain attribute, fields_for will yield a new scope
341
+ # for that attribute. This allows you to create forms that set or change
342
+ # the attributes of a parent object and its associations in one go.
343
+ #
344
+ # Nested attribute writers are normal setter methods named after an
345
+ # association. The most common way of defining these writers is either
346
+ # with +accepts_nested_attributes_for+ in a model definition or by
347
+ # defining a method with the proper name. For example: the attribute
348
+ # writer for the association <tt>:address</tt> is called
349
+ # <tt>address_attributes=</tt>.
350
+ #
351
+ # Whether a one-to-one or one-to-many style form builder will be yielded
352
+ # depends on whether the normal reader method returns a _single_ object
353
+ # or an _array_ of objects.
354
+ #
355
+ # ==== One-to-one
356
+ #
357
+ # Consider a Person class which returns a _single_ Address from the
358
+ # <tt>address</tt> reader method and responds to the
359
+ # <tt>address_attributes=</tt> writer method:
360
+ #
361
+ # class Person
362
+ # def address
363
+ # @address
364
+ # end
365
+ #
366
+ # def address_attributes=(attributes)
367
+ # # Process the attributes hash
368
+ # end
369
+ # end
370
+ #
371
+ # This model can now be used with a nested fields_for, like so:
372
+ #
373
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
374
+ # ...
375
+ # <% person_form.fields_for :address do |address_fields| %>
376
+ # Street : <%= address_fields.text_field :street %>
377
+ # Zip code: <%= address_fields.text_field :zip_code %>
378
+ # <% end %>
379
+ # <% end %>
380
+ #
381
+ # When address is already an association on a Person you can use
382
+ # +accepts_nested_attributes_for+ to define the writer method for you:
383
+ #
384
+ # class Person < ActiveRecord::Base
385
+ # has_one :address
386
+ # accepts_nested_attributes_for :address
387
+ # end
388
+ #
389
+ # If you want to destroy the associated model through the form, you have
390
+ # to enable it first using the <tt>:allow_destroy</tt> option for
391
+ # +accepts_nested_attributes_for+:
392
+ #
393
+ # class Person < ActiveRecord::Base
394
+ # has_one :address
395
+ # accepts_nested_attributes_for :address, :allow_destroy => true
396
+ # end
397
+ #
398
+ # Now, when you use a form element with the <tt>_delete</tt> parameter,
399
+ # with a value that evaluates to +true+, you will destroy the associated
400
+ # model (eg. 1, '1', true, or 'true'):
401
+ #
402
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
403
+ # ...
404
+ # <% person_form.fields_for :address do |address_fields| %>
405
+ # ...
406
+ # Delete: <%= address_fields.check_box :_delete %>
407
+ # <% end %>
408
+ # <% end %>
409
+ #
410
+ # ==== One-to-many
411
+ #
412
+ # Consider a Person class which returns an _array_ of Project instances
413
+ # from the <tt>projects</tt> reader method and responds to the
414
+ # <tt>projects_attributes=</tt> writer method:
415
+ #
416
+ # class Person
417
+ # def projects
418
+ # [@project1, @project2]
419
+ # end
420
+ #
421
+ # def projects_attributes=(attributes)
422
+ # # Process the attributes hash
423
+ # end
424
+ # end
425
+ #
426
+ # This model can now be used with a nested fields_for. The block given to
427
+ # the nested fields_for call will be repeated for each instance in the
428
+ # collection:
429
+ #
430
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
431
+ # ...
432
+ # <% person_form.fields_for :projects do |project_fields| %>
433
+ # <% if project_fields.object.active? %>
434
+ # Name: <%= project_fields.text_field :name %>
435
+ # <% end %>
436
+ # <% end %>
437
+ # <% end %>
438
+ #
439
+ # It's also possible to specify the instance to be used:
440
+ #
441
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
442
+ # ...
443
+ # <% @person.projects.each do |project| %>
444
+ # <% if project.active? %>
445
+ # <% person_form.fields_for :projects, project do |project_fields| %>
446
+ # Name: <%= project_fields.text_field :name %>
447
+ # <% end %>
448
+ # <% end %>
449
+ # <% end %>
450
+ # <% end %>
451
+ #
452
+ # Or a collection to be used:
453
+ #
454
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
455
+ # ...
456
+ # <% person_form.fields_for :projects, @active_projects do |project_fields| %>
457
+ # Name: <%= project_fields.text_field :name %>
458
+ # <% end %>
459
+ # <% end %>
460
+ #
461
+ # When projects is already an association on Person you can use
462
+ # +accepts_nested_attributes_for+ to define the writer method for you:
463
+ #
464
+ # class Person < ActiveRecord::Base
465
+ # has_many :projects
466
+ # accepts_nested_attributes_for :projects
467
+ # end
468
+ #
469
+ # If you want to destroy any of the associated models through the
470
+ # form, you have to enable it first using the <tt>:allow_destroy</tt>
471
+ # option for +accepts_nested_attributes_for+:
472
+ #
473
+ # class Person < ActiveRecord::Base
474
+ # has_many :projects
475
+ # accepts_nested_attributes_for :projects, :allow_destroy => true
476
+ # end
477
+ #
478
+ # This will allow you to specify which models to destroy in the
479
+ # attributes hash by adding a form element for the <tt>_delete</tt>
480
+ # parameter with a value that evaluates to +true+
481
+ # (eg. 1, '1', true, or 'true'):
482
+ #
483
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
484
+ # ...
485
+ # <% person_form.fields_for :projects do |project_fields| %>
486
+ # Delete: <%= project_fields.check_box :_delete %>
487
+ # <% end %>
488
+ # <% end %>
489
+ def fields_for(record_or_name_or_array, *args, &block)
490
+ raise ArgumentError, "Missing block" unless block_given?
491
+ options = args.extract_options!
492
+
493
+ case record_or_name_or_array
494
+ when String, Symbol
495
+ object_name = record_or_name_or_array
496
+ object = args.first
497
+ else
498
+ object = record_or_name_or_array
499
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
500
+ end
501
+
502
+ builder = options[:builder] || ActionView.default_form_builder
503
+ yield builder.new(object_name, object, self, options, block)
504
+ end
505
+
506
+ # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
507
+ # assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify
508
+ # it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
509
+ # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
510
+ # target labels for radio_button tags (where the value is used in the ID of the input tag).
511
+ #
512
+ # ==== Examples
513
+ # label(:post, :title)
514
+ # # => <label for="post_title">Title</label>
515
+ #
516
+ # label(:post, :title, "A short title")
517
+ # # => <label for="post_title">A short title</label>
518
+ #
519
+ # label(:post, :title, "A short title", :class => "title_label")
520
+ # # => <label for="post_title" class="title_label">A short title</label>
521
+ #
522
+ # label(:post, :privacy, "Public Post", :value => "public")
523
+ # # => <label for="post_privacy_public">Public Post</label>
524
+ #
525
+ def label(object_name, method, text = nil, options = {})
526
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
527
+ end
528
+
529
+ # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
530
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
531
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
532
+ # shown.
533
+ #
534
+ # ==== Examples
535
+ # text_field(:post, :title, :size => 20)
536
+ # # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
537
+ #
538
+ # text_field(:post, :title, :class => "create_input")
539
+ # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
540
+ #
541
+ # text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
542
+ # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }"/>
543
+ #
544
+ # text_field(:snippet, :code, :size => 20, :class => 'code_input')
545
+ # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
546
+ #
547
+ def text_field(object_name, method, options = {})
548
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
549
+ end
550
+
551
+ # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
552
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
553
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
554
+ # shown.
555
+ #
556
+ # ==== Examples
557
+ # password_field(:login, :pass, :size => 20)
558
+ # # => <input type="text" id="login_pass" name="login[pass]" size="20" value="#{@login.pass}" />
559
+ #
560
+ # password_field(:account, :secret, :class => "form_input")
561
+ # # => <input type="text" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
562
+ #
563
+ # password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
564
+ # # => <input type="text" id="user_password" name="user[password]" value="#{@user.password}" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
565
+ #
566
+ # password_field(:account, :pin, :size => 20, :class => 'form_input')
567
+ # # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
568
+ #
569
+ def password_field(object_name, method, options = {})
570
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", options)
571
+ end
572
+
573
+ # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
574
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
575
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
576
+ # shown.
577
+ #
578
+ # ==== Examples
579
+ # hidden_field(:signup, :pass_confirm)
580
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
581
+ #
582
+ # hidden_field(:post, :tag_list)
583
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
584
+ #
585
+ # hidden_field(:user, :token)
586
+ # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
587
+ def hidden_field(object_name, method, options = {})
588
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
589
+ end
590
+
591
+ # Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
592
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
593
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
594
+ # shown.
595
+ #
596
+ # ==== Examples
597
+ # file_field(:user, :avatar)
598
+ # # => <input type="file" id="user_avatar" name="user[avatar]" />
599
+ #
600
+ # file_field(:post, :attached, :accept => 'text/html')
601
+ # # => <input type="file" id="post_attached" name="post[attached]" />
602
+ #
603
+ # file_field(:attachment, :file, :class => 'file_input')
604
+ # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
605
+ #
606
+ def file_field(object_name, method, options = {})
607
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options)
608
+ end
609
+
610
+ # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
611
+ # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
612
+ # hash with +options+.
613
+ #
614
+ # ==== Examples
615
+ # text_area(:post, :body, :cols => 20, :rows => 40)
616
+ # # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
617
+ # # #{@post.body}
618
+ # # </textarea>
619
+ #
620
+ # text_area(:comment, :text, :size => "20x30")
621
+ # # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
622
+ # # #{@comment.text}
623
+ # # </textarea>
624
+ #
625
+ # text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
626
+ # # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
627
+ # # #{@application.notes}
628
+ # # </textarea>
629
+ #
630
+ # text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
631
+ # # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
632
+ # # #{@entry.body}
633
+ # # </textarea>
634
+ def text_area(object_name, method, options = {})
635
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
636
+ end
637
+
638
+ # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
639
+ # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
640
+ # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
641
+ # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
642
+ # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
643
+ #
644
+ # ==== Gotcha
645
+ #
646
+ # The HTML specification says unchecked check boxes are not successful, and
647
+ # thus web browsers do not send them. Unfortunately this introduces a gotcha:
648
+ # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
649
+ # invoice the user unchecks its check box, no +paid+ parameter is sent. So,
650
+ # any mass-assignment idiom like
651
+ #
652
+ # @invoice.update_attributes(params[:invoice])
653
+ #
654
+ # wouldn't update the flag.
655
+ #
656
+ # To prevent this the helper generates an auxiliary hidden field before
657
+ # the very check box. The hidden field has the same name and its
658
+ # attributes mimick an unchecked check box.
659
+ #
660
+ # This way, the client either sends only the hidden field (representing
661
+ # the check box is unchecked), or both fields. Since the HTML specification
662
+ # says key/value pairs have to be sent in the same order they appear in the
663
+ # form, and parameters extraction gets the last occurrence of any repeated
664
+ # key in the query string, that works for ordinary forms.
665
+ #
666
+ # Unfortunately that workaround does not work when the check box goes
667
+ # within an array-like parameter, as in
668
+ #
669
+ # <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
670
+ # <%= form.check_box :paid %>
671
+ # ...
672
+ # <% end %>
673
+ #
674
+ # because parameter name repetition is precisely what Rails seeks to distinguish
675
+ # the elements of the array. For each item with a checked check box you
676
+ # get an extra ghost item with only that attribute, assigned to "0".
677
+ #
678
+ # In that case it is preferable to either use +check_box_tag+ or to use
679
+ # hashes instead of arrays.
680
+ #
681
+ # ==== Examples
682
+ # # Let's say that @post.validated? is 1:
683
+ # check_box("post", "validated")
684
+ # # => <input name="post[validated]" type="hidden" value="0" />
685
+ # # <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
686
+ #
687
+ # # Let's say that @puppy.gooddog is "no":
688
+ # check_box("puppy", "gooddog", {}, "yes", "no")
689
+ # # => <input name="puppy[gooddog]" type="hidden" value="no" />
690
+ # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
691
+ #
692
+ # check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
693
+ # # => <input name="eula[accepted]" type="hidden" value="no" />
694
+ # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
695
+ #
696
+ def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
697
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
698
+ end
699
+
700
+ # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
701
+ # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
702
+ # radio button will be checked.
703
+ #
704
+ # To force the radio button to be checked pass <tt>:checked => true</tt> in the
705
+ # +options+ hash. You may pass HTML options there as well.
706
+ #
707
+ # ==== Examples
708
+ # # Let's say that @post.category returns "rails":
709
+ # radio_button("post", "category", "rails")
710
+ # radio_button("post", "category", "java")
711
+ # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
712
+ # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
713
+ #
714
+ # radio_button("user", "receive_newsletter", "yes")
715
+ # radio_button("user", "receive_newsletter", "no")
716
+ # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
717
+ # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
718
+ def radio_button(object_name, method, tag_value, options = {})
719
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
720
+ end
721
+ end
722
+
723
+ module InstanceTagMethods #:nodoc:
724
+ extend ActiveSupport::Concern
725
+ include Helpers::TagHelper, Helpers::FormTagHelper
726
+
727
+ attr_reader :method_name, :object_name
728
+
729
+ DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
730
+ DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
731
+ DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
732
+
733
+ def initialize(object_name, method_name, template_object, object = nil)
734
+ @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
735
+ @template_object = template_object
736
+ @object = object
737
+ if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
738
+ if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
739
+ @auto_index = object.to_param
740
+ else
741
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
742
+ end
743
+ end
744
+ end
745
+
746
+ def to_label_tag(text = nil, options = {})
747
+ options = options.stringify_keys
748
+ tag_value = options.delete("value")
749
+ name_and_id = options.dup
750
+ name_and_id["id"] = name_and_id["for"]
751
+ add_default_name_and_id_for_value(tag_value, name_and_id)
752
+ options.delete("index")
753
+ options["for"] ||= name_and_id["id"]
754
+ content = (text.blank? ? nil : text.to_s) || method_name.humanize
755
+ label_tag(name_and_id["id"], content, options)
756
+ end
757
+
758
+ def to_input_field_tag(field_type, options = {})
759
+ options = options.stringify_keys
760
+ options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
761
+ options = DEFAULT_FIELD_OPTIONS.merge(options)
762
+ if field_type == "hidden"
763
+ options.delete("size")
764
+ end
765
+ options["type"] = field_type
766
+ options["value"] ||= value_before_type_cast(object) unless field_type == "file"
767
+ options["value"] &&= html_escape(options["value"])
768
+ add_default_name_and_id(options)
769
+ tag("input", options)
770
+ end
771
+
772
+ def to_radio_button_tag(tag_value, options = {})
773
+ options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
774
+ options["type"] = "radio"
775
+ options["value"] = tag_value
776
+ if options.has_key?("checked")
777
+ cv = options.delete "checked"
778
+ checked = cv == true || cv == "checked"
779
+ else
780
+ checked = self.class.radio_button_checked?(value(object), tag_value)
781
+ end
782
+ options["checked"] = "checked" if checked
783
+ add_default_name_and_id_for_value(tag_value, options)
784
+ tag("input", options)
785
+ end
786
+
787
+ def to_text_area_tag(options = {})
788
+ options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
789
+ add_default_name_and_id(options)
790
+
791
+ if size = options.delete("size")
792
+ options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
793
+ end
794
+
795
+ content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
796
+ end
797
+
798
+ def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
799
+ options = options.stringify_keys
800
+ options["type"] = "checkbox"
801
+ options["value"] = checked_value
802
+ if options.has_key?("checked")
803
+ cv = options.delete "checked"
804
+ checked = cv == true || cv == "checked"
805
+ else
806
+ checked = self.class.check_box_checked?(value(object), checked_value)
807
+ end
808
+ options["checked"] = "checked" if checked
809
+ add_default_name_and_id(options)
810
+ hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
811
+ checkbox = tag("input", options)
812
+ (hidden + checkbox).html_safe!
813
+ end
814
+
815
+ def to_boolean_select_tag(options = {})
816
+ options = options.stringify_keys
817
+ add_default_name_and_id(options)
818
+ value = value(object)
819
+ tag_text = "<select"
820
+ tag_text << tag_options(options)
821
+ tag_text << "><option value=\"false\""
822
+ tag_text << " selected" if value == false
823
+ tag_text << ">False</option><option value=\"true\""
824
+ tag_text << " selected" if value
825
+ tag_text << ">True</option></select>"
826
+ end
827
+
828
+ def to_content_tag(tag_name, options = {})
829
+ content_tag(tag_name, value(object), options)
830
+ end
831
+
832
+ def object
833
+ @object || @template_object.instance_variable_get("@#{@object_name}")
834
+ rescue NameError
835
+ # As @object_name may contain the nested syntax (item[subobject]) we
836
+ # need to fallback to nil.
837
+ nil
838
+ end
839
+
840
+ def value(object)
841
+ self.class.value(object, @method_name)
842
+ end
843
+
844
+ def value_before_type_cast(object)
845
+ self.class.value_before_type_cast(object, @method_name)
846
+ end
847
+
848
+ module ClassMethods
849
+ def value(object, method_name)
850
+ object.send method_name unless object.nil?
851
+ end
852
+
853
+ def value_before_type_cast(object, method_name)
854
+ unless object.nil?
855
+ object.respond_to?(method_name + "_before_type_cast") ?
856
+ object.send(method_name + "_before_type_cast") :
857
+ object.send(method_name)
858
+ end
859
+ end
860
+
861
+ def check_box_checked?(value, checked_value)
862
+ case value
863
+ when TrueClass, FalseClass
864
+ value
865
+ when NilClass
866
+ false
867
+ when Integer
868
+ value != 0
869
+ when String
870
+ value == checked_value
871
+ when Array
872
+ value.include?(checked_value)
873
+ else
874
+ value.to_i != 0
875
+ end
876
+ end
877
+
878
+ def radio_button_checked?(value, checked_value)
879
+ value.to_s == checked_value.to_s
880
+ end
881
+ end
882
+
883
+ private
884
+ def add_default_name_and_id_for_value(tag_value, options)
885
+ unless tag_value.nil?
886
+ pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
887
+ specified_id = options["id"]
888
+ add_default_name_and_id(options)
889
+ options["id"] += "_#{pretty_tag_value}" unless specified_id
890
+ else
891
+ add_default_name_and_id(options)
892
+ end
893
+ end
894
+
895
+ def add_default_name_and_id(options)
896
+ if options.has_key?("index")
897
+ options["name"] ||= tag_name_with_index(options["index"])
898
+ options["id"] ||= tag_id_with_index(options["index"])
899
+ options.delete("index")
900
+ elsif defined?(@auto_index)
901
+ options["name"] ||= tag_name_with_index(@auto_index)
902
+ options["id"] ||= tag_id_with_index(@auto_index)
903
+ else
904
+ options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
905
+ options["id"] ||= tag_id
906
+ end
907
+ end
908
+
909
+ def tag_name
910
+ "#{@object_name}[#{sanitized_method_name}]"
911
+ end
912
+
913
+ def tag_name_with_index(index)
914
+ "#{@object_name}[#{index}][#{sanitized_method_name}]"
915
+ end
916
+
917
+ def tag_id
918
+ "#{sanitized_object_name}_#{sanitized_method_name}"
919
+ end
920
+
921
+ def tag_id_with_index(index)
922
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
923
+ end
924
+
925
+ def sanitized_object_name
926
+ @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
927
+ end
928
+
929
+ def sanitized_method_name
930
+ @sanitized_method_name ||= @method_name.sub(/\?$/,"")
931
+ end
932
+ end
933
+
934
+ class InstanceTag
935
+ include InstanceTagMethods
936
+ end
937
+
938
+ class FormBuilder #:nodoc:
939
+ # The methods which wrap a form helper call.
940
+ class_inheritable_accessor :field_helpers
941
+ self.field_helpers = (FormHelper.instance_methods - ['form_for'])
942
+
943
+ attr_accessor :object_name, :object, :options
944
+
945
+ def self.model_name
946
+ @model_name ||= Struct.new(:partial_path).new(name.demodulize.underscore.sub!(/_builder$/, ''))
947
+ end
948
+
949
+ def to_model
950
+ self
951
+ end
952
+
953
+ def initialize(object_name, object, template, options, proc)
954
+ @nested_child_index = {}
955
+ @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
956
+ @default_options = @options ? @options.slice(:index) : {}
957
+ if @object_name.to_s.match(/\[\]$/)
958
+ if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
959
+ @auto_index = object.to_param
960
+ else
961
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
962
+ end
963
+ end
964
+ end
965
+
966
+ (field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
967
+ src = <<-end_src
968
+ def #{selector}(method, options = {}) # def text_field(method, options = {})
969
+ @template.send( # @template.send(
970
+ #{selector.inspect}, # "text_field",
971
+ @object_name, # @object_name,
972
+ method, # method,
973
+ objectify_options(options)) # objectify_options(options))
974
+ end # end
975
+ end_src
976
+ class_eval src, __FILE__, __LINE__
977
+ end
978
+
979
+ def fields_for(record_or_name_or_array, *args, &block)
980
+ if options.has_key?(:index)
981
+ index = "[#{options[:index]}]"
982
+ elsif defined?(@auto_index)
983
+ self.object_name = @object_name.to_s.sub(/\[\]$/,"")
984
+ index = "[#{@auto_index}]"
985
+ else
986
+ index = ""
987
+ end
988
+
989
+ if options[:builder]
990
+ args << {} unless args.last.is_a?(Hash)
991
+ args.last[:builder] ||= options[:builder]
992
+ end
993
+
994
+ case record_or_name_or_array
995
+ when String, Symbol
996
+ if nested_attributes_association?(record_or_name_or_array)
997
+ return fields_for_with_nested_attributes(record_or_name_or_array, args, block)
998
+ else
999
+ name = "#{object_name}#{index}[#{record_or_name_or_array}]"
1000
+ end
1001
+ when Array
1002
+ object = record_or_name_or_array.last
1003
+ name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
1004
+ args.unshift(object)
1005
+ else
1006
+ object = record_or_name_or_array
1007
+ name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
1008
+ args.unshift(object)
1009
+ end
1010
+
1011
+ @template.fields_for(name, *args, &block)
1012
+ end
1013
+
1014
+ def label(method, text = nil, options = {})
1015
+ @template.label(@object_name, method, text, objectify_options(options))
1016
+ end
1017
+
1018
+ def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
1019
+ @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
1020
+ end
1021
+
1022
+ def radio_button(method, tag_value, options = {})
1023
+ @template.radio_button(@object_name, method, tag_value, objectify_options(options))
1024
+ end
1025
+
1026
+ def error_message_on(method, *args)
1027
+ @template.error_message_on(@object, method, *args)
1028
+ end
1029
+
1030
+ def error_messages(options = {})
1031
+ @template.error_messages_for(@object_name, objectify_options(options))
1032
+ end
1033
+
1034
+ def submit(value = "Save changes", options = {})
1035
+ @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
1036
+ end
1037
+
1038
+ private
1039
+ def objectify_options(options)
1040
+ @default_options.merge(options.merge(:object => @object))
1041
+ end
1042
+
1043
+ def nested_attributes_association?(association_name)
1044
+ @object.respond_to?("#{association_name}_attributes=")
1045
+ end
1046
+
1047
+ def fields_for_with_nested_attributes(association_name, args, block)
1048
+ name = "#{object_name}[#{association_name}_attributes]"
1049
+ association = args.first.to_model if args.first.respond_to?(:to_model)
1050
+
1051
+ if association.respond_to?(:new_record?)
1052
+ association = [association] if @object.send(association_name).is_a?(Array)
1053
+ elsif !association.is_a?(Array)
1054
+ association = @object.send(association_name)
1055
+ end
1056
+
1057
+ if association.is_a?(Array)
1058
+ explicit_child_index = args.last[:child_index] if args.last.is_a?(Hash)
1059
+ association.map do |child|
1060
+ fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, args, block)
1061
+ end.join
1062
+ elsif association
1063
+ fields_for_nested_model(name, association, args, block)
1064
+ end
1065
+ end
1066
+
1067
+ def fields_for_nested_model(name, object, args, block)
1068
+ if object.new_record?
1069
+ @template.fields_for(name, object, *args, &block)
1070
+ else
1071
+ @template.fields_for(name, object, *args) do |builder|
1072
+ @template.concat builder.hidden_field(:id)
1073
+ block.call(builder)
1074
+ end
1075
+ end
1076
+ end
1077
+
1078
+ def nested_child_index(name)
1079
+ @nested_child_index[name] ||= -1
1080
+ @nested_child_index[name] += 1
1081
+ end
1082
+ end
1083
+ end
1084
+
1085
+ class << ActionView
1086
+ attr_accessor :default_form_builder
1087
+ end
1088
+
1089
+ self.default_form_builder = ::ActionView::Helpers::FormBuilder
1090
+
1091
+ # 2.3 compatibility
1092
+ class << Base
1093
+ def default_form_builder=(builder)
1094
+ ActionView.default_form_builder = builder
1095
+ end
1096
+
1097
+ def default_form_builder
1098
+ ActionView.default_form_builder
1099
+ end
1100
+ end
1101
+
1102
+ end