Активные админ и динамические c аксессоров хранилища не работают на новом ресурсе - PullRequest
1 голос
/ 06 мая 2020

Я хочу сгенерировать формы для ресурса, имеющего postgres jsonb column: data, и хочу, чтобы схема этих форм хранилась в таблице в базе данных. После долгих исследований я нахожусь на 90%, но мой метод не работает в формах ActiveAdmin при создании (не обновлении). Кто-нибудь может это объяснить?

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

Я следую этому предыдущему обсуждению с Rails 6 и ActiveAdmin 2.6.1 и ruby 2.6.5.

Я хочу сохранить Json схемы в таблице SampleActionSchema, которые принадлежат к SampleAction (используя гем json -schema для проверки)

class SampleActionSchema < ApplicationRecord
  validates :category, uniqueness: { case_sensitive: false }, allow_nil: false, allow_blank: true
  validate :schema_is_json_schema

  private

  def schema_is_json_schema
    metaschema = JSON::Validator.validator_for_name("draft4").metaschema
    unless JSON::Validator.validate(metaschema, schema)
       errors.add :schema, 'not a compliant json schema'
    end
  end
end
class SampleAction < ActiveRecord::Base
  belongs_to :sample

  validate :is_sample_action
  validates :name, uniqueness: { case_sensitive: false }

  after_initialize :add_field_accessors
  before_create :add_field_accessors
  before_update :add_field_accessors

  def add_store_accessor field_name
    singleton_class.class_eval {store_accessor :data, field_name.to_sym}
  end

  def add_field_accessors
    num_fields = schema_properties.try(:keys).try(:count) || 0
    schema_properties.keys.each {|field_name| add_store_accessor field_name} if num_fields > 0
  end


  def schema_properties

    schema_arr=SampleActionSchema.where(category: category)
    if schema_arr.size>0
      sc=schema_arr[0]
      if !sc.schema.empty?
        props=sc.schema["properties"]
      else
        props=[]
      end
    else
      []
    end
  end
  private

  def is_sample_action
    sa=SampleActionSchema.where(category: category)
    errors.add :category, 'not a known sample action' unless (sa.size>0)
    errors.add :base, 'incorrect json format' unless (sa.size>0) && JSON::Validator.validate(sa[0].schema, data)
  end

end

Все работает корректно; Например, для простой схемы с именем category: "cleave", где: data выглядит как data: {quality: "good"}, я могу создать ресурс в консоли rails следующим образом:

sa=SampleAction.new(sample_id: 6, name: "test0", data: {}, category: "cleave" )
=> #<SampleAction id: nil, name: "test0", category: "cleave", data: {}, created_at: nil, updated_at: nil, sample_id: 6> 

sa.quality = "good"   => true
sa.save => true

Чтобы эта система работала в Формы AA, я вызываю обычный путь (новый или редактируемый) _admix_sample_action_form с params: {category: "cleave"}, а затем динамически генерирую allow_params:

ActiveAdmin.register SampleAction, namespace: :admix do

  permit_params do
    prms=[:name, :category, :data, :sample_id, :created_at, :updated_at]
    #the first case is creating a new record (gets parameter from admix/sample_actions/new?category="xxx"
    #the second case is updating an existing record
    #falls back to blank (no extra parameters)
    categ = @_params[:category] || (@_params[:sample_action][:category] if @_params[:sample_action]) || nil
    cat=SampleActionSchema.where(category: categ)
    if cat.size>0 && !cat[0].schema.empty?
      cat[0].schema["properties"].each do |key, value|
        prms+=[key.to_sym]
      end
    end
    prms
  end

form do |f|
    f.semantic_errors
    new=f.object.new_record?
    cat=params[:category] || f.object.category
    f.object.category=cat if cat && new
    f.object.add_field_accessors if new
    sas=SampleActionSchema.where(category: cat)
    is_schema=(sas.size>0) && !sas[0].schema.empty?
    if session[:active_sample]
      f.object.sample_id=session[:active_sample]
    end

    f.inputs "Sample Action" do
      f.input :sample_id
      f.input :name
      f.input :category
      if !is_schema
        f.input :data, as: :jsonb
      else
        f.object.schema_properties.each do |key, value|
        f.input key.to_sym, as: :string
        end
      end
    end
    f.actions
  end

