Class: Compony::Components::Form

Inherits:
Compony::Component show all
Defined in:
lib/compony/components/form.rb

Overview

This component is used for the _form partial in the Rails paradigm.

Instance Attribute Summary

Attributes inherited from Compony::Component

#comp_opts, #content_blocks, #parent_comp

Instance Method Summary collapse

Methods inherited from Compony::Component

#before_render, comp_name, #content, #exposed_intents, family_name, #id, #id_path, #id_path_hash, #inspect, #param_name, #path, #remove_content, #remove_content!, #render, #resourceful?, #root_comp, #root_comp?, setup, #sub_comp

Constructor Details

#initialize(*args, cancancan_action: :missing, disabled: false, **kwargs) ⇒ Form

Returns a new instance of Form.



6
7
8
9
10
11
# File 'lib/compony/components/form.rb', line 6

def initialize(*args, cancancan_action: :missing, disabled: false, **kwargs)
  @schema_lines_for_data = [] # Array of procs taking data returning a Schemacop proc
  @cancancan_action = cancancan_action
  @form_disabled = disabled
  super
end

Instance Method Details

#collectObject

Quick access for wrapping collections in Rails compatible format



167
168
169
# File 'lib/compony/components/form.rb', line 167

def collect(...)
  Compony::ModelFields::Anchormodel.collect(...)
end

#disable!Object

DSL method, disables all inputs



172
173
174
# File 'lib/compony/components/form.rb', line 172

def disable!
  @form_disabled = true
end

#fObject

Called inside the form_fields block. This makes the method f available in the block. See also notes for with_simpleform.



161
162
163
164
# File 'lib/compony/components/form.rb', line 161

def f
  fail("The `f` method may only be called inside `form_fields` for #{inspect}.") unless @simpleform
  return @simpleform
end

#field(name, multilang: false, **input_opts) ⇒ Object

Called inside the form_fields block. This makes the method field available in the block. See also notes for with_simpleform. If multilang is true, a suffixed field is generated for every available locale (useful with gem "mobility"). Render the array as you wish.



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
# File 'lib/compony/components/form.rb', line 103

def field(name, multilang: false, **input_opts)
  fail("The `field` method may only be called inside `form_fields` for #{inspect}.") unless @simpleform

  if multilang
    I18n.available_locales.map { |locale| field("#{name}_#{locale}", **input_opts) }
  else
    name = name.to_sym

    input_opts.merge!(disabled: true) if @form_disabled

    # Check per-field authorization
    if @cancancan_action.present? && @controller.current_ability.permitted_attributes(@cancancan_action, @simpleform.object).exclude?(name)
      Rails.logger.debug do
        "Skipping form field #{name.inspect} because the current user is not allowed to perform #{@cancancan_action.inspect} on #{@simpleform.object}."
      end
      return
    end

    hidden = input_opts.delete(:hidden)
    model_field = @simpleform.object.fields[name]
    fail("Field #{name.inspect} is not defined on #{@simpleform.object.inspect} but was requested in #{inspect}.") unless model_field

    if hidden
      return model_field.simpleform_input_hidden(@simpleform, self, **input_opts)
    else
      unless @focus_given || @skip_autofocus
        input_opts[:autofocus] = true unless input_opts.key? :autofocus
        @focus_given = true
      end
      return model_field.simpleform_input(@simpleform, self, **input_opts)
    end
  end
end

#form_fields(&block) ⇒ Object

DSL method, use to set the form content



56
57
58
59
# File 'lib/compony/components/form.rb', line 56

def form_fields(&block)
  return @form_fields unless block_given?
  @form_fields = block
end

#form_params(**new_form_params) ⇒ Object

DSL method, allows to customize parameters given to simple_form_for



177
178
179
# File 'lib/compony/components/form.rb', line 177

def form_params(**new_form_params)
  @form_params = new_form_params
end

#pw_field(name, **input_opts) ⇒ Object

Called inside the form_fields block. This makes the method pw_field available in the block. This method should be called for the fields :password and :password_confirmation Note that :hidden is not supported here, as this would make no sense in conjunction with :password or :password_confirmation.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/compony/components/form.rb', line 140

def pw_field(name, **input_opts)
  fail("The `pw_field` method may only be called inside `form_fields` for #{inspect}.") unless @simpleform
  name = name.to_sym

  # Check for authorization
  unless @cancancan_action.nil? || @controller.current_ability.can?(:set_password, @simpleform.object)
    Rails.logger.debug do
      "Skipping form pw_field #{name.inspect} because the current user is not allowed to perform :set_password on #{@simpleform.object}."
    end
    return
  end

  unless @focus_given || @skip_autofocus
    input_opts[:autofocus] = true unless input_opts.key? :autofocus
    @focus_given = true
  end
  return @simpleform.input name, **input_opts
