loading...

Building My First Sinatra-based Ruby App: Thoughts and Wrap-up

morinoko profile image Felice Forby Updated on ・8 min read

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—was born!

Veggie Tracker dashboard

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

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

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.

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:

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:

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:

<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:

<% 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 or try out Veggie Tracker for yourself :)

Posted on Sep 19 '18 by:

morinoko profile

Felice Forby

@morinoko

I'm a former Japanese translator turned full-stack web engineer. I work mostly with Ruby and Rails, and love working on both the back-end and the front-end. I'm based in Japan :)

Discussion

markdown guide
 

Hi Felice! I have stumbled upon your post while doing research for my upcoming Sinatra portfolio project (I'm taking the online Full Stack Web Developer program with Flatiron too!)! I enjoyed reading your post and have learned a lot especially with using partials. Thank you!

 

Hey Vince! Yay, a fellow Flatironer :) I'm glad my post helped a bit - thanks for reading! Good luck and have fun with your Sinatra project! I'll be wrapping up my Ruby on Rails project soon.

 

Thank you, Felice! I am definitely having fun with my current project! I'm looking forward to your post on your Rails project :D

Hey Vince, I happened to see your article on Learn Magazine about your Sinatra app the other day. Nice read and cool app! Did you design those icons too?

Thank you for checking it out, Felice! I just passed my project review today for that app!

I used FontAwesome for the icons!

Have you scheduled your interview for your Rails project? Good luck!

Nice, congrats on passing your review!

I love FontAwesome. I use it all the time too!

I'm actually doing the community-driven program so unfortunately, I don't get to do interviews or project reviews :(

Thank you! I'm so excited to learn more about Rails and even started whipping up ideas for the future project.

Oh right, but will there be some sort of support at the end of the curriculum?

Looking forward to checking out your Rails project too!

For the community-supported program, there isn't any support at the end either :( I really wanted to do the regular program, but I couldn't fit it in schedule-wise and budget-wise. Still really enjoying the curriculum though!

 

Well, it sounds like you have three users! I really enjoyed the article and am enjoying going through your code. I hope all is well from a fellow flatiron student(maybe you are done) and Japan resident ( well for a few years)!

 

Thanks for reading! I hope you're enjoying the Flatiron program. Awesome to hear that you're in Japan too! How are you liking it?

 

Sorry for the delayed response I just found the comments section. LOL. Going well here, we really enjoy Japan. The people are very friendly and Tokyo is so big and safe. I just started trying to learn some of the language as I have just been here for 6 months. So far so good!

Awesome! There's so much stuff to explore in Japan—enjoy! Have fun learning Japanese too. It can be tricky sometimes but never gets boring ;)