• Stars
    star
    417
  • Rank 103,829 (Top 3 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 10 years ago
  • Updated 7 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A double-entry accounting system for Ruby applications.

DoubleEntry

License MIT Gem Version Build Status Code Climate

Show me the Money

Keep track of all the monies!

DoubleEntry is an accounting system based on the principles of a Double-entry Bookkeeping system. While this gem acts like a double-entry bookkeeping system, as it creates two entries in the database for each transfer, it does not enforce accounting rules, other than optionally ensuring a balance is positive, and through an allowlist of approved transfers.

DoubleEntry uses the Money gem to encapsulate operations on currency values.

Compatibility

DoubleEntry is tested against:

Ruby

  • 2.7.x
  • 3.0.x
  • 3.1.x
  • 3.2.x

Rails

  • 6.0.x
  • 6.1.x
  • 7.0.x

Databases

  • MySQL
  • PostgreSQL
  • SQLite

Installation

In your application's Gemfile, add:

gem 'double_entry'

Download and install the gem with Bundler:

bundle

Generate Rails schema migrations for the required tables:

The default behavior is to store metadata in a json(b) column rather than a separate double_entry_line_metadata table. If you would like the old (1.x) behavior, you can add --no-json-metadata.

rails generate double_entry:install

Update the local database:

rake db:migrate

Interface

The entire API for recording financial transactions is available through a few methods in the DoubleEntry module. For full details on what the API provides, please view the documentation on these methods.

A configuration file should be used to define a set of accounts, and potential transfers between those accounts. See the Configuration section for more details.

Accounts

Money is kept in Accounts.

Each Account has a scope, which is used to subdivide the account into smaller accounts. For example, an account can be scoped by user to ensure that each user has their own individual account.

Scoping accounts is recommended. Unscoped accounts may perform more slowly than scoped accounts due to lock contention.

To get a particular account:

account = DoubleEntry.account(:spending, scope: user)

(This actually returns an Account::Instance object.)

See DoubleEntry::Account for more info.

Balances

Calling:

account.balance

will return the current balance for an account as a Money object.

Transfers

To transfer money between accounts:

DoubleEntry.transfer(
  Money.new(20_00),
  from: one_account,
  to:   another_account,
  code: :a_business_code_for_this_type_of_transfer,
)

The possible transfers, and their codes, should be defined in the configuration.

See DoubleEntry::Transfer for more info.

Metadata

You may associate arbitrary metadata with transfers, for example:

DoubleEntry.transfer(
  Money.new(20_00),
  from: one_account,
  to:   another_account,
  code: :a_business_code_for_this_type_of_transfer,
  metadata: {key1: ['value 1', 'value 2'], key2: 'value 3'},
)

Locking

If you're doing more than one transfer in a single financial transaction, or you're doing other database operations along with the transfer, you'll need to manually lock the accounts you're using:

DoubleEntry.lock_accounts(account_a, account_b) do
  # Perhaps transfer some money
  DoubleEntry.transfer(Money.new(20_00), from: account_a, to: account_b, code: :purchase)
  # Perform other tasks that should be commited atomically with the transfer of funds...
end

The lock_accounts call generates a database transaction, which must be the outermost transaction.

See DoubleEntry::Locking for more info.

Account Checker/Fixer

DoubleEntry tries really hard to make sure that stored account balances reflect the running balances from the double_entry_lines table, but there is always the unlikely possibility that something will go wrong and the calculated balance might get out of sync with the actual running balance of the lines.

DoubleEntry therefore provides a couple of tools to give you some confidence that things are working as expected.

The DoubleEntry::Validation::LineCheck will check the double_entry_lines table to make sure that the balance column correctly reflects the calculated running balance, and that the double_entry_account_balances table has the correct value in the balance column. If either one of these turn out to be incorrect then it will write an entry into the double_entry_line_checks table reporting on the differences.

You can alternatively pass a fixer to the DoubleEntry::Validation::LineCheck.perform method which will try and correct the balances. This gem provides the DoubleEntry::Validation::AccountFixer class which will correct the balance if it's out of sync.

Using these classes is optional and both are provided for additional safety checks. If you want to make use of them then it's recommended to run them in a scheduled job, somewhere on the order of hourly to daily, depending on transaction volume. Keep in mind that this process locks accounts as it inspects their balances, so it will prevent new transactions from being written for a short time.

Here are examples that could go in your scheduled job, depending on your needs:

# Check all accounts & write the results to the double_entry_line_checks table
DoubleEntry::Validation::LineCheck.perform!

# Check & fix accounts (results will also be written to the table)
DoubleEntry::Validation::LineCheck.perform!(fixer: DoubleEntry::Validation::AccountFixer.new)

See DoubleEntry::Validation for more info.

Implementation

All transfers and balances are stored in the lines table. As this is a double-entry accounting system, each transfer generates two lines table entries: one for the source account, and one for the destination.

Lines table entries also store the running balance for the account. To retrieve the current balance for an account, we find the most recent lines table entry for it.

See DoubleEntry::Line for more info.

AccountBalance records cache the current balance for each Account, and are used to perform database level locking.

Transfer metadata is stored in a json(b) column on both the source and destination lines of the transfer.

Configuration

A configuration file should be used to define a set of accounts, optional scopes on the accounts, and permitted transfers between those accounts.

The configuration file should be kept in your application's load path. For example, config/initializers/double_entry.rb. By default, this file will be created when you run the installer, but you will need to fill out your accounts.

For example, the following specifies two accounts, savings and checking. Each account is scoped by User (where User is an object with an ID), meaning each user can have their own account of each type.

This configuration also specifies that money can be transferred between the two accounts.

require 'double_entry'

DoubleEntry.configure do |config|
  # Use json(b) column in double_entry_lines table to store metadata instead of separate metadata table
  config.json_metadata = true

  config.define_accounts do |accounts|
    user_scope = ->(user) do
      raise 'not a User' unless user.class.name == 'User'
      user.id
    end
    accounts.define(identifier: :savings,  scope_identifier: user_scope, positive_only: true)
    accounts.define(identifier: :checking, scope_identifier: user_scope)
  end

  config.define_transfers do |transfers|
    transfers.define(from: :checking, to: :savings,  code: :deposit)
    transfers.define(from: :savings,  to: :checking, code: :withdraw)
  end
end

By default an account's currency is the same as Money.default_currency from the money gem.

You can also specify a currency on a per account basis. Transfers between accounts of different currencies are not allowed.

DoubleEntry.configure do |config|
  config.define_accounts do |accounts|
    accounts.define(identifier: :savings,  scope_identifier: user_scope, currency: 'AUD')
  end
end

Testing with RSpec

Transfering money needs to be run as a top level transaction. This conflicts with RSpec's default behavior of creating a new transaction for every test, causing an exception of type DoubleEntry::Locking::LockMustBeOutermostTransaction to be raised. This behavior may be disabled by adding the following lines into your rails_helper.rb.

RSpec.configure do |config|
  # ...
  # This first line should already be there. You will need to add the second one
  config.use_transactional_fixtures = true
  DoubleEntry::Locking.configuration.running_inside_transactional_fixtures = true
  # ...
end

Jackhammer

Run a concurrency test on the code.

This spawns a bunch of processes, and does random transactions between a set of accounts, then validates that all the numbers add up at the end.

You can also tell it to flush out the account balances table at regular intervals, to validate that new account balances records get created with the correct balances from the lines table.

./script/jack_hammer -t 20
Cleaning out the database...
Setting up 5 accounts...
Spawning 20 processes...
Flushing balances
Process 1 running 1 transfers...
Process 0 running 1 transfers...
Process 3 running 1 transfers...
Process 2 running 1 transfers...
Process 4 running 1 transfers...
Process 5 running 1 transfers...
Process 6 running 1 transfers...
Process 7 running 1 transfers...
Process 8 running 1 transfers...
Process 9 running 1 transfers...
Process 10 running 1 transfers...
Process 11 running 1 transfers...
Process 12 running 1 transfers...
Process 13 running 1 transfers...
Process 14 running 1 transfers...
Process 16 running 1 transfers...
Process 15 running 1 transfers...
Process 17 running 1 transfers...
Process 19 running 1 transfers...
Process 18 running 1 transfers...
Reconciling...
All the Line records were written, FTW!
All accounts reconciled, FTW!
Done successfully :)

