简体   繁体   中英

NoMethodError: undefined method '' for nil:NilClass

I have these models and controller codes:

class Item < ActiveRecord::Base
 has_many :receivers_collateral_items, through: :received_trades, source: :wanted_item
 has_many :receivers_wanted_items, through: :received_trades, source: :collateral_item
 has_many :requesters_collateral_items, through: :requested_trades, source: :collateral_item
 has_many :requesters_wanted_items, through: :requested_trades, source: :wanted_item
 validates :year, :price, presence: true, numericality: true
 validates :shares, numericality: 
                     { only_integer: true, greater_than_or_equal_to: 0, 
                     less_than_or_equal_to: :build_shares }

 accepts_nested_attributes_for :receivers_collateral_items, :receivers_wanted_items, 
                               :requesters_collateral_items, :requesters_wanted_items, 
                               allow_destroy: true
end




class Trade < ActiveRecord::Base
 belongs_to :trade_requester, class_name: "User"
 belongs_to :trade_recipient, class_name: "User"
 belongs_to :wanted_item, class_name: "Item"
 belongs_to :collateral_item, class_name: "Item"
 validates :trade_requester, :trade_recipient, :wanted_item, :collateral_item, presence: true
 validates :shares, numericality: 
                     { only_integer: true, greater_than_or_equal_to: 0, 
                     less_than_or_equal_to: :max_shares }

 accepts_nested_attributes_for :wanted_item, :collateral_item, allow_destroy: true

 def max_shares
    if wanted_item.shares > collateral_item.shares
        collateral_item.shares
    else
        wanted_item.shares
    end
 end
end


class TradesController < ApplicationController
 def create
  @trade = current_user.requested_trades.build(trade_params)
 end

 private
  def trade_params
   params.require(:trade).permit(:trade_requester_id, :trade_recipient_id, :wanted_item_id, :collateral_item_id, :shares)
  end
end

But I'm getting NoMethodError: undefined method 'shares' for nil:NilClass and a bd rollback. Steps to reproduce error:

t = Trade.new
t.trade_requester_id = User.find(1)
t.trade_recipient_id = User.find(2)
t.wanted_item_id = Item.second
t.collateral_item_id = Item.first
t.shares = 100
t.save

The stack trace indicates that it comes from the numericality validation in Trade , but it seems to me that the class shares is being called on should exist. I should be able to call t.wanted_item and get that item, but instead I get nil . t.wanted_item_id , however, returns that id. Why is this?

This is probably because the new record does is missing a wanted_item or a collateral_item . If either of this two associations returns nil , then an exception will be thrown. To fix this use try for older versions of Ruby, or the safe navigation operator ( &. ) for Ruby 2.3 or later.

This should solve your issue (pre Ruby 2.3):

def max_shares
  if wanted_item.try(:shares).to_i > collateral_item.try(:shares).to_i
    wanted_item.shares
  else
    collateral_item.try(:shares).to_i
  end
end

The try method will return the value of wanted_item.shares and collateral_item.shares if they are not nil . If one of them is nil then try will catch the exception and return nil . The to_i converts nil to zero.

So collateral_item.try(:shares).to_i will return the value of shares if collateral_item exists, and it will return zero if collateral_item is nil .

If you are using Ruby 2.3 or later, you should to replace _item.try(:shares).to_i with _item&.shares.to_i . The latter is cleaner looking and faster in execution .

Update

For replication, be sure you are assigning records to your associations and not to their ids:

t = Trade.new
t.trade_requester = User.find(1)
t.trade_recipient = User.find(2)
t.wanted_item = Item.second
t.collateral_item = Item.first
t.shares = 100
t.save!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM