2015-09-26 23 views
4

我在訂購時遇到了麻煩。我發佈了錯誤信息。我認爲這個問題與OrderController.rb中的create方法有關,我確實已經定義了total_price方法,但是..除此之外,我不知道如何解決這個問題。任何幫助,將不勝感激。謝謝。Rails 4:未定義的方法`total_price'爲零:NilClass,訂單控制器

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

作爲一個標準的說明,任何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 

我個人認爲,這突出了一個更深層次的問題與您的代碼結構:

  • You're creating an OrderForm object and yet processing @order_form.order
  • Your controller is full of tiny methods which bloat it up big time
  • Your controller is for orders, yet builds OrderForm objects

我會盡我所能,讓我的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 

我想包括的地方,我必須這樣做另一次。

現在,你可以做到以下幾點:

@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 
+0

謝謝你,那是很棒的信息 – JamesRocky

2

的問題是在你的charge_user方法內部OrdersController類,你把這個代碼:

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

你並不真正在這個方法中定義@order,即@ordernil在這裏,這是你在這裏造成的問題,你得到這個錯誤:undefined method total_price for nil:NilClass

設置charge_user方法內@order值調用這行代碼,使之前,請確保@ordernil

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方法調用:

if charge_user(@order_form.order) 
    redirect_to root_path, notice: "Thank you for placing the order." 
else 
    # rest of the code 
end 

這將解決您的問題。

+0

我不太明白。 '訂單'是'無'?但是爲什麼當我只將'create'方法設置爲'redirect_to root_path'時,它工作正常? – JamesRocky

+0

當您調用:'order.total_price'時,出現以下錯誤消息:'未定義的方法total_price爲nil:NilClass',這表示它會嘗試按照您的錯誤消息調用:nil.total_price。 –

+0

哦,等等,問題出現在你調用這個代碼的'charge_user'方法中:transaction = OrderTransaction.new @order,params [:payment_method_nonce]',這裏你沒有'@ order',它的'nil 「這裏,不?這是造成這個問題。 –

相關問題