Future Direction

See the Github project issues.

Development Environment Setup

We're using Docker to provide a convenient and consistent environment for executing tests during development. This allows engineers to quickly set up a productive development environment.

Note: Most development files are mounted in the Docker container. This enables engineers to edit files in their favourite editor (on the host OS) and have the changes immediately available in the Docker container to be exercised.

One exception to this is the RSpec configuration. Changes to these files will require a rebuild of the Docker image (step 2).

Prerequisites:

  • Docker
  • Docker Compose
  • Git
  1. Clone this repo.

    git clone [email protected]:envato/double_entry.git && cd double_entry
  2. Build the Docker image we'll use to run tests

    docker-compose build --pull double_entry
  3. Startup a container and attach a terminal. This will also start up a MySQL and Postgres database.

    docker-compose run --rm double_entry ash
  4. Run the tests

    DB=mysql bundle exec rspec
    DB=postgres bundle exec rspec
    DB=sqlite bundle exec rspec
  5. When finished, exit the container terminal and shut down the databases.

    exit
    docker-compose down

Contributors

Many thanks to those who have contributed to both this gem, and the library upon which it was based, over the years:

  • Anthony Sellitti - @asellitt
  • Clinton Forbes - @clinton
  • Eaden McKee - @eadz
  • Giancarlo Salamanca - @salamagd
  • Jiexin Huang - @jiexinhuang
  • Keith Pitt - @keithpitt
  • Kelsey Hannan - @KelseyDH
  • Mark Turnley - @rabidcarrot
  • Martin Jagusch - @MJIO
  • Martin Spickermann - @spickermann
  • Mary-Anne Cosgrove - @macosgrove
  • Orien Madgwick - @orien
  • Pete Yandall - @notahat
  • Rizal Muthi - @rizalmuthi
  • Ryan Allen - @ryan-allen
  • Samuel Cochran - @sj26
  • Stefan Wrobel - @swrobel
  • Stephanie Staub - @stephnacios
  • Trung Lê - @joneslee85
  • Vahid Ta'eed - @vahid

