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, **kwargs) ⇒ Form

Returns a new instance of Form.



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

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

Instance Method Details

#collectObject

Quick access for wrapping collections in Rails compatible format



154
155
156
# File 'lib/compony/components/form.rb', line 154

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

#fObject

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



148
149
150
151
# File 'lib/compony/components/form.rb', line 148

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

#field(name, **input_opts) ⇒ Object

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



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

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

  # 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

#form_fields(&block) ⇒ Object

DSL method, use to set the form content



51
52
53
54
# File 'lib/compony/components/form.rb', line 51

def form_fields(&block)
  return @form_fields unless block_given?
  @form_fields = block
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.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/compony/components/form.rb', line 127

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



207
208
209
210
211
212
213
214
# File 'lib/compony/components/form.rb', line 207

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



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

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) ⇒ 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.



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/compony/components/form.rb', line 168

def schema_field(field_name)
  # 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

#schema_fields(*field_names) ⇒ Object (protected)

DSL method, mass-assigns schema fields



202
203
204
# File 'lib/compony/components/form.rb', line 202

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.



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

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.



186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/compony/components/form.rb', line 186

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 #{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



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

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



217
218
219
# File 'lib/compony/components/form.rb', line 217

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.



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

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