In a continuation of the last post, today I’m going to go through the actual code that I’ve been using to set up the database, and explain why I made some of the choices I did. I will be using flask-sqlalchemy and an SQLite database for my implementation, which is what I used last time as well.
Ingredient and RecipeLine
Our first table is the Ingredient
table.
Recall from my last post that the Ingredient
table need only store the name of each ingredient. Originally, I’d planned for that name to be the primary key of the table, but seeing as ingredients could be an arbitrary length, I ultimately felt it was better to include a primary key table as well. I am specifying that the “name” attribute be unique
, which more or less accomplishes the same goal.
Additionally, I’ve implemented a validator here that establishes a few base requirements for every ingredient. Namely, they must be in all lower case, and cannot contain any other characters. I’m adding this to reduce duplicates; the idea of storing “all-purpose flour” and “all-purpose flour” differently seems pointless. Note that there’s nothing implemented at this point to convert a non-standard ingredient form into a standard form; I’ll be adding that elsewhere. This is merely to serve as a last line of defense to make sure that improperly formatted data isn’t added to the database.
Next up is the RecipeLine
table.
Again, this table is straightforward. Each recipe line holds an id
, the text
of the line, and a recipe_id
that relates to the Recipe
it is a part of. We haven’t implemented the Recipe
table yet, but 'recipe.id'
will be the name of the primary key for the Recipe
table.
Additionally, with these two classes implemented, we can build our first association table, between the RecipeLine
s and associated Ingredients
.
We then add two relationship
attributes, one for Ingredient
and one for RecipeLine
. I chose to use the back_populates
attribute rather than the backref
attributed because I like the additional control provided by the former.
Now the association is established, but there’s still one final check to make: we need to make sure that any Ingredient
added to a line is actually in the line. This is accomplished by another pair of validators, one in Ingredient
and one in RecipeLine
, which compare the name
attribute of Ingredient
to the text
attribute of RecipeLine
, to confirm that the Ingredient
actually does appear in the RecipeLine
.
Again, these should never be called, as our program will hopefully be smart enough to never try to add an Ingredient
to a RecipeLine
that doesn’t feature it. But if it does, we’re covered.
Recipes
Next up is the Recipe
class. This one consists of a name
, a url
, and a collection of RecipeLine
s.
Now that we’ve implemented the Recipe
class, we can add a relationship
attribute back to the RecipeLine
class:
Since the relationship between Recipe
s and RecipeLine
s is one-to-many, we don’t need an association table here.
GroceryLists and Users
Now, we implement a GroceryList
class to represent the actual lists. On it’s own, it holds nothing except a name
and an id
.
However, the addition of GroceryList
s means we can add our second association table, which links Recipe
s and GroceryList
s.
This gives us our many-to-many relationship between Recipe
s and GroceryList
s, ensuring that a user could reuse a given Recipe
for a new GroceryList
. It also means, theoretically, that a user could have a GroceryList
with a Recipe
on it that they did not create, and I’m going to have to check for that when I start adding permissions. I like the idea of a user being able to use a Recipe
they didn’t add, but not being able to alter the ingredients on it. If they want to alter them, they can make a copy. But I’m getting ahead of myself.
Our final main table is the User
table, which holds an email
, a hashed_password
, and an access_level
.
We can also add our final association table, with the necessary relationship
s in GroceryList
and User
.
This ensures that each User
can own an arbitrary number of GroceryList
s, and each GroceryList
can be owned by an arbitrary number of User
s.
And with that, the setup of the database is complete. I’ll probably need to go back and tweak a few things, but overall the implementation was really smooth. As I’m writing this, I’m pretty deep into serializing everything (teaser for my next post), and so far this implementation has served me really well. All in all, I’m very glad that I took the time to sit down and plan this out; it feels like a much, much stronger foundation for my app.
Next Steps
- integrate
flask-marshmallow
to serialize all the objects - begin construction of the api endpoints