Assigning Two Fields from One Dropdown Selection with Elixir
Tags: elixir, phoenix, forms, thisweekilearned
Published: 2020-08-18 14:34:50

This week I was tasked with having to assign two fields based on the select of a single dropdown. There are undoubtedly more elegant ways to solve this problem, with multiple dropdowns, or a dropdown that is populated based on the selection of of a previous dropdown. But, because of the stack we are currently working with, and the bounds I had to work within, my task was very explicit: assign two data points based on the selection from one dropdown.

Based on the route I took to accomplish this task, it ended up being more of an exercise in changeset manipulation than  getting clever with the dropdown itself. The exact data I had to work with needs to stay private, so I have come up with a _loose_ example with different content, but that strives for the same end result.

Let's say there's a form that allows a user to choose his or her favorite movie from a dropdown of options. But, for analytical purposes, you also wanted to be able to record the data point of what his or her favorite movie genre is (like I said, this is a pretty _loose_ example). 

There would be a Survey schema, and at the end of this example that schema will have two `belongs_to` associations, one to `:movie`, and another to `:genre`. So, if the user selected _Caddyshack_ as the movie, the `genre` field would be set to `comedy`, and if the user selected _The Shinning_, the `genre` field would be set to `horror`.

A list of tuples that include each `movie` record's `name` and `id`, to be used as the data for the movie select dropdown, would look something like this:

{% gist https://gist.github.com/noelworden/64f2750391ce195a4b9505338f162a9e %}

A simple schema module for this one question survey would look something like this, if we don't worry about the `genre` yet:

{% gist https://gist.github.com/noelworden/54ea4e2c55a03076057d87037bf3ed7b %}

Pretty straightforward, user is shown a collection on movie titles, and will select his or her favorite. Now, to add in the genre. I found this could be done by adding a private function that assigns `genre_id` based on `movie_id`.

{% gist https://gist.github.com/noelworden/0c868a0da60769cf27d80c36f0c82714 %}

Let me break it down a bit. A variable `movie_id` is set, and then a `cond` statement is entered, and based on what the content of the `movie_id` variable is, the `genre_id` field is set accordingly using the [change/2](https://hexdocs.pm/ecto/Ecto.Changeset.html#change/2) function provided by Ecto. Since there is a dropdown with predictable options, the conditional options can be hard coded in this way.

There is one catch, it's standard practice to pass an empty changeset when sending a user to a `/new` route in the controller. With that being the case, that empty changeset would enter this private function, and error out, because there is no conditional for `movie_id: nil`. The error shown in the logs would be:

`no cond clause evaluated to a truthy value`

Again, because the data being evaluated is bound by what is provided in the dropdown, and the dropdown selection in this case would be set as `required` in the form html, it can be assumed that the only time an empty `movie_id` field reaches this logic is on the initial `/new` page render. Knowing this, a sort of catch-all can be added to the end of the conditional:

{% gist https://gist.github.com/noelworden/8274e22131f1e0a275cd43b8e6f73a9c %}

With that at the bottom of the `cond` statement it will now return the empty `changeset` when rendering the `/new` form. The entire function would now look like this:


{% gist https://gist.github.com/noelworden/a3763d0cf57ec57bf5e57e5adc91b58d %}

Now, this private function can be inserted into the changeset, and the `validate_required` check can be updated to include `genre_id`:

{% gist https://gist.github.com/noelworden/d3c99fecdc86d337f25fdf932834e28a %}

And the Survey schema can be updated to include the `genre_id` association, so the entire schema would be updated to look like this:

{% gist https://gist.github.com/noelworden/f4c0e7d53108d0f8d14c05467467cc5f %}

With all that in place, the `genre_id` field will be automatically populated based on the content of the user-selected `movie_id`.

The task of needing to assigning two fields from one dropdown is something that could most definitely have more than one solution. I would love to hear anyone else's take on the how to handle something like this. 

------

This post is part of an ongoing *This Week I Learned* [series](https://dev.to/noelworden/beginning-of-a-blog-series-5aj3). I welcome any critique, feedback, or suggestions in the comments.