My Sinatra Project

Before we get started

Figooooo
7 min readMar 4, 2021

This is my second project at Flatiron School. We are required to make an MVC app using Sinatra. MVC is a software design pattern that divides the related program logic into three interconnected elements. By being a pattern, it makes everything similar and much easier. I designed a static food-related website with a lot of CSS stylesheets last year. So I figure that it’s finally going to pay off if I build an app that allows users to CRUD their recipes. Guilty that I just want my app to look nicer.

Set up the structure

I tried to use the Corneal gem for this project but it failed me during the migration phase. Since it’s really confusing and time-consuming I didn’t spend too much time to fix that so I just used it to generate the structure and files, then I deleted the spec folder and reconfigured the environment, Gemfile, Rakefile, and config.ru. Here’s my app tree.

Gems I used

besides almost conventional ‘require_all’ to load up codes from other files and ‘pry’ to debug, all the gems I used in this project are as below.

  • sinatra: Turn my app into a Ruby web application
  • activerecord: Map objects to the database
  • rake: Run tasks
  • sinatra-activerecord: Give us access to some rake tasks
  • sqlite3: Manage my database
  • thin: Offer an easy way to start the app
  • shotgun: Test my app without constantly shutting down and going back on
  • bcrypt: Salt user password
  • tux: Playground for this project
  • sinatra-flash: Show error messages

Tearing my static website apart

As I went through my previous static webpage I found it very promising to make something out of these sections.

This header part contains the logo, navbar, buttons, which is perfect for the home page.

Now I have the footer too. Too bad that I couldn’t get those icons to load. I really like that color change while hovering on social media icons. Now that I have these two-part I can set them for layout.

All I need now is some content and a form template. I’ll only have to adjust some CSS styling for effect.

Tables and Migrations

The next step is to create database tables and migration files. Basically what information you want in your models is defined here by inheriting from ActiveRecord. The user model is very simple just username, email, and password. But in this case, we want our user’s password to be salted so instead of just putting a plain password for the column name, we use password_digest.

Recipes table

Here’s the migration file for creating my recipe table. For the model I wanted it to have a name, ingredients, cooking steps, difficulty level, and cooking time. These are the model properties that I want my recipe to have. Also, it has two id-related columns, author_id, and save_id. They are all foreign keys, I’m going to explain this later in model associations. Lastly, it has a save_times column for a feature that I can manipulate some recipes by their popularity.

Models and model associations

User Model
Recipe Model
  • I want the user’s password in this app to be more secure, so I use this has_secure_password which provides a method to verify passwords and requires users to input password when signing up.
  • I want users to input valid information when signing up so I set up these validations that users can not register either without a username or email or using an existing username or email.
  • I want users can not only create their own recipes but also save recipes that are not created by them. In association speaking, a user can have many created(authored) recipes and many saved recipes. So I tell the user object that it has many authored_recipes which actually it’s looking for the recipe through a specified foreign key. On the other hand, a recipe object belongs to an author which is indeed a user by aliasing class name to be the user. By separating two associations apart, we have two arrays, user.authored_recipes and user.saved_recipes. The drawback is that we can not check which of the users have saved a specific recipe because that recipe’s save_id will always be the last saver’s user-id

Controllers and sessions

For the best practice of separation of concerns, I create two controllers that inherit from the application controller. The user controller will be responsible for handling CRUD actions (in this case just create and read actions )for the user. And recipe controller’s job is to deal with requests for recipes. In order to properly use these two controllers, we need to add them into the config.ru file like use UsersController. Since Sinatra only offers access to the get and post method, if we’re going to use patch/put/delete, we’ll have to add use Rack::MethodOverride on top of those use controller statements.

Controllers are the soul of MVC structure, they are complicated for handling most of the logic of the app yet simple that can be described as RESTful routes. We can simply summarize it as 4 Gets, 1 Post, 1 Patch/Put, 1 Delete.

4 GETS

  • GET ‘/obj’ : renders index page to display all obj
  • GET ‘/obj/new’ : renders new page to display the form that creates a new obj
  • GET ‘/obj/:id’ : renders show page to display one obj based on ID in the URL
  • GET’/obj/:id/edit’ : renders edit page to display edit form based on ID in the URL

1 POST

  • POST’/obj’ : creates one obj based on the data user send through the form

1 PATCH/PUT

  • PATCH’/obj/:id’ : modifies an existing obj based on ID in the URL
  • PUT’/obj/:id’ : replaces an existing obj based on ID in the URL

To tell the difference, put will create a new obj but patch will only modify the original obj.

1 DELETE

  • DELETE’/obj/:id’ : deletes one obj based on ID in the URL

We could create routes we need to fulfill different purposes flexibly, but they’re not getting out of this pattern. For instance, I created a plaza feature that users can randomly see some other recipes using a GET ’/obj’, and a save functionality through a POST request.

As part of the requirement, also it’s important for the user’s experience and app logic. Users can only CUD whatever belongs to them. But HTTP is a stateless protocol, treating each request as an independent transaction that is unable to use information from any previous requests. This means there is no way within the hypertext transfer protocol to remember a user’s identity from page to page. Here’s where our sessions come into play. We can simply just assign session[:user_id] = user.id to store the user’s data in this hash. Once we find that session[:user_id] is not nil meaning this user has already logged in, this user can play around without logging in on every page. And we could also check if the data that this user wants to modify or delete belongs to this user by matching data obj’s user to this current user.

Views

This is where we render out ERB files. We can write HTML code here just like in a plain old .html file. More important we can use a substitution tag (<%=) and the scripting tag (<%) to write embedded Ruby which stands for ERB in this file. Sinatra is also configured to look for our .erb files in our Views directory by set :views, 'app/views'

ERB file is the place we code to display the page in ways we want it to be and set the action and method of our forms. Make sure that action in the form matches the route in the controller, otherwise you’ll end up seeing an ugly error page. Wait! What if I want to patch or delete some data? Remember that use Rack::MethodOverride? Add a corresponding input tag into your form to override that post method.

<input id=”hidden” type=”hidden” name=”_method” value=”patch”>
<input id=”hidden” type=”hidden” name=”_method” value=”delete”>

Words in the end

  • Finish the logic of your app first, if you have some time, then polish up the styling. I found that as a software engineering developer, CSS is way too time-consuming and hard to be perfected.
  • Use incognito mode to develop, in this way you won’t be deceived by cache in your browser!!
  • Use up Pry, tux, and error information. They are extremely helpful!
  • Use flash.now[:alert] instead of flash[:message] if you want to add some bonus hint to your user because sometimes flash[:message] won’t go away

Thank you for reading!

--

--