Module: DoubleEntry
- Defined in:
- lib/double_entry.rb,
lib/double_entry/line.rb,
lib/double_entry/account.rb,
lib/double_entry/locking.rb,
lib/double_entry/version.rb,
lib/double_entry/transfer.rb,
lib/double_entry/aggregate.rb,
lib/double_entry/day_range.rb,
lib/double_entry/reporting.rb,
lib/double_entry/hour_range.rb,
lib/double_entry/line_check.rb,
lib/double_entry/time_range.rb,
lib/double_entry/week_range.rb,
lib/double_entry/year_range.rb,
lib/double_entry/month_range.rb,
lib/double_entry/configurable.rb,
lib/double_entry/line_aggregate.rb,
lib/double_entry/account_balance.rb,
lib/double_entry/aggregate_array.rb,
lib/double_entry/time_range_array.rb,
lib/generators/double_entry/install/install_generator.rb
Overview
Keep track of all the monies!
This module provides the public interfaces for everything to do with transferring money around the system.
Defined Under Namespace
Modules: Configurable, Generators, Locking, Reporting Classes: Account, AccountBalance, AccountWouldBeSentNegative, Aggregate, AggregateArray, DayRange, DuplicateAccount, DuplicateTransfer, HourRange, Line, LineAggregate, LineCheck, MonthRange, RequiredMetaMissing, TimeRange, TimeRangeArray, Transfer, TransferIsNegative, TransferNotAllowed, UnknownAccount, UserAccountNotLocked, WeekRange, YearRange
Constant Summary collapse
- VERSION =
"0.1.0"
Class Attribute Summary collapse
-
.accounts ⇒ Object
Returns the value of attribute accounts.
-
.transfers ⇒ Object
Returns the value of attribute transfers.
Class Method Summary collapse
-
.account(identifier, options = {}) ⇒ DoubleEntry::Account::Instance
Get the particular account instance with the provided identifier and scope.
- .aggregate(function, account, code, options = {}) ⇒ Object
- .aggregate_array(function, account, code, options = {}) ⇒ Object
-
.balance(account, options = {}) ⇒ Money
Get the current balance of an account, as a Money object.
- .describe(line) ⇒ Object private
-
.lock_accounts(*accounts) { ... } ⇒ Object
Lock accounts in preparation for transfers.
-
.reconciled?(account) ⇒ Boolean
private
This is used by the concurrency test script.
-
.scopes_with_minimum_balance_for_account(minimum_balance, account_identifier) ⇒ Array<Fixnum>
Identify the scopes with the given account identifier holding at least the provided minimum balance.
- .table_name_prefix ⇒ Object
-
.transfer(amount, options = {}) ⇒ Object
Transfer money from one account to another.
Class Attribute Details
.accounts ⇒ Object
Returns the value of attribute accounts.
47 48 49 |
# File 'lib/double_entry.rb', line 47 def accounts @accounts end |
.transfers ⇒ Object
Returns the value of attribute transfers.
47 48 49 |
# File 'lib/double_entry.rb', line 47 def transfers @transfers end |
Class Method Details
.account(identifier, options = {}) ⇒ DoubleEntry::Account::Instance
Get the particular account instance with the provided identifier and scope.
62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/double_entry.rb', line 62 def account(identifier, = {}) account = @accounts.detect do |current_account| current_account.identifier == identifier and ([:scope] ? current_account.scoped? : !current_account.scoped?) end if account DoubleEntry::Account::Instance.new(:account => account, :scope => [:scope]) else raise UnknownAccount.new("account: #{identifier} scope: #{[:scope]}") end end |
.aggregate(function, account, code, options = {}) ⇒ Object
234 235 236 |
# File 'lib/double_entry.rb', line 234 def aggregate(function, account, code, = {}) DoubleEntry::Aggregate.new(function, account, code, ).formatted_amount end |
.aggregate_array(function, account, code, options = {}) ⇒ Object
238 239 240 |
# File 'lib/double_entry.rb', line 238 def aggregate_array(function, account, code, = {}) DoubleEntry::AggregateArray.new(function, account, code, ) end |
.balance(account, options = {}) ⇒ Money
Get the current balance of an account, as a Money object.
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/double_entry.rb', line 129 def balance(account, = {}) scope_arg = [:scope] ? [:scope].id.to_s : nil scope = (account.is_a?(Symbol) ? scope_arg : account.scope_identity) account = (account.is_a?(Symbol) ? account : account.identifier).to_s from, to, at = [:from], [:to], [:at] code, codes = [:code], [:codes] # time based scoping conditions = if at # lookup method could use running balance, with a order by limit one clause # (unless it's a reporting call, i.e. account == symbol and not an instance) ['account = ? and created_at <= ?', account, at] # index this?? elsif from and to ['account = ? and created_at >= ? and created_at <= ?', account, from, to] # index this?? else # lookup method could use running balance, with a order by limit one clause # (unless it's a reporting call, i.e. account == symbol and not an instance) ['account = ?', account] end # code based scoping if code conditions[0] << ' and code = ?' # index this?? conditions << code.to_s elsif codes conditions[0] << ' and code in (?)' # index this?? conditions << codes.collect { |c| c.to_s } end # account based scoping if scope conditions[0] << ' and scope = ?' conditions << scope # This is to work around a MySQL 5.1 query optimiser bug that causes the ORDER BY # on the query to fail in some circumstances, resulting in an old balance being # returned. This was biting us intermittently in spec runs. # See http://bugs.mysql.com/bug.php?id=51431 if Line.connection.adapter_name.match /mysql/i use_index = "USE INDEX (lines_scope_account_id_idx)" end end if (from and to) or (code or codes) # from and to or code lookups have to be done via sum Money.new(Line.where(conditions).sum(:amount)) else # all other lookups can be performed with running balances line = Line.select("id, balance").from("#{Line.quoted_table_name} #{use_index}").where(conditions).order('id desc').first line ? line.balance : Money.empty end end |
.describe(line) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
224 225 226 227 228 229 230 231 232 |
# File 'lib/double_entry.rb', line 224 def describe(line) # make sure we have a test for this refactoring, the test # conditions are: i forget... but it's important! if line.credit? @transfers.find(line.account, line.partner_account, line.code) else @transfers.find(line.partner_account, line.account, line.code) end.description.call(line) end |
.lock_accounts(*accounts) { ... } ⇒ Object
Lock accounts in preparation for transfers.
This creates a transaction, and uses database-level locking to ensure that we're the only ones who can transfer to or from the given accounts for the duration of the transaction.
219 220 221 |
# File 'lib/double_entry.rb', line 219 def lock_accounts(*accounts, &block) DoubleEntry::Locking.lock_accounts(*accounts, &block) end |
.reconciled?(account) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This is used by the concurrency test script.
247 248 249 250 251 252 253 |
# File 'lib/double_entry.rb', line 247 def reconciled?(account) scoped_lines = Line.where(:account => "#{account.identifier}", :scope => "#{account.scope}") sum_of_amounts = scoped_lines.sum(:amount) final_balance = scoped_lines.order(:id).last[:balance] cached_balance = AccountBalance.find_by_account(account)[:balance] final_balance == sum_of_amounts && final_balance == cached_balance end |
.scopes_with_minimum_balance_for_account(minimum_balance, account_identifier) ⇒ Array<Fixnum>
Identify the scopes with the given account identifier holding at least the provided minimum balance.
194 195 196 197 198 199 200 201 |
# File 'lib/double_entry.rb', line 194 def scopes_with_minimum_balance_for_account(minimum_balance, account_identifier) select_values(sanitize_sql_array([<<-SQL, account_identifier, minimum_balance.cents])).map {|scope| scope.to_i } SELECT scope FROM #{AccountBalance.table_name} WHERE account = ? AND balance >= ? SQL end |
.table_name_prefix ⇒ Object
255 256 257 |
# File 'lib/double_entry.rb', line 255 def table_name_prefix 'double_entry_' end |
.transfer(amount, options = {}) ⇒ Object
Transfer money from one account to another.
Only certain transfers are allowed. Define legal transfers in your configuration file.
If you're doing more than one transfer in one hit, or you're doing other database operations along with your transfer, you'll need to use the lock_accounts method.
108 109 110 111 112 113 114 115 116 117 |
# File 'lib/double_entry.rb', line 108 def transfer(amount, = {}) raise TransferIsNegative if amount < Money.new(0) from, to, code, , detail = [:from], [:to], [:code], [:meta], [:detail] transfer = @transfers.find(from, to, code) if transfer transfer.process!(amount, from, to, code, , detail) else raise TransferNotAllowed.new([from.identifier, to.identifier, code].inspect) end end |