I'm currently working on a DSL in relation to accounting. What I'd like to be able to do is:
accountant do
credit @account_1, -@amount
debit @account_2, @amount
end
Currently, this executes the following method:
class Accountant
def accountant &block
AccountantHelpe开发者_JS百科r.class_eval(&block)
end
end
...Which in turn executes the block on the AccountantHelper class, calling the "credit" and "debit" methods respectively:
class AccountantHelper
def self.credit account, amount
account.credit amount
end
def self.debit account, amount
account.debit amount
end
end
(Please hold back any fire about using class_eval() -- this is only a prototype after all!)
The goal is for the block to act as a transaction, ensuring that if the entire block can't be executed successfully, then none of it should. However in addition to this, it should also verify the integrity of the data passed into the block. In this case I need to verify that there is both a "credit" and a "debit" method within the block (in double-entry accounting, for every credit there must also be at least one debit, and vice versa). Currently I could call:
accountant do
credit @account_1, @amount
end
...And the code will execute without any errors. This would be a bad thing as there is no corresponding "debit" to keep the accounts in balance.
Is it possible to verify what gets passed into the block? Or am I heading down the wrong path here?
I guess you can make your credit
and debit
actions "lazy", so that they are executed by the wrapper method, after the validation. Here's a proof of concept, similar to yours, but without metaprogramming part, skipped for clarity:
def transaction
yield
if @actions.map(&:last).inject(&:+) == 0
@actions.each do |account, amount|
@accounts[account] += amount
end
@actions = []
puts 'transaction executed ok'
else
puts 'balance not zero, rolling back transaction'
# rollback (effectively, do nothing)
end
end
def credit account, amount
@actions << [account, amount]
end
def debit account, amount
@actions<< [account, -amount]
end
@actions = []
@accounts = {a: 0, b: 0, c: 0} # start with three blank accounts
transaction do
credit :a, 5
debit :b, 2
debit :c, 3
end
#=>transaction executed ok
p @accounts
#=>{:a=>5, :b=>-2, :c=>-3}
transaction do
credit :a, 5
debit :b, 4
end
#=> balance not zero, rolling back transaction
p @accounts
#=> {:a=>5, :b=>-2, :c=>-3}
精彩评论