Movatterモバイル変換


[0]ホーム

URL:


Skip to main content
More atrubyonrails.org:

Action View Form Helpers

Forms are a common interface for user input in web applications. However, form markup can be tedious to write and maintain because of the need to handle form controls, naming, and attributes. Rails simplifies this by providing view helpers, which are methods that output HTML form markup. This guide will help you understand the different helper methods and when to use each.

After reading this guide, you will know:

  • How to create basic forms, such as a search form.
  • How to work with model-based forms for creating and editing specific database records.
  • How to generate select boxes from multiple types of data.
  • What date and time helpers Rails provides.
  • What makes a file upload form different.
  • How to post forms to external resources and specify setting anauthenticity_token.
  • How to build complex forms.

This guide is not intended to be a complete list of all available form helpers. Please refer tothe Rails API documentation for an exhaustive list of form helpers and their arguments.

1. Working with Basic Forms

The main form helper isform_with.

<%=form_withdo|form|%>  Form contents<%end%>

When called without arguments, it creates an HTML<form> tag with the value of themethod attribute set topost and the value of theaction attribute set to the current page. For example, assuming the current page is a home page at/home, the generated HTML will look like this:

<formaction="/home"accept-charset="UTF-8"method="post"><inputtype="hidden"name="authenticity_token"value="Lz6ILqUEs2CGdDa-oz38TqcqQORavGnbGkG0CQA8zc8peOps-K7sHgFSTPSkBx89pQxh3p5zPIkjoOTiA_UWbQ"autocomplete="off">  Form contents</form>

Notice that the form contains aninput element with typehidden. Thisauthenticity_token hidden input is required for non-GET form submissions.This token is a security feature in Rails used to prevent cross-site request forgery (CSRF) attacks, and form helpers automatically generate it for every non-GET form (assuming the security feature is enabled). You can read more about it in theSecuring Rails Applications guide.

1.1. A Generic Search Form

One of the most basic forms on the web is a search form. This form contains:

  • a form element with "GET" method,
  • a label for the input,
  • a text input element, and
  • a submit element.

Here is how to create a search form withform_with:

<%=form_withurl:"/search",method: :getdo|form|%><%=form.label:query,"Search for:"%><%=form.search_field:query%><%=form.submit"Search"%><%end%>

This will generate the following HTML:

<formaction="/search"accept-charset="UTF-8"method="get"><labelfor="query">Search for:</label><inputtype="search"name="query"id="query"><inputtype="submit"name="commit"value="Search"data-disable-with="Search"></form>

Notice that for the search form we are using theurl option ofform_with. Settingurl: "/search" changes the form action value from the default current page path toaction="/search".

In general, passingurl: my_path toform_with tells the form where to make the request. The other option is to pass Active Model objects to the form, as you will learnbelow. You can also useURL helpers.

The search form example above also shows theform builder object. You will learn about the many helpers provided by the form builder object (likeform.label andform.text_field) in the next section.

