Cacheable: a simple gem for caching methods in Ruby

Today we’re releasing a new gem that makes it easy to add caching to any Ruby code. It’s called cacheable!

The cacheable gem is an aspect-oriented, unobtrusive approach to caching. It turns this example code (taken from the Rails caching documentation):

class Product < ApplicationRecord
  def competing_price
    Rails.cache.fetch("#{cache_key}/competing_price") do
      Competitor::API.find_price(id)
    end
  end
end

Into this:

class Product < ApplicationRecord
  cacheable :competing_price

  def competing_price
    Competitor::API.find_price(id)
  end
end

And to help handle more complex caching scenarios, cacheable includes many more powerful features too: conditional caching, dynamic cache key generation, utility methods to skip and clear the cache, and more.

You can read the full story behind cacheable here, and start using cacheable by reading our docs on GitHub. Building cacheable enabled us to clean up Splitwise’s balance-caching code significantly, and it helped us solve several hard-to-diagnose bugs in the process!

Why cacheable?

At Splitwise, we found ourselves writing the same crufty caching code over and over again. Wrapping our code in caching blocks made it harder to read and more difficult to test. And any time that we wanted to perform more complex logic in one of our cached methods – for example, conditional caching – it would cause that method to balloon in size and complexity.

We decided to step back and reexamine our first principles – specifically, the single responsibility principle. We realized that a method like Product#competing_price shouldn’t be responsible for caching at all! We began to think, “What if we could extract the caching logic from our cached methods? What if each method was only responsible for its own logic, and the caching could be tested and built separately?”

Enter aspect-oriented programming (AOP). By switching to an aspect-oriented approach, we were able to build a caching framework that was easier to use, easier to test, and MUCH more reliable across the various parts of our codebase.

Try it yourself!

We’ve been using cacheable in production on Splitwise for many months, and today we’re excited to share it with the rest of the world 🙂 Check out our docs on GitHub for complete instructions on how to start using cacheable in your own Ruby app. We look forward to making additional improvements, and we welcome enhancements and pull requests from the Ruby community!

Presenting Cacheable

Splitwise is proud to present Cacheable, a gem designed to facilitate and normalize method caching.

Splitwise is designed to take the stress out of sharing expenses. If people think we’re doing a good job, they’re going to put more expenses in Splitwise. This is great! But eventually, a Splitwise group has so many expenses that it takes a non-trivial amount of time to calculate the total balance. We don’t want a server that is taking time to crunch numbers to increase stress, so we needed to speed this process up. Since balances only change when there is activity in the group, we decided to solve this classic space/time trade off by caching the most recent balance calculation.

While our performance improved, our code developed a smell. Caching logic had to be custom added into any method that could benefit from it. Not only was our code no longer DRY (Don’t Repeat Yourself), we were noticing occasional inconsistencies at runtime, requiring cache recalculation. This was not overly surprising, as we all know that cache invalidation is one of the two hard things. But because correct balances are the core of Splitwise, we need them to work correctly all of the time.

We continued to tweak the caching logic, built workarounds, and even added conditional caching clauses to avoid race conditions. But we still had problems. As if this wasn’t frustrating enough, the extra caching code made our tests more and more complicated. We had intended to pay for time (a faster better user experience), with space (memory for cache), but also received the hidden fee of increased maintenance costs. There simply had to be a better way.

We decided to step back and reexamine our first principles, specifically the single responsibility principle (SRP). While it’s primarily for modules and classes, it can be applied to methods too. Our method for computing the balance should not be responsible for caching at all — not checking, validating, or populating, conditionally or not. We began to think, “What if we could extract the caching logic from the method? What if the method was only responsible for the balance, and the caching could be tested and built separately?”

As an interpreted language, Ruby makes method composition easy to accomplish with metaprogramming, or code that writes code. A good deal of the incredible functionality found in Rails and other gems comes from Ruby’s embrace of metaprogramming. If you’ve used Ruby for any amount of time, you’ve likely encountered metaprogramming whether you knew it or not because it is typically small, unobtrusive, and well encapsulated. In our case, we extracted the caching logic from our methods and moved it to a dedicated module. This module can then be included in other classes and a single directive used to initiate the metaprogramming which will wrap the indicated methods with caching behavior.