More Repositories

1

envato-wordpress-toolkit

Wordpress toolkit for authors of items available from the Envato Market sites.
PHP
373
star
2

wp-envato-market

WordPress Theme & Plugin management for the Envato Market.
PHP
340
star
3

zxcvbn-ruby

Ruby port of Dropbox's zxcvbn javascript lib
Ruby
314
star
4

stack_master

The missing CloudFormation tool
Ruby
290
star
5

envato-theme-check

The WordPress Theme Check plugin for Envato
PHP
205
star
6

aldous

Brave New World for Rails with more cohesion, less coupling and greater development speed for all
Ruby
128
star
7

pagerduty

📟 A Ruby gem for talking to the Pagerduty Events API
Ruby
99
star
8

event_sourcery

A library for building event sourced applications in Ruby
Ruby
84
star
9

safe_shell

Safely execute shell commands and get their output.
Ruby
74
star
10

react-breakpoints

Respond to changes in a DOM element's size. With React Breakpoints, element queries are no longer "web design's unicorn" 🦄
TypeScript
72
star
11

envato-wordpress-toolkit-library

WordPress Toolkit Library for Envato Marketplace hosted items
PHP
71
star
12

lumberg

Ruby library for the WHM & cPanel API; It's not a half day or anything like that.
Ruby
56
star
13

rails_4_session_flash_backport

Rails 4 Session Flash Backport
Ruby
52
star
14

ami-spec

Acceptance testing your AMIs
Ruby
50
star
15

heroku-deploy

Complete zero downtime deploys for Rails applications on Heroku
Ruby
41
star
16

rack-ecg

Health check page as Rack middleware
Ruby
39
star
17

splinter

