调用 ActiveRecord#find_or_create_by 时如何修复错误 ActiveRecord::RecordNotSaved

How to fix Error ActiveRecord::RecordNotSaved when calling ActiveRecord#find_or_create_by

我在网站上工作,每当有人忘记表单中的空字段时,我都会收到一条错误消息

You cannot call create unless the parent is saved

这是痕迹:

Application Trace
app/views/technicians/offer_comments/_offer_comment.html.slim:1:in `_app_views_technicians_offer_comments__offer_comment_html_slim__1148413763742950523_70319840794240'
app/views/offer_comments/index.html.slim:2:in `_app_views_offer_comments_index_html_slim___917297770217289302_70319839456700'
app/views/shared/offers/_comments.html.slim:8:in `_app_views_shared_offers__comments_html_slim__3779418887197040636_70319839163900'
app/views/technicians/auctions/show.html.slim:98:in `block in _app_views_technicians_auctions_show_html_slim___1454306351028108654_70319829646100'
app/helpers/application_helper.rb:14:in `rescue in cache'
app/helpers/application_helper.rb:6:in `cache'
app/views/technicians/auctions/show.html.slim:1:in `_app_views_technicians_auctions_show_html_slim___1454306351028108654_70319829646100'
app/controllers/technicians/offers_controller.rb:54:in `update'

错误出现在这个html.slim视图的第一行:

- offer_comment.read_receipts.find_or_create_by user: current_user

.comment id="offer-#{offer_comment.offer_id}-comment-#{offer_comment.id}"
  .by
    - if offer_comment.user == current_user
      = t ".you"
    - else
      = t ".not_you"
    = " - "
    = t '.date', date: time_ago_in_words(offer_comment.created_at)

  .content
    = raw markdown offer_comment.content

有趣的是,这个错误只发生在我调用另一个对象时,offers,在呈现先前代码的主视图中:show.html.slim(最后一行)

ul#customer-auction-tabs.tabs.clean.collapse(data-tabs)
            a#auctions-tabs-chevron href="#" 
              i#auctions-tabs-chevron-icon.fas.fa-chevron-up
            li.tabs-title class=chat_active_class
              a#chat-tab href="#chat" aria-selected="true"= t '.tabs.chat'
            li.tabs-title class=offer_active_class
              a#offers-tab href="#offers"= t '.tabs.offer'
            - if comments_count > 0
              li.tabs-title class=comments_active_class
                a#comments-tab href="#comments"= t '.tabs.comments'
            li.tabs-title class=other_active_class
              a#other-tab href="#other"= t '.tabs.other'

          .auctions.tabs-content data-tabs-content="customer-auction-tabs"
            #chat.tabs-panel class=chat_active_class
              = render partial: "shared/auctions/chat", locals: { auction: auction }

            #offers.tabs-panel class=offer_active_class
              = render partial: "shared/offers/new", locals: { offer: offer }

            #comments.tabs-panel class=comments_active_class
              = render partial: 'shared/offers/comments', locals: { offer: offer }

            #other.tabs-panel class=other_active_class
              - if auction.offers.count.zero?
                = t "ingen andre bud endnu"
              = render "shared/offers/other"
              .offers= render offers

我不明白它是如何工作的,因为 find_or_create_by 显然应该可以工作,即使对象没有被保存。

谁能帮我解决这个问题,最好完全避免在视图中使用像 find_or_create_by 这样的逻辑?

这是优惠模型的一部分:

class Offer < ApplicationRecord
  has_paper_trail

  belongs_to :auction, -> { with_deleted }, counter_cache: true, touch: true
  belongs_to :technician, counter_cache: true, foreign_key: :technician_id

  has_one :settings, through: :technician

  has_many :comments, -> { order(created_at: :asc) }, class_name: "OfferComment"

  has_one :review, as: :target
  delegate :rating, to: :review, allow_nil: true
  delegate :rating_date, to: :review, allow_nil: true
  delegate :rating_comment, to: :review, allow_nil: true

  validates :description, presence: true
  validates :cents, presence: true, numericality: { only_integer: true, greater_than: 0 }

  validate :amount_validity

  scope :not_by, ->(technician) { where.not(technician: technician) }

这也是在使用空字段更新表单时调用的控制器更新操作:

class Technicians::OffersController < Technicians::ApplicationController
  rescue_from ActiveRecord::RecordNotFound do
    render "technicians/auctions/lost", status: 404
  end

  def update
    offer.attributes = offer_params

    changed = offer.changed?

    if offer.save
      OfferUpdatedWorker.perform_async offer.id if changed
      flash[:info] = t(".success")
      redirect_to [:technicians, auction]
    else
      flash.now[:error] = t(".failure")
      render "technicians/auctions/show",
        locals: { auction: auction, offer: offer },
        status: 400
    end
  end

另一个需要注意的重要文件是最初调用“technicians/auctions/show”的拍卖控制器

  def show
    render(:lost) && return if lost?

    render :show, locals: {
      offers: sorted_o,
      auction: auction,
      #other: other,      
      offer: offer,
    } if stale? **cache_options(auction.id, auction.updated_at)
  end

  private
#=begin
    def sorted_o
      @sorted_o ||= begin
        field = (%w[cheapest closest guarantee] & [params[:sort]])[0].presence || "cheapest"

        case field
        when "closest"
          auction
            .offers
            .includes(:auction, :technician, :review)
            .sort_by { |o| distance(o.technician, auction) }
        when "guarantee"
          auction
            .offers
            .includes(:auction, :technician, :review)
            .joins(:settings)
            .order("technician_settings.guarantee desc")
        else
          auction
            .offers
            .includes(:auction, :technician, :review)
            .order(cents: :asc)
        end
      end
    end
#=end
    def offer
      @offer ||= auction.offers.by(current_user) ||
        auction.offers.new(technician: current_user)
    end

看起来您需要先保存 offer_comment,然后才能创建属于它的 read_receipt,这是有道理的 - offer_comment 直到已保存。

这可能会让您解决问题。

offer_comment.tap(&:save).read_receipts.find_or_create_by user: current_user

我修好了。问题的原因是当 update 操作在 offers_controller.rb 中失败时它调用没有 offers 变量的 show 视图,这个变量在某种程度上与 offer_comments,但我不确定 how/why 因为 offer_comments 应该只与变量 offer 而不是 offers.

但是,当我检查控制台时 offer_comments 中有空元素,所以我继续将视图更改为仅显示非空元素,然后错误堆栈指向 offers未在 show 视图中定义。