The 'method' in collection_select
Tags: rails
Published: 2020-03-02 17:39:27

When using a form helper for choosing an option (or multiple options) from a selection, the `method` argument can be a big hang up. It has to be a method that can be called on the object, and is also a parameter passed into `create` back in the controller - very confusing! Below I’ll go through how the associations between your models and your selection options determine what method to use in your form helper and what will go in your allowed parameters.

###Getting started:

Let’s work with a domain of pet owners, with two models: pets and owners. Here’s what we have so far:
```ruby
Pet.create(species: "dog", name: "Pearl")
Pet.create(species: "cat", name: "Dasher")
Pet.create(species: "cat", name: "Yoda")
```
In this domain, an owner can have many pets and a pet can only belong to one owner.
```ruby
class Owner < ApplicationRecord
    has_many :pets
end

class Pet < ApplicationRecord
    belongs_to :owner, optional: true
end
```
*I wanted to create pets without assigning a foreign key immediately - that's what the* `optional: true` *is doing there.*

###The new.html.erb form

In the form for a new owner, I’ll use `form_for` and the `collection_select` form helper so that a new owner can choose from the current list of pets.

Remember the format for `collection_select`:
```ruby
collection_select(object, method, collection, value_method, text_method, 
     options = {}, html_options = {})
```
In this case `object` will be the owner instance `@owner`, and I’ll write my `form_for` with a block that will create fields already connected to my object, like so:
```
<%= form_for @owner do |f| %>
    <%= f.label :name %>
    <%= f.text_field :name %><br>

    <%= f.label :pets %>
    <%= f.collection_select :pet_ids, Pet.all, :id, :name %><br>

    <%= f.submit %>
<% end %>
```
In this format, `@owner` is already connected to the FormBuilder object `f` so `pet_ids` (the method) is the first argument.

Finally, the html generated by `collection_select` looks like this:
```html
<select name="owner[pet_ids]" id="owner_pet_ids">
   <option value="1">Pearl</option>
   <option value="2">Dasher</option>
   <option value="3">Yoda</option>
</select>
```
###Digging into the Method

For starters, `method` has to be a method that can be called on my owner instance. We could use `nil?` (a method that can be called on any object) and my form would still render with the following html:
```html
<select name="owner[nil]" id="owner_nil>
```
But that wouldn’t be very useful for us. We want a method that will create the appropriate associations after the selection: in this case, a pet should be associated with a new owner. Remember that defining the associations between models gives us some extra methods to use. For a `has_many` relationship, we’ve gained (among others) the following two methods:
```
pet_ids
pet_ids=(ids)
```
What about `pet_id`? If I try using this method, Rails yells at me with a `NoMethodError` and won’t even render my form.

This part is critical: **Because an owner `has_many` pets, the method `pet_ids` must be plural!** Doesn’t matter that we’re only able to select one pet from our drop-down menu. The method is pluralized because of our defined relationships. On the flip side, in my new pet form, the method I would use to assign an owner to a new pet would be `owner_id`. A pet `belongs_to` a single owner, and therefore the method call is also singular. 

Ok, we’ve got a method. What happens next? Back in our controller, we’re going to pass in our allowed parameters to create a new owner instance. Here are the parameters I’m allowing:
```ruby
def owner_params
    params.require(:owner).permit(:name, :pet_ids)
end
```
Notice that again, `pet_ids` is plural here, and also that is is not an array of `pet_ids`. Because `collection_select` is only allowing us to choose one pet, the value of `pet_ids` is just one id. Here's what `owner_params` look like after I create a new owner "Monica" and give her a new pet "Pearl":
```ruby
<ActionController::Parameters {"name"=>"Monica", "pet_ids"=>"1"} permitted: true>
```
When these params get passed into the `create` method, `pet_ids=` is called on my new owner instance, which updates the pets table by assigning the `owner_id` to the selected pet, and the association is created. 
(Also, if any of those pets had previous owners, they've been stolen. A pet can only belong to one owner!)

###Selecting Multiple Options

What happens when I have the option to select multiple pets for my new owner? I can do this with `collection_check_boxes` or `collection_radio_buttons` (the arguments that these helpers take are identical to the ones for `collection_select`), or I can add an html option to my `collection_select`, like this:
```
<%= f.collection_select :pet_ids, Pet.all, :id, :name, {}, {multiple: true} %>
```
The form this generates is pretty ugly, but it gets the job done. To actually select multiple options, the Command or Shift key has to be used.
![Ugly Way to Select Multiple Options](https://dev-to-uploads.s3.amazonaws.com/i/z7vrkvasrbueeta3fsj3.png) <figcaption>An ugly form.</figcaption>

In this case, my method stays the same: I still call `pet_ids` on an owner instance no matter how many pets the owner is associated with. But the generated html looks different, and hints at the next step:
```html
<select multiple="multiple" name="owner[pet_ids][]" id="owner_pet_ids">
```
In the allowed parameters, I now have to account for the multiple pet option:
```ruby
def owner_params
    params.require(:owner).permit(:name, pet_ids: [])
end
```
`Pet_ids=` will accept an array of ids and update each pet row accordingly. 

Form helpers are just another example of Rails magic happening behind the scenes, and they work great- just as long as you remember when to pluralize!