Chop your specs in half with Splinter, a collection of helpers to submit forms with RSpec and Capybara.
Ruby
30
star
18

extensions-sketch-plugin

Envato Elements - Sketch Plugin (Beta)
JavaScript
26
star
19

unwrappr

🍫 bundle update PRs: Automated. Annotated. Announced daily.
Ruby
26
star
20

exiv2

A simple wrapper around exiv2
Ruby
25
star
21

jwt_signed_request

Request signing and verification made easy
Ruby
24
star
22

iodized

Iodine rich feature toggling. Lack of iodine can lead to development delays, and stunted growth.
Elixir
21
star
23

react-ab-experiment

A/B Experiment React Component
JavaScript
18
star
24

event_sourcery_todo_app

Example event_sourcery app
Ruby
15
star
25

awsraw

Minimal AWS client
Ruby
14
star
26

moo_moo

Implements OpenSRS XML API
Ruby
14
star
27

create-react-icon

Convert one or multiple svg files into React component
JavaScript
13
star
28

aspect_ratio

Image aspect ratio calculation utility
Ruby
13
star
29

event_sourcery-postgres

Postgres event store implementation for EventSourcery
Ruby
13
star
30

github-pull-request-buildkite-plugin

Open Github pull requests via your Buildkite builds
Shell
13
star
31

cookie-consent

Some helper functions to deal with cookie-consent
TypeScript
11
star
32

knuckle_cluster

Gem to interrogate and connect to ECS clusters, Spot fleets and Autoscaling groups
Ruby
10
star
33

cloudformation_rspec

Test your CloudFormation templates
Ruby
10
star
34

aws-account-concierge

Opensource AWS account management tool
Ruby
9
star
35

flv

FLV Parser
Ruby
9
star
36

ejsonkms

Integrates EJSON with AWS KMS
Go
9
star
37

subvalid

Subjective validation for Plain Old Ruby Objects
Ruby
8
star
38

forked

Forked manages long running worker processes
Ruby
8
star
39

react-resize-observer-hook

Observe multiple DOM elements with a single ResizeObserver.
TypeScript
8
star
40

heroku-buildpack-libsodium

libsodium/rbnacl buildpack for Heroku
Shell
8
star
41

foundation-design-system-tokens

Design Tokens for the Foundation Design System
JavaScript
8
star
42

guide

Document your application with a living component library and styleguide
Ruby
8
star
43

studio-client-uploader

JavaScript
8
star
44

outatime

Choose versioned S3 files from a point in time.
Ruby
7
star
45

wp-image-size-limit

Wordpress plugin for limiting the image file size
PHP
7
star
46

aws-s3-sync-buildkite-plugin

Shell
7
star
47

rails_session_key_rotator

Graceful secret key rotation for the signed cookie store in Rails.
Ruby
6
star
48

babushka-deps

Ruby
6
star
49

lambda-deploy-buildkite-plugin

A Buildkite plugin to deploy AWS Lambda function code
Shell
6
star
50

double_entry-reporting

Reporting on Double Entry accounts and transfers
Ruby
6
star
51

medusa

The most beautiful parallel build system, evar.
JavaScript
5
star
52

hamburglar

Hamburglar helps you prevent fraudulent orders
Ruby
5
star
53

fake_aws

Ruby
5
star
54

market-api-gateway-js

JavaScript SDK for marketplace api-gateway
JavaScript
5
star
55

ejson_wrapper

Combines EJSON with AWS KMS
Ruby
5
star
56

bundle-update-buildkite-plugin

Update Ruby gem dependencies in a Buildkite build!
Shell
5
star
57

stop-the-line-buildkite-plugin

Stop Buildkite pipelines based on build meta-data values.
Shell
5
star
58

encapsulate_as_money

Surprise me ;)
Ruby
5
star
59

event_sourcery_generators

An opinionated CLI tool for building event-sourced Ruby services with EventSourcery
Ruby
4
star
60

imageresize

Image resizing for Envato Sites author tools
Go
4
star
61

react-ab-experiment-example

React AB Experiment example app
JavaScript
4
star
62

