ActiveRecord в Rails 5.2: сохранение / обновление не сохраняет запись, даже если она говорит, что делает - PullRequest
3 голосов
/ 19 февраля 2020

В моем приложении Rails 5.2.4.1, работающем с ruby 2.6.5, postgres 10.11 и pg gem версии 1.1.4, у меня есть такая модель ActiveRecord:

class RealEstate < ApplicationRecord
  belongs_to :developer, optional: true
  belongs_to :customer, optional: true
  # Do not fetch heavy/noisy columns such as JSONB metadata
  scope :light, -> { select(RealEstate.column_names.map!(&:to_sym) - [:metadata]) }
  scope :active, -> { where(active: true) }
  has_many :lots, -> { where(lot: true) }, class_name: 'RealEstate', foreign_key: 'program_id'
  belongs_to :program, class_name: 'RealEstate', optional: true

  enum old_or_new: {
    old: 0,
    new_building: 1 # developers new real estate programs
  }

  enum transaction_type: {
    rent: 0,
    buy: 1
  }

  enum property_category: {
    apartment: 0,
    house: 1,
    other: 2
  }
end

Базовая модель данных в db/structure.sql выглядит следующим образом (некоторые столбцы анонимны):

CREATE TABLE public.real_estates (
    id bigint NOT NULL,
    geom public.geometry(Point),
    title character varying,
    description text,
    address character varying,
    old_or_new integer DEFAULT 0 NOT NULL,
    property_category integer DEFAULT 0 NOT NULL,
    some_string character varying,
    some_string_2 text,
    price integer,
    created_at timestamp without time zone DEFAULT now() NOT NULL,
    updated_at timestamp without time zone DEFAULT now() NOT NULL,
    some_string_3 character varying,
    metadata jsonb DEFAULT '{}'::jsonb NOT NULL,
    some_string_4 character varying,
    active boolean DEFAULT true NOT NULL,
    customer_id bigint,
    some_string_5 character varying,
    transaction_type integer DEFAULT 0 NOT NULL,
    some_string_6 character varying,
    some_boolean_1 boolean DEFAULT false NOT NULL,
    country_code character varying(2) DEFAULT 'FR'::character varying,
    some_boolean_2 boolean DEFAULT true NOT NULL,
    some_timestamp_1 timestamp without time zone,
    some_timestamp_2 timestamp without time zone,
    some_array integer[] DEFAULT '{}'::integer[],
    some_string_7 character varying,
    some_count integer,
    some_other_integer integer,
    some_boolean_3 boolean DEFAULT true NOT NULL,
    some_string_8 character varying,
    some_timestamp_3 timestamp without time zone,
    some_boolean_4 boolean DEFAULT false NOT NULL,
    program_id bigint,
    some_string_9 character varying,
    developer_id bigint,
    some_boolean_4 boolean DEFAULT false,
    some_boolean_5 boolean DEFAULT false NOT NULL,
    CONSTRAINT countrycode CHECK (((country_code)::text ~* '[A-Z][A-Z]'::text))
);

Как вы можете видеть, обратных вызовов Rails нет. «Странности» - мы используем postgres столбец JSONB и postgres столбец целочисленного массива.

Теперь у нас есть маршрут обновления администратора для редактирования некоторых из этих записей. Он не следует соглашениям о присвоении имен и не использует активные проверки записей, но dry-validations:

require 'dry-validation'

module Admin
  class NewRealEstatesController < Admin::BaseController
    before_action :set_form, only: [:edit, :update]

    def update
      validation_result = AdminRealEstateSchema.call(real_estate_listing_params.to_h)
      if validation_result.success?
        # Don't mind column mappings with the dry schema validator, there might be inconsistencies
       # because I anonymized the column names, but the mapping is correct and validations pass
        @estate.some_string_1 =  params[:real_estate][:some_string_1]
        @estate.some_string_2 = params[:real_estate][:some_string_2]
        @estate.some_string_3 = params[:real_estate][:some_string_3]
        @estate.property_category = params[:real_estate][:property_category]
        @estate.some_string_4 = params[:real_estate][:some_string_4]
        @estate.price = params[:real_estate][:price]
        @estate.some_string_5 = params[:real_estate][:some_string_5]
        @estate.active = params[:real_estate][:active]
        @estate.some_timestamp_1 = params[:real_estate][:some_timestamp_1]
        @estate.some_timestamp_2 = params[:real_estate][:some_timestamp_2]
        @estate.some_count = params[:real_estate][:some_count]
        @estate.surface = params[:real_estate][:surface]
        @estate.some_boolean_1 = params[:real_estate][:some_boolean_1]
        @estate.some_boolean_2 = params[:real_estate][:some_boolean_2]
        res = @estate.save
        render json: { result: 'done', debug: { res_save_ar: res, changed: @estate.saved_changes?, \
          changes: @estate.saved_changes, previous_changes: @estate.previous_changes } }, status: 200
      else
        respond_to do |format|
          format.html { render :edit }
          format.json { render json: { errors: validation_result.errors.map{|key, error| "#{key} : #{error[0]}"} },  \
            status: :unprocessable_entity, serializer: nil }
        end
      end
    end

    def edit; end

    private 

    def set_form
      @estate = RealEstate.find(params[:id])
    end

    def real_estate_listing_params
      params[:real_estate_listing].permit(...) # all strong params edited in route, it's correct
    end
  end
end

AdminRealEstateSchema = Dry::Validation.Form do
  configure { config.type_specs = true, config.messages = :i18n }
  PROPERTY_CATEGORIES = %w(house apartment other)
  required(:property_category, :string).filled(:str?, included_in?: PROPERTY_CATEGORIES)
  required(:some_string_1, :string).filled(:str?)
  required(:some_string_2, :string).filled
  optional(:some_string_3).maybe(:str?)
  optional(:some_string_4).maybe(:str?)
  optional(:some_string_4).maybe(:str?)
  optional(:some_string_5).maybe(:str?)
  required(:price, :int).filled(:int?)
  optional(:some_other_int).maybe(:int?)
  optional(:some_count).maybe(:int?)
  required(:active, :bool).filled(:bool?)
  required(:some_boolean_1, :bool).filled(:bool?)
  required(:some_boolean_2, :bool).filled(:bool?)
  required(:some_time_1, :date_time).filled(:date_time?)
  optional(:some_time_2).maybe(:date_time?)
end

Представление правильное и используется remote: true.

Теперь о проблеме: иногда, но без согласованности, запись сохраняется и обновляется благодаря форме, иногда абсолютно не , как откат изменений, хотя save возвращает true, как видно из возвращенного JSON в веб-консоли.

ActiveRecord также правильно и отображает все столбцы, которые должны быть обновлены / сохранены с правильными значениями, как видно из @estate.saved_changes

Rspe c проверяет все проходы. Но только в процессе производства мы сталкиваемся с этой ошибкой.

Теперь отметим несколько важных моментов:

  • Мы уже сталкивались с ошибкой create_or_find_by через несколько месяцев go. Невозможно правильно создавать записи. Из-за крайних сроков я прибегнул к raw SQL через ActiveRecord::Base.connection.execute, и он работает.
  • Эта таблица очень активна, с большим количеством фоновых заданий для вставки / чтения данных от клиентов.
  • Там в этой таблице нет пользовательских триггеров postgres.

Я впадаю в отчаяние и думаю, что это может быть ошибка в нашем коде (очевидно) или ошибка в activerecord.

Есть идеи или мысли?

...