method

form_with

Importance_5
v6.0.0 - Show latest stable - 0 notes - Class: ActionView::Helpers::FormHelper
form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block) public

Creates a form tag based on mixing URLs, scopes, or models.

# Using just a URL:
<%= form_with url: posts_path do |form| %>
  <%= form.text_field :title %>
<% end %>
# =>
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="title">
</form>

# Adding a scope prefixes the input field names:
<%= form_with scope: :post, url: posts_path do |form| %>
  <%= form.text_field :title %>
<% end %>
# =>
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="post[title]">
</form>

# Using a model infers both the URL and scope:
<%= form_with model: Post.new do |form| %>
  <%= form.text_field :title %>
<% end %>
# =>
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="post[title]">
</form>

# An existing model makes an update form and fills out field values:
<%= form_with model: Post.first do |form| %>
  <%= form.text_field :title %>
<% end %>
# =>
<form action="/posts/1" method="post" data-remote="true">
  <input type="hidden" name="_method" value="patch">
  <input type="text" name="post[title]" value="<the title of the post>">
</form>

# Though the fields don't have to correspond to model attributes:
<%= form_with model: Cat.new do |form| %>
  <%= form.text_field :cats_dont_have_gills %>
  <%= form.text_field :but_in_forms_they_can %>
<% end %>
# =>
<form action="/cats" method="post" data-remote="true">
  <input type="text" name="cat[cats_dont_have_gills]">
  <input type="text" name="cat[but_in_forms_they_can]">
</form>

The parameters in the forms are accessible in controllers according to their name nesting. So inputs named title and post[title] are accessible as params[:title] and params[:post][:title] respectively.

By default form_with attaches the data-remote attribute submitting the form via an XMLHTTPRequest in the background if an Unobtrusive JavaScript driver, like rails-ujs, is used. See the :local option for more.

For ease of comparison the examples above left out the submit button, as well as the auto generated hidden fields that enable UTF-8 support and adds an authenticity token needed for cross site request forgery protection.

Resource-oriented style

In many of the examples just shown, the :model passed to form_with is a resource. It corresponds to a set of RESTful routes, most likely defined via resources in config/routes.rb.

So when passing such a model record, Rails infers the URL and method.

<%= form_with model: @post do |form| %>
  ...
<% end %>

is then equivalent to something like:

<%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %>
  ...
<% end %>

And for a new record

<%= form_with model: Post.new do |form| %>
  ...
<% end %>

is equivalent to something like:

<%= form_with scope: :post, url: posts_path do |form| %>
  ...
<% end %>

form_with options

  • :url - The URL the form submits to. Akin to values passed to url_for or link_to. For example, you may use a named route directly. When a :scope is passed without a :url the form just submits to the current URL.

  • :method - The method to use when submitting the form, usually either “get” or “post”. If “patch”, “put”, “delete”, or another verb is used, a hidden input named _method is added to simulate the verb over post.

  • :format - The format of the route the form submits to. Useful when submitting to another resource type, like :json. Skipped if a :url is passed.

  • :scope - The scope to prefix input field names with and thereby how the submitted parameters are grouped in controllers.

  • :namespace - A namespace for your form to ensure uniqueness of id attributes on form elements. The namespace attribute will be prefixed with underscore on the generated HTML id.

  • :model - A model object to infer the :url and :scope by, plus fill out input field values. So if a title attribute is set to “Ahoy!” then a title input field’s value would be “Ahoy!”. If the model is a new record a create form is generated, if an existing record, however, an update form is generated. Pass :scope or :url to override the defaults. E.g. turn params[:post] into params[:article].

  • :authenticity_token - Authenticity token to use in the form. Override with a custom authenticity token or pass false to skip the authenticity token field altogether. Useful when submitting to an external resource like a payment gateway that might limit the valid fields. Remote forms may omit the embedded authenticity token by setting config.action_view.embed_authenticity_token_in_remote_forms = false. This is helpful when fragment-caching the form. Remote forms get the authenticity token from the meta tag, so embedding is unnecessary unless you support browsers without JavaScript.

  • :local - By default form submits are remote and unobtrusive XHRs. Disable remote submits with local: true.

  • :skip_enforcing_utf8 - If set to true, a hidden input with name utf8 is not output.

  • :builder - Override the object used to build the form.

  • :id - Optional HTML id attribute.

  • :class - Optional HTML class attribute.

  • :data - Optional HTML data attributes.

  • :html - Other optional HTML attributes for the form tag.

Examples

When not passing a block, form_with just generates an opening form tag.

<%= form_with(model: @post, url: super_posts_path) %>
<%= form_with(model: @post, scope: :article) %>
<%= form_with(model: @post, format: :json) %>
<%= form_with(model: @post, authenticity_token: false) %> # Disables the token.

For namespaced routes, like admin_post_url:

<%= form_with(model: [ :admin, @post ]) do |form| %>
  ...
<% end %>

If your resource has associations defined, for example, you want to add comments to the document given that the routes are set correctly:

<%= form_with(model: [ @document, Comment.new ]) do |form| %>
  ...
<% end %>

Where @document = Document.find(params[:id]).

Mixing with other form helpers

While form_with uses a FormBuilder object it’s possible to mix and match the stand-alone FormHelper methods and methods from FormTagHelper:

<%= form_with scope: :person do |form| %>
  <%= form.text_field :first_name %>
  <%= form.text_field :last_name %>

  <%= text_area :person, :biography %>
  <%= check_box_tag "person[admin]", "1", @person.company.admin? %>

  <%= form.submit %>
<% end %>

Same goes for the methods in FormOptionsHelper and DateHelper designed to work with an object as a base, like FormOptionsHelper#collection_select and DateHelper#datetime_select.

Setting the method

You can force the form to use the full array of HTTP verbs by setting

method: (:get|:post|:patch|:put|:delete)

in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the form will be set to POST and a hidden input called _method will carry the intended verb for the server to interpret.

Setting HTML options

You can set data attributes directly in a data hash, but HTML options besides id and class must be wrapped in an HTML key:

<%= form_with(model: @post, data: { behavior: "autosave" }, html: { name: "go" }) do |form| %>
  ...
<% end %>

generates

<form action="/posts/123" method="post" data-behavior="autosave" name="go">
  <input name="_method" type="hidden" value="patch" />
  ...
</form>

Removing hidden model id’s

The form_with method automatically includes the model id as a hidden field in the form. This is used to maintain the correlation between the form data and its associated model. Some ORM systems do not use IDs on nested models so in this case you want to be able to disable the hidden id.

In the following example the Post model has many Comments stored within it in a NoSQL database, thus there is no primary key for comments.

<%= form_with(model: @post) do |form| %>
  <%= form.fields(:comments, skip_id: true) do |fields| %>
    ...
  <% end %>
<% end %>

Customized form builders

You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers, then use your custom builder. For example, let’s say you made a helper to automatically add labels to form inputs.

<%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
  <%= form.text_field :first_name %>
  <%= form.text_field :last_name %>
  <%= form.text_area :biography %>
  <%= form.check_box :admin %>
  <%= form.submit %>
<% end %>

In this case, if you use:

<%= render form %>

The rendered template is people/_labelling_form and the local variable referencing the form builder is called labelling_form.

The custom FormBuilder class is automatically merged with the options of a nested fields call, unless it’s explicitly set.

In many cases you will want to wrap the above in another helper, so you could do something like the following:

def labelled_form_with(**options, &block)
  form_with(**options.merge(builder: LabellingFormBuilder), &block)
end
Show source
Register or log in to add new notes.