Toolbox to create complex pricing models
Installation
Add this line to your application's Gemfile:
gem "prezzo"
And then execute:
$ bundle
Or install it yourself as:
$ gem install prezzo
Usage
Prezzo::Context
The Prezzo::Context
is a source of data for your calculators. Basically, it receives a hash of params and it validates its content, in order to make the calculations safe.
e.g.:
module Uber
class Context
include Prezzo::Context
CATEGORIES = ["UberX", "UberXL", "UberBlack"].freeze
validations do
required(:category).filled(included_in?: CATEGORIES)
required(:distance).filled(:float?)
required(:total_cars).filled(:int?)
required(:available_cars).filled(:int?)
end
end
end
context = Uber::Context.new(category: "UberBlack", ...)
# when valid
context.valid?
#=> true
# when invalid
context.valid?
#=> false
context.errors
# { distance: ["must be a float"]}
Prezzo::Calculator
The Prezzo::Calculator
is a simple interface for injecting dependencies on your calculators and calculating the price. Basically, it makes it possible to receive the context, an Hash of parameters containing the necessary information to calculate your price or a Prezzo::Context.
e.g.:
require "prezzo"
module Uber
class PricePerDistanceCalculator
include Prezzo::Calculator
def calculate
price_per_kilometer * distance
end
def price_per_kilometer
1.30
end
def distance
context.fetch(:distance)
end
end
end
context = Uber::Context.new(distance: 10.0)
Uber::PricePerDistanceCalculator.new(context).calculate
#=> 20.0
Context Validation
If you initialize the context with a hash, it will skip the validation, however, any object that responds to .valid?
will attempt a validation, and it will fail if valid? returns false.
Prezzo::Composable
The Prezzo::Composable
module is an abstraction that provides a nice way of injecting other calculators define how the price will be composed with all of those calculators.
e.g.:
require "prezzo"
module Uber
class RidePriceCalculator
include Prezzo::Calculator
include Prezzo::Composable
composed_by base_fare: BaseFareCalculator,
price_per_distance: PricePerDistanceCalculator,
def calculate
base_fare + price_per_distance
end
end
end
context = Uber::Context.new(distance: 10.0)
Uber::RidePriceCalculator.new(context).calculate
#=> 47.3
Prezzo::Explainable
The Prezzo::Explainable
module is an abstraction that provides a nice way of representing how the price was composed.
e.g.:
require "prezzo"
module Uber
class RidePriceCalculator
include Prezzo::Calculator
include Prezzo::Composable
include Prezzo::Explainable
composed_by base_fare: BaseFareCalculator,
price_per_distance: PricePerDistanceCalculator,
explain_with :base_fare, :price_per_distance
def calculate
base_fare + price_per_distance
end
end
end
context = Uber::Context.new(distance: 10.0)
Uber::RidePriceCalculator.new(context).explain
#=> { total: 25.6, components: { base_fare: 4.3, price_per_distance: 21.3 } }
Multiline explain_with
explain_with
can be splitted into several lines.
class RidePriceCalculator
include Prezzo::Explainable
explain_with :base_fare
explain_with :price_per_distance
end
explain_with
with the recursive: false
option
class FooCalculator
include Prezzo::Calculator
include Prezzo::Explainable
explain_with :bar, :baz
def calculate
bar + baz
end
def bar
10
end
def baz
20
end
end
class QuxCalculator
include Prezzo::Calculator
include Prezzo::Composable
include Prezzo::Explainable
composed_by foo: FooCalculator
explain_with :foo, recursive: false
def calculate
foo + 5
end
end
QuxCalculator#explain
now produces
{
total: 35,
components: {
foo: 30
}
}
but not
{
total: 35,
components: {
foo: {
total: 30,
components: {
bar: 10,
baz: 20
}
}
}
}
Check the full Uber pricing for more complete example with many calculators and factors.
Development
After checking out the repo, run make
to install dependencies. Then, run make spec
to run the tests. You can also run make console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run make install
. To release a new version, update the version number in version.rb
, and then run make release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Please consider reading out Contributing Guide.
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.