Author
Gian ZasGian Zas leads Moove It’s Software Engineering Studio. As a technical architect he has worked with several of our clients on their key technical challenges and has specific expertise in finance. He studied programming at Uruguay’s ORT University.
We recently released Ruy, a Ruby library which we define as a lightweight rules evaluator. We readily admit that this definition doesn’t exactly explain what Ruy is about, or how it works, not because it’s hard to understand, but because we can’t really find the right form of words to describe it.
The use case
Some time ago, one of our clients challenged us to build a loyalty program system, capable of letting card issuers define their own programs, receive incoming transactions from consumers, calculate reward points and, finally, allow consumers to exchange the points they had earned for cash discounts.
Card issuers would configure their loyalty programs via web forms, define which conditions transactions from consumers had to meet and how many points would be earned.
Here are two example loyalty program configurations:
- 1 reward point every U$S 10 spent at a particular restaurant.
- For purchases at Vintage Clothing Bt, offer 30 reward points for purchases over U$S 200, and 12 reward points for purchases over U$S 100.
Looking at the other side of a transaction, when someone has dinner at Mario & Luigi’s Restaurant, the system receives the payment details with information about the relevant merchant, amount spent and card details.
The link between those two sides needs to be something that enables us to define all the conditions, receive the transaction information, evaluate the conditions, and then return the appropriate ratio of points to amount spent. That link is Ruy.
The rise
After considering all these requirements, we decided to build a library that provided a way to define boolean conditions, receive a context containing the information needed to evaluate the conditions, and produce the required result at the end.
With Ruy, the first example turns into this:
# 1 reward point every U$S 10 spent at a particular restaurant. restaurants = Ruy::Rule.new restaurants.eq 'Restaurant', :merchant_burden # matches purchases at restaurants restaurants.outcome [1, 10] # points / amount ratio = 1 / 10
The second example turns into this:
# For purchases at Vintage Clothing Bt, offer 30 reward points for purchases # over U$S 200, and 12 reward points for purchases over U$S 100. vintage_bt = Ruy::Rule.new vintage_bt.eq 'VINTAGE-CLOTH-BT', :merchant_code vintage_bt.outcome 30 do greater_than 200, :amount # 30 points when amount > 200 end vintage_bt.outcome 12 do greater_than 100, :amount # 12 points when amount > 100 end
When a sales transaction comes in, it must also be evaluated against those programs.
restaurants.call({ amount: 250, merchant_code: 'Mario & Luigi', merchant_buden: 'Restaurants' }) # => [1, 10] restaurants.call({ amount: 125, merchant_code: 'VINTAGE-CLOTH-BT', merchant_buden: 'Clothing' }) => 12
At its core, Ruy is essentially no more that what is demonstrated, here. Of course, there are several condition methods besides eq and greater_than, custom attributes at rule level, and fallback values that can be defined when no outcome condition matches. Don’t forget to take a look at the project’s README.
The expansion
After using Ruy for a while, as part of the loyalty system previously mentioned, another client needed to process events coming from certain IoT devices and undertake certain actions, depending on what the owners of those devices had configured.
Ruy seemed to be a good fit for that project, as well, so we decided to extract it from the project in which it was being incubated and take the opportunity to make it available to the wider community. From its inception to its current state, we strove not to divert Ruy from its core purpose, being a tool to define programmatically a set of conditions and outcome values, and evaluate a context against them.
At the time of writing, Ruy is running as a key part of two systems, for clients in two different industries, processing large numbers of incoming events and payment transactions.
The future
We have a few ideas about the future direction of the library and it’d be great to hear your thoughts about Ruy. Its source code is relatively easy to follow, so please take a look at it and get back to us with any feedback you have.