File: form_helper.rb

package info (click to toggle)
ruby-client-side-validations 23.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 272 kB
  • sloc: ruby: 788; javascript: 671; makefile: 2
file content (141 lines) | stat: -rw-r--r-- 5,030 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# frozen_string_literal: true

module ClientSideValidations
  module ActionView
    module Helpers
      module FormHelper
        class Error < StandardError
        end

        def form_for(record, options = {}, &block)
          return super unless options[:validate]

          # We are not going to use super here, because we need
          # to inject the csv options in a data attribute in a clean way.
          # So we basically reimplement the whole form_for method
          raise ArgumentError, 'Missing block' unless block

          options[:html] ||= {}

          # Moving the switch statement to another method to
          # lower complexity
          model, object_name = check_record(record, options)

          remote = options.delete(:remote)

          if remote && !embed_authenticity_token_in_remote_forms && options[:authenticity_token].blank?
            options[:authenticity_token] = false
          end

          options[:model] = model
          options[:scope] = object_name
          options[:local] = !remote
          options[:skip_default_ids] = false
          options[:allow_method_names_outside_object] = options.fetch(:allow_method_names_outside_object, false)

          form_with(**options, &block)
        end

        def apply_csv_form_for_options!(object, options)
          options[:html][:validate] = true if options[:validate]
          options[:html][:method] ||= options[:method]

          apply_form_for_options! object, options
        end

        def fields_for(record_name, record_object = nil, options = {}, &)
          # Order matters here. Rails mutates the `options` object
          builder = instantiate_builder(record_name, record_object, options)
          output = capture(builder, &)

          build_bound_validators! options

          output
        end

        private

        def check_record(record, options)
          case record
          when String, Symbol
            raise ClientSideValidations::ActionView::Helpers::FormHelper::Error, 'Using form_for(:name, @resource) is not supported with ClientSideValidations. Please use form_for(@resource, as: :name) instead.'
          else
            object = record.is_a?(Array) ? record.last : record
            raise ArgumentError, 'First argument in form cannot contain nil or be empty' unless object

            object_name = options[:as] || model_name_from_record_or_class(object).param_key
            apply_csv_form_for_options!(object, options)
          end

          [record, object_name]
        end

        def build_bound_validators!(options)
          return unless @validators

          options[:validators].each do |key, value|
            if @validators.key?(key)
              @validators[key].merge! value
            else
              @validators[key] = value
            end
          end
        end

        def construct_validators
          @validators.each_with_object({}) do |object_opts, validator_hash|
            next unless object_opts[0].respond_to?(:client_side_validation_hash)

            option_hash = object_opts[1].each_with_object({}) do |attr, result|
              result[attr[0]] = attr[1][:options]
            end

            validation_hash = object_opts[0].client_side_validation_hash(option_hash)

            option_hash.each_key do |attr|
              add_validator validator_hash, validation_hash, object_opts[1][attr][:name], attr
            end
          end
        end

        def add_validator(validator_hash, validation_hash, name, attr)
          if validation_hash.key?(attr)
            validator_hash[name] = validation_hash[attr]
          elsif attr.to_s.end_with?('_id')
            association_name = attr.to_s.gsub(/_id\Z/, '').to_sym
            add_validator_with_association validator_hash, validation_hash, name, association_name
          elsif attr.to_s.end_with?('_ids')
            association_name = attr.to_s.gsub(/_ids\Z/, '').pluralize.to_sym
            add_validator_with_association validator_hash, validation_hash, name, association_name
          end
        end

        def add_validator_with_association(validator_hash, validation_hash, name, association_name)
          return unless validation_hash.key?(association_name)

          validator_hash[name] = validation_hash[association_name]
        end

        def number_format
          if ClientSideValidations::Config.number_format_with_locale && defined?(I18n)
            I18n.t('number.format').slice :separator, :delimiter
          else
            { separator: '.', delimiter: ',' }
          end
        end

        def apply_csv_html_options!(html_options, options, builder)
          html_options.delete 'validate'

          csv_options = {
            html_settings: builder.client_side_form_settings(options, self),
            number_format: number_format,
            validators:    construct_validators
          }

          html_options['data-client-side-validations'] = csv_options.to_json
        end
      end
    end
  end
end