AppleCart: My First Production Rails App
I pushed my first production Rails app to Heroku this week. Feels exciting to have a real project launch to solve a real problem after having started working with Rails such a long time ago.
What is AppleCart?
A friend of mine, Jen, has been very involved in the Making Strides Against Breast Cancer organization. There is a yearly walk that involves raising money for the cause. The last few years, she has resold gourmet candy apples from another friend who is a chocolatier as one of her fundraising efforts.
Jen came to me a few weeks before fundraising was to begin asking if I had a way to keep track of her apple orders. Orders involve someone looking over a list of available apples, choosing the apples they want and quantities, paying in cash, and then waiting while Jen orders the apples and distributes them a week or so later.
I initially recommended a free online store such as Shopify or Storenvy, but she said she did not want to deal with credit cards or any of the fees involved. It would also make it more difficult for people that just wanted to pay in cash.
I thought about the requirements a little more and realized it was probably perfect for a simple two screen Rails app. (Plus, you’re not a real developer until you’ve written a shopping cart app…) I promised her I’d get started on it right away and have it done in two weeks.
Here are some screenshots of the final product.
The first step was diagramming out the workflow. The workflow for a customer is pretty simple for this app.
- Customer creates an account.
- Customer chooses apples from a single browsing page.
- Customer edits quantities in cart.
- Customer submits order, confirming they will be purchasing those apples.
- Jen collects money from all customers and marks them off as paid.
- Jen orders apples.
- Jen receives apples.
- Jen delivers apples to each customer, marking them off as they are delivered.
The object diagram is actually pretty simple too. The main objects are:
- Users (of course)
- Orders (collections of apples chosen by the customers)
- Items (in our case, they’re all apples)
- OrderItems (a line item in an specific order)
I won’t go into all the details (you can look at my schema.rb), but some relevant things:
- A user always has one order (it is auto generated by the user model if does not exist when requested).
- An item (apple) has a price and cost, so that apple specific totals can be shown.
- An order and item are joined by an OrderItem table which includes a quantity.
- An order has three possible states: open, processing, or delivered. An open order is one that has not been “confirmed” by the customer, and therefore is not counted in most totals shown to the admin. An order in “processing” state is submitted, but hasn’t been delivered yet. An delivered order has been received by the customer.
- An order also has a boolean “paid?” flag that the admin will set to true when they have received payment for that order. This could have arguably been rolled into the state machine after “processing”, but to handle any edge cases I decided to make it separate. It also makes it easier to convey to the customer what each state means. (Now that I’m thinking about it even further, it might have been good to change the state machine to open->confirmed->paid->processing->delivered.)
- The app should stop accepting new orders when the admin wants, so there needs to be a global flag for whether sale is enabled.
- A user should only be able to see their own order, but an admin can see/edit anyone’s order.
- A user should be able to check the status of their order even after sale is closed.
- An admin needs to see aggregate data including how many of each apple needs to be bought from the supplier, how many orders are in each state, and how much money is being processed.
There are other rules, but that’s a good sampling. Those are starting to look like some combination of testing and user stories, but unfortunately, I did neither formally for this project. One of my next big goals is TDD, and this would have been a great project for it, but the time constraint excuse got me again.
I decided on each interface problem one at a time.
The first decision was whether I wanted anonymous users (not logged in visitors) to be able to view the apples. In retrospect, this would have probably been better for the UX because it would have given potential customers the opportunity to view the selection before committing the time to create a user account. I went with a forced account creation though because I was worried it would have taken me too long to figure out how the additional logic would work.
Going with that decision for now, I had to decide whether the store section (viewing pictures of apples) and the shopping cart would be one view or two views. One of my requirements was that we have decent sized pictures of the apples, which doesn’t lend itself well to a nice tabular view like most people are used to seeing with shopping carts. The only compromise would have been to embed the cart view in the sidebar of the store view. This would have been nice, but I think it would have been necessary to have a full page cart view anyway, so it would just be a nice-to-have.
Once I decided on two views, it was time to decide how customers would decide on which apples they wanted (lots of decisions). I could make a view for each individual apple, but that seemed unnecessary for the small amount of information I had on each apple. It would also slow down the process of selecting apples.
I decided to put an “add to cart” button next to each apple. I was briefly thinking about having a quantity box next to each as well, but it seemed simpler to have the button be just an AJAX callback to increment the amount of that apple by one. That way, there wouldn’t have to be multiple form submission buttons, or a confusing combination of cart aesthetics. I’m not quite sure how this is going to work out, as I haven’t gotten to do any user testing yet, but I’m looking forward to see how (un)intuitive it is for regular users.
The AJAX response tells the user how many of that apple they now have in their cart, and updates on each successive click. Once they’ve scrolled through the seven apples, they’re met with a big question at the bottom of the page “Done shopping? View your cart” which guides customers to the next step.
In the cart view, there is a familiar tabular list (with no thumbnails) with quantities, prices, and line prices. Because the store page wasn’t very flexible at specifying quantities, the customer can edit quantities at this step. I sketched out a few ways to implement editing, including inline editing, transforming the show into edit view on a button press, or just using a separate view for it. I chose the last option both for simplicity, and because inline editing may have been unintuitive and required more explanation than a simple form. The middle option may have been the best in retrospect.
The final user action is confirming their order and moving it to the next step. Once this is done, they can track their order by logging in and viewing their cart at any time. I get the luxury of not keeping multiple carts because of the nature of this project, so I took advantage of the simplicity.
So I started building. I started things off with drone.bz again which helped knock off the basic gems I needed.
I pulled a few gems from other recent projects I had, including using Thin for my development server and Quiet Assets. Haml is a staple now. Nested_form and simple_form for the little bit of form work I needed to do. Easy Roles simply because I needed that Admin identifier and wanted something a little more robust than a boolean just in case.
I decided to try out migrant again, which in some ways saved time, but probably ended up hindering more than helping. Devise and high_voltage. And bootstrap has been a huge timesaver and taught me a bunch more about SASS and CSS. I thought I’d use Dragonfly for image uploading, but since I didn’t need dynamically added images (and didn’t want to set up S3 later in the project), I decided to go with just simple asset pipeline links.
The final bit was stateflow as my state machine helper. I’d like to try out something different next time, although it did do the job.
With most of that in place, I did my models and migrations (a little backwards because of migrant), then controllers and views, controllers and views. Dropped back to models to add methods. Tweaked things here and there. Nothing to see here folks.
But seriously, I learned some great new stuff this go around. I dug a little deeper into rails view helpers, which I had never used before. They helped a little to clean up some of the tricky view logic I had. Some views I wrote toward the end I must admit I started slipping and using direct model accessing in the view (bad!). But overall, my code was a lot cleaner than in past projects where I was focusing more on getting it working than cracking down on technical debt.
I wrestled with Twitter Bootstrap bugs at times, and I still am awful at design, but the upside was that I did my first bootstrap skinning and some more customization than usual. I dug a little more into SASS, although I’m still a bit confused about import order and had to throw in some ugly hacks in order to get the customization I needed.
I had the core built on my first Saturday working on the project, and then started the skinning and admin page stuff the next day. The next weekend I worked on more admin functionality, cleaned up the design a bit and added some final copy.
I was a little nervous that I hadn’t done any deployment before and really needed everything to go smoothly with his project. It took a little digging and a little poking around, but I did indeed get my first heroku instance up and running. I made myself a little cheat sheet of all the new command line stuff I needed to remember and worked with Postgres for the first time.
I delt with my first 500 server errors and tailed my first heroku logs. It was exciting to see something I built up and available for all to see. I pushed several times over the course of building, and then for the last time (hopefully) last night.
I’m looking forward to seeing the reception to the site. I’m hoping people will find it relatively usable and get some constructive feedback from real users as to where my assumptions were held true and where they fell flat.