我正在制作财务应用程序,我希望在我的视图中显示余额的运行总计,类似于大多数在线银行平台的工作方式。我不知道该怎么做。我在我的数据库中将积分作为正数和借记存储为负数。所以我基本上需要按日期排序,并在我的视图中为新列累加量,以显示运行余额。
在我的模型中,我根据这里的大量搜索来定义了这个:
def running_total
running_total = self.inject(0) { |sum, p| sum + p.amount }
end
但它似乎没有起作用。我收到错误:
#< Transaction:0x00007f15bd9dae70>的未定义方法`inject'; 你的意思是?检查
任何想法都会受到赞赏,谢谢!
根据@spickermann的建议,我对代码进行了一些更新,现在正在创建新事务或修改旧事务时正确计算运行余额,但我仍然无法将后续记录添加到编辑上一个事务时更新运行余额。正如在控制台中看到的那样,previous_transaction方法被激发以选择晚于我正在编辑的事务的事务,但是该值未在数据库中更新。
transaction.rb
class Transaction < ApplicationRecord
belongs_to :account
attr_accessor :trx_type
#default_scope { order('trx_date, id DESC') }
validates_presence_of :trx_type, :message => "Please select debit or credit"
validates :trx_date, presence: true
validates :description, presence: true, length: { maximum: 150 }
validates :amount, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :memo, length: { maximum: 500 }
before_save :convert_amount, :set_running_balance
after_create :update_account_balance_new
after_update :update_account_balance_edit
after_destroy :update_account_balance_destroy
after_save :recalculate_running_balance, on: :update
scope :desc, -> { order('trx_date, id DESC') }
# Determine the transaction_type for existing records based on amount
def transaction_type
if !new_record?
if self.amount >= 0
return ['Credit', 'credit']
else
return ['Debit', 'debit']
end
else
return ['Debit', 'debit']
end
end
private
def set_running_balance
previous_balance = previous_transaction.try(:running_balance) || 0
self.running_balance = previous_balance + amount
end
def recalculate_running_balance
# this will recursively trigger the `recalculate_next_running_balance`
# callback on the following transactions and thereby update all later
# transactions
next_transaction.try(:save)
end
def previous_transaction
scope = Transaction.where(account: account).order(:id)
scope = scope.where('id < ?', id) if persisted?
scope.last
end
def next_transaction
return if new_record?
Transaction.where(account: account).where('id > ?', id).order(:id).first
end
def convert_amount
if self.trx_type == "debit"
self.amount = -self.amount.abs
end
end
def update_account_balance_new
@account = Account.find(account_id)
@account.update_attributes(current_balance: @account.current_balance + amount)
end
def update_account_balance_edit
@account = Account.find(account_id)
if saved_change_to_amount?
@account.update_attributes(current_balance: @account.current_balance - amount_was + amount)
end
end
def update_account_balance_destroy
@account = Account.find(account_id)
@account.update_attributes(current_balance: @account.current_balance - amount_was)
end
end
控制台
Processing by TransactionsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"9GuEIo7a7OUAMA3O26keE8zOlptfzd+F9Enp43hl0A7sh/5ioTDAud0AzLriOWiquU+wbyOoDgK8o6z9OZyLzA==", "transaction"=>{"trx_type"=>"debit", "trx_date(1i)"=>"2018", "trx_date(2i)"=>"3", "trx_date(3i)"=>"26", "description"=>"Meijer", "amount"=>"100.00", "memo"=>""}, "commit"=>"Update Transaction", "account_id"=>"3", "id"=>"21"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
Account Load (0.5ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."user_id" = $1 AND "accounts"."id" = $2 LIMIT $3 [["user_id", 1], ["id", 3], ["LIMIT", 1]]
Transaction Load (0.4ms) SELECT "transactions".* FROM "transactions" WHERE "transactions"."account_id" = $1 AND "transactions"."id" = $2 LIMIT $3 [["account_id", 3], ["id", 21], ["LIMIT", 1]]
(0.1ms) BEGIN
Transaction Load (0.4ms) SELECT "transactions".* FROM "transactions" WHERE "transactions"."account_id" = 3 AND (id < 21) ORDER BY "transactions"."id" DESC LIMIT $1 [["LIMIT", 1]]
SQL (0.7ms) UPDATE "transactions" SET "amount" = $1, "running_balance" = $2, "updated_at" = $3 WHERE "transactions"."id" = $4 [["amount", "-100.0"], ["running_balance", "1800.0"], ["updated_at", "2018-03-26 15:14:53.354282"], ["id", 21]]
Account Load (0.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2 [["id", 3], ["LIMIT", 1]]
DEPRECATION WARNING: The behavior of `attribute_was` inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To maintain the current behavior, use `attribute_before_last_save` instead. (called from update_account_balance_edit at /home/sitheris/dev/railsapps/olubalance/app/models/transaction.rb:85)
DEPRECATION WARNING: The behavior of `attribute_changed?` inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To maintain the current behavior, use `saved_change_to_attribute?` instead. (called from update_account_balance_edit at /home/sitheris/dev/railsapps/olubalance/app/models/transaction.rb:85)
DEPRECATION WARNING: The behavior of `changed_attributes` inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To maintain the current behavior, use `saved_changes.transform_values(&:first)` instead. (called from update_account_balance_edit at /home/sitheris/dev/railsapps/olubalance/app/models/transaction.rb:85)
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
SQL (0.3ms) UPDATE "accounts" SET "current_balance" = $1, "updated_at" = $2 WHERE "accounts"."id" = $3 [["current_balance", "1200.0"], ["updated_at", "2018-03-26 15:14:53.358782"], ["id", 3]]
NEXT TRANSACTION
Transaction Load (0.3ms) SELECT "transactions".* FROM "transactions" WHERE "transactions"."account_id" = 3 AND (id > 21) ORDER BY "transactions"."id" ASC LIMIT $1 [["LIMIT", 1]]
Account Load (0.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2 [["id", 3], ["LIMIT", 1]]
(3.5ms) COMMIT
Redirected to http://localhost:3000/accounts/3/transactions/21
Completed 302 Found in 22ms (ActiveRecord: 7.4ms)
答案 0 :(得分:1)
假设Transaction
是用户的单个交易,您需要执行以下操作:
current_user.transactions.pluck(:amount).sum
答案 1 :(得分:1)
我会将存款总额存储在借方和贷方旁边的数据库中。
为什么?
说:优化读取数据并在将新的借记或贷记行保存到数据库时计算余额。
实现计算 - 保存基本上只需要改变两件事:
迁移以添加余额列并回填现有记录。列的格式(整数,十进制)取决于您的设置,对于如何确定回填的范围(我假设用户):
def up
add_column :transactions, :balance, :integer
# This is just a quick and dirty implementation and will run very
# slowly. But for a few thousand records it might be fast enough.
User.find_each { |user| user.transactions.first.try(:save) }
change_column_null :transactions, :balance, false
end
def down
drop_column :transactions, :balance
end
你的模型中有两个回调:
before_save :set_running_balance
after_save :recalculate_running_balance, on: :update
private
def set_running_balance
previous_balance = previous_transaction_for_user.try(:balance) || 0
self.balance = previous_balance + amount
end
def recalculate_running_balance
# this will recursively trigger the `recalculate_next_running_balance`
# callback on the following transactions and thereby update all later
# transactions
next_transaction_for_user.try(:save)
end
def previous_transaction_for_user
scope = Transaction.where(user: user).order(:id)
scope = scope.where('id < ?', id) if persisted?
scope.last
end
def next_transaction_for_user
return if new_record?
Transaction.where(user: user).where('id > ?', id).order(:id).first
end
通过这些更改,您甚至可以使用简单的<%= transaction.balance %>
在分页或过滤页面上显示运行余额。
答案 2 :(得分:0)
我尝试过使用pluck的建议,但我无法使用它。我可以考虑将这个逻辑移到数据库列,但我觉得这比我提出的解决方案更复杂。在我看来,我最终做到了这一点,以实现我的需要。如果在模型或其他地方有更好的方法,我可以接受建议。谢谢!
<% @running_balance = 0 %>
<% @transactions.each do |transaction| %>
<% @running_balance = @running_balance + transaction.amount %>
<tr class="row m-0">
<td class="col-sm-1 text-center"><%= link_to transaction.id, [transaction.account, transaction] %></td>
<td class="col-sm-1 text-center"><%= transaction.trx_date.strftime('%m/%d/%Y') %></td>
<td class="col-sm-4"><%= transaction.description %></td>
<td class="col-sm-2 text-right"><%= if transaction.amount >= 0 then number_to_currency(transaction.amount) end %></td>
<td class="col-sm-2 text-right"><%= if transaction.amount < 0 then "(" + number_to_currency(transaction.amount.abs) + ")" end %></td>
<td class="col-sm-2 text-right"><%= number_to_currency(@running_balance) %></td>
</tr>
<% end %>