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



170
171
172
# File 'lib/compony/components/form.rb', line 170

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

#disable!Object

DSL method, disables all inputs



175
176
177
# File 'lib/compony/components/form.rb', line 175

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.



164
165
166
167
# File 'lib/compony/components/form.rb', line 164

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.



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

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



59
60
61
62
# File 'lib/compony/components/form.rb', line 59

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



180
181
182
# File 'lib/compony/components/form.rb', line 180

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.



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

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



238
239
240
241
242
243
244
245
# File 'lib/compony/components/form.rb', line 238

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



75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/compony/components/form.rb', line 75

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")



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

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



233
234
235
# File 'lib/compony/components/form.rb', line 233

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.



188
189
190
# File 'lib/compony/components/form.rb', line 188

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.



217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/compony/components/form.rb', line 217

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



65
66
67
68
69
70
71
72
# File 'lib/compony/components/form.rb', line 65

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



248
249
250
# File 'lib/compony/components/form.rb', line 248

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.



94
95
96
97
98
99
100
101
# File 'lib/compony/components/form.rb', line 94

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