Rails如何计算视图上的运行总计?

时间:2018-03-21 20:25:22

标签: ruby-on-rails ruby ruby-on-rails-5

我正在制作财务应用程序,我希望在我的视图中显示余额的运行总计,类似于大多数在线银行平台的工作方式。我不知道该怎么做。我在我的数据库中将积分作为正数和借记存储为负数。所以我基本上需要按日期排序,并在我的视图中为新列累加量,以显示运行余额。

在我的模型中,我根据这里的大量搜索来定义了这个:

    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)

3 个答案:

答案 0 :(得分:1)

假设Transaction是用户的单个交易,您需要执行以下操作:

current_user.transactions.pluck(:amount).sum

答案 1 :(得分:1)

我会将存款总额存储在借方和贷方旁边的数据库中。

为什么?

  1. 您不想在每个页面视图上重新计算运行总计。
  2. 您不想计算从0年前开始的当前运行总数,此时您想要的只是今天的余额。
  3. 说:优化读取数据并在将新的借记或贷记行保存到数据库时计算余额。

    实现计算 - 保存基本上只需要改变两件事:

    迁移以添加余额列并回填现有记录。列的格式(整数,十进制)取决于您的设置,对于如何确定回填的范围(我假设用户):

    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 %>