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
A list of tuples that include each
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
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
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
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
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.