Все работает нормально, если я редактирую существующий ресурс (как создано в консоли выше). Форма отображается, и все поля Dynami c обновляются при отправке. Но при создании нового ресурса, где, например: данные имеют форму data: {quality: "good"}, я получаю

ActiveModel::UnknownAttributeError in Admix::SampleActionsController#create
unknown attribute 'quality' for SampleAction.

Я попытался как add_accessors в форме, так и переопределить новую команду, чтобы добавить аксессоры после инициализации (они не нужны, потому что обратный вызов ActiveRecord, кажется, выполняет работу в нужное время).

def new
  build_resource
  resource.add_field_accessors
  new!
end

Каким-то образом, когда ресурс создается в контроллере AA, кажется невозможным сохранить аксессоры, даже если они нормально работают в консоли. У кого-нибудь есть стратегия по правильной инициализации ресурса?

1 Ответ

0 голосов
/ 07 мая 2020

РЕШЕНИЕ:

Я проследил, что делает AA, чтобы вычислить минимальное количество необходимых команд. Необходимо было добавить код в build_new_resource, чтобы гарантировать, что любой новый созданный ресурс AA имеет правильное поле: category, и после этого сделать вызов для динамического добавления ключей store_accessor во вновь созданный экземпляр.

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

Здесь есть пара уродливых решений, одно из них состоит в том, что добавление параметров к активному админскому вызову нового маршрута не ожидается от AA, но оно по-прежнему работает. Я предполагаю, что этот параметр можно было бы передать другим способом, но быстро и грязно. Другой заключается в том, что мне пришлось заставить форму генерировать переменную сеанса для хранения того, какая схема была использована, чтобы сборка после отправки формы знала, поскольку нажатие кнопки «Создать перемещение» очищает параметры из URL-адреса. .

Операции следующие: для модели под названием Move with field: данные, которые должны быть динамически сериализованы в поля в соответствии с таблицами схемы json, оба admin/moves/new?category="cleave" и admin/moves/#/edit находят "расщепление" "схему из таблицы схемы и правильно создать и заполнить форму сериализованными параметрами. И прямая запись в db

m=Move.new(category: "cleave")          ==> true
m.update(name: "t2", quality: "fine")   ==> true

работает должным образом. Таблица схемы определяется как:

require "json-schema"
class SampleActionSchema < ApplicationRecord
  validates :category, uniqueness: { case_sensitive: false }, allow_nil: false, allow_blank: true
  validate :schema_is_json_schema

  def self.schema_keys(categ)
    sas=SampleActionSchema.find_by(category: categ)
    schema_keys= sas.nil? ? [] : sas[:schema]["properties"].keys.map{|k| k.to_sym}
  end

  private

  def schema_is_json_schema
    metaschema = JSON::Validator.validator_for_name("draft4").metaschema
    unless JSON::Validator.validate(metaschema, schema)
       errors.add :schema, 'not a compliant json schema'
    end
  end
end

Таблица перемещения, в которой используется эта схема:

class Move < ApplicationRecord
  after_initialize :add_field_accessors

  def add_field_accessors
    if category!=""
      keys=SampleActionSchema.schema_keys(category)
      keys.each {|k| singleton_class.class_eval{store_accessor :data, k}}
    end
  end
end

Наконец, рабочий контроллер:

ActiveAdmin.register Move do
  permit_params do
    #choice 1 is for new records, choice 2 is for editing existing
    categ = @_params[:category] || (@_params[:move][:category] if @_params[:move]) || ""
    keys=SampleActionSchema.schema_keys(categ)
    prms = [:name, :data] + keys
  end

  form do |f|
    new=f.object.new_record?
    f.object.category=params[:category] if new
    if new
      session[:current_category]=params[:category]
      f.object.add_field_accessors
    else
      session[:current_category] = ""
    end
    keys=SampleActionSchema.schema_keys(f.object.category)
    f.inputs do
      f.input :name
      f.input :category
      keys.each {|k| f.input k}
    end
    f.actions
  end

  controller do
   def build_new_resource
    r=super
    r.assign_attributes(category: session[:current_category])
    r.add_field_accessors
    r
   end
  end
end
...