Assigning Two Fields from One Dropdown Selection with Elixir

noelworden profile image Noel Worden ・3 min read

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:

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

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.

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 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:

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:

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

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

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. I welcome any critique, feedback, or suggestions in the comments.

Posted on by:

noelworden profile

Noel Worden


Software Engineer in Boulder, CO - Writing code and getting strategically lost in the mountains


markdown guide