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

#action, #before_render, #comp_cst, comp_cst, comp_name, #comp_name, #content, family_cst, #family_cst, family_name, #family_name, #id, #inspect, #param_name, #path, #path_hash, #remove_content, #remove_content!, #render, #render_actions, #resourceful?, #root_comp, #root_comp?, setup, #skip_action, #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



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

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

#disable!Object

DSL method, disables all inputs



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

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.



158
159
160
161
# File 'lib/compony/components/form.rb', line 158

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.



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

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



53
54
55
56
# File 'lib/compony/components/form.rb', line 53

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



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

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.



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

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



232
233
234
235
236
237
238
239
# File 'lib/compony/components/form.rb', line 232

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



69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/compony/components/form.rb', line 69

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



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

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



227
228
229
# File 'lib/compony/components/form.rb', line 227

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.



182
183
184
# File 'lib/compony/components/form.rb', line 182

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.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/compony/components/form.rb', line 211

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



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

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



242
243
244
# File 'lib/compony/components/form.rb', line 242

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.



88
89
90
91
92
93
94
95
# File 'lib/compony/components/form.rb', line 88

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