packer-ami-copy-buildkite-plugin

A buildkite plugin which simplifies the process of building and copying an AMI with packer
Shell
4
star
63

create-datadog-event-buildkite-plugin

An experimental Buildkite plugin which creates Datadog events.
Shell
4
star
64

heroku-container-deploy-buildkite-plugin

Deploy pre-built docker images to Heroku using Heroku Container Registry
Shell
4
star
65

barricade

better locking
Ruby
4
star
66

google-analytics-for-wordpress

WordPress.org Plugin Mirror
PHP
4
star
67

build-failed-notify-slack-buildkite-plugin

@'s the creator of the failed build via a mapping file
Shell
4
star
68

browser-privacy-checker

Handy React 🎣 module to check whether can use user cookie and similar technologies from browser
TypeScript
4
star
69

unwrappr-demo

Demonstrating the power of Unwrappr
Ruby
3
star
70

marketplace-stats-as-a-service

A little micro service to cache and expose Envato Marketplace stats(total items and total users)
JavaScript
3
star
71

docker-cache-buildkite-plugin

Build a multi-stage compatible cache image and store it in S3
Shell
3
star
72

puppet-hubot

Puppet
3
star
73

sprockets-resilience

Stop Rails from killing your site when assets aren't precompiled
Ruby
3
star
74

rack_fake_s3

Ruby
3
star
75

mandrill-merge

We all hate spam, but sometimes you need to get an important message out to a large group of people. Mandrill is great at allowing you to define templates, sending emails out, and keeping track of sends and responses. But how do you get the data from your database over to Mandrill quickly and easily? That's where MandrillMerge comes in...
Ruby
3
star
76

chisel-scripts

Chisel scripts created to debug any issues
Lua
2
star
77

siren-call

A gem for interacting with Siren style REST APIs
Ruby
2
star
78

backbone.proxy-view

A proxy view is an instance of Backbone.View that doesn't render any of its own HTML
CoffeeScript
2
star
79

iodized_ruby_client

Ruby
2
star
80

eventory

A new take on event sourcing in ruby
Ruby
2
star
81

docker-size-annotation-buildkite-plugin

Annotates the build with a docker image size
Shell
2
star
82

thrift-erlang

copy + paste of Apache thrift erlang lib. Mirrored here so it can be used for rebar/mix etc
Erlang
2
star
83

fancy-confluence

CSS
2
star
84

spot_build

Agents are transient, Jobs are forever
Ruby
2
star
85

logstash-filter-dynamo_enrich

Enrich Logstash events with data from DynamoDB
Ruby
2
star
86

batch_retry

Batch send data with retries and exponential backoff
Python
2
star
87

rack_request_ip_strategies

Replacement for Rack::Request#ip
Ruby
2
star
88

heartbeat

Heartbeat plugin that exposes an API to monitor internal services
Ruby
2
star
89

wordpress-https

A fork of an older version of wordpress-https plugin that disables some super-slow functionality.
PHP
2
star
90

ejson2env-buildkite-plugin

A Buildkite plugin for exporting environment variables stored in ejson files using ejson2env
Shell
2
star
91

backbone.autocomplete-view

A template driven autocomplete view for Backbone
CoffeeScript
2
star
92

swarm

Ruby
2
star
93

cloudformation-output-buildkite-plugin

Collects cloudformation output from an AWS stack and add them as environment variables
Shell
2
star
94

studio-asset-service-uploader

An uploader for the studio asset service
JavaScript
1
star
95

puppet_module_provider

Ruby
1
star
96

babel-plugin-postcss-cssmodules-transform

Babel Plugin Transform PostCSS and extract static CSS
JavaScript
1
star
97

studio-asset-service-client-js

A javascript client for the studio asset service
JavaScript
1
star
98

reproduce-fluentd-cascading-buffer

Reproduce cascading failure when Fluentd's internal memory buffer becomes full
Shell
1
star
99

virtellus

A smaller, faster, less powerful Virtus
Ruby
1
star
100

auth0-logs-to-s3

JavaScript
1
star