To run tests type:
rspec
To run console type:
bin/console
The app is the implementation of Cabify's Ruby coding exercise: https://gist.github.com/patriciagao/377dca8920ba3b1fc8da
Here you can find Elixir implementation of the same exercise.
Those examples:
Items: VOUCHER, TSHIRT, MUG
Total: 32.50€
Items: VOUCHER, TSHIRT, VOUCHER
Total: 25.00€
Items: TSHIRT, TSHIRT, TSHIRT, VOUCHER, TSHIRT
Total: 81.00€
Items: VOUCHER, TSHIRT, VOUCHER, VOUCHER, MUG, TSHIRT, TSHIRT
Total: 74.50€
are included in spec/integration/checkout_spec.rb
Object of Checkout
class is the interface for #scan
and #total
actions.
co = Checkout.new
co.scan('VOUCHER')
co.total
Scan pipelines operations. Each operation responds to #call
and takes as an input CheckoutStruct
object
and outputs new CheckoutStruct
object. It's implemented in such manner, that it can easily be wrapped in a
computational context (Either monad) for some neat error handling. For example tt can be refactored with ease into dry-transaction.
Pricing rules are auto injected into Checkout
as a dependency from application Container
. They can also
be passed explicitly:
my_pricing_rules = [
Class.new(BaseRule) do
def calculate_items(items)
items
end
end
]
Checkout.new(pricing_rules: my_pricing_rules)
To add a new rule simply:
- Define class inheriting from
BaseRule
that implements#calculate_items
, which takes as inputArray<Checkout::ItemStruct>
and requires the same as output. - Include new rule in
PricingRules::Manifest::ACTIVE
.
PricingRuleRepo
is the interface to pricing_rules
ROM relation. It's resolved from app container on :pricing_rule_repo
key.
PricingRules::Manifest::KLASS_TO_CODE_MAP
binds rule classes with persisted rule code.
Rules can also be easily stubbed in tests:
require 'dry/container/stub'
Container.enable_stubs!
Container.stub(:pricing_rules, [->(checkout_struct) { checkout_struct }])
This rule depends on code, discount and quantity preferences. For all checkout items within code scope, if their quantity is equal or greater than quantity preference, then for each item preferred discount is applied. For different type of items specific required volume and discount can be applied.
This rule depends on code and quantity preferences. Quantity preference defines for how many items of the same type buyer gets the same item for free. For different types of items, specific quantity can be required to fulfill promotion. E.g. you can set that for 3 mugs you get one free, and for 4 t-shirts you get one for free.
In the real life app this would probably be some SQL RDBMS. Here we have simple YAML file (db/cabify_db.yml
).
What is important is that interface to the data is completely decoupled from the persistance layer, so it can be replaced with
different storage. ROM library is used for mapping between stored data and Ruby objects.