ruby-on-rails - NoMethodError at/calculate undefined method `id' for nil :NilClass in Rails Appilcation

标签 ruby-on-rails ruby ruby-on-rails-4

我继承了一个 Rails 应用程序,我应该对其进行调试并开始运行,但问题是我对 Rails 并不熟悉。我已经对这里和其他地方的错误进行了研究,但我仍然不知道如何解决这个错误。

这是我的计算模型的代码

class Calculation < ActiveRecord::Base
  belongs_to :user
  has_many :calculation_expenses, dependent: :delete_all
  has_many :user_account_stats, dependent: :delete_all
  accepts_nested_attributes_for :calculation_expenses

  def self.calculate_for(user, period = 'weeks', periods_count = 5*53 - 1)
    user_accounts = {}
    checking_ids = []
    total_checking = 0
    total_expense_per_account = {}
    user.user_accounts.each do |ua|
      total_expense_per_account[ua.id] = 0
      user_accounts[ua.id] = ua.amount
      if ua.is_checking?
        checking_ids << ua.id
        total_checking = total_checking + ua.amount
      end
    end

    monthly = false
    monthly = true if period == 'months'
    user.user_account_stats.where('calculations.is_monthly = ?', monthly).delete_all
    user.calculation_expenses.where('calculations.is_monthly = ?', monthly).delete_all
    IncomeStat.where('income_id IN (?)', user.incomes.map(&:id)).delete_all
    user.calculations.where(is_monthly: monthly).delete_all

    calcs = []
    calcs_expenses = []
    accounts_stats = []
    income_stats = []
    left_to_bugdet = []
    expenses = user.expenses.to_a
    incomes = user.incomes.where(is_system: false).to_a
    system_income_id = user.incomes.where(is_system: true).first.id
    goals = user.expenses.where(is_goal: true).reorder('rank_of_priority ASC').to_a
    goal_ids = goals.map(&:id)
    current_goals_balance = {}
    current_expenses_total = {}
    goal_payments_made = {}
    goal_monthly_override = {} #using this for loan payments with extra payed sum
    loan_pay_off = {}
    expenses_monthly = {}

    expenses.each do |expense|
      current_expenses_total[expense.id] = 0
      expenses_monthly[expense.id] = expense.amount
    end

    start_dt = Date.today
    start_dt += 1 + ((5 - start_dt.wday) % 7)

# firstly we need to calculate all incomes and expenses
    for i in 0..periods_count
      week_start = start_dt + i.send(period)
      week_end = week_start + 1.send(period) - 1.day
      total_income = 0
      total_expense = 0
      current_expenses = {}
      scheduled_expenses = {}
      expense_per_account = {}
      user_accounts.each_pair do |key, amount|
        expense_per_account[key] = 0
      end

      incomes.each do |income| #add incomes
        cnt = income.exists_between week_start, week_end
        income_amount = cnt * income.amount
        total_income = total_income + income_amount
        income_stats << [income_amount, income.id]
        user_accounts[income.user_account_id] = user_accounts[income.user_account_id] + income_amount
        if checking_ids.include? income.user_account_id
          total_checking = total_checking + income_amount
        end
      end
      income_stats << [0, system_income_id]

      expenses.each do |expense| #substract expenses
        cnt = expense.exists_between week_start, week_end
        scheduled_expenses[expense.id] = (cnt > 0)
        goal_payments_made[expense.id] ||= 0
        goal_payments_made[expense.id] = goal_payments_made[expense.id] + cnt
        ea = expense.amount
        ea = goal_monthly_override[expense.id] unless goal_monthly_override[expense.id].nil?
        expense_amount = cnt * ea
        if expense.is_loan? && cnt > 0
          loan_pay_off[expense.id] ||= 0
          if expense.get_debt_sum - loan_pay_off[expense.id] < expense_amount
            expense_amount = (expense.get_debt_sum - loan_pay_off[expense.id]) * (1 + expense.interest / Frequency.get_interest_amount(expense.frequency_id) / 100.0)
          end
          loan_pay_off[expense.id] = loan_pay_off[expense.id] + expense.get_main_part(loan_pay_off[expense.id], expense_amount)
        end

        if checking_ids.include? expense.user_account_id
          total_expense = total_expense + expense_amount
        end
        user_accounts[expense.user_account_id] = user_accounts[expense.user_account_id] - expense_amount
        current_expenses[expense.id] = expense_amount
        expense_per_account[expense.user_account_id] = expense_per_account[expense.user_account_id] + expense_amount
        if checking_ids.include? expense.user_account_id
          total_checking = total_checking - expense_amount
        end
        current_expenses_total[expense.id] = current_expenses_total[expense.id] + expense_amount
        total_expense_per_account[expense.user_account_id] = total_expense_per_account[expense.user_account_id] + expense_amount
      end

      extra_money = total_checking - user.minimum_safety_level
      j = i - 1
      while extra_money < 0 && j > 0  do  #respect minimum safety level by reducing extra money levels on previous periods
        reduce_extra = -extra_money
        if calcs[j][5] < reduce_extra
          reduce_extra = calcs[j][5]
        end
        calcs[j][5] = calcs[j][5] - reduce_extra
        extra_money = extra_money + reduce_extra
        calcs[i]
        j = j - 1
      end
      extra_money = 0 if extra_money < 0

      calcs << [week_end, user.id, total_income, total_expense, monthly, extra_money]

      current_expenses.each_pair do |expense_id, budgeted|
        calcs_expenses << [expense_id, budgeted, nil, current_expenses_total[expense_id], loan_pay_off[expense_id] || 0, scheduled_expenses[expense_id], expenses_monthly[expense_id]]
      end
      a = [] #temporary save stats
      user_accounts.each_pair do |user_account_id, amount|
        a << [user_account_id, amount, amount, expense_per_account[user_account_id], nil, total_expense_per_account[user_account_id]]
      end
      accounts_stats << a
    end

    extra_payments_data = []
    accounts_stats = accounts_stats.map { |a| a }.sum
    self.save_data(user, monthly, expenses.size, calcs, calcs_expenses, extra_payments_data, accounts_stats, income_stats)

    #find last calculation without extra money
    last_zero = Calculation.where(user_id: user.id, is_monthly: monthly, extra_money: 0).reorder('dt DESC').first
    ## remove local extra money before last zero - we cannot spend them
    unless last_zero.nil?
      Calculation.where(user_id: user.id, is_monthly: monthly).where('id < ?', last_zero.id).update_all extra_money: 0
      # # find an extra money we can spend
      em_count = Calculation.where(user_id: user.id, is_monthly: monthly).where('extra_money > 0').count
      g = 0
      id = 0
      while em_count > 0 && g < periods_count - 1 do
        calc = Calculation.where(user_id: user.id, is_monthly: monthly).where('extra_money > 0 AND id > ?', id).reorder('extra_money ASC').first
        unless calc.nil?
          goals.each do |goal|
            unless goal.ends_before? calc.dt
              ce = calc.calculation_expenses.where(expense_id: goal.id).first
              amount = goal.aggressiveness / 100.0 * calc.extra_money
              goal_left = goal.get_debt_sum - ce.paid_off
              amount = goal_left if goal_left < amount
              if amount > 0
                ce.update_budgeted ce.budgeted + amount
              end
            end
          end
          g = g + 1
          puts "#{g} of #{periods_count}"
          id = calc.id
          #find last calculation without extra money
          last_zero = Calculation.where(user_id: user.id, is_monthly: monthly, extra_money: 0).reorder('dt DESC').first
          # remove local extra money before last zero - we cannot spend them
          Calculation.where(user_id: user.id, is_monthly: monthly).where('id < ?', last_zero.id).update_all extra_money: 0
        else
          g = periods_count
        end
      end
    end
  end

  def self.save_data(user, monthly, expenses_count, calcs, calcs_expenses, extra_payments_data, accounts_stats, income_stats, dt = nil)
    # write calculations to database
    Calculation.import([:dt, :user_id, :total_income, :total_expense, :is_monthly, :extra_money], calcs, validate: false)
    # write calculation ids to arrays
    if dt.nil?
      ids = user.calculations.where(is_monthly: monthly).pluck(:id)
    else
      ids = user.calculations.where(is_monthly: monthly).where('dt >= ?', dt).pluck(:id)
    end
    cur_ind = 0
    cur_ind2 = 0
    cur_ind3 = 0
    num = expenses_count
    num2 = user.incomes.count
    num3 = user.user_accounts.size
    ids.each_with_index do |id, ind|
      num.times do
        calcs_expenses[cur_ind] << ids[ind]
        cur_ind = cur_ind + 1
      end
      num2.times do
        income_stats[cur_ind2] << ids[ind]
        cur_ind2 = cur_ind2 + 1
      end
      num3.times do
        accounts_stats[cur_ind3] << ids[ind]
        cur_ind3 = cur_ind3 + 1
      end
    end
    # write all data with 2 queries
    UserAccountStat::import([:user_account_id, :amount, :initial_amount, :budgeted, :actual, :total, :calculation_id], accounts_stats, validate: false)
    CalculationExpense::import([:expense_id, :budgeted, :actual, :total, :paid_off, :is_scheduled, :monthly_payment, :calculation_id], calcs_expenses, validate: false)
    IncomeStat::import([:amount, :income_id, :calculation_id], income_stats, validate: false)
    user.expenses_groups.each do |group|
      group.calculate_data
    end
  end

  def as_json(options)
    {
        id: self.id,
        total_income: self.total_income,
        total_expense: self.total_expense,
        extra_money: self.extra_money,
        dt: self.dt
    }
  end
end

这是我的计算 Controller

class CalculationsController < ApplicationController
  def index
    unless current_user.calculations.any?
      redirect_to '/calculate'
    end
  end

  def load
    respond_to do |format|
      format.json { render json: get_data }
    end
  end

  def calc
    Calculation.calculate_for(current_user)
    redirect_to calculations_path
  end

  def update
    @calc = current_user.calculation_expenses.where(id: params[:id]).first
    @calc.update_actual params[:value]
    respond_to do |format|
      format.json { render json: get_data }
    end
  end

  def update_budgeted
    @calc = current_user.calculation_expenses.where(id: params[:id]).first
    @calc.update_budgeted params[:value]
    respond_to do |format|
      format.json { render json: get_data }
    end
  end

  def group
    if params[:id].blank?
      @group = ExpensesGroup.create(name: params[:name], user_id: current_user.id)
    else
      @group = current_user.expenses_groups.where(id: params[:id]).first
      unless @group.nil?
        @group.update_attribute :name, params[:name]
      end
    end
  end
end

当我在本地运行应用程序时,我可以看到问题出在计算模型中的 calculate_for 方法中的这一行

system_income_id = user.incomes.where(is_system: true).first.id

并在计算控件的cal方法中加上这一行

Calculation.calculate_for(current_user)

这是来自 rails 控制台的错误日志

NoMethodError - undefined method `id' for nil:NilClass:
app/models/calculation.rb:36:in `calculate_for'
app/controllers/calculations_controller.rb:16:in `calc'
 actionpack (4.2.3) lib/action_controller/metal/implicit_render.rb:4:in   `send_action'
  actionpack (4.2.3) lib/abstract_controller/base.rb:198:in `process_action'
