My Tasty Meals App is a SPA application built with a Ruby API backend and JavaScript frontend. Besides reading meal recipes from around the world, this app allows users to create, update or delete their recipes. Users can also search a meal by name and filter meals by category.
1. The Backend
The app contains a Rails API backend that follows RESTful convention. When I created the backend folder in the terminal, I used api
flag to leave out the unnecessary features. Below is the command to create the API backend for the application:
rails new tasty-meals-app-backend --api
I have two models for this project, a category
model and a meal
model. The relationship between the two models is:
category: has_many :meals
meal: belongs_to :category
There are some new takeaways I've gained from this project when creating the backend:
Adding model relationship when using resource
generator
Below are the command lines I used to generate all the resources.
rails g resource Category name
rails g resource Meal name thumb ingredients instruction category:belongs_to
We can omit the data type if it's a string. Also, we can specify the model relationship by adding belongs_to
or references
.
Since I already included --api flag, when I used the resource
generator it would skip generating views and helpers for us. Also it will have the controllers inherit from ActionController::API
instead of ActionController::Base
.
Using Serializer to format data
There are mainly three ways to translate our Ruby objects into JSON format when our backend communicates with the frontend. We can directly include the format in the render
method in the controller. We can also include the Serializer in the model class. In this project, I implemented the data translation by adding the active_model_serializers
gem. Here is how:
In the gemfile, add gem 'active_model_serializers', '~> 0.10.2'
and run bundle install
.
After running rails g serializer category
and rails g serializer meal
in the terminal, set up the attributes in the files:
Upon setting things up as above, when we invoke the #index action in the Category
controller, Rails will recognize automatically the serializer we include, instead of calling Category.all
, we are actually calling the serialized data of categories using the attribute
method we specified.
CORS
Since browsers do not allow unwanted HTTP requests from being sent to a server, they would restrict requests from a different origin. That's why we need enable CORS (Cross Origin Resource Sharing) to allow our server to specify from what origins it will permit.
2. The Frontend
On the frontend side, this app contains a single HTML page, styling with Bootstrap along with plain CSS. The app also uses Vanilla JavaScript codes to implement all the user interactions.
Using Classes
I created four classes for my project: the CategoryApi
class and the MealApi
class are responsible for handling all fetch requests for the categories and meals; the Meal
class and the Category
class enable to create an category or an meal object that encapsulates both data and behaviors.
Here is how I built out the Meal
class to create an meal object and attach it to the DOM.
First I had the constructor prototypal method that was triggered every time when I invoked the new
keyword to create a new meal
object. The meal
backend object was passed into the constructor as the argument of the constructor. Here I used the destructuring method (cleaner and more easily to read) to assign the values. Besides the properties of each meal, I also set up the HTML element that would contain this meal object, and attached event listeners particular to each meal object. Then, I created static all = []
to save all the meal objects. Every newly created meal object would be pushed into this array.
Static methods VS Instance methods
Most of the time, if I was dealing with a specific meal object, it’s gonna be a instance method. For example, below are two instance methods for rendering an single meal object and attaching it to the DOM.
When dealing with the collection of meal objects, I defined the static method as below.
3. Communication Between Backend and Frontend
All the interactions between the users and the server are handled asynchronously on my app. I included at least 4 AJAX calls that covers full CRUD functionality - create, read, update and delete a meal object. JSON is used as the communication format for my app.
The above code shows how I handles the meal collection sent back from the API using the fetch
method. The flow is after I make a fetch request, I take the meal
object from the backend and create a frontend meal
object immediately. The newly created meal
frontend object has a bunch of prototype methods and static methods we can call on and manipulate with, for example, the render
method the attachToDom
method.
4. Further Thoughts
This project has a lot room for improvement. For example, I still need to work on optimizing the search functionality. I also want to add a user login system to my project.
Top comments (0)