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.
Top comments (0)