actionpack (4.2.3) lib/action_controller/metal/rendering.rb:10:in `process_action'
 actionpack (4.2.3) lib/abstract_controller/callbacks.rb:20:in `block in process_action'
 activesupport (4.2.3) lib/active_support/callbacks.rb:115:in `call'
 activesupport (4.2.3) lib/active_support/callbacks.rb:553:in `block (2 levels) in compile'
 activesupport (4.2.3) lib/active_support/callbacks.rb:503:in `call'
 activesupport (4.2.3) lib/active_support/callbacks.rb:88:in `run_callbacks'
 actionpack (4.2.3) lib/abstract_controller/callbacks.rb:19:in `process_action'
 actionpack (4.2.3) lib/action_controller/metal/rescue.rb:29:in `process_action'
 actionpack (4.2.3) lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
 activesupport (4.2.3) lib/active_support/notifications.rb:164:in `block in instrument'
 activesupport (4.2.3) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
  activesupport (4.2.3) lib/active_support/notifications.rb:164:in `instrument'
 actionpack (4.2.3) lib/action_controller/metal/instrumentation.rb:30:in `process_action'
 actionpack (4.2.3) lib/action_controller/metal/params_wrapper.rb:250:in `process_action'
 activerecord (4.2.3) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
 actionpack (4.2.3) lib/abstract_controller/base.rb:137:in `process'
 actionview (4.2.3) lib/action_view/rendering.rb:30:in `process'
 actionpack (4.2.3) lib/action_controller/metal.rb:196:in `dispatch'
 actionpack (4.2.3) lib/action_controller/metal/rack_delegation.rb:13:in `dispatch'
 actionpack (4.2.3) lib/action_controller/metal.rb:237:in `block in action'
actionpack (4.2.3) lib/action_dispatch/routing/route_set.rb:76:in `dispatch'
actionpack (4.2.3) lib/action_dispatch/routing/route_set.rb:45:in `serve'
actionpack (4.2.3) lib/action_dispatch/journey/router.rb:43:in `block in serve'
actionpack (4.2.3) lib/action_dispatch/journey/router.rb:30:in `serve'
actionpack (4.2.3) lib/action_dispatch/routing/route_set.rb:821:in `call'
meta_request (0.3.0) lib/meta_request/middlewares   /app_request_handler.rb:13:in `call'
rack-contrib (1.1.0) lib/rack/contrib/response_headers.rb:17:in `call'
meta_request (0.3.0) lib/meta_request/middlewares/headers.rb:16:in `call'
meta_request (0.3.0) lib/meta_request/middlewares                        /meta_request_handler.rb:13:in `call'
warden (1.2.3) lib/warden/manager.rb:35:in `block in call'
warden (1.2.3) lib/warden/manager.rb:34:in `call'
rack (1.6.4) lib/rack/etag.rb:24:in `call'
rack (1.6.4) lib/rack/conditionalget.rb:25:in `call'
rack (1.6.4) lib/rack/head.rb:13:in `call'
actionpack (4.2.3) lib/action_dispatch/middleware/params_parser.rb:27:in `call'
actionpack (4.2.3) lib/action_dispatch/middleware/flash.rb:260:in `call'
rack (1.6.4) lib/rack/session/abstract/id.rb:225:in `context'
rack (1.6.4) lib/rack/session/abstract/id.rb:220:in `call'
actionpack (4.2.3) lib/action_dispatch/middleware/cookies.rb:560:in `call'
activerecord (4.2.3) lib/active_record/query_cache.rb:36:in `call'
activerecord (4.2.3) lib/active_record/connection_adapters/abstract/connection_pool.rb:653:in `call'
activerecord (4.2.3) lib/active_record/migration.rb:377:in `call'
actionpack (4.2.3) lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
activesupport (4.2.3) lib/active_support/callbacks.rb:84:in `run_callbacks'
actionpack (4.2.3) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
 actionpack (4.2.3) lib/action_dispatch/middleware/reloader.rb:73:in `call'
actionpack (4.2.3) lib/action_dispatch/middleware/remote_ip.rb:78:in `call'
better_errors (2.1.1) lib/better_errors/middleware.rb:84:in `protected_app_call'
better_errors (2.1.1) lib/better_errors/middleware.rb:79:in `better_errors_call'
better_errors (2.1.1) lib/better_errors/middleware.rb:57:in `call'
actionpack (4.2.3) lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'
web-console (2.2.1) lib/web_console/middleware.rb:39:in `call'
actionpack (4.2.3) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
railties (4.2.3) lib/rails/rack/logger.rb:38:in `call_app'
railties (4.2.3) lib/rails/rack/logger.rb:20:in `block in call'
activesupport (4.2.3) lib/active_support/tagged_logging.rb:68:in `block in tagged'
activesupport (4.2.3) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (4.2.3) lib/active_support/tagged_logging.rb:68:in `tagged'
railties (4.2.3) lib/rails/rack/logger.rb:20:in `call'
quiet_assets (1.1.0) lib/quiet_assets.rb:27:in `call_with_quiet_assets'
actionpack (4.2.3) lib/action_dispatch/middleware/request_id.rb:21:in `call'
rack (1.6.4) lib/rack/methodoverride.rb:22:in `call'
rack (1.6.4) lib/rack/runtime.rb:18:in `call'
activesupport (4.2.3) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
rack (1.6.4) lib/rack/lock.rb:17:in `call'
actionpack (4.2.3) lib/action_dispatch/middleware/static.rb:116:in `call'
rack (1.6.4) lib/rack/sendfile.rb:113:in `call'
railties (4.2.3) lib/rails/engine.rb:518:in `call'
railties (4.2.3) lib/rails/application.rb:165:in `call'
rack (1.6.4) lib/rack/lock.rb:17:in `call'
rack (1.6.4) lib/rack/content_length.rb:15:in `call'
rack (1.6.4) lib/rack/handler/webrick.rb:88:in `service'
/home/xwattsh/.rbenv/versions/2.2.3/lib/ruby/2.2.0/webrick     /httpserver.rb:138:in `service'
  /home/xwattsh/.rbenv/versions/2.2.3/lib/ruby/2.2.0/webrick   /httpserver.rb:94:in `run'
   /home/xwattsh/.rbenv/versions/2.2.3/lib/ruby/2.2.0/webrick/server.rb:294:in `block in start_thread'