Previously, each method, in addition to calculating data and caching, would not only create its own key to use in the cache, but be responsible for conditionally clearing the cache. How should a unified caching system handle something as hard and thorny as cache invalidation? As with many things, Rails has an opinion. Rails uses key-based cache expiration which ties the generated cache key to the object’s state. ActiveRecord has a method called `cache_key` for this which creates the key from the object’s class, id, and updated_at timestamp. Put simply, when an object is created or updated in the database, its cache key changes. The fresh key has no value in the cache and the old cached value is associated with a key that is no longer generated. When combined with many cache systems that remove the least recently used (LRU) values over time or as they run out of space, you get an effective way to sidestep the cache invalidation problem.

This was a good starting point, as we had decided our caching library should have a general opinion on the format of the key for ease of use and that it should be easily modified. By default, the key under which a cacheable method stores its value is made up of the result of `cache_key` (or the class’ name if undefined) and the method being called. This allows an easy point of modification where `cache_key` can be overwritten to provide any details necessary about the instance being cached. Finally, a method’s result depends on its arguments. However, its cache key may not. Since there wasn’t good default behavior here, we added the ability to specify a `key_format` proc when invoking Cacheable so that the key can be fine tuned using the instance, method name, and method arguments.

We noticed benefits as soon as we began writing the code. The balance calculating methods were DRYing back up, and now so was the caching logic. In addition, the code was getting more and more maintainable. It became easier to read, test, and modify as the disparate responsibilities were pulled apart and isolated. With the caching logic encapsulated, it also became trivial to add caching to additional methods and peer review these changes as often, only a single line of code was required.

It’s no small victory increasing your developers’ productivity and happiness, but how does it stack up in production? I’m happy to say that simply untangling the caching code from business logic dropped our error rate by an order of magnitude. Instead of a handful of inconsistent cache hits and necessary recalculations a day, now we saw the same or fewer a month. Clarity has its benefits.

We didn’t know it beforehand, but we learned we had stumbled onto aspect-oriented programming (AOP). I am no expert in AOP, but this foray showed it to be a useful tool to reduce the cost of code maintenance. It allowed us to separate the code, and thus the responsibilities for aspects, or behaviors such as caching and calculating balances, and composing new methods from modules. These aspects are able to be more thoroughly and reliably tested independently of each other. Increasing code reliability saves us maintenance by avoiding it entirely. In addition, the aspects are easier to understand when independent and take less time to maintain and augment without risk of regression.

We make use of many wonderful open source projects every day and strongly believe in giving back to the community. We’ve found this abstraction to be easy to use and love how it has increased our code’s reliability. It’s ready for out-of-the-box use with Rails and we’d love for you to try it out and let us know what you think. The GitHub repository has more information in addition to implementation instructions and examples.

Announcing Splitwise for iOS : version 4

The groups tab shows your recent groups and outstanding balances, as well as non-group expenses.
Splitwise v4 includes tabbed navigation, balance breakdowns, and an easier way to add bills.

Our whole team has been crafting a major new version of Splitwise for iPhone over the past few months. We’re excited to announce that it went live on the App Store earlier this morning.

We’ve focused on improving the two most important parts of Splitwise: adding bills and checking balances. The “add bill” form has been streamlined into a single screen, and the new navigation tab bar makes it easier to see balances with both groups and friends.

In addition to the major changes, we’ve included popularly requested features like spending totals for groups and exporting to spreadsheet. We’ve also added many more subtle improvements and bug fixes that greatly improve Splitwise for iOS.

Continuing our tradition of naming major releases after silly animal names, Splitwise version 4 is called “Lazy Salmon.” Read on for a detailed tour of what’s been added, changed and improved. Continue reading Announcing Splitwise for iOS : version 4

Splitwise Seed Funding – We’re Hiring

Dear users,

I’m happy to announce today that Splitwise has raised $1.4MM in new seed funding from leading tech investors. Raising outside money means we can continue to invest in building the world’s best product for reducing the stress of money in relationships. Hooray!

We’re using that money to hire talented people to help us make Splitwise even better. If you are a software engineer who wants to work on a product you love, please hop on over to our jobs page.


