[Ruby] [RubyOnRails] How to implement pulldown in form_with based on table data

5 minute read


While implementing with the intention of introducing pull-down selection to form_with, there were places where I had to repeat some trial and error, so this is left as a reminder.


・Ruby 2.6.5 ・Rails 6.0.3

Assumptions and what you want to do


There is a shop table that registers information about restaurants. There is a station table that contains a list of station data for the Yamanote Line. The two tables have a many-to-many relationship and form an association through an intermediate table (shop_station table).

Thing you want to do

I want to implement the following functions. ** ① When creating shop data, I want to select the nearest station by pull-down and register. ** ** ② You do not have to register the nearest station. You can register up to 2 stations. ** **③ Naturally, I want to register all the nearest stations at once with one form submission. **

I will leave the above implementation procedure as a reminder.

*By the way, I read many articles that it is better to generate a list with active hash instead of creating a table one by one when registering static data, but like this time, with a many-to-many relationship When it comes to (and when the shop has multiple stations), I don’t see much handling, so I’m using a table. For example, if a shop has only one station, it would be better to register the id of the active hash as station_id, but if you have multiple stations, I think that a different table is preferable from the viewpoint of table normalization, In that case, I couldn’t see how to utilize the active hash. I would appreciate it if someone could understand it and let me know.

Well, I will write the procedure below.

① Create and implement pull-down options based on table data

This time, we will implement the form in the new action of the shop controller. I will extract and write the code first.



before_action :set_select_lists, only: [:new]

def new
    @shop = Shop.new

  def set_select_lists
    @stations = Station.all.map {|station| [station.name, station.id]}



  = form_with model: [@owner, @shop], html: {class: "shopform"}, local: true do |f|

- # omitted (indentation is appropriate for syntax highlighting, sorry, please indent according to haml notation)

= f.select :station_ids, @stations,{},{class: "select"}
= f.select :station_ids, @stations,{},{class: "select"}
*By registering the nearest station, it will be displayed in the station designated search.

Each explanation is as follows.


@stations is creating the data that is the basis of this choice. The following is a brief description.

Station.all All station table data

Station.all.map {~} Process each data (record) in all data in the Station table as shown in {}

Station.all.map {|station| [station.name, station.id]} A hash is generated in which each record is a station, station.name (the value of the name column of each record) is the key, and station.id (the value of the id column) is the value.


= f.select :station_ids, @stations,{},{class: “select”}

Send data with form_with. Create pulldown with f.select. When sending data, send it with the key :station_ids. Choices are based on @stations. The class name is select.

The key defined by the controller will be ** “choice displayed in pulldown” **, and the value will be ** “value sent by params” **. When people choose, we want to select by station name, and when registering data, we want to link by id. That’s why the key and value are set like this.

*By the way, since the class attribute must be set with the fourth argument, {} is inserted between them to make an empty third argument exist.

The figure which implemented these is as follows.

![Screenshot 2020-06-29 23.56.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619380/6012c719-8132-577f-ffd7-(83bf4ad385eb.png)

This figure shows two identical pull-downs side by side, one open. Certainly station.name is displayed as an option.

② Make the nearest station registration optional

In the image above, since the list is created using table data, there is no “option” that “does not select station”. ** In order to make the common “Please choose from the following”, I rewrote the controller as follows.


def set_select_lists
    @stations = Station.all.map {|station| [station.name, station.id] }.unshift(["Please choose from below", nil])

I added .unshift([“Please choose from below”, nil]) to @stations. unshift is a method that adds an element to the very beginning of the array. I added the phrase “Please choose from the following” as the key, and the option to pass nil as the value.

It is judged that adding a record such as “there is no nearest station” to the table data itself is not preferable because the intermediate table will increase the number of records in vain.

![Screenshot 2020-06-30 0.01.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619380/0da21756-311f-421e-da85-(4ab60c1d65ea.png)

Now you have successfully implemented the pulldown you want.

③ Register all the nearest stations at once with one form submission

I thought that I could already register if I did so far, but when I actually skipped the data from params, the following problems occurred. **Since both pull-downs send data with the same key of station_ids, the second value overwrites the first value and only one value is sent. **

It was obvious at the binding.pry. But what I want to do is send multiple station_ids and register. Ie I want to send an array of station_ids

I think there are several ways to do it, but I rewrote the view as follows (this is probably a technique, but I could still use it in the form afterwards)


= f.select :station_ids, @stations,{},{name:'shop[station_ids][]', class: "select"}
= f.select :station_ids, @stations,{},{name:'shop[station_ids][]', class: "select"}

The name attribute has been added to the previous state. By adding a name attribute to form_with, you can specify ** “how do you want to send the params?”.

You can see what kind of data is sent by actually looking at params in binding.pry. station_ids are sent as params[:shop][:station_ids].

I wanted to make this an array, so by adding [] to the end of the name attribute, both data can be sent safely in the form of an array.


def shop_params
    params.require(:shop).permit(:name, :address, :capacity, :owner_id, :genre_id, :mark_ids,introduces_attributes: [:content, :image, :number],station_ids: [])

By the way, I said earlier that the data is sent in the form of params[:shop][:station_ids], so as mentioned above, in strong parameters **First, :shop is required, and the key in it is By permitting, the data will be saved safely. ** This was well understood in the binding.pry sprinkling. Was funny.

At the end

The method specified by the name attribute is quite powerful, so I would like to know if you can see a smarter method. However, I also implemented each value of the form after this with ruggedly.

I learned the most from looking at how params are sent in binding.pry.

*Because I am a beginner, I would be grateful if you could point me out.