最佳答案

=>这条线不安全:

system_income_id = user.incomes.where(is_system: true).first.id

当is_system设置为true,用户没有收入时会抛出异常。

您可以检查 .first 是否为 nil,或者将其放在 try/catch block 中。无论哪种情况,都取决于您如何处理 .first 不存在的情况。

例如。

first_system_income = user.incomes.where(is_system: true).first

if first_system_income.present?
   //do your normal stuff here
else
  //handle the exception case
end

或(不推荐)

begin
  system_income_id = user.incomes.where(is_system: true).first.id
rescue => e
  //handle the exception case
  puts e
end

另外,这是一种方法中的一些严肃逻辑!既然您继承了该项目,那么一些评论或重构可能会有所帮助:)

关于ruby-on-rails - NoMethodError at/calculate undefined method `id' for nil :NilClass in Rails Appilcation,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34448375/

相关文章:

ruby - 如何使用 Capybara 跳过 SSL 证书验证

ruby - Ruby 中的 block 绑定(bind)

ruby - 在条件中声明变量

ruby-on-rails - 将静态 JSON 文件解析为 Rails 对象

ruby-on-rails - 具有多个命名空间的 Rails 应用程序

ios - 如果没有身份验证,Spotify 搜索 API 是否不再可用?

javascript - 验证用户身份,使用 XHR 请求重定向

ruby-on-rails - SQLite3 "LIKE"或 PostgreSQL "ILIKE"的通用 Ruby 解决方案?

ruby-on-rails - rails 4 : Syntax for testing partials

ruby-on-rails - Respond_to block 不起作用