Splitwise has been a labor of love for the founders for nearly four years. This year has been incredible: Splitwise users have shared over $1B in expenses so far in 2014, just counting US dollars. We’ve been featured alongside companies with dozens or hundreds of engineers. I’ve found that most users are surprised to learn we currently have only two engineers, and that our CTO is also our lead designer.

Our goal for Splitwise is to remove the stress of sharing money in our most important relationships. How does this translate into a product roadmap? While still being annoyingly vague, allow me to explain our priorities for the coming years.

Continue reading Splitwise Seed Funding – We’re Hiring

Coming to Splitwise for Android: Offline mode, multiple payers

When we released Splitwise for Android v3 last November the whole team let out a sigh of relief: We no longer had an Android app that sucked, but rather a native, sleek beast we could all be proud of.

Seems like all our Android users breathed a sigh of relief, too — our average rating in the Google Play store climbed by .3 in a matter of days, and we got tons of excited emails, Tweets and blog comments from our long-suffering Android folk.

Today we’re pumped to announce that we’ve kept our Android-awesomeness momentum going. We released multiple payers a couple days ago (version 3.2.4), and offline mode is being beta-tested right now by some very kind community members. We’ll release it to the masses once we’ve slain all the bugs.

Our hearts do a little dance every step our Android app takes towards feature parity with web and iPhone. As always, we’re so grateful to have you (yes, you) along for the journey.

Take the leap to learn how co-founder and lead mobile dev Marshall got offline mode for Android up and running. Spoiler alert: It was hard.


Co-founder and lead mobile dev Marshall was inspired by Dropbox’s “incredibly helpful” dev blog posts on their Carousel and photo-sharing infrastructure. Their put-it-in-the-queue model is the foundation of how our offline mode for Android works.

Previously, the app was designed to communicate with the server each and every time a you made or edited a bill. In the absence of an internet connection that communication would simply fail.

Now, the app keeps a log of all changes made, and tries to push them to the server in the order they were committed. When you don’t have a connection the app can simply skip over anything it can’t immediately execute, intellegently saving it for later. The best part is it can do all this in the background, meaning you can add a bill and then immediately do some other task, like checking a balance with a friend, without being subjected to an annoying saving toast and lost seconds. This means that even if you never use offline mode for Android, you’ll now enjoy a better, faster add bill experience.

Conversely, our offline mode for iPhone still tries to save the expense on the server. Once the request has failed, the app marks the expense to be saved later. This still makes you sit through the saving screen, waiting for 30 seconds until the request times out.

About this new iPhone/Android difference Marshall was happy to note “this is the first case of our Android app having something significantly more awesome than iPhone”. We know it’s long over-due!

Marshall also lauds Path and the Robolectric testing platform as critical resources during this development cycle.

Improvments to Splitwise Notifications

Recent activity feed, on web.
Recent activity feed, on web.

The team is happy announce the release of a broad set of improvements to how Splitwise notifications are generated and presented. Usability and ease-of-knowing-what’s-up are going to skyrocket thanks to an improved ‘Recent activity’ feed (now on mobile, too) and more sophisticated push notifications.

All 3 of our devs — Ryan, Marshall and Caleb — have been hard at work on this effort since December, when we introduced expense comments for the web and decided to really commit to building out notifications. Marshall and Caleb have focused on Android and iPhone respectively, with Ryan taking care of back end stuff, the web experience and overall look of the new features.

We made these changes to eliminate all possible sources of confusion when viewing Splitwise, because confusion causes uncertainty and stress! We’re waiting eagerly for your feedback on the changes, at feedback@splitwise.com.

Take the leap to see a more in-depth explanation of the changes.

Continue reading Improvments to Splitwise Notifications

Improvements to Delete Friend Functionality

Late last week, Ryan made a much-needed improvement to Splitwise’s delete friend functionality. Now, you can delete any friend as long as they’re not in a group with you.

Previously, Splitwise blocked you from deleting friends if you had ever added an expense outside of a group with them AND another friend. This was happening because deleting friends deleted all expense records between you two, and Splitwise wanted to block multi-party records from being deleted in case one of the parties on the expense wasn’t all settled up yet. Ryan built a work-around that will enable you to delete the friends you want, without obliterating a piece of someone else’s balance puzzle.