end

#schema(wrapper_key, &block) ⇒ Object (protected)

DSL method, use to replace the form's schema and wrapper key for a completely manual schema



235
236
237
238
239
240
241
242
# File 'lib/compony/components/form.rb', line 235

def schema(wrapper_key, &block)
  if block_given?
    @schema_wrapper_key = wrapper_key
    @schema_block = block
  else
    fail 'schema requires a block to be given'
  end
end

#schema_block_for(data, controller) ⇒ Object

Attr reader for @schema_block with auto-calculated default



72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/compony/components/form.rb', line 72

def schema_block_for(data, controller)
  if @schema_block
    return @schema_block
  else
    # If schema was not called, auto-infer a default
    local_schema_lines_for_data = @schema_lines_for_data
    return proc do
      local_schema_lines_for_data.each do |schema_line|
        schema_line_proc = schema_line.call(data, controller) # This may return nil, e.g. is the user is not authorized to set a field
        instance_exec(&schema_line_proc) unless schema_line_proc.nil?
      end
    end
  end
end

#schema_field(field_name, multilang: false) ⇒ Object (protected)

DSL method, adds a new field to the schema whitelisting a single field of data_class This auto-generates the correct schema line for the field. If multilang is true, a suffixed field is generated for every available locale (useful with gem "mobility")



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/compony/components/form.rb', line 192

def schema_field(field_name, multilang: false)
  if multilang
    I18n.available_locales.each { |locale| schema_field("#{field_name}_#{locale}") }
  else
    # This runs upon component setup.
    @schema_lines_for_data << proc do |data, controller|
      # This runs within a request context.
      field = data.class.fields[field_name.to_sym] || fail("No field #{field_name.to_sym.inspect} found for #{data.inspect} in #{inspect}.")
      # Check per-field authorization
      if @cancancan_action.present? && controller.current_ability.permitted_attributes(@cancancan_action.to_sym, data).exclude?(field.name.to_sym)
        Rails.logger.debug do
          "Skipping form schema_field #{field_name.inspect} because the current user is not allowed to perform #{@cancancan_action.inspect} on #{data}."
        end
        next nil
      end
      next field.schema_line
    end
  end
end

#schema_fields(*field_names) ⇒ Object (protected)

DSL method, mass-assigns schema fields



230
231
232
# File 'lib/compony/components/form.rb', line 230

def schema_fields(*field_names)
  field_names.each { |field_name| schema_field(field_name) }
end

#schema_line(&block) ⇒ Object (protected)

DSL method, adds a new line to the schema whitelisting a single param inside the schema's wrapper The block should be something like str? :foo and will run in a Schemacop3 context.



185
186
187
# File 'lib/compony/components/form.rb', line 185

def schema_line(&block)
  @schema_lines_for_data << proc { |_data, _controller| block }
end

#schema_pw_field(field_name) ⇒ Object (protected)

DSL method, adds a new password field to the schema whitelisting This checks for the permission :set_password and auto-generates the correct schema line for the field.



214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/compony/components/form.rb', line 214

def schema_pw_field(field_name)
  # This runs upon component setup.
  @schema_lines_for_data << proc do |data, controller|
    # This runs within a request context.
    # Check per-field authorization
    unless @cancancan_action.nil? || controller.current_ability.can?(:set_password, data)
      Rails.logger.debug do
        "Skipping form schema_pw_field #{field_name.inspect} because the current user is not allowed to perform :set_password on #{data}."
      end
      next nil
    end
    next proc { obj? field_name.to_sym }
  end
end

#schema_wrapper_key_for(data) ⇒ Object

Attr reader for @schema_wrapper_key with auto-calculated default



62
63
64
65
66
67
68
69
# File 'lib/compony/components/form.rb', line 62

def schema_wrapper_key_for(data)
  if @schema_wrapper_key.present?
    return @schema_wrapper_key
  else
    # If schema was not called, auto-infer a default
    data.model_name.singular
  end
end

#skip_autofocusObject (protected)

DSL method, skips adding autofocus to the first field



245
246
247
# File 'lib/compony/components/form.rb', line 245

def skip_autofocus
  @skip_autofocus = true
end

#with_simpleform(simpleform, controller) ⇒ Object

TODO:

Refactor? Could this be greatly simplified by having form_field to |f| ?

This method is used by render to store the simpleform instance inside the component such that we can call methods from inside form_fields. This is a workaround required because the form does not exist when the RequestContext is being built, and we want the method field to be available inside the form_fields block.



91
92
93
94
95
96
97
98
# File 'lib/compony/components/form.rb', line 91

def with_simpleform(simpleform, controller)
  @simpleform = simpleform
  @controller = controller
  @focus_given = false
  yield
  @simpleform = nil
  @controller = nil
end