Building My First Sinatra-based Ruby App: Thoughts and Wrap-up
Tags: sinatra, ruby, beginners
Published: 2018-09-19 23:39:05

---
title: Building My First Sinatra-based Ruby App: Thoughts and Wrap-up
published: true
description: Overview of building my first Sinatra app
tags: sinatra, ruby, beginners
---

I recently finished building my first full-fledged Ruby app using Sinatra! The project was done as part of my coursework for Flatiron's Full Stack Developer Program, but instead of just building something for dev practice, I wanted my app to be live and usable (and hopefully useful!) for someone.

This post is a basic wrap-up for the project, including how I got started and things I learned and tried out.

## Getting the idea

What better way to get an idea than to identify a problem that you can help solve for someone else? Sounds easy, but I think "finding a problem to solve" is often harder than it first seems. People's problems are complicated and at this point in time, I'm not equipped will the skills to bust out a super complicated app on my own, so it took some time to pinpoint an appropriate problem to solve.

I decided to ask a friend who is a farmer how I could possibly help. After a conversation about how she plans what to grow, it turns out she has a hard time keeping track of when to plant what vegetables. She had been writing down which seeds to plant in a weekly planner, but would inevitably overlook things or forget to plant some vegetable once the page was turned. Plus, every season she would have to go back and re-look up the exact planting months for many of the vegetables and rewrite everything again.

While she didn't want to be bothered using a full-blown (and complicated) farm management app for her small farm, having a simple way to keep track of which seeds to plant in which month would be a great help. And from that, my simple app—[Veggie Tracker](https://veggie-tracker.herokuapp.com/)—was born!

