actionview 5.0.7.2 → 5.1.7
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +169 -345
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/action_view/base.rb +19 -19
- data/lib/action_view/buffers.rb +1 -1
- data/lib/action_view/context.rb +1 -1
- data/lib/action_view/dependency_tracker.rb +4 -5
- data/lib/action_view/digestor.rb +22 -13
- data/lib/action_view/flows.rb +5 -6
- data/lib/action_view/gem_version.rb +2 -2
- data/lib/action_view/helpers/active_model_helper.rb +8 -8
- data/lib/action_view/helpers/asset_tag_helper.rb +62 -36
- data/lib/action_view/helpers/asset_url_helper.rb +111 -49
- data/lib/action_view/helpers/atom_feed_helper.rb +12 -13
- data/lib/action_view/helpers/cache_helper.rb +32 -20
- data/lib/action_view/helpers/capture_helper.rb +2 -2
- data/lib/action_view/helpers/controller_helper.rb +2 -2
- data/lib/action_view/helpers/csrf_helper.rb +3 -3
- data/lib/action_view/helpers/date_helper.rb +119 -109
- data/lib/action_view/helpers/debug_helper.rb +2 -3
- data/lib/action_view/helpers/form_helper.rb +440 -31
- data/lib/action_view/helpers/form_options_helper.rb +12 -12
- data/lib/action_view/helpers/form_tag_helper.rb +20 -19
- data/lib/action_view/helpers/javascript_helper.rb +6 -6
- data/lib/action_view/helpers/number_helper.rb +48 -46
- data/lib/action_view/helpers/output_safety_helper.rb +8 -8
- data/lib/action_view/helpers/record_tag_helper.rb +2 -2
- data/lib/action_view/helpers/rendering_helper.rb +2 -3
- data/lib/action_view/helpers/sanitize_helper.rb +16 -12
- data/lib/action_view/helpers/tag_helper.rb +194 -77
- data/lib/action_view/helpers/tags/base.rb +121 -102
- data/lib/action_view/helpers/tags/check_box.rb +17 -17
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +9 -8
- data/lib/action_view/helpers/tags/collection_helpers.rb +60 -60
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +3 -2
- data/lib/action_view/helpers/tags/collection_select.rb +2 -2
- data/lib/action_view/helpers/tags/date_select.rb +36 -36
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +2 -2
- data/lib/action_view/helpers/tags/label.rb +4 -0
- data/lib/action_view/helpers/tags/password_field.rb +1 -1
- data/lib/action_view/helpers/tags/radio_button.rb +4 -4
- data/lib/action_view/helpers/tags/select.rb +9 -9
- data/lib/action_view/helpers/tags/text_area.rb +1 -1
- data/lib/action_view/helpers/tags/text_field.rb +5 -5
- data/lib/action_view/helpers/tags/translator.rb +14 -12
- data/lib/action_view/helpers/text_helper.rb +20 -19
- data/lib/action_view/helpers/translation_helper.rb +6 -6
- data/lib/action_view/helpers/url_helper.rb +48 -46
- data/lib/action_view/helpers.rb +1 -1
- data/lib/action_view/layouts.rb +51 -47
- data/lib/action_view/log_subscriber.rb +25 -9
- data/lib/action_view/lookup_context.rb +19 -25
- data/lib/action_view/path_set.rb +19 -19
- data/lib/action_view/railtie.rb +13 -4
- data/lib/action_view/record_identifier.rb +6 -6
- data/lib/action_view/renderer/abstract_renderer.rb +17 -17
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +7 -1
- data/lib/action_view/renderer/partial_renderer.rb +188 -187
- data/lib/action_view/renderer/renderer.rb +4 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +45 -47
- data/lib/action_view/renderer/template_renderer.rb +64 -66
- data/lib/action_view/rendering.rb +4 -5
- data/lib/action_view/routing_url_for.rb +9 -13
- data/lib/action_view/tasks/cache_digests.rake +7 -7
- data/lib/action_view/template/error.rb +5 -15
- data/lib/action_view/template/handlers/builder.rb +7 -7
- data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +9 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +81 -0
- data/lib/action_view/template/handlers/erb/erubis.rb +81 -0
- data/lib/action_view/template/handlers/erb.rb +9 -76
- data/lib/action_view/template/handlers.rb +4 -4
- data/lib/action_view/template/html.rb +2 -4
- data/lib/action_view/template/resolver.rb +107 -90
- data/lib/action_view/template/text.rb +5 -8
- data/lib/action_view/template/types.rb +1 -1
- data/lib/action_view/template.rb +26 -27
- data/lib/action_view/test_case.rb +20 -21
- data/lib/action_view/testing/resolvers.rb +29 -30
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +20 -8
- data/lib/action_view.rb +5 -5
- data/lib/assets/compiled/rails-ujs.js +683 -0
- metadata +18 -12
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
10
|
-
require
|
|
11
|
-
require
|
|
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 "action_view/helpers/active_model_helper"
|
|
6
|
+
require "action_view/model_naming"
|
|
7
|
+
require "action_view/record_identifier"
|
|
8
|
+
require "active_support/core_ext/module/attribute_accessors"
|
|
9
|
+
require "active_support/core_ext/hash/slice"
|
|
10
|
+
require "active_support/core_ext/string/output_safety"
|
|
11
|
+
require "active_support/core_ext/string/inflections"
|
|
12
12
|
|
|
13
13
|
module ActionView
|
|
14
14
|
# = Action View Form Helpers
|
|
@@ -467,13 +467,305 @@ module ActionView
|
|
|
467
467
|
)
|
|
468
468
|
|
|
469
469
|
options[:url] ||= if options.key?(:format)
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
470
|
+
polymorphic_path(record, format: options.delete(:format))
|
|
471
|
+
else
|
|
472
|
+
polymorphic_path(record, {})
|
|
473
|
+
end
|
|
474
474
|
end
|
|
475
475
|
private :apply_form_for_options!
|
|
476
476
|
|
|
477
|
+
mattr_accessor(:form_with_generates_remote_forms) { true }
|
|
478
|
+
|
|
479
|
+
# Creates a form tag based on mixing URLs, scopes, or models.
|
|
480
|
+
#
|
|
481
|
+
# # Using just a URL:
|
|
482
|
+
# <%= form_with url: posts_path do |form| %>
|
|
483
|
+
# <%= form.text_field :title %>
|
|
484
|
+
# <% end %>
|
|
485
|
+
# # =>
|
|
486
|
+
# <form action="/posts" method="post" data-remote="true">
|
|
487
|
+
# <input type="text" name="title">
|
|
488
|
+
# </form>
|
|
489
|
+
#
|
|
490
|
+
# # Adding a scope prefixes the input field names:
|
|
491
|
+
# <%= form_with scope: :post, url: posts_path do |form| %>
|
|
492
|
+
# <%= form.text_field :title %>
|
|
493
|
+
# <% end %>
|
|
494
|
+
# # =>
|
|
495
|
+
# <form action="/posts" method="post" data-remote="true">
|
|
496
|
+
# <input type="text" name="post[title]">
|
|
497
|
+
# </form>
|
|
498
|
+
#
|
|
499
|
+
# # Using a model infers both the URL and scope:
|
|
500
|
+
# <%= form_with model: Post.new do |form| %>
|
|
501
|
+
# <%= form.text_field :title %>
|
|
502
|
+
# <% end %>
|
|
503
|
+
# # =>
|
|
504
|
+
# <form action="/posts" method="post" data-remote="true">
|
|
505
|
+
# <input type="text" name="post[title]">
|
|
506
|
+
# </form>
|
|
507
|
+
#
|
|
508
|
+
# # An existing model makes an update form and fills out field values:
|
|
509
|
+
# <%= form_with model: Post.first do |form| %>
|
|
510
|
+
# <%= form.text_field :title %>
|
|
511
|
+
# <% end %>
|
|
512
|
+
# # =>
|
|
513
|
+
# <form action="/posts/1" method="post" data-remote="true">
|
|
514
|
+
# <input type="hidden" name="_method" value="patch">
|
|
515
|
+
# <input type="text" name="post[title]" value="<the title of the post>">
|
|
516
|
+
# </form>
|
|
517
|
+
#
|
|
518
|
+
# # Though the fields don't have to correspond to model attributes:
|
|
519
|
+
# <%= form_with model: Cat.new do |form| %>
|
|
520
|
+
# <%= form.text_field :cats_dont_have_gills %>
|
|
521
|
+
# <%= form.text_field :but_in_forms_they_can %>
|
|
522
|
+
# <% end %>
|
|
523
|
+
# # =>
|
|
524
|
+
# <form action="/cats" method="post" data-remote="true">
|
|
525
|
+
# <input type="text" name="cat[cats_dont_have_gills]">
|
|
526
|
+
# <input type="text" name="cat[but_in_forms_they_can]">
|
|
527
|
+
# </form>
|
|
528
|
+
#
|
|
529
|
+
# The parameters in the forms are accessible in controllers according to
|
|
530
|
+
# their name nesting. So inputs named +title+ and <tt>post[title]</tt> are
|
|
531
|
+
# accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt>
|
|
532
|
+
# respectively.
|
|
533
|
+
#
|
|
534
|
+
# By default +form_with+ attaches the <tt>data-remote</tt> attribute
|
|
535
|
+
# submitting the form via an XMLHTTPRequest in the background if an
|
|
536
|
+
# Unobtrusive JavaScript driver, like rails-ujs, is used. See the
|
|
537
|
+
# <tt>:local</tt> option for more.
|
|
538
|
+
#
|
|
539
|
+
# For ease of comparison the examples above left out the submit button,
|
|
540
|
+
# as well as the auto generated hidden fields that enable UTF-8 support
|
|
541
|
+
# and adds an authenticity token needed for cross site request forgery
|
|
542
|
+
# protection.
|
|
543
|
+
#
|
|
544
|
+
# === Resource-oriented style
|
|
545
|
+
#
|
|
546
|
+
# In many of the examples just shown, the +:model+ passed to +form_with+
|
|
547
|
+
# is a _resource_. It corresponds to a set of RESTful routes, most likely
|
|
548
|
+
# defined via +resources+ in <tt>config/routes.rb</tt>.
|
|
549
|
+
#
|
|
550
|
+
# So when passing such a model record, Rails infers the URL and method.
|
|
551
|
+
#
|
|
552
|
+
# <%= form_with model: @post do |form| %>
|
|
553
|
+
# ...
|
|
554
|
+
# <% end %>
|
|
555
|
+
#
|
|
556
|
+
# is then equivalent to something like:
|
|
557
|
+
#
|
|
558
|
+
# <%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %>
|
|
559
|
+
# ...
|
|
560
|
+
# <% end %>
|
|
561
|
+
#
|
|
562
|
+
# And for a new record
|
|
563
|
+
#
|
|
564
|
+
# <%= form_with model: Post.new do |form| %>
|
|
565
|
+
# ...
|
|
566
|
+
# <% end %>
|
|
567
|
+
#
|
|
568
|
+
# is equivalent to something like:
|
|
569
|
+
#
|
|
570
|
+
# <%= form_with scope: :post, url: posts_path do |form| %>
|
|
571
|
+
# ...
|
|
572
|
+
# <% end %>
|
|
573
|
+
#
|
|
574
|
+
# ==== +form_with+ options
|
|
575
|
+
#
|
|
576
|
+
# * <tt>:url</tt> - The URL the form submits to. Akin to values passed to
|
|
577
|
+
# +url_for+ or +link_to+. For example, you may use a named route
|
|
578
|
+
# directly. When a <tt>:scope</tt> is passed without a <tt>:url</tt> the
|
|
579
|
+
# form just submits to the current URL.
|
|
580
|
+
# * <tt>:method</tt> - The method to use when submitting the form, usually
|
|
581
|
+
# either "get" or "post". If "patch", "put", "delete", or another verb
|
|
582
|
+
# is used, a hidden input named <tt>_method</tt> is added to
|
|
583
|
+
# simulate the verb over post.
|
|
584
|
+
# * <tt>:format</tt> - The format of the route the form submits to.
|
|
585
|
+
# Useful when submitting to another resource type, like <tt>:json</tt>.
|
|
586
|
+
# Skipped if a <tt>:url</tt> is passed.
|
|
587
|
+
# * <tt>:scope</tt> - The scope to prefix input field names with and
|
|
588
|
+
# thereby how the submitted parameters are grouped in controllers.
|
|
589
|
+
# * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and
|
|
590
|
+
# <tt>:scope</tt> by, plus fill out input field values.
|
|
591
|
+
# So if a +title+ attribute is set to "Ahoy!" then a +title+ input
|
|
592
|
+
# field's value would be "Ahoy!".
|
|
593
|
+
# If the model is a new record a create form is generated, if an
|
|
594
|
+
# existing record, however, an update form is generated.
|
|
595
|
+
# Pass <tt>:scope</tt> or <tt>:url</tt> to override the defaults.
|
|
596
|
+
# E.g. turn <tt>params[:post]</tt> into <tt>params[:article]</tt>.
|
|
597
|
+
# * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
|
|
598
|
+
# Override with a custom authenticity token or pass <tt>false</tt> to
|
|
599
|
+
# skip the authenticity token field altogether.
|
|
600
|
+
# Useful when submitting to an external resource like a payment gateway
|
|
601
|
+
# that might limit the valid fields.
|
|
602
|
+
# Remote forms may omit the embedded authenticity token by setting
|
|
603
|
+
# <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
|
|
604
|
+
# This is helpful when fragment-caching the form. Remote forms
|
|
605
|
+
# get the authenticity token from the <tt>meta</tt> tag, so embedding is
|
|
606
|
+
# unnecessary unless you support browsers without JavaScript.
|
|
607
|
+
# * <tt>:local</tt> - By default form submits are remote and unobstrusive XHRs.
|
|
608
|
+
# Disable remote submits with <tt>local: true</tt>.
|
|
609
|
+
# * <tt>:skip_enforcing_utf8</tt> - By default a hidden field named +utf8+
|
|
610
|
+
# is output to enforce UTF-8 submits. Set to true to skip the field.
|
|
611
|
+
# * <tt>:builder</tt> - Override the object used to build the form.
|
|
612
|
+
# * <tt>:id</tt> - Optional HTML id attribute.
|
|
613
|
+
# * <tt>:class</tt> - Optional HTML class attribute.
|
|
614
|
+
# * <tt>:data</tt> - Optional HTML data attributes.
|
|
615
|
+
# * <tt>:html</tt> - Other optional HTML attributes for the form tag.
|
|
616
|
+
#
|
|
617
|
+
# === Examples
|
|
618
|
+
#
|
|
619
|
+
# When not passing a block, +form_with+ just generates an opening form tag.
|
|
620
|
+
#
|
|
621
|
+
# <%= form_with(model: @post, url: super_posts_path) %>
|
|
622
|
+
# <%= form_with(model: @post, scope: :article) %>
|
|
623
|
+
# <%= form_with(model: @post, format: :json) %>
|
|
624
|
+
# <%= form_with(model: @post, authenticity_token: false) %> # Disables the token.
|
|
625
|
+
#
|
|
626
|
+
# For namespaced routes, like +admin_post_url+:
|
|
627
|
+
#
|
|
628
|
+
# <%= form_with(model: [ :admin, @post ]) do |form| %>
|
|
629
|
+
# ...
|
|
630
|
+
# <% end %>
|
|
631
|
+
#
|
|
632
|
+
# If your resource has associations defined, for example, you want to add comments
|
|
633
|
+
# to the document given that the routes are set correctly:
|
|
634
|
+
#
|
|
635
|
+
# <%= form_with(model: [ @document, Comment.new ]) do |form| %>
|
|
636
|
+
# ...
|
|
637
|
+
# <% end %>
|
|
638
|
+
#
|
|
639
|
+
# Where <tt>@document = Document.find(params[:id])</tt>.
|
|
640
|
+
#
|
|
641
|
+
# When using labels +form_with+ requires setting the id on the field being
|
|
642
|
+
# labelled:
|
|
643
|
+
#
|
|
644
|
+
# <%= form_with(model: @post) do |form| %>
|
|
645
|
+
# <%= form.label :title %>
|
|
646
|
+
# <%= form.text_field :title, id: :post_title %>
|
|
647
|
+
# <% end %>
|
|
648
|
+
#
|
|
649
|
+
# See +label+ for more on how the +for+ attribute is derived.
|
|
650
|
+
#
|
|
651
|
+
# === Mixing with other form helpers
|
|
652
|
+
#
|
|
653
|
+
# While +form_with+ uses a FormBuilder object it's possible to mix and
|
|
654
|
+
# match the stand-alone FormHelper methods and methods
|
|
655
|
+
# from FormTagHelper:
|
|
656
|
+
#
|
|
657
|
+
# <%= form_with scope: :person do |form| %>
|
|
658
|
+
# <%= form.text_field :first_name %>
|
|
659
|
+
# <%= form.text_field :last_name %>
|
|
660
|
+
#
|
|
661
|
+
# <%= text_area :person, :biography %>
|
|
662
|
+
# <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
|
|
663
|
+
#
|
|
664
|
+
# <%= form.submit %>
|
|
665
|
+
# <% end %>
|
|
666
|
+
#
|
|
667
|
+
# Same goes for the methods in FormOptionHelper and DateHelper designed
|
|
668
|
+
# to work with an object as a base, like
|
|
669
|
+
# FormOptionHelper#collection_select and DateHelper#datetime_select.
|
|
670
|
+
#
|
|
671
|
+
# === Setting the method
|
|
672
|
+
#
|
|
673
|
+
# You can force the form to use the full array of HTTP verbs by setting
|
|
674
|
+
#
|
|
675
|
+
# method: (:get|:post|:patch|:put|:delete)
|
|
676
|
+
#
|
|
677
|
+
# in the options hash. If the verb is not GET or POST, which are natively
|
|
678
|
+
# supported by HTML forms, the form will be set to POST and a hidden input
|
|
679
|
+
# called _method will carry the intended verb for the server to interpret.
|
|
680
|
+
#
|
|
681
|
+
# === Setting HTML options
|
|
682
|
+
#
|
|
683
|
+
# You can set data attributes directly in a data hash, but HTML options
|
|
684
|
+
# besides id and class must be wrapped in an HTML key:
|
|
685
|
+
#
|
|
686
|
+
# <%= form_with(model: @post, data: { behavior: "autosave" }, html: { name: "go" }) do |form| %>
|
|
687
|
+
# ...
|
|
688
|
+
# <% end %>
|
|
689
|
+
#
|
|
690
|
+
# generates
|
|
691
|
+
#
|
|
692
|
+
# <form action="/posts/123" method="post" data-behavior="autosave" name="go">
|
|
693
|
+
# <input name="_method" type="hidden" value="patch" />
|
|
694
|
+
# ...
|
|
695
|
+
# </form>
|
|
696
|
+
#
|
|
697
|
+
# === Removing hidden model id's
|
|
698
|
+
#
|
|
699
|
+
# The +form_with+ method automatically includes the model id as a hidden field in the form.
|
|
700
|
+
# This is used to maintain the correlation between the form data and its associated model.
|
|
701
|
+
# Some ORM systems do not use IDs on nested models so in this case you want to be able
|
|
702
|
+
# to disable the hidden id.
|
|
703
|
+
#
|
|
704
|
+
# In the following example the Post model has many Comments stored within it in a NoSQL database,
|
|
705
|
+
# thus there is no primary key for comments.
|
|
706
|
+
#
|
|
707
|
+
# <%= form_with(model: @post) do |form| %>
|
|
708
|
+
# <%= form.fields(:comments, skip_id: true) do |fields| %>
|
|
709
|
+
# ...
|
|
710
|
+
# <% end %>
|
|
711
|
+
# <% end %>
|
|
712
|
+
#
|
|
713
|
+
# === Customized form builders
|
|
714
|
+
#
|
|
715
|
+
# You can also build forms using a customized FormBuilder class. Subclass
|
|
716
|
+
# FormBuilder and override or define some more helpers, then use your
|
|
717
|
+
# custom builder. For example, let's say you made a helper to
|
|
718
|
+
# automatically add labels to form inputs.
|
|
719
|
+
#
|
|
720
|
+
# <%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
|
|
721
|
+
# <%= form.text_field :first_name %>
|
|
722
|
+
# <%= form.text_field :last_name %>
|
|
723
|
+
# <%= form.text_area :biography %>
|
|
724
|
+
# <%= form.check_box :admin %>
|
|
725
|
+
# <%= form.submit %>
|
|
726
|
+
# <% end %>
|
|
727
|
+
#
|
|
728
|
+
# In this case, if you use:
|
|
729
|
+
#
|
|
730
|
+
# <%= render form %>
|
|
731
|
+
#
|
|
732
|
+
# The rendered template is <tt>people/_labelling_form</tt> and the local
|
|
733
|
+
# variable referencing the form builder is called
|
|
734
|
+
# <tt>labelling_form</tt>.
|
|
735
|
+
#
|
|
736
|
+
# The custom FormBuilder class is automatically merged with the options
|
|
737
|
+
# of a nested +fields+ call, unless it's explicitly set.
|
|
738
|
+
#
|
|
739
|
+
# In many cases you will want to wrap the above in another helper, so you
|
|
740
|
+
# could do something like the following:
|
|
741
|
+
#
|
|
742
|
+
# def labelled_form_with(**options, &block)
|
|
743
|
+
# form_with(**options.merge(builder: LabellingFormBuilder), &block)
|
|
744
|
+
# end
|
|
745
|
+
def form_with(model: nil, scope: nil, url: nil, format: nil, **options)
|
|
746
|
+
options[:allow_method_names_outside_object] = true
|
|
747
|
+
options[:skip_default_ids] = true
|
|
748
|
+
|
|
749
|
+
if model
|
|
750
|
+
url ||= polymorphic_path(model, format: format)
|
|
751
|
+
|
|
752
|
+
model = model.last if model.is_a?(Array)
|
|
753
|
+
scope ||= model_name_from_record_or_class(model).param_key
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
if block_given?
|
|
757
|
+
builder = instantiate_builder(scope, model, options)
|
|
758
|
+
output = capture(builder, &Proc.new)
|
|
759
|
+
options[:multipart] ||= builder.multipart?
|
|
760
|
+
|
|
761
|
+
html_options = html_options_for_form_with(url, model, options)
|
|
762
|
+
form_tag_with_body(html_options, output)
|
|
763
|
+
else
|
|
764
|
+
html_options = html_options_for_form_with(url, model, options)
|
|
765
|
+
form_tag_html(html_options)
|
|
766
|
+
end
|
|
767
|
+
end
|
|
768
|
+
|
|
477
769
|
# Creates a scope around a specific model object like form_for, but
|
|
478
770
|
# doesn't create the form tags themselves. This makes fields_for suitable
|
|
479
771
|
# for specifying additional model objects in the same form.
|
|
@@ -720,6 +1012,74 @@ module ActionView
|
|
|
720
1012
|
capture(builder, &block)
|
|
721
1013
|
end
|
|
722
1014
|
|
|
1015
|
+
# Scopes input fields with either an explicit scope or model.
|
|
1016
|
+
# Like +form_with+ does with <tt>:scope</tt> or <tt>:model</tt>,
|
|
1017
|
+
# except it doesn't output the form tags.
|
|
1018
|
+
#
|
|
1019
|
+
# # Using a scope prefixes the input field names:
|
|
1020
|
+
# <%= fields :comment do |fields| %>
|
|
1021
|
+
# <%= fields.text_field :body %>
|
|
1022
|
+
# <% end %>
|
|
1023
|
+
# # => <input type="text" name="comment[body]>
|
|
1024
|
+
#
|
|
1025
|
+
# # Using a model infers the scope and assigns field values:
|
|
1026
|
+
# <%= fields model: Comment.new(body: "full bodied") do |fields| %<
|
|
1027
|
+
# <%= fields.text_field :body %>
|
|
1028
|
+
# <% end %>
|
|
1029
|
+
# # =>
|
|
1030
|
+
# <input type="text" name="comment[body] value="full bodied">
|
|
1031
|
+
#
|
|
1032
|
+
# # Using +fields+ with +form_with+:
|
|
1033
|
+
# <%= form_with model: @post do |form| %>
|
|
1034
|
+
# <%= form.text_field :title %>
|
|
1035
|
+
#
|
|
1036
|
+
# <%= form.fields :comment do |fields| %>
|
|
1037
|
+
# <%= fields.text_field :body %>
|
|
1038
|
+
# <% end %>
|
|
1039
|
+
# <% end %>
|
|
1040
|
+
#
|
|
1041
|
+
# Much like +form_with+ a FormBuilder instance associated with the scope
|
|
1042
|
+
# or model is yielded, so any generated field names are prefixed with
|
|
1043
|
+
# either the passed scope or the scope inferred from the <tt>:model</tt>.
|
|
1044
|
+
#
|
|
1045
|
+
# When using labels +fields+ requires setting the id on the field being
|
|
1046
|
+
# labelled:
|
|
1047
|
+
#
|
|
1048
|
+
# <%= fields :comment do |fields| %>
|
|
1049
|
+
# <%= fields.label :body %>
|
|
1050
|
+
# <%= fields.text_field :body, id: :comment_body %>
|
|
1051
|
+
# <% end %>
|
|
1052
|
+
#
|
|
1053
|
+
# See +label+ for more on how the +for+ attribute is derived.
|
|
1054
|
+
#
|
|
1055
|
+
# === Mixing with other form helpers
|
|
1056
|
+
#
|
|
1057
|
+
# While +form_with+ uses a FormBuilder object it's possible to mix and
|
|
1058
|
+
# match the stand-alone FormHelper methods and methods
|
|
1059
|
+
# from FormTagHelper:
|
|
1060
|
+
#
|
|
1061
|
+
# <%= fields model: @comment do |fields| %>
|
|
1062
|
+
# <%= fields.text_field :body %>
|
|
1063
|
+
#
|
|
1064
|
+
# <%= text_area :commenter, :biography %>
|
|
1065
|
+
# <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
|
|
1066
|
+
# <% end %>
|
|
1067
|
+
#
|
|
1068
|
+
# Same goes for the methods in FormOptionHelper and DateHelper designed
|
|
1069
|
+
# to work with an object as a base, like
|
|
1070
|
+
# FormOptionHelper#collection_select and DateHelper#datetime_select.
|
|
1071
|
+
def fields(scope = nil, model: nil, **options, &block)
|
|
1072
|
+
options[:allow_method_names_outside_object] = true
|
|
1073
|
+
options[:skip_default_ids] = true
|
|
1074
|
+
|
|
1075
|
+
if model
|
|
1076
|
+
scope ||= model_name_from_record_or_class(model).param_key
|
|
1077
|
+
end
|
|
1078
|
+
|
|
1079
|
+
builder = instantiate_builder(scope, model, options)
|
|
1080
|
+
capture(builder, &block)
|
|
1081
|
+
end
|
|
1082
|
+
|
|
723
1083
|
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
|
|
724
1084
|
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
|
|
725
1085
|
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
|
|
@@ -965,6 +1325,7 @@ module ActionView
|
|
|
965
1325
|
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
|
|
966
1326
|
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
|
|
967
1327
|
#
|
|
1328
|
+
# # Let's say that @user.receive_newsletter returns "no":
|
|
968
1329
|
# radio_button("user", "receive_newsletter", "yes")
|
|
969
1330
|
# radio_button("user", "receive_newsletter", "no")
|
|
970
1331
|
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
|
|
@@ -1048,7 +1409,7 @@ module ActionView
|
|
|
1048
1409
|
# Returns a text_field of type "time".
|
|
1049
1410
|
#
|
|
1050
1411
|
# The default value is generated by trying to call +strftime+ with "%T.%L"
|
|
1051
|
-
# on the
|
|
1412
|
+
# on the object's value. It is still possible to override that
|
|
1052
1413
|
# by passing the "value" option.
|
|
1053
1414
|
#
|
|
1054
1415
|
# === Options
|
|
@@ -1174,6 +1535,34 @@ module ActionView
|
|
|
1174
1535
|
end
|
|
1175
1536
|
|
|
1176
1537
|
private
|
|
1538
|
+
def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
|
|
1539
|
+
skip_enforcing_utf8: false, **options)
|
|
1540
|
+
html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
|
|
1541
|
+
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
|
|
1542
|
+
html_options[:enforce_utf8] = !skip_enforcing_utf8
|
|
1543
|
+
|
|
1544
|
+
html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
|
|
1545
|
+
|
|
1546
|
+
# The following URL is unescaped, this is just a hash of options, and it is the
|
|
1547
|
+
# responsibility of the caller to escape all the values.
|
|
1548
|
+
html_options[:action] = url_for(url_for_options || {})
|
|
1549
|
+
html_options[:"accept-charset"] = "UTF-8"
|
|
1550
|
+
html_options[:"data-remote"] = true unless local
|
|
1551
|
+
|
|
1552
|
+
html_options[:authenticity_token] = options.delete(:authenticity_token)
|
|
1553
|
+
|
|
1554
|
+
if !local && html_options[:authenticity_token].blank?
|
|
1555
|
+
html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms
|
|
1556
|
+
end
|
|
1557
|
+
|
|
1558
|
+
if html_options[:authenticity_token] == true
|
|
1559
|
+
# Include the default authenticity_token, which is only generated when it's set to nil,
|
|
1560
|
+
# but we needed the true value to override the default of no authenticity_token on data-remote.
|
|
1561
|
+
html_options[:authenticity_token] = nil
|
|
1562
|
+
end
|
|
1563
|
+
|
|
1564
|
+
html_options.stringify_keys!
|
|
1565
|
+
end
|
|
1177
1566
|
|
|
1178
1567
|
def instantiate_builder(record_name, record_object, options)
|
|
1179
1568
|
case record_name
|
|
@@ -1182,7 +1571,7 @@ module ActionView
|
|
|
1182
1571
|
object_name = record_name
|
|
1183
1572
|
else
|
|
1184
1573
|
object = record_name
|
|
1185
|
-
object_name = model_name_from_record_or_class(object).param_key
|
|
1574
|
+
object_name = model_name_from_record_or_class(object).param_key if object
|
|
1186
1575
|
end
|
|
1187
1576
|
|
|
1188
1577
|
builder = options[:builder] || default_form_builder_class
|
|
@@ -1248,7 +1637,7 @@ module ActionView
|
|
|
1248
1637
|
|
|
1249
1638
|
# The methods which wrap a form helper call.
|
|
1250
1639
|
class_attribute :field_helpers
|
|
1251
|
-
self.field_helpers = [:fields_for, :label, :text_field, :password_field,
|
|
1640
|
+
self.field_helpers = [:fields_for, :fields, :label, :text_field, :password_field,
|
|
1252
1641
|
:hidden_field, :file_field, :text_area, :check_box,
|
|
1253
1642
|
:radio_button, :color_field, :search_field,
|
|
1254
1643
|
:telephone_field, :phone_field, :date_field,
|
|
@@ -1270,7 +1659,7 @@ module ActionView
|
|
|
1270
1659
|
end
|
|
1271
1660
|
|
|
1272
1661
|
def self._to_partial_path
|
|
1273
|
-
@_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/,
|
|
1662
|
+
@_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "")
|
|
1274
1663
|
end
|
|
1275
1664
|
|
|
1276
1665
|
def to_partial_path
|
|
@@ -1284,19 +1673,23 @@ module ActionView
|
|
|
1284
1673
|
def initialize(object_name, object, template, options)
|
|
1285
1674
|
@nested_child_index = {}
|
|
1286
1675
|
@object_name, @object, @template, @options = object_name, object, template, options
|
|
1287
|
-
@default_options = @options ? @options.slice(:index, :namespace) : {}
|
|
1676
|
+
@default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
|
|
1677
|
+
|
|
1678
|
+
convert_to_legacy_options(@options)
|
|
1679
|
+
|
|
1288
1680
|
if @object_name.to_s.match(/\[\]$/)
|
|
1289
|
-
if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")
|
|
1681
|
+
if (object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
|
|
1290
1682
|
@auto_index = object.to_param
|
|
1291
1683
|
else
|
|
1292
1684
|
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
|
|
1293
1685
|
end
|
|
1294
1686
|
end
|
|
1687
|
+
|
|
1295
1688
|
@multipart = nil
|
|
1296
1689
|
@index = options[:index] || options[:child_index]
|
|
1297
1690
|
end
|
|
1298
1691
|
|
|
1299
|
-
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
|
|
1692
|
+
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
|
|
1300
1693
|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
|
1301
1694
|
def #{selector}(method, options = {}) # def text_field(method, options = {})
|
|
1302
1695
|
@template.send( # @template.send(
|
|
@@ -1574,18 +1967,28 @@ module ActionView
|
|
|
1574
1967
|
end
|
|
1575
1968
|
|
|
1576
1969
|
record_name = if index
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1970
|
+
"#{object_name}[#{index}][#{record_name}]"
|
|
1971
|
+
elsif record_name.to_s.end_with?("[]")
|
|
1972
|
+
record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
|
|
1973
|
+
"#{object_name}#{record_name}"
|
|
1974
|
+
else
|
|
1975
|
+
"#{object_name}[#{record_name}]"
|
|
1976
|
+
end
|
|
1584
1977
|
fields_options[:child_index] = index
|
|
1585
1978
|
|
|
1586
1979
|
@template.fields_for(record_name, record_object, fields_options, &block)
|
|
1587
1980
|
end
|
|
1588
1981
|
|
|
1982
|
+
# See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
|
|
1983
|
+
def fields(scope = nil, model: nil, **options, &block)
|
|
1984
|
+
options[:allow_method_names_outside_object] = true
|
|
1985
|
+
options[:skip_default_ids] = true
|
|
1986
|
+
|
|
1987
|
+
convert_to_legacy_options(options)
|
|
1988
|
+
|
|
1989
|
+
fields_for(scope || model, model, **options, &block)
|
|
1990
|
+
end
|
|
1991
|
+
|
|
1589
1992
|
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
|
|
1590
1993
|
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
|
|
1591
1994
|
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
|
|
@@ -1712,7 +2115,7 @@ module ActionView
|
|
|
1712
2115
|
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
|
|
1713
2116
|
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
|
|
1714
2117
|
#
|
|
1715
|
-
# # Let's say that @user.
|
|
2118
|
+
# # Let's say that @user.receive_newsletter returns "no":
|
|
1716
2119
|
# radio_button("receive_newsletter", "yes")
|
|
1717
2120
|
# radio_button("receive_newsletter", "no")
|
|
1718
2121
|
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
|
|
@@ -1809,7 +2212,7 @@ module ActionView
|
|
|
1809
2212
|
# post:
|
|
1810
2213
|
# create: "Add %{model}"
|
|
1811
2214
|
#
|
|
1812
|
-
def submit(value=nil, options={})
|
|
2215
|
+
def submit(value = nil, options = {})
|
|
1813
2216
|
value, options = nil, value if value.is_a?(Hash)
|
|
1814
2217
|
value ||= submit_default_value
|
|
1815
2218
|
@template.submit_tag(value, options)
|
|
@@ -1934,6 +2337,12 @@ module ActionView
|
|
|
1934
2337
|
@nested_child_index[name] ||= -1
|
|
1935
2338
|
@nested_child_index[name] += 1
|
|
1936
2339
|
end
|
|
2340
|
+
|
|
2341
|
+
def convert_to_legacy_options(options)
|
|
2342
|
+
if options.key?(:skip_id)
|
|
2343
|
+
options[:include_id] = !options.delete(:skip_id)
|
|
2344
|
+
end
|
|
2345
|
+
end
|
|
1937
2346
|
end
|
|
1938
2347
|
end
|
|
1939
2348
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
1
|
+
require "cgi"
|
|
2
|
+
require "erb"
|
|
3
|
+
require "action_view/helpers/form_helper"
|
|
4
|
+
require "active_support/core_ext/string/output_safety"
|
|
5
|
+
require "active_support/core_ext/array/extract_options"
|
|
6
|
+
require "active_support/core_ext/array/wrap"
|
|
7
7
|
|
|
8
8
|
module ActionView
|
|
9
9
|
# = Action View Form Option Helpers
|
|
@@ -363,7 +363,7 @@ module ActionView
|
|
|
363
363
|
html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled)
|
|
364
364
|
html_attributes[:value] = value
|
|
365
365
|
|
|
366
|
-
content_tag_string(:option, text, html_attributes)
|
|
366
|
+
tag_builder.content_tag_string(:option, text, html_attributes)
|
|
367
367
|
end.join("\n").html_safe
|
|
368
368
|
end
|
|
369
369
|
|
|
@@ -578,7 +578,7 @@ module ActionView
|
|
|
578
578
|
end
|
|
579
579
|
|
|
580
580
|
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
|
|
581
|
-
zone_options.safe_concat content_tag("option".freeze,
|
|
581
|
+
zone_options.safe_concat content_tag("option".freeze, "-------------", value: "", disabled: true)
|
|
582
582
|
zone_options.safe_concat "\n"
|
|
583
583
|
|
|
584
584
|
zones = zones - priority_zones
|
|
@@ -651,12 +651,12 @@ module ActionView
|
|
|
651
651
|
# The HTML specification says when nothing is select on a collection of radio buttons
|
|
652
652
|
# web browsers do not send any value to server.
|
|
653
653
|
# Unfortunately this introduces a gotcha:
|
|
654
|
-
# if a +User+ model has a +category_id+ field
|
|
655
|
-
# any strong parameters idiom like
|
|
654
|
+
# if a +User+ model has a +category_id+ field and in the form no category is selected, no +category_id+ parameter is sent. So,
|
|
655
|
+
# any strong parameters idiom like:
|
|
656
656
|
#
|
|
657
657
|
# params.require(:user).permit(...)
|
|
658
658
|
#
|
|
659
|
-
# will raise an error since no
|
|
659
|
+
# will raise an error since no <tt>{user: ...}</tt> will be present.
|
|
660
660
|
#
|
|
661
661
|
# To prevent this the helper generates an auxiliary hidden field before
|
|
662
662
|
# every collection of radio buttons. The hidden field has the same name as collection radio button and blank value.
|
|
@@ -800,7 +800,7 @@ module ActionView
|
|
|
800
800
|
end
|
|
801
801
|
|
|
802
802
|
def prompt_text(prompt)
|
|
803
|
-
prompt.kind_of?(String) ? prompt : I18n.translate(
|
|
803
|
+
prompt.kind_of?(String) ? prompt : I18n.translate("helpers.select.prompt", default: "Please select")
|
|
804
804
|
end
|
|
805
805
|
end
|
|
806
806
|
|