Rails 4.0 неопределенный метод ` ‘ для nil: NilClass

#ruby-on-rails #ruby #null #actioncontroller

#ruby-on-rails #ruby #null #actioncontroller

Вопрос:

Я пытаюсь добавить строку в свой orders_items контроллер, чтобы, если это новый порядок, увеличить счетчик с нуля до единицы. итак, я создал действие, которое выполняет это до вызова сохранения, но когда я пытаюсь это сделать, я получаю:

 undefined method ` ' for nil:NilClass
 
 def create
    @order_item = @order.order_items.find_or_initialize_by_product_id(params[:product_id])
    # Error below
    @order_item.quantity  = 1

    respond_to do |format|
      if @order_item.save
    end
end
 

order_item.rb :

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

    validates :order_id, :product, presence: true
    validates :quantity, numericality: { only_integer: true, greater_than: 0 }

    def subtotal
    quantity * product.price
  end
end
 

order_items_controller.rb :

 class OrderItemsController < ApplicationController
  before_action :set_order_item, only: [:show, :edit, :destroy]
  before_action :load_order, only: [:create]

  # GET /order_items
  # GET /order_items.json
  def index
    @order_items = OrderItem.all
  end

  # GET /order_items/new
  def new
    @order_item = OrderItem.new
  end

  # POST /order_items
  # POST /order_items.json
  def create
    @order_item = @order.order_items.find_or_initialize_by_product_id(params[:product_id])
    @order_item.quantity  = 1

    respond_to do |format|
      if @order_item.save
        format.html { redirect_to @order, notice: 'Successfully added product to cart.' }
        format.json { render action: 'show', status: :created, location: @order_item }
      else
        format.html { render action: 'new' }
        format.json { render json: @order_item.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /order_items/1
  # PATCH/PUT /order_items/1.json
  def update
      @order_item = OrderItem.find(params[:id])
        respond_to do |format|
          if order_item_params[:quantity].to_i == 0
             @order_item.destroy

             format.html { redirect_to @order_item.order, notice: 'Order item was successfully updated.' }
             format.json { head :no_content }

        elsif @order_item.update(order_item_params)

            format.html { redirect_to @order_item.order, notice: 'Successfully updated the order item.' }
            format.json { head :no_content }
        else
            format.html { render action: 'edit' }
            format.json { render json: @order_item.errors, status: :unprocessable_entity}
        end
      end
  end

  # DELETE /order_items/1
  # DELETE /order_items/1.json
  def destroy
    @order_item.destroy
    respond_to do |format|
      format.html { redirect_to @order_item.order }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.

    def load_order
        @order = Order.find_or_initialize_by_id(session[:order_id], status: "unsubmitted")
        if @order.new_record?
            @order.save!
            session[:order_id] = @order.id
        end
    end

    def set_order_item
      @order_item = OrderItem.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def order_item_params
      params.require(:order_item).permit(:product_id, :order_id, :quantity)
    end
end
 

Файл миграции:

 class AddDefaultQuantityToOrderItems < ActiveRecord::Migration
  def change
    change_column :order_items, :quantity, :integer, default: 0
  end
end
 

rails console :

 @order.order_items.find_or_initialize_by_product_id(params[:product_id])
NoMethodError: undefined method `order_items' for nil:NilClass
    from (irb):1
    from /usr/local/rvm/gems/ruby-2.0.0-p247/gems/railties-4.0.4/lib/rails/commands/console.rb:90:in `start'
    from /usr/local/rvm/gems/ruby-2.0.0-p247/gems/railties-4.0.4/lib/rails/commands/console.rb:9:in `start'
    from /usr/local/rvm/gems/ruby-2.0.0-p247/gems/railties-4.0.4/lib/rails/commands.rb:62:in `<top (required)>'
    from bin/rails:4:in `require'
    from bin/rails:4:in `<main>'
 

Модель order.rb :

 class Order < ActiveRecord::Base

  has_many :order_items, dependent: :destroy

  def total
    order_items.map(amp;:subtotal).sum
  end

end
 

orders_controller.rb :

 class OrdersController < ApplicationController
  before_action :set_order, only: [:show, :edit, :update, :destroy]

  # GET /orders
  # GET /orders.json
  def index
    @orders = Order.all
  end

  # GET /orders/1
  # GET /orders/1.json
  def show
  end

  # GET /orders/new
  def new
    @order = Order.new
  end

  # GET /orders/1/edit
  def edit
  end

  # POST /orders
  # POST /orders.json
  def create
    @order = Order.new(order_params)

    respond_to do |format|
      if @order.save
        format.html { redirect_to @order, notice: 'Order was successfully created.' }
        format.json { render action: 'show', status: :created, location: @order }
      else
        format.html { render action: 'new' }
        format.json { render json: @order.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /orders/1
  # PATCH/PUT /orders/1.json
  def update
    respond_to do |format|
      if @order.update(order_params)
        format.html { redirect_to @order, notice: 'Order was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @order.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /orders/1
  # DELETE /orders/1.json
  def destroy
    @order.destroy
    respond_to do |format|
      format.html { redirect_to products_path }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_order
      @order = Order.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def order_params
      params.require(:order).permit(:user_id, :status)
    end
end
 

Update

Rake routes shows route call for /:product_id

product.rb

 class Product < ActiveRecord::Base
    validates_numericality_of :price
    validates :stock ,numericality: { greater_than_or_equal_to:  0 }
end
 

order.rb

 # @Chiperific added proper 4-line indentation for SO viewing.
class OrderItem < ActiveRecord::Base
    belongs_to :order
    belongs_to :product

    validates :order_id, :product, presence: true
    validates :quantity, numericality: { only_integer: true, greater_than: 0 }


    def subtotal
    quantity * product.price
  end
end
 

заказать элементы.rb

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

validates :order_id, :product, presence: true
validates :quantity, numericality: { only_integer: true, greater_than: 0 }


def subtotal
    quantity * product.price
    end
end
 

когда я добавляю

 def create
  @order_item = OrderItems.find_or_initialize_by_product_id(params[:product_id])
  @order_item.quantity  =1
 

я получаю NameError в OrderItemsController#create — неинициализированная константа OrderItemsController::Orderitems

определить create @order_item = Orderitems.find_or_initialize_by_product_id(параметры[:product_id]) <-ошибка @order_item.количество = 1

если я попытаюсь

 @order = Order.find(params[:order_id])
  @order_item = @order.order_items.find_or_initialize_by_product_id(params[:product_id])
  @order_item.quantity  = 1
 

я получаю ActiveRecord::RecordNotFound в OrderItemsController#create — не удалось найти порядок без идентификатора

 def create
    @order = Order.find(params[:order_id])
    @order_item = @order.order_items.find_or_initialize_by_product_id(params[:product_id])<-error 
    @order_item.quantity  = 1
 

Комментарии:

1. Я предполагаю, что ваше количество не начинается с 0. Попробуйте использовать отладчик и выполнить пошаговое выполнение или создать элемент заказа из консоли, чтобы увидеть, начинается ли количество с 0 с самого начала. Просто чтобы перепроверить, ваша миграция была запущена и сервер был перезапущен?

2. @order_item.quantity не определен. Возможно, вам потребуется его инициализировать

3. Есть ли вероятность, что вы @order_item существовали ДО того, как в вашей схеме базы данных было установлено значение по умолчанию? Я предполагаю, что это произойдет, если вы запустите консоль rails @order_item.quantity , это приведет к nil и не 0

4. @Anthony: По сути, с миграцией все должно быть в порядке. Когда миграция завершена, все quantity присутствующие в вашей таблице order_items были инициализированы в 0. Я думаю, что проблема возникает сама по @order_item себе. Вы пробовали @order.order_items.find_or_initialize_by_product_id(params[:product_id]) прямо на своей консоли?

5. @bobbystouket Некоторые базы данных (например, Postgres) не присваивают автоматически обновленные значения по умолчанию существующим записям столбцов. Ему нужно было бы сделать OrderItem.all и посмотреть, не были ли какие-либо величины установлены на ноль после этой миграции, и вручную исправить их.

Ответ №1:

Я не вижу, где @order определено. Поскольку @order равно null, @order_items также будет.

Я предполагаю, что ваш URL-адрес просмотра выглядит примерно так /orders/:order_id/order_items/:id , поэтому давайте используем параметр URL, чтобы убедиться, что вы получили Order Order_ID инициированный or .

Вариант 1: не беспокойтесь о @order

 def create
  @order_item = OrderItem.find_or_initialize_by_product_id(params[:product_id])
  @order_item.quantity  =1

  ...

end
 

Вариант 2: сначала установите `@order`

 def create
  @order = Order.find(params[:order_id])
  @order_item = @order.order_items.find_or_initialize_by_product_id(params[:product_id])
  @order_item.quantity  = 1

  ...

end
 

Другая потенциальная проблема:

.find_or_initialize_by_product_id(params[:product_id]) на самом деле не выполняется поиск или инициализация.

Похоже, это ваше отношение к таблице:

 [Orders]`````
              |--> [Order_Items]
[Products]___/
 
  1. Я не понимаю, где accepts_nested_attributes_for :order_items у вас есть orders.rb
  2. Вероятно, вам также нужно accepts_nested_attributes_for :order_items products.rb
  3. Есть ли у Order_Item записи Product_id поле? Я вижу, где вы проверяете наличие :product , но нет :product_id , как выглядит ваша схема? Что произойдет, если вы попытаетесь `find__или_initialize_by_product(параметры[:product_id])
  4. Ваш маршрут требует /:product_id или /:product или ни того, ни другого? (он же есть ?) params[:product_id]

Обновить:

Спасибо за обновленные комментарии. Пожалуйста, добавьте весь используемый URI, всю строку Rake Routes или файл Routes.rb. Или, хотя я знаю, что это не лучшая практика, вы можете опубликовать ссылку на проект на Github, и я посмотрю там.

Я все еще убежден, что (по крайней мере, одна из) проблем заключается в том, что @order не устанавливается.

Попробовать его в консоли Rails — отличный способ узнать .find_or_initialize_by_product_id , работает ли

Но консоль rails не может извлекать параметры, и вы все еще не назначаете @order первым. Для тестирования в консоли rails попробуйте:

 @order = Order.first #<----(just so you get @order assigned)
@order.order_items.find_or_initialize_by_product_id(1)
 

Также:

 @order_item = OrderItems.find_or_initialize_by_product_id(params[:product_id])
 

OrderItems должно быть в единственном числе (мой плохой !!):

 @order_item = OrderItem.find_or_initialize_by_product_id(params[:product_id])
 

Я уделю более пристальное внимание этому сообщению и буду более своевременен в своих сообщениях, поскольку уверен, что вы расстраиваетесь. Держитесь, ваша методология хороша.

Но может быть и другой способ, если мы не сможем устранить эти проблемы. Можем ли мы найти количество элементов заказа, подсчитав количество записей с определенным идентификатором заказа вместо статического сохранения числа в базе данных? Просто мысль, если вы готовы отказаться от этой проблемы.

Ответ №2:

Вы можете инициализировать @order_item.quantity, поместив это в свой класс OrderItem:

 def quantity
  self.quantity ||= 0
end
 

Комментарии:

1. Проблема не в этом. Количество инициализируется в 0 из схемы.

2. @Chiperific вы ошибаетесь, это именно проблема. В показанном ruby-коде нет инициализации количества. Он применяется при сохранении и может быть применен к схеме, но при создании нового OrderItem quantity значения равно нулю. Это также именно то, что указывает ошибка: is not defined for nil таким образом, количество равно нулю. Это очень чистое решение, другой альтернативой было бы добавить after_initialize в модель, чтобы явно установить количество равным 0, если nil . Это должно быть принятым ответом.

3. Итак, почему OP получает эту ошибку независимо от того, является ли это новой записью или нет?

Ответ №3:

Перейдите в свой метод find_or_initialize_by_product_id и убедитесь, что вы инициализируете атрибут quantity равным 0.

Вот простое воспроизведение вашей ошибки — обратите внимание, что я никогда не инициализировал x значение 0:

 $ irb
irb(main):001:0> x  = 5
NoMethodError: undefined method ` ' for nil:NilClass
    from (irb):1
    from /usr/bin/irb:11:in `<main>'
 

Комментарии:

1. @order_item определен в предыдущей строке.

2. Вы правы — я имел в виду атрибут «количество».

Ответ №4:

В Rails 4 вы можете использовать следующее:

 @order_item = @order.order_items.where(product_id: params[:product_id]).first_or_create
 

вместо

 @order_item = @order.order_items.find_or_initialize_by_product_id(params[:product_id])
 

и это:

 @order = Order.where(session[:order_id], status: "unsubmitted").first_or_initialize
 

вместо

 @order = Order.find_or_initialize_by_id(session[:order_id], status: "unsubmitted")