Rails 4:nil的未定义方法`total_price':NilClass,Order Controller

时间:2015-09-26 01:13:26

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

我在订购时遇到了麻烦。我发布了下面的错误。我认为问题与OrderController.rb中的create方法有关,我确实已经定义了total_price方法,但除此之外我不知道如何解决这个问题。任何帮助,将不胜感激。谢谢。

enter image description here

class OrderTransaction
  def initialize order, nonce
    @order = order
    @nonce = nonce
  end

  def execute
    @result = Braintree::Transaction.sale(
      amount: order.total_price,
      payment_method_nonce: nonce
    )
  end

  def ok?
    @result.success?
  end

  private

  attr_reader :order, :nonce
end
class Order < ActiveRecord::Base
  belongs_to :user
  has_many :order_items

  def total_price
    order_items.inject(0) { |sum, item| sum + item.total_price }
  end
end
class OrdersController < ApplicationController
  before_filter :initialize_cart

  def index
    @orders = Order.order(created_at: :desc).all
  end

  def create
    @order_form = OrderForm.new(
      user: User.new(order_params[:user]),
      cart: @cart
    )

    if @order_form.save
      notify_user
      if charge_user
        redirect_to root_path, notice: "Thank you for placing the order."
      else
        flash[:warning] = <<EOF
Your order ID is #{@order_form.order.id}.
<br/>
Something went wrong.
EOF
        redirect_to new_payment_order_path(@order_form.order)
      end
    else
      render "carts/checkout"
    end
  end

  def update
    @order = Order.find params[:id]
    @previous_state = @order.state

    if @order.update state_order_params
      notify_user_about_state
      redirect_to orders_path, notice: "Order was updated."
    end
  end


  def new_payment
    @order = Order.find params[:id]
    @client_token = Braintree::ClientToken.generate
  end

  def pay
    @order = Order.find params[:id]
    transaction = OrderTransaction.new @order, params[:payment_method_nonce]
    transaction.execute
    if transaction.ok?
      redirect_to root_path, notice: "Thank you for placing the order."
    else
      render "orders/new_payment"
    end
  end

  private

  def notify_user
    @order_form.user.send_reset_password_instructions
    OrderMailer.order_confirmation(@order_form.order).deliver
  end

  def notify_user_about_state
    OrderMailer.state_changed(@order, @previous_state).deliver
  end

  def order_params
    params.require(:order_form).permit(
      user: [ :name, :phone, :address, :city, :country, :postal_code, :email ]
    )
  end

  def charge_user
    transaction = OrderTransaction.new @order, params[:payment_method_nonce]
    transaction.execute
    transaction.ok?
  end

  def state_order_params
    params.require(:order).permit(:state)
  end
end
class OrderItem < ActiveRecord::Base
  belongs_to :order
  belongs_to :product

  def total_price
    self.quantity * self.product.price
  end
end
  class OrderForm
  include ActiveModel::Model

  attr_accessor :user, :order # credit_card
  attr_writer :cart

  def save
    set_password_for_user

    if valid?
      persist
      true
    else
      false
    end
  end

  def has_errors?
    user.errors.any?
  end

  private

  def valid?
    user.valid?
  end

  def persist
    user.save
    @order = Order.create! user: user

    build_order_items
  end

  def set_password_for_user
    user.password = Digest::SHA1.hexdigest(user.email + Time.now.to_s)[0..8]
  end

  def build_order_items
    @cart.items.each do |item|
      @order.order_items.create! product_id: item.product_id, quantity: item.quantity
    end
  end

end
class OrderItem < ActiveRecord::Base
  belongs_to :order
  belongs_to :product

  def total_price
    self.quantity * self.product.price
  end
end

2 个答案:

答案 0 :(得分:2)

问题出在charge_userOrdersController类中,您调用此代码:

transaction = OrderTransaction.new @order, params[:payment_method_nonce]

你在这个方法中没有真正定义@order,即@order在这里是nil,这会导致你在这里遇到问题并且你得到这个错误:{{1} }

在调用此行代码之前,在undefined method total_price for nil:NilClass方法中设置@order值,并确保charge_user @order

nil

一种可能的解决方案是修改transaction = OrderTransaction.new @order, params[:payment_method_nonce] 方法以采用charge_user这样的参数:

order

并且,在您的def charge_user(order) transaction = OrderTransaction.new order, params[:payment_method_nonce] transaction.execute transaction.ok? end 方法调用中,这样:

create

这将解决您的问题。

答案 1 :(得分:2)

作为标准注释,任何NilClass错误基本上意味着您尚未定义您尝试操作的变量。

解决问题的关键是找出为什么没有定义变量,并填充它。

def execute
    @result = Braintree::Transaction.sale(
      amount: order.total_price,
      payment_method_nonce: nonce
    )
end

这是Rails表示该变量未填充的地方。

但是,与编程中的许多问题一样,问题的原因可能没有定义......

我最初认为问题在于你没有打电话给@order。但是,该类使用order初始化,因此不应该成为问题。所以你必须看看你如何调用这个类:

transaction = OrderTransaction.new @order, params[:payment_method_nonce]

这推测@order已定义。

我猜测它不是。

这就是我要做的事情:

def create
    @order_form = OrderForm.new(
      user: User.new(order_params[:user]),
      cart: @cart
    )

    if @order_form.save
      notify_user
      @order = @order_form.order #-> not efficient but should create @order
      if charge_user
        redirect_to root_path, notice: "Thank you for placing the order."
      else
        flash[:warning] = <<EOF
Your order ID is #{@order_form.order.id}.
<br/>
Something went wrong.
EOF
        redirect_to new_payment_order_path(@order_form.order)
      end
    else
      render "carts/checkout"
    end
end

就我个人而言,我认为这突出了代码结构的一个更深层次的问题:

  
      
  • 您正在创建OrderForm对象,但正在处理@order_form.order
  •   
  • 你的控制器充满了很小的方法,这些方法让人很沮丧
  •   
  • 您的控制器用于订单,但构建OrderForm个对象
  •   

我尽力做出controller as thin as possible

#app/controllers/orders_controller.rb
class OrdersController < ApplicationController
   def new 
      @order = current_user.order.new
   end
   def create
      @order = current_user.order.new order_params
      if @order.save 
         @order.charge
      end
   end

   private

   def order_params
      params.require(:order).permit(:x, :y, :z, order_products_attributes: [:product, :qty])
   end
end

我有更模块化的模型结构:

#app/models/order.rb
class Order < ActiveRecord::Base
   belongs_to :user
   has_many :order_products
   has_many :products, through: :order_products, extend ProductQty
   has_many :payments, inverse_of: :order

   scope :cart, -> { order_products }

   def total_price
      products.pluck(:price, :qty) #-> need to work out
   end

   def charge
       payment = payments.create
       payment.execute ? payment.success : payment.error #-> something conditional
   end
end

#app/models/order_product.rb
class OrderProduct < ActiveRecord::Base
   #columns id | order_id | product_id | qty | created_at | updated_at
   belongs_to :order
   belongs_to :product

end

#app/models/payment.rb
class Payment < ActiveRecord::Base
   belongs_to :order, inverse_of: :payments

   def execute
      Braintree::Transaction.sale(amount: order.total_price)
   end
end

#app/models/product.rb
class Product < ActiveRecord::Base
   has_many :order_products
   has_many :orders, through: :order_products
end

#app/models/concerns/product_qty.rb
module ProductQty

    #Load
    def load
       products.each do |qty|
          proxy_association.target << qty
       end
    end

    #Private
    private

    #Products
    def products
       return_array = []
       through_collection.each_with_index do |through,i|
           associate = through.send(reflection_name)
           associate.assign_attributes({qty: items[i]})
           return_array.concat Array.new(1).fill( associate )
       end
       return_array
    end

    #######################
    #      Variables      #
    #######################

    #Association
    def reflection_name
            proxy_association.source_reflection.name
    end

    #Foreign Key
    def through_source_key
            proxy_association.reflection.source_reflection.foreign_key
    end

    #Primary Key
    def through_primary_key
            proxy_association.reflection.through_reflection.active_record_primary_key
    end

    #Through Name
    def through_name
       proxy_association.reflection.through_reflection.name
    end

    #Through
    def through_collection
       proxy_association.owner.send through_name
    end

    #Captions
    def items
       through_collection.map(&:qty)
    end

    #Target
    def target_collection
       proxy_association.target
    end

end

我想在某处包含 cart ,我将不得不再次这样做。

目前,您可以执行以下操作:

@order = current_user.orders.find params[:id]
@order.products.each do |product|
   product.qty #-> 5

@order.total_price #-> prices * qtys

-

这不完整或经过测试,但我希望它能告诉您如何通过模块化来大幅提升代码结构。 IE保留尽可能多的方法绑定到您的对象。

简而言之,您应该能够执行以下操作:

@order = current_users.orders.find params[:id]
if @order.payments.any?
   @payment = @order.payment.first
   @payment.success?
end