For every forminput element, anid attribute is generated from its name ("query" in above example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript.

Use "GET" as the method for search forms. In general, Railsconventions encourage using the right HTTP verb for controller actions. Using "GET" forsearch allows users to bookmark a specific search.

1.2. Helpers for Generating Form Elements

The form builder object yielded byform_with provides many helper methods for generating common form elements such as text fields, checkboxes, and radio buttons.

The first argument to these methods is always the name of the input. This isuseful to remember because when the form is submitted, that name will be passedto the controller along with the form data in theparams hash. The name will be the key in theparams for the value entered by the user for that field.

For example, if the form contains<%= form.text_field :query %>, then youwould be able to get the value of this field in the controller withparams[:query].

When naming inputs, Rails uses certain conventions that make it possible to submit parameters with non-scalar values such as arrays or hashes, which will also be accessible inparams. You can read more about them in theForm Input Naming Conventions and Params Hash section of this guide. For details on the precise usage of these helpers, please refer to theAPI documentation.

1.2.1. Checkboxes

A Checkbox is a form control that allows for a single value to be selected or deselected. A group of Checkboxes is generally used to allow a user to choose one or more options from the group.

Here's an example with three checkboxes in a form:

<%=form.checkbox:biography%><%=form.label:biography,"Biography"%><%=form.checkbox:romance%><%=form.label:romance,"Romance"%><%=form.checkbox:mystery%><%=form.label:mystery,"Mystery"%>

The above will generate the following:

<inputname="biography"type="hidden"value="0"autocomplete="off"><inputtype="checkbox"value="1"name="biography"id="biography"><labelfor="biography">Biography</label><inputname="romance"type="hidden"value="0"autocomplete="off"><inputtype="checkbox"value="1"name="romance"id="romance"><labelfor="romance">Romance</label><inputname="mystery"type="hidden"value="0"autocomplete="off"><inputtype="checkbox"value="1"name="mystery"id="mystery"><labelfor="mystery">Mystery</label>

The first parameter tocheckbox is the name of the input which can be found in theparams hash. If the user has checked the "Biography" checkbox only, theparams hash would contain:

{"biography"=>"1","romance"=>"0","mystery"=>"0"}

You can useparams[:biography] to check if that checkbox is selected by the user.

The checkbox's values (the values that will appear inparams) can optionally be specified using thechecked_value andunchecked_value parameters. See theAPI documentation for more details.

There is also acollection_checkboxes, which you can learn about in theCollection Related Helpers section.

1.2.2. Radio Buttons

Radio buttons are form controls that only allow the user to select one option at a time from the list of choices.

For example, radio buttons for choosing your favorite ice cream flavor:

<%=form.radio_button:flavor,"chocolate_chip"%><%=form.label:flavor_chocolate_chip,"Chocolate Chip"%><%=form.radio_button:flavor,"vanilla"%><%=form.label:flavor_vanilla,"Vanilla"%><%=form.radio_button:flavor,"hazelnut"%><%=form.label:flavor_hazelnut,"Hazelnut"%>

The above will generate the following HTML:

<inputtype="radio"value="chocolate_chip"name="flavor"id="flavor_chocolate_chip"><labelfor="flavor_chocolate_chip">Chocolate Chip</label><inputtype="radio"value="vanilla"name="flavor"id="flavor_vanilla"><labelfor="flavor_vanilla">Vanilla</label><inputtype="radio"value="hazelnut"name="flavor"id="flavor_hazelnut"><labelfor="flavor_hazelnut">Hazelnut</label>

The second argument toradio_button is the value of the input. Because these radio buttons share the same name (flavor), the user will only be able to select one of them, andparams[:flavor] will contain either"chocolate_chip","vanilla", orhazelnut.

Always use labels for checkbox and radio buttons. They associate text witha specific option using thefor attribute and, by expanding the clickableregion, make it easier for users to click the inputs.

1.3. Other Helpers of Interest

There are many other form controls including text, email, password, date, and time. The below examples show some more helpers and their generated HTML.

Date and time related helpers:

<%=form.date_field:born_on%><%=form.time_field:started_at%><%=form.datetime_local_field:graduation_day%><%=form.month_field:birthday_month%><%=form.week_field:birthday_week%>

Output:

<inputtype="date"name="born_on"id="born_on"><inputtype="time"name="started_at"id="started_at"><inputtype="datetime-local"name="graduation_day"id="graduation_day"><inputtype="month"name="birthday_month"id="birthday_month"><inputtype="week"name="birthday_week"id="birthday_week">

Helpers with special formatting:

<%=form.password_field:password%><%=form.email_field:address%><%=form.telephone_field:phone%><%=form.url_field:homepage%>

Output:

<inputtype="password"name="password"id="password"><inputtype="email"name="address"id="address"><inputtype="tel"name="phone"id="phone"><inputtype="url"name="homepage"id="homepage">

Other common helpers:

<%=form.textarea:message,size:"70x5"%><%=form.hidden_field:parent_id,value:"foo"%><%=form.number_field:price,in:1.0..20.0,step:0.5%><%=form.range_field:discount,in:1..100%><%=form.search_field:name%><%=form.color_field:favorite_color%>

Output:

<textareaname="message"id="message"cols="70"rows="5"></textarea><inputvalue="foo"autocomplete="off"type="hidden"name="parent_id"id="parent_id"><inputstep="0.5"min="1.0"max="20.0"type="number"name="price"id="price"><inputmin="1"max="100"type="range"name="discount"id="discount"><inputtype="search"name="name"id="name"><inputvalue="#000000"type="color"name="favorite_color"id="favorite_color">

Hidden inputs are not shown to the user but instead hold data like any textual input. Values inside them can be changed with JavaScript.

If you're using password input fields, you might want to configure your application to prevent those parameters from being logged. You can learn about how in theSecuring Rails Applications guide.

2. Creating Forms with Model Objects

2.1. Binding a Form to an Object

Theform_with helper has a:model option that allows you to bind the form builder object to a model object. This means that the form will be scoped to that model object, and the form's fields will be populated with values from that model object.

For example, if we have a@book model object:

@book=Book.new# => #<Book id: nil, title: nil, author: nil>

And the following form to create a new book:

<%=form_withmodel:@bookdo|form|%><div><%=form.label:title%><%=form.text_field:title%></div><div><%=form.label:author%><%=form.text_field:author%></div><%=form.submit%><%end%>

It will generate this HTML:

<formaction="/books"accept-charset="UTF-8"method="post"><inputtype="hidden"name="authenticity_token"value="ChwHeyegcpAFDdBvXvDuvbfW7yCA3e8gvhyieai7DhG28C3akh-dyuv-IBittsjPrIjETlQQvQJ91T77QQ8xWA"autocomplete="off"><div><labelfor="book_title">Title</label><inputtype="text"name="book[title]"id="book_title"></div><div><labelfor="book_author">Author</label><inputtype="text"name="book[author]"id="book_author"></div><inputtype="submit"name="commit"value="Create Book"data-disable-with="Create Book"></form>

Some important things to notice when usingform_with with a model object:

  • The formaction is automatically filled with an appropriate value,action="/books". If you were updating a book, it would beaction="/books/42".
  • The form field names are scoped withbook[...]. This means thatparams[:book] will be a hash containing all these field's values. You can read more about the significance of input names in chapterForm Input Naming Conventions and Params Hash of this guide.
  • The submit button is automatically given an appropriate text value, "Create Book" in this case.

Typically your form inputs will mirror model attributes. However, they don't have to. If there is other information you need you can include a field in your form and access it viaparams[:book][:my_non_attribute_input].

2.1.1. Composite Primary Key Forms

If you have a model with acomposite primary key, the form building syntax is the same with slightly different output.

For example, to update a@book model object with a composite key[:author_id, :id] like this:

@book=Book.find([2,25])# => #<Book id: 25, title: "Some book", author_id: 2>

The following form:

<%=form_withmodel:@bookdo|form|%><%=form.text_field:title%><%=form.submit%><%end%>

Will generate this HTML output:

<formaction="/books/2_25"method="post"accept-charset="UTF-8"><inputname="authenticity_token"type="hidden"value="ChwHeyegcpAFDdBvXvDuvbfW7yCA3e8gvhyieai7DhG28C3akh-dyuv-IBittsjPrIjETlQQvQJ91T77QQ8xWA"/><inputtype="text"name="book[title]"id="book_title"value="Some book"/><inputtype="submit"name="commit"value="Update Book"data-disable-with="Update Book"></form>

Note the generated URL contains theauthor_id andid delimited by anunderscore. Once submitted, the controller canextract each primary keyvalue from the parameters and update the record as it would with a singularprimary key.

2.1.2. Thefields_for Helper

Thefields_for helper is used to render fields for related model objectswithin the same form. The associated "inner" model is usually related to the"main" form model via an Active Record association. For example, if you had aPerson model with an associatedContactDetail model, you could create asingle form with inputs for both models like so:

<%=form_withmodel:@persondo|person_form|%><%=person_form.text_field:name%><%=fields_for:contact_detail,@person.contact_detaildo|contact_detail_form|%><%=contact_detail_form.text_field:phone_number%><%end%><%end%>

The above will produce the following output:

<formaction="/people"accept-charset="UTF-8"method="post"><inputtype="hidden"name="authenticity_token"value="..."autocomplete="off"/><inputtype="text"name="person[name]"id="person_name"/><inputtype="text"name="contact_detail[phone_number]"id="contact_detail_phone_number"/></form>

The object yielded byfields_for is a form builder like the one yielded byform_with. Thefields_for helper creates a similar binding but withoutrendering a<form> tag. You can learn more aboutfields_for in theAPIdocs.

2.2. Relying on Record Identification

When dealing with RESTful resources, calls toform_with can be simplified by relying onrecord identification. This means you pass the model instance and have Rails figure out the model name, method, and other things. In the example below for creating a new record, both calls toform_with generate the same HTML:

# longer way:form_with(model:@article,url:articles_path)# short-hand:form_with(model:@article)

Similarly, for editing an existing article like below, both the calls toform_with will also generate the same HTML:

# longer way:form_with(model:@article,url:article_path(@article),method:"patch")# short-hand:form_with(model:@article)

Notice how the short-handform_with invocation is conveniently the same, regardless of the record being new or existing. Record identification is smart enough to figure out if the record is new by askingrecord.persisted?. It also selects the correct path to submit to, and the name based on the class of the object.

This is assuming that theArticle model is declared withresources :articles in the routes file.

If you have asingular resource, you will need to callresource andresolve for it to work withform_with:

resource:articleresolve("Article"){[:article]}

Declaring a resource has a number of side effects. See theRails Routing from the Outside In guide for more information on setting up and using resources.

When you're usingsingle-table inheritance with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify:url, and:scope (the model name) explicitly.

2.3. Working with Namespaces

If you have namespaced routes,form_with has a shorthand for that. For example, if your application has anadmin namespace:

form_withmodel:[:admin,@article]

The above will create a form that submits to theAdmin::ArticlesController inside the admin namespace, therefore submitting toadmin_article_path(@article) in the case of an update.

If you have several levels of namespacing then the syntax is similar:

form_withmodel:[:admin,:management,@article]

For more information on Rails' routing system and the associated conventions, please see theRails Routing from the Outside In guide.

2.4. Forms with PATCH, PUT, or DELETE Methods

The Rails framework encourages RESTful design, which means forms in your application will make requests where themethod isPATCH,PUT, orDELETE in addition toGET andPOST. However, HTML formsdon't support methods other thanGET andPOST when it comes to submitting forms.

Rails works around this limitation by emulating other methods over POST with a hidden input named"_method". For example:

form_with(url:search_path,method:"patch")

The above form Will generate this HTML output:

<formaction="/search"accept-charset="UTF-8"method="post"><inputtype="hidden"name="_method"value="patch"autocomplete="off"><inputtype="hidden"name="authenticity_token"value="R4quRuXQAq75TyWpSf8AwRyLt-R1uMtPP1dHTTWJE5zbukiaY8poSTXxq3Z7uAjXfPHiKQDsWE1i2_-h0HSktQ"autocomplete="off"><!-- ... --></form>

When parsing POSTed data, Rails will take into account the special_method parameter and proceed as if the request's HTTP method was the one set as the value of_method (PATCH in this example).

When rendering a form, submission buttons can override the declaredmethod attribute through theformmethod: keyword:

<%=form_withurl:"/posts/1",method: :patchdo|form|%><%=form.button"Delete",formmethod: :delete,data:{confirm:"Are you sure?"}%><%=form.button"Update"%><%end%>

Similar to<form> elements, most browsersdon't support overriding form methods declared throughformmethod other thanGET andPOST.

Rails works around this issue by emulating other methods over POST through a combination offormmethod,value, andname attributes:

<formaccept-charset="UTF-8"action="/posts/1"method="post"><inputname="_method"type="hidden"value="patch"/><inputname="authenticity_token"type="hidden"value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71"/><!-- ... --><buttontype="submit"formmethod="post"name="_method"value="delete"data-confirm="Are you sure?">Delete</button><buttontype="submit"name="button">Update</button></form>

In this case, the "Update" button will be treated asPATCH and the "Delete" button will be treated asDELETE.

3. Making Select Boxes with Ease

Select boxes, also known as drop-down list, allow users to select from a list of options. The HTML for select boxes requires a decent amount of markup - one<option> element for each option to choose from. Rails provides helper methods to help generate that markup.

For example, let's say we have a list of cities for the user to choose from. We can use theselect helper:

<%=form.select:city,["Berlin","Chicago","Madrid"]%>

The above will generate this HTML output:

<selectname="city"id="city"><optionvalue="Berlin">Berlin</option><optionvalue="Chicago">Chicago</option><optionvalue="Madrid">Madrid</option></select>

And the selection will be available inparams[:city] as usual.

We can also specify<option> values that differ from their labels:

<%=form.select:city,[["Berlin","BE"],["Chicago","CHI"],["Madrid","MD"]]%>

Output:

<selectname="city"id="city"><optionvalue="BE">Berlin</option><optionvalue="CHI">Chicago</option><optionvalue="MD">Madrid</option></select>

This way, the user will see the full city name, butparams[:city] will be one of"BE","CHI", or"MD".

Lastly, we can specify a default choice for the select box with the:selected argument:

<%=form.select:city,[["Berlin","BE"],["Chicago","CHI"],["Madrid","MD"]],selected:"CHI"%>

Output:

<selectname="city"id="city"><optionvalue="BE">Berlin</option><optionvalue="CHI"selected="selected">Chicago</option><optionvalue="MD">Madrid</option></select>

3.1. Option Groups for Select Boxes

In some cases we may want to improve the user experience by grouping related options together. We can do so by passing aHash (or comparableArray) toselect:

<%=form.select:city,{"Europe"=>[["Berlin","BE"],["Madrid","MD"]],"North America"=>[["Chicago","CHI"]],},selected:"CHI"%>

Output:

<selectname="city"id="city"><optgrouplabel="Europe"><optionvalue="BE">Berlin</option><optionvalue="MD">Madrid</option></optgroup><optgrouplabel="North America"><optionvalue="CHI"selected="selected">Chicago</option></optgroup></select>

3.2. Binding Select Boxes to Model Objects

Like other form controls, a select box can be bound to a model attribute. For example, if we have a@person model object like:

@person=Person.new(city:"MD")

The following form:

<%=form_withmodel:@persondo|form|%><%=form.select:city,[["Berlin","BE"],["Chicago","CHI"],["Madrid","MD"]]%><%end%>

Will output this select box:

<selectname="person[city]"id="person_city"><optionvalue="BE">Berlin</option><optionvalue="CHI">Chicago</option><optionvalue="MD"selected="selected">Madrid</option></select>

The only difference is that the selected option will be found inparams[:person][:city] instead ofparams[:city].

Notice that the appropriate option was automatically markedselected="selected". Since this select box was bound to an existing@person record, we didn't need to specify a:selected argument.

4. Using Date and Time Form Helpers

In addition to thedate_field andtime_field helpers mentionedearlier, Rails provides alternative date and time form helpers that render plain select boxes. Thedate_select helper renders a separate select box for year, month, and day.

For example, if we have a@person model object like:

@person=Person.new(birth_date:Date.new(1995,12,21))

The following form:

<%=form_withmodel:@persondo|form|%><%=form.date_select:birth_date%><%end%>

Will output select boxes like:

<selectname="person[birth_date(1i)]"id="person_birth_date_1i"><optionvalue="1990">1990</option><optionvalue="1991">1991</option><optionvalue="1992">1992</option><optionvalue="1993">1993</option><optionvalue="1994">1994</option><optionvalue="1995"selected="selected">1995</option><optionvalue="1996">1996</option><optionvalue="1997">1997</option><optionvalue="1998">1998</option><optionvalue="1999">1999</option><optionvalue="2000">2000</option></select><selectname="person[birth_date(2i)]"id="person_birth_date_2i"><optionvalue="1">January</option><optionvalue="2">February</option><optionvalue="3">March</option><optionvalue="4">April</option><optionvalue="5">May</option><optionvalue="6">June</option><optionvalue="7">July</option><optionvalue="8">August</option><optionvalue="9">September</option><optionvalue="10">October</option><optionvalue="11">November</option><optionvalue="12"selected="selected">December</option></select><selectname="person[birth_date(3i)]"id="person_birth_date_3i"><optionvalue="1">1</option>  ...<optionvalue="21"selected="selected">21</option>  ...<optionvalue="31">31</option></select>

Notice that, when the form is submitted, there will be no single value in theparams hash that contains the full date. Instead, there will be several values with special names like"birth_date(1i)". However, Active Model knows how to assemble these values into a full date, based on the declared type of the model attribute. So we can passparams[:person] toPerson.new orPerson#update just like we would if the form used a single field to represent the full date.

In addition to thedate_select helper, Rails providestime_select which outputs select boxes for the hour and minute. There isdatetime_select as well which combines both date and time select boxes.

4.1. Select Boxes for Time or Date Components

Rails also provides helpers to render select boxes for individual date and time components:select_year,select_month,select_day,select_hour,select_minute, andselect_second. These helpers are "bare" methods, meaning they are not called on a form builder instance. For example:

<%=select_year2024,prefix:"party"%>

The above outputs a select box like:

<selectid="party_year"name="party[year]"><optionvalue="2019">2019</option><optionvalue="2020">2020</option><optionvalue="2021">2021</option><optionvalue="2022">2022</option><optionvalue="2023">2023</option><optionvalue="2024"selected="selected">2024</option><optionvalue="2025">2025</option><optionvalue="2026">2026</option><optionvalue="2027">2027</option><optionvalue="2028">2028</option><optionvalue="2029">2029</option></select>

For each of these helpers, you may specify aDate orTime object instead of a number as the default value (for example<%= select_year Date.today, prefix: "party" %> instead of the above), and the appropriate date and time parts will be extracted and used.

4.2. Selecting Time Zone

When you need to ask users what time zone they are in, there is a very convenienttime_zone_select helper to use.

Typically, you would have to provide a list of time zone options for users to select from. This can get tedious if not for the list of pre-definedActiveSupport::TimeZone objects. Thetime_with_zone helper wraps this and can be used as follows:

<%=form.time_zone_select:time_zone%>

Output:

<selectname="time_zone"id="time_zone"><optionvalue="International Date Line West">(GMT-12:00) International Date Line West</option><optionvalue="American Samoa">(GMT-11:00) American Samoa</option><optionvalue="Midway Island">(GMT-11:00) Midway Island</option><optionvalue="Hawaii">(GMT-10:00) Hawaii</option><optionvalue="Alaska">(GMT-09:00) Alaska</option>  ...<optionvalue="Samoa">(GMT+13:00) Samoa</option><optionvalue="Tokelau Is.">(GMT+13:00) Tokelau Is.</option></select>

5. Collection Related Helpers

If you need to generate a set of choices from a collection of arbitrary objects, Rails hascollection_select,collection_radio_button, andcollection_checkboxes helpers.

To see when these helpers are useful, suppose you have aCity model and correspondingbelongs_to :city association withPerson:

classCity<ApplicationRecordendclassPerson<ApplicationRecordbelongs_to:cityend

Assuming we have the following cities stored in the database:

City.order(:name).map{|city|[city.name,city.id]}# => [["Berlin", 1], ["Chicago", 3], ["Madrid", 2]]

We can allow the user to choose from the cities with the following form:

<%=form_withmodel:@persondo|form|%><%=form.select:city_id,City.order(:name).map{|city|[city.name,city.id]}%><%end%>

The above will generate this HTML:

<selectname="person[city_id]"id="person_city_id"><optionvalue="1">Berlin</option><optionvalue="3">Chicago</option><optionvalue="2">Madrid</option></select>

The above example shows how you'd generate the choices manually. However, Rails has helpers that generate choices from a collection without having to explicitly iterate over it. These helpers determine the value and text label of each choice by calling specified methods on each object in the collection.

When rendering a field for abelongs_to association, you must specify the name of the foreign key (city_id in the above example), rather than the name of the association itself.

5.1. Thecollection_select Helper

To generate a select box, we can usecollection_select:

<%=form.collection_select:city_id,City.order(:name),:id,:name%>

The above outputs the same HTML as the manual iteration above:

<selectname="person[city_id]"id="person_city_id"><optionvalue="1">Berlin</option><optionvalue="3">Chicago</option><optionvalue="2">Madrid</option></select>

The order of arguments forcollection_select is different from the order forselect. Withcollection_select we specify the value method first (:id in the example above), and the text label method second (:name in the example above). This is opposite of the order used when specifying choices for theselect helper, where the text label comes first and the value second (["Berlin", 1] in the previous example).

5.2. Thecollection_radio_buttons Helper

To generate a set of radio buttons, we can usecollection_radio_buttons:

<%=form.collection_radio_buttons:city_id,City.order(:name),:id,:name%>

Output:

<inputtype="radio"value="1"name="person[city_id]"id="person_city_id_1"><labelfor="person_city_id_1">Berlin</label><inputtype="radio"value="3"name="person[city_id]"id="person_city_id_3"><labelfor="person_city_id_3">Chicago</label><inputtype="radio"value="2"name="person[city_id]"id="person_city_id_2"><labelfor="person_city_id_2">Madrid</label>

5.3. Thecollection_checkboxes Helper

To generate a set of check boxes — for example, to support ahas_and_belongs_to_many association — we can usecollection_checkboxes:

<%=form.collection_checkboxes:interest_ids,Interest.order(:name),:id,:name%>

Output:

<inputtype="checkbox"name="person[interest_id][]"value="3"id="person_interest_id_3"><labelfor="person_interest_id_3">Engineering</label><inputtype="checkbox"name="person[interest_id][]"value="4"id="person_interest_id_4"><labelfor="person_interest_id_4">Math</label><inputtype="checkbox"name="person[interest_id][]"value="1"id="person_interest_id_1"><labelfor="person_interest_id_1">Science</label><inputtype="checkbox"name="person[interest_id][]"value="2"id="person_interest_id_2"><labelfor="person_interest_id_2">Technology</label>

6. Uploading Files

A common task with forms is allowing users to upload a file. It could be an avatar image or a CSV file with data to process. File upload fields can be rendered with thefile_field helper.

<%=form_withmodel:@persondo|form|%><%=form.file_field:csv_file%><%end%>

The most important thing to remember with file uploads is that the rendered form'senctype attributemust be set tomultipart/form-data. This is done automatically if you use afile_field inside aform_with. You can also set the attribute manually:

<%=form_withurl:"/uploads",multipart:truedo|form|%><%=file_field_tag:csv_file%><%end%>

Both of which, output the following HTML form:

<formenctype="multipart/form-data"action="/people"accept-charset="UTF-8"method="post"><!-- ... --></form>

Note that, perform_with conventions, the field names in the two forms above will be different. In the first form, it will beperson[csv_file] (accessible viaparams[:person][:csv_file]), and in the second form it will be justcsv_file (accessible viaparams[:csv_file]).

6.1. CSV File Upload Example

When usingfile_field, the object in theparams hash is an instance ofActionDispatch::Http::UploadedFile. Here is an example of how to save data in an uploaded CSV file to records in your application:

require"csv"defuploaduploaded_file=params[:csv_file]ifuploaded_file.present?csv_data=CSV.parse(uploaded_file.read,headers:true)csv_data.eachdo|row|# Process each row of the CSV file# SomeInvoiceModel.create(amount: row['Amount'], status: row['Status'])Rails.logger.inforow.inspect#<CSV::Row "id":"po_1KE3FRDSYPMwkcNz9SFKuaYd" "Amount":"96.22" "Created (UTC)":"2022-01-04 02:59" "Arrival Date (UTC)":"2022-01-05 00:00" "Status":"paid">endend# ...end

If the file is an image that needs to be stored with a model (e.g. user's profile picture), there are a number of tasks to consider, like where to store the file (on Disk, Amazon S3, etc), resizing image files, and generating thumbnails, etc.Active Storage is designed to assist with these tasks.

7. Customizing Form Builders

We call the objects yielded byform_with orfields_for Form Builders. Form builders allow you to generate form elements associated with a model objectand are an instance ofActionView::Helpers::FormBuilder. This class can be extended to add custom helpers for your application.

For example, if you want to display atext_field along with alabel across your application, you could add the following helper method toapplication_helper.rb:

moduleApplicationHelperdeftext_field_with_label(form,attribute)form.label(attribute)+form.text_field(attribute)endend

And use it in a form as usual:

<%=form_withmodel:@persondo|form|%><%=text_field_with_labelform,:first_name%><%end%>

But you can also create a subclass ofActionView::Helpers::FormBuilder, andadd the helpers there. After defining thisLabellingFormBuilder subclass:

classLabellingFormBuilder<ActionView::Helpers::FormBuilderdeftext_field(attribute,options={})# super will call the original text_field methodlabel(attribute)+superendend

The above form can be replaced with:

<%=form_withmodel:@person,builder:LabellingFormBuilderdo|form|%><%=form.text_field:first_name%><%end%>

If you reuse this frequently you could define alabeled_form_with helper that automatically applies thebuilder: LabellingFormBuilder option:

moduleApplicationHelperdeflabeled_form_with(**options,&block)options[:builder]=LabellingFormBuilderform_with(**options,&block)endend

The above can be used instead ofform_with:

<%=labeled_form_withmodel:@persondo|form|%><%=form.text_field:first_name%><%end%>

All three cases above (thetext_field_with_label helper, theLabellingFormBuilder subclass, and thelabeled_form_with helper) will generate the same HTML output:

<formaction="/people"accept-charset="UTF-8"method="post"><!-- ... --><labelfor="person_first_name">First name</label><inputtype="text"name="person[first_name]"id="person_first_name"></form>

The form builder used also determines what happens when you do:

<%=renderpartial:f%>

Iff is an instance ofActionView::Helpers::FormBuilder, then this will render theform partial, setting the partial's object to the form builder. If the form builder is of classLabellingFormBuilder, then thelabelling_form partial would be rendered instead.

Form builder customizations, such asLabellingFormBuilder, do hide the implementation details (and may seem like an overkill for the simple example above). Choose between different customizations, extendingFormBuilder class or creating helpers, based on how frequently your forms use the custom elements.

8. Form Input Naming Conventions andparams Hash

All of the form helpers described above help with generating the HTML for form elements so that the user can enter various types of input. How do you access the user input values in the Controller? Theparams hash is the answer. You've already seen theparams hash in the above example. This section will more explicitly go over naming conventions around how form input is structured in theparams hash.

Theparams hash can contain arrays and arrays of hashes. Values can be at the top level of theparams hash or nested in another hash. For example, in a standardcreate action for a Person model,params[:person] will be a hash of all the attributes for thePerson object.

Note that HTML forms don't have an inherent structure to the user input data, all they generate is name-value string pairs. The arrays and hashes you see in your application are the result of parameter naming conventions that Rails uses.

The fields in theparams hash need to bepermitted in the controller.

8.1. Basic Structure

The two basic structures for user input form data are arrays and hashes.

Hashes mirror the syntax used for accessing the value inparams. For example, if a form contains:

<inputid="person_name"name="person[name]"type="text"value="Henry"/>

theparams hash will contain

{"person"=>{"name"=>"Henry"}}

andparams[:person][:name] will retrieve the submitted value in the controller.

Hashes can be nested as many levels as required, for example:

<inputid="person_address_city"name="person[address][city]"type="text"value="New York"/>

The above will result in theparams hash being

{"person"=>{"address"=>{"city"=>"New York"}}}

The other structure is an Array. Normally Rails ignores duplicate parameter names, but if the parameter name ends with an empty set of square brackets[] then the parameters will be accumulated in an Array.

For example, if you want users to be able to input multiple phone numbers, you could place this in the form:

<inputname="person[phone_number][]"type="text"/><inputname="person[phone_number][]"type="text"/><inputname="person[phone_number][]"type="text"/>

This would result inparams[:person][:phone_number] being an array containing the submitted phone numbers:

{"person"=>{"phone_number"=>["555-0123","555-0124","555-0125"]}}

8.2. Combining Arrays and Hashes

You can mix and match these two concepts. One element of a hash might be an array as in the previous exampleparams[:person] hash has a key called[:phone_number] whose value is an array.

You also can have an array of hashes. For example, you can create any number of addresses by repeating the following form fragment:

<inputname="person[addresses][][line1]"type="text"/><inputname="person[addresses][][line2]"type="text"/><inputname="person[addresses][][city]"type="text"/><inputname="person[addresses][][line1]"type="text"/><inputname="person[addresses][][line2]"type="text"/><inputname="person[addresses][][city]"type="text"/>

This would result inparams[:person][:addresses] being an array of hashes. Each hash in the array will have the keysline1,line2, andcity, something like this:

{"person"=>{"addresses"=>[{"line1"=>"1000 Fifth Avenue","line2"=>"","city"=>"New York"},{"line1"=>"Calle de Ruiz de Alarcón","line2"=>"","city"=>"Madrid"}]}}

It's important to note that while hashes can be nested arbitrarily, only one level of "arrayness" is allowed. Arrays can usually be replaced by hashes. For example, instead of an array of model objects, you can have a hash of model objects keyed by their id or similar.

Array parameters do not play well with thecheckbox helper. According to the HTML specification unchecked checkboxes submit no value. However it is often convenient for a checkbox to always submit a value. Thecheckbox helper fakes this by creating an auxiliary hidden input with the same name. If the checkbox is unchecked only the hidden input is submitted. If it is checked then both are submitted but the value submitted by the checkbox takes precedence. There is ainclude_hidden option that can be set tofalse if you want to omit this hidden field. By default, this option istrue.

8.3. Hashes with an Index

Let's say you want to render a form with a set of fields for each of a person'saddresses. Thefields_for helper with its:index option can assist:

<%=form_withmodel:@persondo|person_form|%><%=person_form.text_field:name%><%@person.addresses.eachdo|address|%><%=person_form.fields_foraddress,index:address.iddo|address_form|%><%=address_form.text_field:city%><%end%><%end%><%end%>

Assuming the person has two addresses with IDs 23 and 45, the above form wouldrender this output:

<formaccept-charset="UTF-8"action="/people/1"method="post"><inputname="_method"type="hidden"value="patch"/><inputid="person_name"name="person[name]"type="text"/><inputid="person_address_23_city"name="person[address][23][city]"type="text"/><inputid="person_address_45_city"name="person[address][45][city]"type="text"/></form>

Which will result in aparams hash that looks like:

{"person"=>{"name"=>"Bob","address"=>{"23"=>{"city"=>"Paris"},"45"=>{"city"=>"London"}}}}

All of the form inputs map to the"person" hash because we calledfields_foron theperson_form form builder. Also, by specifyingindex: address.id, werendered thename attribute of each city input asperson[address][#{address.id}][city] instead ofperson[address][city]. Thisway you can tell whichAddress records should be modified when processing theparams hash.

You can find more details aboutfields_for index option in theAPI docs.

9. Building Complex Forms

As your application grows, you may need to create more complex forms, beyond editing a single object. For example, when creating aPerson you can allow the user to create multipleAddress records (home, work, etc.) within the same form. When editing aPerson record later, the user should be able to add, remove, or update addresses as well.

9.1. Configuring the Model for Nested Attributes

For editing an associated record for a given model (Person in this case), Active Record provides model level support via theaccepts_nested_attributes_for method:

classPerson<ApplicationRecordhas_many:addresses,inverse_of: :personaccepts_nested_attributes_for:addressesendclassAddress<ApplicationRecordbelongs_to:personend

This creates anaddresses_attributes= method onPerson that allows you to create, update, and destroy addresses.

9.2. Nested Forms in the View

The following form allows a user to create aPerson and its associated addresses.

<%=form_withmodel:@persondo|form|%>  Addresses:<ul><%=form.fields_for:addressesdo|addresses_form|%><li><%=addresses_form.label:kind%><%=addresses_form.text_field:kind%><%=addresses_form.label:street%><%=addresses_form.text_field:street%>        ...</li><%end%></ul><%end%>

When an association accepts nested attributes,fields_for renders its block once for every element of the association. In particular, if a person has no addresses, it renders nothing.

A common pattern is for the controller to build one or more empty children so that at least one set of fields is shown to the user. The example below would result in 2 sets of address fields being rendered on the new person form.

For example, the aboveform_with with this change:

defnew@person=Person.new2.times{@person.addresses.build}end

Will output the following HTML:

<formaction="/people"accept-charset="UTF-8"method="post"><inputtype="hidden"name="authenticity_token"value="lWTbg-4_5i4rNe6ygRFowjDfTj7uf-6UPFQnsL7H9U9Fe2GGUho5PuOxfcohgm2Z-By3veuXwcwDIl-MLdwFRg"autocomplete="off">  Addresses:<ul><li><labelfor="person_addresses_attributes_0_kind">Kind</label><inputtype="text"name="person[addresses_attributes][0][kind]"id="person_addresses_attributes_0_kind"><labelfor="person_addresses_attributes_0_street">Street</label><inputtype="text"name="person[addresses_attributes][0][street]"id="person_addresses_attributes_0_street">        ...</li><li><labelfor="person_addresses_attributes_1_kind">Kind</label><inputtype="text"name="person[addresses_attributes][1][kind]"id="person_addresses_attributes_1_kind"><labelfor="person_addresses_attributes_1_street">Street</label><inputtype="text"name="person[addresses_attributes][1][street]"id="person_addresses_attributes_1_street">        ...</li></ul></form>

Thefields_for yields a form builder. The parameter names will be whataccepts_nested_attributes_for expects. For example, when creating a personwith 2 addresses, the submitted parameters inparams would look like this:

{"person"=>{"name"=>"John Doe","addresses_attributes"=>{"0"=>{"kind"=>"Home","street"=>"221b Baker Street"},"1"=>{"kind"=>"Office","street"=>"31 Spooner Street"}}}}

The actual value of the keys in the:addresses_attributes hash is not important. But they need to be strings of integers and different for each address.

If the associated object is already saved,fields_for autogenerates a hidden input with theid of the saved record. You can disable this by passinginclude_id: false tofields_for.

{"person"=>{"name"=>"John Doe","addresses_attributes"=>{"0"=>{"id"=>1,"kind"=>"Home","street"=>"221b Baker Street"},"1"=>{"id"=>"2","kind"=>"Office","street"=>"31 Spooner Street"}}}}

9.3. Permitting Parameters in the Controller

As usual you need todeclare the permittedparameters in the controllerbefore you pass them to the model:

defcreate@person=Person.new(person_params)# ...endprivatedefperson_paramsparams.expect(person:[:name,addresses_attributes:[[:id,:kind,:street]]])end

9.4. Removing Associated Objects

You can allow users to delete associated objects by passingallow_destroy: true toaccepts_nested_attributes_for

classPerson<ApplicationRecordhas_many:addressesaccepts_nested_attributes_for:addresses,allow_destroy:trueend

If the hash of attributes for an object contains the key_destroy with a valuethat evaluates totrue (e.g.1,'1',true, or'true') then the object will bedestroyed. This form allows users to remove addresses:

<%=form_withmodel:@persondo|form|%>  Addresses:<ul><%=form.fields_for:addressesdo|addresses_form|%><li><%=addresses_form.checkbox:_destroy%><%=addresses_form.label:kind%><%=addresses_form.text_field:kind%>        ...</li><%end%></ul><%end%>

The HTML for the_destroy field:

<inputtype="checkbox"value="1"name="person[addresses_attributes][0][_destroy]"id="person_addresses_attributes_0__destroy">

You also need to update the permitted params in your controller to includethe_destroy field:

defperson_paramsparams.require(:person).permit(:name,addresses_attributes:[:id,:kind,:street,:_destroy])end

9.5. Preventing Empty Records

It is often useful to ignore sets of fields that the user has not filled in. You can control this by passing a:reject_if proc toaccepts_nested_attributes_for. This proc will be called with each hash of attributes submitted by the form. If the proc returnstrue then Active Record will not build an associated object for that hash. The example below only tries to build an address if thekind attribute is set.

classPerson<ApplicationRecordhas_many:addressesaccepts_nested_attributes_for:addresses,reject_if:lambda{|attributes|attributes["kind"].blank?}end

As a convenience you can instead pass the symbol:all_blank which will create a proc that will reject records where all the attributes are blank excluding any value for_destroy.

10. Forms to External Resources

Rails form helpers can be used to build a form for posting data to an external resource. If the external API expects anauthenticity_token for the resource, this can be passed as anauthenticity_token: 'your_external_token' parameter toform_with:

<%=form_withurl:'http://farfar.away/form',authenticity_token:'external_token'do%>  Form contents<%end%>

At other times, the fields that can be used in the form are limited by an external API and it may be undesirable to generate anauthenticity_token. Tonot send a token, you can passfalse to the:authenticity_token option:

<%=form_withurl:'http://farfar.away/form',authenticity_token:falsedo%>  Form contents<%end%>

11. Using Tag Helpers without a Form Builder

In case you need to render form fields outside of the context of a form builder, Rails provides tag helpers for common form elements. For example,checkbox_tag:

<%=checkbox_tag"accept"%>

Output:

<inputtype="checkbox"name="accept"id="accept"value="1"/>

Generally, these helpers have the same name as their form builder counterparts plus a_tag suffix. For a complete list, see theFormTagHelper API documentation.

12. Usingform_tag andform_for

Beforeform_with was introduced in Rails 5.1 its functionality was split betweenform_tag andform_for. Both are now discouraged in favor ofform_with, but you can still find them being used in some codebases.



Back to top
[8]ページ先頭

©2009-2025 Movatter.jp