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.

Upgrades to our PayPal mobile integration

Splitwise is excited to announce that we’ve upgraded our PayPal integrations on iOS and Android to the latest technology from PayPal. We believe the changes made increase the usability, security, and reliability of the integration.

As part of the upgrade process, the PayPal mobile integration was temporarily unavailable on iOS and Android for several weeks starting Friday, June 29th, 2018. (You may have seen an “under construction” message on the PayPal button.) During the upgrade, the PayPal integration on web (splitwise.com) remained live.

Change Log:

Updated 10/17 Belatedly updated to reflect that the update has been completed and the changes are live; Splitwise+PayPal integration is once again usable on all platforms

Updated 8/20 Updated to reflect late August roll out
Updated 7/16 
Updated to reflect expected launch date (early August 2018)

 

RESOLVED: Paytm v6.6.0 caused a bug in the Splitwise+Paytm integration

Last week, Paytm released an Android update (v6.6.0) that caused a glitch in the Splitwise+Paytm integration. We have alerted Paytm to the issue and both companies will work quickly towards a resolution. March 2nd Update: This issue is now resolved. Please update to the newest versions of Paytm and Splitwise.

The integration has been disabled for now and will be re-enabled when we’re confident everything is working as expected. We will post here with more information as it becomes available. The integration has now been re-enabled.

A small number of users who settled up with Paytm in the last few days (since upgrading to Paytm v6.6.0) may not have a matching payment in their Splitwise ledger. Users who were potentially affected have been personally contacted via email.

Continue reading RESOLVED: Paytm v6.6.0 caused a bug in the Splitwise+Paytm integration

Splitwise and Zapier: Turn emails into expenses, and more!

We’re excited to announce that Splitwise is now supported by Zapier. This makes it easy to connect Splitwise to the other services you love, and automate certain workflows.

Splitwise is supported as both a trigger and an action: New expenses can cause things to happen on other platforms (see Add Trello tasks for new Splitwise expenses), and new expenses can be created automatically after things happen on other platforms (see Create Splitwise expenses for new Google Sheets rows).    

Below is an in-depth how-to for my favorite Zap: How to turn email receipts into Splitwise expenses automatically. 

Many thanks to our friends over at Zapier, who built this integration using the Splitwise API.

Continue reading Splitwise and Zapier: Turn emails into expenses, and more!

Announcing a Splitwise+Paytm Integration for Android

Good news for our Indian users – Splitwise has integrated with Paytm! Now you can repay any friend on Splitwise in seconds, using your Paytm account.

To access the integration, open a group or friendship where you owe money, tap “Settle Up”, then tap Pay with Paytm:

Screen Shot 2017-05-17 at 5.14.27 PM.png
“Pay with Paytm” appears if you owe an Indian Rupee balance and have the Paytm app installed. Initiate Paytm payments from within Splitwise to have them be automatically recorded.

After you tap “Pay with Paytm”, Splitwise will prompt you to confirm the recipient’s phone number and the payment amount. Hitting “Next” will launch the Paytm app to complete the transaction.

Screen Shot 2017-05-17 at 5.16.21 PM.png
You only have to confirm a phone number once for a given friend, and can use your phone contacts if you’ve given Splitwise permission to access them. To edit a friend’s Paytm phone number in the future, tap on the phone number on the confirmation screen (pictured right).

Once the payment is complete, you’ll be pushed back into the Splitwise app. Your Paytm payment will automatically be added to Splitwise and your balance will be updated.

Screen Shot 2017-05-18 at 10.25.35 AM
You can tap on the payment to see your confirmation number and other details.

A few things to note about using the Splitwise Paytm integration:

  • You need to have the Paytm app installed on your phone. If you do not have the Paytm app, you won’t see “Pay with Paytm” when you tap “Settle Up”.
  • You can only settle an Indian Rupee balance (INR). If you go to settle a non-INR balance, you won’t see “Pay with Paytm”.
  • Only Android users using Splitwise 4.1.9 and above will be able to send Paytm payments using this integration for now.

Users on iPhone and web will be able to receive and view Paytm payments, but not send them. With Paytm’s help, we hope to bring the integration to iPhone and other platforms when we’re able.

We’re thrilled to be launching our first payment integration outside the US, and we hope Paytm support makes Splitwise even better for our users in India. To share feedback or ask questions about the integration, please send an email to support@splitwise.com – we’d love to hear from you.

Venmo v7.0 broke the Venmo Splitwise integration (UPDATED: fixed on March 29)

Update 10:57am March 29: The Splitwise Venmo iOS integration has been re-enabled! To use it please upgrade to the latest versions of Venmo and Splitwise. If you need any assistance, please send a note to support@splitwise.com.

Update 2:55pm March 28: The iPhone integration will be re-enabled tomorrow March 29. Please make sure you’ve updated to Venmo version 7.0.1 and Splitwise version 4.4.7. If you need to Settle Up today, please log into our website (splitwise.com); the Splitwise Venmo web integration is working at this time. Or you can send your payment via Venmo directly and use our “Record a cash payment” feature to subsequently update your balance in Splitwise.

Update 9:30am March 22: Venmo is testing a release that will fix this issue.

Update 5:15pm March 21: Android Venmo payments are back to working normally.

Venmo released an iPhone update yesterday (version 7.0) that is preventing the Splitwise integration from working properly with the Venmo iPhone app. We are working with Venmo to resolve the issue and will post here with more information soon.

In the meantime, we’ve temporarily disabled the Venmo button in the Splitwise app until this is resolved (on both iPhone and Android).

Venmo payments made on the Splitwise Android app and website are not affected. Android users should have the Venmo integration re-enabled by the end of the day. If you need help with your account, please email us at support@splitwise.com. Thanks for your patience.