![Veggie Tracker dashboard](https://thepracticaldev.s3.amazonaws.com/i/qme3xgoiqy2t1yu5k0f1.png)

## Getting started

As a beginner, the experience of building an app, even a simple one, from scratch can certainly be daunting. I've been able to build more complicated things thanks to tutorials and/or starter code (e.g. the Ruby on Rails Tutorial), but in the end, it just doesn't soak in as well as having to think and plan out every little detail all by yourself.

The first thing I did was not code, but write out a basic overview of what the app needed to do, more or less from the user perspective. Boiling it down to the very core concept, I just took a quote from the conversation with my friend: 

"I want to be able to see what vegetables I need to plant this month."

I imagined what the user would need to do for this to happen. First and foremost, users would need to be able to create accounts and save their data. They would also need to be able to add all the vegetables they wanted to keep track of. I figured it might be useful to be able to keep track of vegetables in different fields, farms, or garden plots so I decided that user should be able to create multiple farms.

This translated into three Ruby classes: Users, Farms, and Vegetables. Next, I planned out how the class associations and the database should be set up.

My first attempt connected my class in a simple way:

![First attempt at database schema](https://thepracticaldev.s3.amazonaws.com/i/u9kdzoqcfcuuvwdw1zno.png)

However, this database schema didn't work very well in cases where a user had more than one farm with certain vegetables planted on multiple farms because the vegetables could only belong to one farm. I had to work in a pivot table that would allow me to deal with such cases, so I refactored my schema like so:

![Refactor of database schema](https://thepracticaldev.s3.amazonaws.com/i/iuqp51uyxjm8fptz2v2n.png)

I really wanted to keep the vegetables associated with users through the farms, but I couldn't quite get it to work after implementing the pivot table :/

Anyway, now that I had both a plan from the user perspective and the basic layout of the app from the technical side, the path to coding the app was pretty much laid out for me. Still, there were several challenges I ran into as I tried out a few new things I hadn't done before.


## New challenges and new tricks

### Using tests
Though I had never really written my own tests before, I had worked a lot with test-driven development at my previous job as well as in Flatiron's curriculum. I figured it was a good time to try and implement tests for my own app.

Figuring out how to correctly hook up and configure the test suite (Rspec) so it would actually work was somewhat difficult and I had to rely on code from other code examples, but eventually, I got it figured out. The hardest part for me was trying to implement a flow in which I wrote tests before code. Because I'm not experienced with writing tests yet, the tests I tried to write did not actually work at first, so I found myself having to write the application code first so I could figure out what was wrong with the test code.

This was definitely not the ideal workflow and slowed down my coding progress, but eventually having basic tests to help make sure changes implemented while coding didn't break the code did indeed allow me to catch several sneaky bugs that I wouldn't have noticed otherwise. Having a better understanding of how to write good tests and how to actually use Rspec (or another testing suite) is definitely on my to-do list.

### I18n
Because my friend for whom I was building the app for was Japanese, the app needed to be in Japanese, but I also wanted an English version. I had been wanting to learn how to use I18n for a long time, so this was my chance! I had experience using I18n on a basic level at my previous job because our app was available in several languages, but had never implemented it from scratch. Considering that many services need to be localized into at least two languages, I think it's a great idea to learn how to use I18n.

Even though I knew I wanted to have Veggie Tracker available into two languages, I waited until I was halfway through coding the app to implement it. Bad idea. I ran into many problems getting the localized routes to work (I used slugs to differentiate the languages, e.g. `/ja` and `/en`) and had to go back and fix almost all the routes and links. If you're planning on using I18n, set it up in the beginning to avoid major debugging headaches later!

### Using CGI to URL-encode slugs

Instead of using object IDs for all my routes, I wanted something more friendly for my URLs. For example, `veggie-tracker.com/users/felice/farms/cattail-farm` is more readable and intuitive than `veggie-tracker.com/users/12/farms/239`. 

These nice, sluggified routes instantly broke my app whenever a Japanese username or farm name was used because the Japanese characters are not URL-friendly. Luckily, after some research, I learned there was an easy way to fix this problem: [Ruby's CGI class](https://ruby-doc.org/stdlib-1.9.3/libdoc/cgi/rdoc/CGI.html).

The CGI class can help you with a lot, including "retrieving HTTP request parameters, managing cookies, and generating HTML output." For my purposes, its `#escape` method can be used to make any string, including non-English characters, URL-friendly. It can be as simple as this:

```ruby
require 'cgi'

CGI.escape(slug)
```

### Using different layouts for different pages in Sinatra

In Sinatra, you can create a layout template that all your pages will use by creating a `layout.erb` file in your views folder. Sinatra automatically checks if you have a file named `layout.erb` and will use it if you do. The layout template will generally include code repeated on each page, such as your head, header, navigation, footer, etc.

What if you run into a situation where you need a different layout for certain pages? Well, you can tell Sinatra to use a different layout for any route in your controller using the `:layout` option. For example, I wanted the option for a narrow layout for certain pages that just included a simple login or signup form:

```ruby
get '/login' do
  if logged_in?
    redirect to "/users/#{current_user.slug}"
  else
    erb :'sessions/login', :layout => :'narrow-layout'
  end
end
```

The above code is for the login page. Assuming the user is not already logged in, the controller tells Sinatra to use the `login.erb` file located in the `sessions` directory using the `erb :'sessions/login'` code. The `:layout => :'narrow-layout'` code then tells Sinatra to use the narrow template instead of the default layout template. This assumes there another template file in the views folder called `narrow-layout.erb`.

### Using partials to keep your view files cleaner and DRYer in Sinatra

When your Sinatra view files start to get so complex that they're hard to read or you find yourself copying and pasting the same HTML in multiple places, try separating out chunks of your HTML into partials.

Partials are handy because they can be reused in multiple places so you can avoid duplicating HTML, which also means you only need to edit it in one place if the code happens to change. 

Separating out sections of HTML can make complex views easier to manage and wrap your head around because you will no longer need to sift through lines and lines of code trying to find the exact place you need to edit. For example, I separated out my navbar HTML and head HTML into separate partial files since these sections tend to get long and complex really fast.

If you've used Rails, you've probably already used the same technique using `render` to insert chunks of HTML/erb code into your views. It's slightly different in Sinatra, as there is no `render` keyword. My approach was to create a `partials` directory in my `views` directory. Any partial can then be directly inserted into the HTML using erb like so:

```erb
<header>
  <%= erb :'partials/navbar' %>
</header>
```

The above code assumes there is a `navbar.erb` file inside a `partials` directory. Of course, you can name your directories anything you want. I also found this trick useful for separating out complex HTML code that changed depending on some condition like whether the user is logged in or not:

```erb
<% if logged_in? %>
  <%= erb :'partials/welcome' %>
<% else %>
  <%= erb :'partials/onboarding' %>
<% end %>
```

### Getting user feedback
The nice thing about making Veggie Tracker for an actual person was that I could get real user feedback. Once I was confident the app was "perfect," I showed it to my friend. Within in just a few seconds, she pointed out several parts that needed improvement in term of usability and function. During development, it can be hard to keep the user's perspective in mind because you're just trying to get the app to at least work correctly.

After the initial feedback, I had to go back to coding for another few hours just to fix up a few things, but that's really the exciting part of development I think—making your creation ever the more intuitive, easy-to-use, and of course, helpful for real people!

I always try to be careful with feature requests, though. Users will request every little feature under the sun, but if you try to implement them all, your app will quickly get out of hand and its true purpose muddled. This happened a lot at my previous job. Even if we implemented features, users would end up not using them. It's easy to add things, but if you want to take it away after you realize you don't need it, that's going to be much harder. Now, I always try to ask myself if a certain feature is truly going to contribute to solving the core problem that my app is trying to solve.

---

There are a million other things I could write about, but I'm going to wrap it up here for now. Even though the Veggie Tracker app will probably only ever have one user, I'm excited to have it up and running and looking forward to improving it little by little.

See the project on [Github](https://github.com/morinoko/veggie_tracker) or try out [Veggie Tracker](https://veggie-tracker.herokuapp.com/) for yourself :)