Not long after moving the Austin Time Exchange to Ruby on Rails, we started receiving requests for a groups feature since some local groups were interested in having their own currency but did not want to run their own instance of the software and their members were already on the existing system.
Since we’re a fork of insoshi, merging the groups feature from another fork is easy. Add some code to make simple payments per-group…Boom! Done.
As you might imagine, that half-baked effort didn’t work out.
The right answer is functional equivalency with the original currency: every currency has the same user interface as the original one. This means every currency can be associated with requests and offers. It means every currency has the same level of administrative support, graphs and even OpenTransact support. Also, each group wants to define its own rules for its currency with different roles.
That sounds like it could be complicated. Fortunately, Ryan Bates has created a simple authorization gem called CanCan which for our purposes can help us create and maintain rules for a number of currencies. However, before we start adding rules whilly-nilly, who and what process is responsible for managing the rules?
The right answer is not the software developer. Of course, the rules should be designed by the people organizing the currency. And, in the spirit of agile programming and/or agile banking, it helps if we start out with the bare minimum amount of rules. As participation increases, new contingencies will arise and the system will need to accommodate them with new rules or protocols agreed upon by the interested parties.
To accommodate these new contingencies, non-programmers might like to describe new rules or behavior scenarios in plain English with Cucumber. These plain English scenarios can be easily translated into Ruby RSpec code examples. For instance, a simple Cucumber scenario was demonstrated in “Using Insoshi to Support Community Currencies” at LSRC 2009 to show how trivial it is to design a payment system without charging heavy handed overdraft fees since banks were collecting $38 billion in overdraft fees that year. Cucumber is very useful but it is not essential.
What seems to be essential is for the programmer to write RSpec examples to simulate the new contingencies identified by the interested parties. An RSpec example ends with an expectation which will fail until we adapt the CanCan authorization rules to accommodate it.
Suppose we started a community currency without credit limits. This works for some period of time until an account holder makes one or more payments of a substantial amount without reciprocating in a reasonable amount of time.
Now we have a new contingency to address and we will simulate it with RSpec and it will fail because there are no rules in place to make the software behave as expected when the newly identified contingency occurs. Thankfully, the programmer is lazy and doesn’t prematurely write any RSpec code. Instead, the programmer grabs one or more jelly-filled doughnuts while the interested parties agree on the rules to accommodate the contingency.
They decide a configurable per-member credit limit would be a simple general solution and would avoid hard-coded limits in the code. So, how would we simulate this contingency in an RSpec example? Let’s focus on the most relevant details of the spec.
|@e.amount = 1.0|
|@e.customer = @p2|
|@membership.roles = ['individual']|
|account = @p2.account(@g)|
|account.balance = 0.0|
|account.credit_limit = 0.5|
First, we say the amount of the exchange object @e is 1.0 units and the payer is the person object @p2. All activity related to exchanges now occur in the context of a group. Therefore, the roles (and rights associated therewith) are assigned to the @membership object rather than directly to the person. In this case, “individual” is the only role the payer is assigned. Another meaningful membership role is “admin” and there can be several admins per group.
Finally, we fetch the account, give it a balance of 0 and a credit limit of 0.5 for this example. Therefore, we should expect the payment to fail because the magnitude of the payment exceeds the sum of the balance and the credit limit. However, if there are no rules defined, the payment will succeed and this example will fail. To fix it, we add the following rule to the Ability class.
|can :create, Exchange do |exchange||
|# the presence of group is validated when creating an exchange|
|# group will be nil for the new method, so allow it|
|membership = Membership.mem(person,exchange.group)|
|account = person.account(exchange.group)|
|account && (account.authorized? exchange.amount)|
The authorized? method is called on the payer’s account to see if the payment is allowed. If the credit limit is nil, the payment is allowed. Otherwise, it is allowed if the magnitude of the payment is less than or equal to the sum of the payer’s account balance and credit limit as shown in the Account class.
|credit_limit.nil? or (amount <= balance + credit_limit)|
Now, the RSpec example passes and the programmer can tell the interested parties that their policy is now implemented until the next time a new contingency arises.