Module: DynamicFormHelper

Defined in:
app/helpers/dynamic_form_helper.rb

Overview

Instance Method Summary collapse

Instance Method Details

#error_messages_for(*params) ⇒ String?

Renders a Bootstrap callout summarizing validation errors for the given objects.

Parameters:

  • params (Array<Object, Symbol>)

    model instances or instance variable names,
    with an optional trailing options hash

  • options (Hash)

    a customizable set of options

Returns:

  • (String, nil)

    HTML error summary, or nil when there are no errors



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'app/helpers/dynamic_form_helper.rb', line 13

def error_messages_for(*params)
  options = params.extract_options!.symbolize_keys
  options[:header_message] = false
  objects = Array.wrap(options.delete(:object) || params).map do |object|
    unless object.is_a?(ActiveRecord::Associations::CollectionProxy)
      object = instance_variable_get(:"@#{object}") unless object.respond_to?(:to_model)
      object = convert_to_model(object)

      options[:object_name] ||= object.class.model_name.human.downcase if object.class.respond_to?(:model_name)
    end
    object
  end

  objects = objects.flatten.compact
  count = objects.reduce(0) { |sum, object| sum + object.errors.count }

  return if count.zero?

  options[:object_name] ||= params.first

  I18n.with_options locale: options[:locale], scope: %i[errors template] do |locale|
    header_message = if options.include?(:header_message)
                       options[:header_message]
                     else
                       locale.t :header, count: count, model: options[:object_name].to_s.tr('_', ' ')
                     end

    message = options.include?(:message) ? options[:message] : locale.t(:body)
    error_messages = objects.flat_map { |object| object.errors.full_messages }.uniq

    # Use CalloutComponent for consistent styling with flash messages.
    #
    # `data-error-summary` is the hook the global turbo:submit-end handler
    # (client/js/crm/setup/turbo_config.js) uses to scroll the page back to
    # this summary after a 422 form submission, so a user who clicked Save
    # at the bottom of a long form actually sees that validation failed.
    #
    # The inline `scroll-margin-top` clears the fixed CRM navbar so the
    # alert lands fully visible — the browser honours scroll-margin-top
    # during scrollIntoView().
    render Railsboot::CalloutComponent.new(
      variant: 'danger',
      style: 'scroll-margin-top: 6rem',
      data: { 'error-summary': true }
    ) do
      (:div, class: 'd-flex gap-3') do
        (:div, class: 'flex-shrink-0') do
          fa_icon('octagon-exclamation', family: 'fa-sharp fa-solid', class: 'text-danger')
        end +
          (:div, class: 'flex-grow-1') do
            contents = []
            contents << (:h4, header_message) if header_message.present?
            contents << (:p, raw(message)) if message.present?
            contents << (:ul, error_messages.map { |msg| (:li, raw(msg)) }.join.html_safe, style: 'margin-top:0.5em')
            contents.join.html_safe
          end +
          (:div, class: 'flex-shrink-0') do
            tag.button(type: 'button', class: 'btn-close', onclick: "this.closest('.callout').style.display='none'", 'aria-label': 'Close')
          end
      end
    end
  end
end