PackList Pandemonium Project

In this project, I was tasked with creating a basic content management system using the Sinatra MVC framework. I’m a big advocate for traveling. I’ve both studied & worked overseas, and the lessons I learned about the world & myself continue to serve me to this day. One of the most exciting times for me is figuring out what I need to bring on a trip, so I created an app called PackList Pandemonium.

Before I started coding, I had to make some design choices, both in terms of presentation & structure of the back-end data. In terms of presentation, I decided to go with a list of checkboxes, as opposed to a series of text fields. With the way HTML’s rendered, text fields take up more space than a list of checkboxes, so creating a packing list long enough to cover even a local weekend getaway would involve an irritatingly long form. Also, I couldn’t tell you how many times reading a predefined packing list (the internet is full of them) sparked the thought that I forgot to add such-and-such item, so using checkboxes seemed like a better idea from a user experience standpoint.

As far as the back-end data, I knew that I would need separate SQL tables for users & lists, but I was a bit unsure on how best to represent the items (more on that later). I didn’t have to make a lot of decisions on the app’s overall structure, since the main functions of the models, views, and controllers is already predefined; however, I did have to choose the depth & breadth of information within each piece. I’m a big fan of only gathering & using the bare minimum, which depends on the use-case. The users table only has columns for username, password, and lists; the lists table only has columns for name, items, and user id. I can definitely see a use-case where more details about each would be really helpful, but I wouldn’t learn more about building this kind of app by adding additional columns.

I have to thank the creator(s) of the corneal ruby gem. This gem creates the file hierarchy & configuration files necessary to start a Sinatra project, and saved me about one or two days’ worth of work. With two bash commands, I got to the point where I could start building the app.

I’m finding that with any project (coding or otherwise), I learn what I need to learn as I work through it. Most times, I just have to start something in order to figure it out, which sounds counter-intuitive. After stubbing-out a basic landing page, I started on the data creation tasks with a short list of about five items in the view and playing with the params. When using checkboxes, params returns a key-value pair with the value of the HTML “name” tag as the key with a value of “on,” if the box is checked, and not return anything if no boxes are checked. So take the following line of HTML:

<input type=’checkbox’ name=’sunglasses’>Sunglasses

This will return the following for the params:

params = {“sunglasses” => “on”}

However, this got really messy with multiple items, especially because I set up a text field for the list’s name, which became its own key-value pair inside params. So to grab the item names, I set it up as a collection:

<input type=’checkbox’ name=’item[sunglasses]’>Sunglasses

This creates its own hash, and is much more manageable:

params = {“item” => {“sunglasses” => “on”}}

With this, I could iterate through params[:item] inside the controllers to grab the item names. Great! So only the items that were checked show-up in params, which gave me an idea for the delete functions.

In addition to deleting the lists, I wanted to give the user the option to delete their account. For both, I created a checkbox with text saying that the user understands the repercussions of deletion, and only takes place if it’s checked. It both provides validation that they really want to delete that data, and mitigates accidental deletions.

Now, back to the list-item relationship. At first, I tried just shoveling the item name into the “items” attribute of the list object, and I kept getting a nil value. After much kvetching & research (array is not a valid data type in sqlite3), I decided to make an items model. This involved setting-up a has-many-through relationship between users & items in addition to the has-many & belongs-to; thankfully, ActiveRecord makes this very easy & simple to implement. No need to create any join tables, each model only needed two lines of code to instantiate these relationships. Now the items attribute of the list object returns the item objects!

At this point, I hadn’t touched the update/edit function. All the item names were contained in a view, and made editing slightly more impossible than punching a hole through a block of cement with my head. This also goes against the function of the views; views * present * data in a human-readable format, not be the repository for the data. So I tried setting-up a fourth model, called BaseList with a class method that simply returned an array of strings of item names. Iterating through it in the view was a simple affair, and so was verifying if it contains an item in the user’s list. I decided to make separate class methods for each section (general, electronics, camping, toiletries, and clothing). I arrived on class methods for two major reasons: 1) I wasn’t planning on creating objects with this model, so no need to differentiate scope, and 2) I like how it looks semantically (BaseList.camping). This brought it back in line with the functions of views & models, and made it MUCH easier to maintain. Taking a quick look at the difference:

<input type=’checkbox’ name=’item[sunglasses]’>Sunglasses

Versus:

in the model:

[‘sunglasses’, ‘maps’, ‘compass’, ‘laptop’]

in the view:

<ul>

<%BaseList.general.each do |item|%>

<li><input type=’checkbox’ name=’item[<%=item%>]’><%=item%></li>

<%end%>

</ul>

In the first example, you’d have to write that for every…single…item. In the second example, you just have to add another string to the array in the model, and the view adds another checkbox row for you.

Learning how to build a project with Sinatra MVC was a lot of fun, and I definitely got a sense of the kinds of projects that Sinatra would be a good fit for. If you have a small pet project with limited scope & user base, Sinatra fits into that very nicely. However, it quickly becomes insufficient for something at scale. I doubt that PackList Pandemonium stretches the limit of what Sinatra can do, but even so, I can tell that it’s very much a bare-bones web framework.

I’ve posted the source code on github: https://github.com/kerneltux0/PackList_Pandemonium under the GPL Version 3 license. The open-source movement has been instrumental in helping people get started in software development, so I plan on releasing every personal project under the GPL.

Leave a Reply