Class: Bureaucrat::Forms::Form

Inherits:
Object
  • Object
show all
Includes:
Utils
Defined in:
lib/bureaucrat/forms.rb

Overview

Base class for forms. Forms are a collection of fields with data that knows how to render and validate itself.

Bound vs Unbound forms

A form is ‘bound’ if it was initialized with a set of data for its fields, otherwise it is ‘unbound’. Only bound forms can be validated. Unbound forms always respond with false to valid? and return an empty list of errors.

Direct Known Subclasses

Bureaucrat::Formsets::ManagementForm

Constant Summary

Constants included from Utils

Utils::ESCAPES

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils

#blank_value?, #conditional_escape, #escape, #flatatt, #format_string, #make_bool, #make_float, #mark_safe, #pretty_name

Constructor Details

#initialize(data = nil, options = {}) ⇒ Form

Instantiates a new form bound to the passed data (or unbound if data is nil)

data is a hash of => value for this form to be bound (will be unbound if nil)

Possible options are:

:prefix          prefix that will be used for fields when rendered
:auto_id         format string that will be used when generating
                 field ids (default: 'id_%s')
:initial         hash of {field_name => default_value}
                 (doesn't make a form bound)
:error_class     class used to represent errors (default: ErrorList)
:label_suffix    suffix string that will be appended to labels' text
                 (default: ':')
:empty_permitted boolean value that specifies if this form is valid
                 when empty


206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/bureaucrat/forms.rb', line 206

def initialize(data=nil, options={})
  @is_bound = !data.nil?
  @data = StringAccessHash.new(data || {})
  @files = options.fetch(:files, {})
  @auto_id = options.fetch(:auto_id, 'id_%s')
  @prefix = options[:prefix]
  @initial = StringAccessHash.new(options.fetch(:initial, {}))
  @error_class = options.fetch(:error_class, Fields::ErrorList)
  @label_suffix = options.fetch(:label_suffix, ':')
  @empty_permitted = options.fetch(:empty_permitted, false)
  @errors = nil
  @changed_data = nil

  @fields = self.class.base_fields.dup
  @fields.each { |key, value| @fields[key] = value.dup }
end

Instance Attribute Details

#auto_idObject

Format string for field id generator



174
175
176
# File 'lib/bureaucrat/forms.rb', line 174

def auto_id
  @auto_id
end

#cleaned_dataObject

Validated and cleaned data



182
183
184
# File 'lib/bureaucrat/forms.rb', line 182

def cleaned_data
  @cleaned_data
end

#dataObject

Data associated to this form => value



178
179
180
# File 'lib/bureaucrat/forms.rb', line 178

def data
  @data
end

#error_classObject

Error object class for this form



168
169
170
# File 'lib/bureaucrat/forms.rb', line 168

def error_class
  @error_class
end

#error_css_classObject

Required class for this form



172
173
174
# File 'lib/bureaucrat/forms.rb', line 172

def error_css_class
  @error_css_class
end

#fieldsObject

Fields belonging to this form



184
185
186
# File 'lib/bureaucrat/forms.rb', line 184

def fields
  @fields
end

#filesObject

TODO: complete implementation



180
181
182
# File 'lib/bureaucrat/forms.rb', line 180

def files
  @files
end

#initialObject

Hash of => initial_value



176
177
178
# File 'lib/bureaucrat/forms.rb', line 176

def initial
  @initial
end

#required_css_classObject

Required class for this form



170
171
172
# File 'lib/bureaucrat/forms.rb', line 170

def required_css_class
  @required_css_class
end

Class Method Details

.base_fieldsObject

Fields associated to the form class



152
153
154
# File 'lib/bureaucrat/forms.rb', line 152

def self.base_fields
  @base_fields ||= {}
end

.field(name, field_obj) ⇒ Object

Declares a named field to be used on this form.



157
158
159
# File 'lib/bureaucrat/forms.rb', line 157

def self.field(name, field_obj)
  base_fields[name] = field_obj
end

.inherited(c) ⇒ Object

Copy data to the child class



162
163
164
165
# File 'lib/bureaucrat/forms.rb', line 162

def self.inherited(c)
  super(c)
  c.instance_variable_set(:@base_fields, base_fields.dup)
end

Instance Method Details

#[](name) ⇒ Object

Access a named field



231
232
233
234
# File 'lib/bureaucrat/forms.rb', line 231

def [](name)
  field = @fields[name] or return nil
  BoundField.new(self, field, name)
end

#add_initial_prefix(field_name) ⇒ Object

Generates an initial-prefix for field named field_name



253
254
255
# File 'lib/bureaucrat/forms.rb', line 253

def add_initial_prefix(field_name)
  "initial-#{add_prefix(field_name)}"
end

#add_prefix(field_name) ⇒ Object

Generates a prefix for field named field_name



248
249
250
# File 'lib/bureaucrat/forms.rb', line 248

def add_prefix(field_name)
  @prefix ? "#{@prefix}-#{field_name}" : field_name
end

#bound?Boolean

Checks if this form was initialized with data.

Returns:

  • (Boolean)


187
# File 'lib/bureaucrat/forms.rb', line 187

def bound? ; @is_bound; end

#changed?Boolean

true if the form has data that isn’t equal to its initial data

Returns:

  • (Boolean)


314
315
316
# File 'lib/bureaucrat/forms.rb', line 314

def changed?
  changed_data && !changed_data.empty?
end

#changed_dataObject

List names for fields that have changed data



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/bureaucrat/forms.rb', line 319

def changed_data
  if @changed_data.nil?
    @changed_data = []

    @fields.each do |name, field|
      prefixed_name = add_prefix(name)
      data_value = field.widget.
        value_from_formdata(@data, prefixed_name)

      if !field.show_hidden_initial
        initial_value = @initial.fetch(name, field.initial)
      else
        initial_prefixed_name = add_initial_prefix(name)
        hidden_widget = field.hidden_widget.new
        initial_value = hidden_widget.
          value_from_formdata(@data, initial_prefixed_name)
      end

      @changed_data << name if
        field.widget.has_changed?(initial_value, data_value)
    end
  end

  @changed_data
end

#cleanObject

Performs the last step of validations on the form, override in subclasses to customize behaviour.



309
310
311
# File 'lib/bureaucrat/forms.rb', line 309

def clean
  @cleaned_data
end

#eachObject

Iterates over the fields



224
225
226
227
228
# File 'lib/bureaucrat/forms.rb', line 224

def each
  @fields.each do |name, field|
    yield BoundField.new(self, field, name)
  end
end

#empty_permitted?Boolean

true if the form is valid when empty

Returns:

  • (Boolean)


258
259
260
# File 'lib/bureaucrat/forms.rb', line 258

def empty_permitted?
  @empty_permitted
end

#errorsObject

Errors for this forms (runs validations)



237
238
239
240
# File 'lib/bureaucrat/forms.rb', line 237

def errors
  full_clean if @errors.nil?
  @errors
end

#full_cleanObject

Runs all the validations for this form. If the form is invalid the list of errors is populated, if it is valid, cleaned_data is populated



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/bureaucrat/forms.rb', line 270

def full_clean
  @errors = Fields::ErrorHash.new

  return unless bound?

  @cleaned_data = StringAccessHash.new

  return if empty_permitted? && !changed?

  @fields.each do |name, field|
    value = field.widget.
      value_from_formdata(@data, add_prefix(name))

    begin
      if field.is_a?(Fields::FileField)
        initial = @initial.fetch(name, field.initial)
        @cleaned_data[name] = field.clean(value, initial)
      else
        @cleaned_data[name] = field.clean(value)
      end

      clean_method = 'clean_%s' % name
      @cleaned_data[name] = send(clean_method) if respond_to?(clean_method)
    rescue ValidationError => e
      @errors[name] = e.messages
      @cleaned_data.delete(name)
    end
  end

  begin
    @cleaned_data = clean
  rescue ValidationError => e
    @errors[:__NON_FIELD_ERRORS] = e.messages
  end
  @cleaned_data = nil if @errors && !@errors.empty?
end

#hidden_fieldsObject

List of hidden fields.



352
353
354
# File 'lib/bureaucrat/forms.rb', line 352

def hidden_fields
  @fields.select {|f| f.hidden?}
end

#label_attributes(name, field) ⇒ Object

Attributes for labels, override in subclasses to customize behaviour



362
363
364
# File 'lib/bureaucrat/forms.rb', line 362

def label_attributes(name, field)
  {}
end

#multipart?Boolean

true if this form contains fields that require the form to be multipart

Returns:

  • (Boolean)


347
348
349
# File 'lib/bureaucrat/forms.rb', line 347

def multipart?
  @fields.any? {|f| f.widget.multipart_form?}
end

#non_field_errorsObject

Returns the list of errors that aren’t associated to a specific field



263
264
265
# File 'lib/bureaucrat/forms.rb', line 263

def non_field_errors
  errors.fetch(:__NON_FIELD_ERRORS, @error_class.new)
end

#populate_object(object) ⇒ Object

Populates the passed object’s attributes with data from the fields



367
368
369
370
371
# File 'lib/bureaucrat/forms.rb', line 367

def populate_object(object)
  @fields.each do |name, field|
    field.populate_object(object, name, @cleaned_data[name])
  end
end

#valid?Boolean

Perform validation and returns true if there are no errors

Returns:

  • (Boolean)


243
244
245
# File 'lib/bureaucrat/forms.rb', line 243

def valid?
  @is_bound && (errors.nil? || errors.empty?)
end

#visible_fieldsObject

List of visible fields



357
358
359
# File 'lib/bureaucrat/forms.rb', line 357

def visible_fields
  @fields.select {|f| !f.hidden?}
end