Building a Multi-Input, Multi-Layer Filter w/Ruby
Coding can feel like trying to solve a Rubik’s cube.. With each twist you might seem to be getting closer on one side, but the same twist that helps one side, messes everything else up.
Each time you get closer to the final solution you are presented with a new problem, and are forced to think of other ways to solve the full problem at hand, not just one piece of it.
Let me introduce you to Herbals — my Mod2 project for Flatiron Schools Software Engineering program. Mod2 is essentially weeks 4–6 of coding school. Check out my creation here: https://herbalstrains.web.app/
The main feature is a multi-input, multi-layer checkbox filter where users can select any number of “Desired Effects”, “Current Issues”, and “Potential Issues to Avoid”. The app then filters through nearly 2,000 strains of cannabis and outputs only strains that provide the Desired Effects, help with Current Issues, and rules out any strains containing any Potential Issues to Avoid.
The issue I ran into was that the API’s data was nested in a strange way that didn’t provide ease for this type of filtering.
(Keep in mind, this project was made after roughly only 5 weeks of coding school, so some of the techniques are pretty basic but seemed difficult at the time, others are pretty hacky work-arounds, and a refactor is definitely in order, or even a rebuild using React & Node…)
Here are roughly the steps to set this up how I did at the time:
- Set up the form with checkboxes contained inside of named divs for categories. My divs are named “positive-effects”, “medical-effects”, and “negative-effects”.
- Add an event listener to the form. On Submit, get the user selected boxes, and turn the values into an array of params:
3. Send a post request to the backend joiner controller that joins the Strains and Effects tables. I creatively named mine “Joiners”.. I know, bad practice.
4. THE LOGIC (more on this below).
5. Fetch the backend/joiners, and append strain information to strain cards, and strain cards to the DOM.
Back to the logic piece.. The main focus for this Blog post:
After trying many attempts at using Ruby’s filter, find, find_by, where, or even crazy nested if statements to check which strains contain all of the selected positive and medical effects and DON’T contain any of the negative effects, I found myself realizing that like most things, it’s much easier broken into smaller steps.
In the Joiners controller Create action:
Step 1) Destroy all previous Joiners.
def create Joiner.destroy_allend
Step 2) Create Variables for each type of effect, and set to empty arrays.
selected_positive_effects = []selected_negative_effects = []selected_medical_effects = []
Step 3) Enumerate through all params (sent from front end as user selected effects). Push the each type of effect into the appropriate array.
params.each do |key, value| if (key.include? "effect" && "positive") selected_positive_effects.push(key.split("-").first) elsif (key.include? "effect" && "medical") selected_medical_effects.push(key.split("-").first) elsif (key.include? "effect" && "negative") selected_negative_effects.push(key.split("-").first) endend
At this point I now have three arrays; one containing user selected Positive Effects, one containing user selected Medical Effects, and one containing user selected Negative Effects. Here comes the hard part:
Step 4) Enumerate through the entire Strain catalog, with a nested enumeration through the user selected positive effects, and return only the strains in the catalog that contain ALL of the user selected positive effects — store that array as a new variable.
clears_positive = Strain.all.select do |strain| selected_positive_effects.each do |pos_effect| break if strain.positive_effects.exclude? pos_effect endend
Step 5) Enumerate through the NEW VARIABLE that we got in step 4, with a nested enumeration through the user selected medical effects, and return only the strains from the new variable array that contain ALL of the user selected medical effects, and store that array as another new variable.
clears_positive_and_medical = clears_positive.select do |strain| selected_medical_effects.each do |med_effect| break if strain.medical_effects.exclude? med_effect endend
Now this array contains only strains that meet the first 2 filter criteria.
Step 6) Enumerate through the last new variable, with a nested enumeration through the user selected negative effects, and DON’T return strains that contain any of the user selected negative effects. Store this new array as its own variable. This array has met all the provided criteria.
clears_all_three = clears_positive_and_medical.select do |strain| selected_negative_effects.each do |neg_effect| break if strain.negative_effects.include? neg_effect endend
Step 7) Create one final array of full strain objects by taking your last array of strain names, and finding the full objects in the data base.
final_strain_array = clears_all_three.map do |strain| Strain.find_by name: strain.nameend
Step 8) Use your Create method to actually create the Joiner instances. Map through the final array of full strain objects, and create a Joiner instance for each strain that meets the criteria. Here you can also decide which information you’d like to display on the front end.
@joiners = final_strain_array.map do |strain| Joiner.create( strain_id: strain.id, name: strain.name, category: strain.race, flavors: strain.flavor, positive_effects: strain.positive_effects, medical_effects: strain.medical_effects, negative_effects: strain.negative_effects )end
Step 9) Redirect to the front end where you will fetch your new Joiner instances as shown above.
redirect_to "http://localhost:3001/suggestions.html"
Looking back on this, there are a lot of things that they teach you in Mod2 that aren’t exactly ‘correct’, or relevant to the vast world of professional programming, but it was all a good learning experience, and through writing this blog post I now see a lot of things that could be simplified, refactored, or re-written using more efficient techniques. It’s excellent to see how far I’ve come since this project, and I’m excited to share my more recent projects with you soon!
Cheers!