Проблема
HR-система для IT-стартапа и для септик-сервиса — это разные миры. Стартапу нужно поле "GitHub username", клинике — "Номер лицензии", транспортной компании — "Категория ADR-допуска".
Классический подход — миграция на каждое поле. Не масштабируется.
Решение: Dictionary с field_schema
class Dictionary < ApplicationRecord
# kind: :field_schema | :reference | :list
# entity_type: "Employee" | "JobOpening" | ...
# schema: JSONB — описание полей
scope :field_schemas_for, ->(entity) {
where(kind: :field_schema, entity_type: entity.class.name, company: Current.company)
}
end
Как выглядит schema
{
"fields": [
{
"key": "github_username",
"label": { "ru": "GitHub", "en": "GitHub" },
"type": "string",
"required": false,
"validation": "^[a-zA-Z0-9-]+$"
},
{
"key": "adr_category",
"label": { "ru": "Категория ADR", "en": "ADR Category" },
"type": "select",
"options": ["A", "B", "C", "D"],
"required": true
}
]
}
Рендеринг формы
ERB-partial _custom_fields_form.html.erb итерирует по schema и рендерит нужный input по type:
string→ text_fieldselect→ select с optionsdate→ date_fieldnumber→ number_fieldboolean→ check_boxtext→ text_area
Валидация
class CustomFieldsValidator < ActiveModel::Validator
def validate(record)
schema = Dictionary.field_schemas_for(record)
schema.each do |dict|
dict.parsed_fields.each do |field|
value = record.custom_data&.dig(field["key"])
if field["required"] && value.blank?
record.errors.add(:base, "#{field.dig('label', I18n.locale.to_s)} is required")
end
if field["validation"].present? && value.present?
unless value.match?(Regexp.new(field["validation"]))
record.errors.add(:base, "#{field.dig('label', I18n.locale.to_s)} format is invalid")
end
end
end
end
end
end
AI Bootstrap
company_bootstrap агент через чат спрашивает владельца: "Чем занимается компания? Какие должности? Какие документы нужны?" — и генерирует полную schema для всех сущностей.
Результат
- Один
custom_data JSONBстолбец вместо десятков миграций - HR настраивает поля через UI без разработчика
- Валидация и AI-разбор подхватывают schema автоматически
- Паттерн переиспользуется в Triage (custom fields per ticket type)
Комментарии (0)