Sunday, March 22, 2020

Rails Project: What's for Dinner?

If I'm honest, Rails was the most difficult unit for me so far. I really, really liked Sinatra. I liked that at the end of my Sinatra project, I had a functional application and I could literally explain every line of code. I knew what it did, and I felt comfortable experimenting by changing it. As soon as we switched over to Rails, I felt like I no longer understood anything! Sure, it's possible in Rails to generate an entire app with basic CRUD functionality in seconds ... but what the heck is actually happening??? I found that to be a theme with Rails. Even now that I have finished my project, half of the files are still a big mystery to me. What exactly does database.yml do? What are all these initializer files? I've been told that this is the thing about Rails, that there's always more to learn and understand, to be patient ... but still, this is outside of my comfort zone. I like to understand all of the nuts and bolts.

Which is not to say that I'm not proud of the project I built! I called my application What's for Dinner? and it's designed to solve a most basic need of parents everywhere: finding kid-friendly weeknight dinners. Users can submit recipes, view recipes, filter recipes by category, search for recipes, and save recipes to a "recipe box" (as long as they have an account). They can also sign in through Google! (Let me tell you, after the headache this feature gave me, I will never take for granted being able to do this on any site I visit, ever again!)

Rather than walking through the entire design, I'll focus on a key understanding I gained through the process of building my project:

Nested Forms

These have been my biggest obstacle in Rails -- specifically, understanding all of the different form macros (form_for, form_tag) and using them to generate forms for nested attributes. After much trial and error, I think I finally understand the beauty of form_for!

In my domain, a Recipe has many RecipeSteps (something like recipe_id: 15, step_number: 1, content: "Boil water") and RecipeIngredients (a table joining ingredients to recipes, something like recipe_id: 15, ingredient_id: 12, quantity: "1 cup"). When a user wants to create a new recipe, I first ask them how many steps and ingredients their recipe has (an annoyance I hope will no longer be necessary once we learn JavaScript). I then use my RecipesController#new action to build a new Recipe and build the correct number of RecipeIngredients and RecipeSteps associated with that Recipe object:

Then, in my /views/recipes/new file, I can use fields_for to capture data for those nested attributes (RecipeIngredients and RecipeSteps). Note that recipe_form is the form builder object defined at the start of my form_for; i.e., <%= form_for @recipe do |recipe_form| %>

So what happens when the submit button is clicked, triggering the RecipesController#create action? Well, a new Recipe record is generated and then there is an attempt to save it to the database. But, this simple-sounding step - "a new Recipe record is generated" - is actually quite involved! In particular, the Recipe class has custom setter methods responsible for creating the associated RecipeIngredient and RecipeStep records while creating the Recipe object they belong to:

And that's that. Now that I've talked through it from start to finish, I wonder why it ever seemed to mysterious!

Thanks so much for following me on this coding journey. Please feel free to check out the repo for my Rails project here; I'm also hoping to "go live" with this project one day soon (once I've made some upgrades, of course!). Comments and suggestions are all welcome!