Class: CustomField

Inherits:
Field
  • Object
show all
Defined in:
app/models/fields/custom_field.rb

Overview

Implementation Notes


Adding database columns dynamically is not usually recommended, and can be potentially unsafe. However, the only alternative would be static migrations, and we want to let users add custom fields without having to restart their server. We could not use the EAV model for performance reasons, since we need to retain the ability to filter and search across thousands of records with potentially hundreds of custom fields.

We have solved all of the major issues:

  • Custom fields for view templates are dynamically generated based on normalized database records in the ‘fields’ table, instead of ActiveRecord’s cached attributes.

  • Concurrency issues are resolved by using ‘method_missing’ to refresh a model’s column information when fields are added by a separate server process.

  • The custom field can be renamed or deleted, but database columns are never renamed or destroyed. A rake task can be run to purge any orphaned columns.

  • Custom field types can only be changed if the database can support the transition. For example, you can change an ‘email’ field to a ‘string’, but not to a ‘datetime’, since changing the type of the database column would cause data to be lost.

Direct Known Subclasses

CustomFieldPair

Constant Summary collapse

SAFE_DB_TRANSITIONS =
{
  any: [%w[date time timestamp], %w[integer float]],
  one: { 'string' => 'text' }
}

Constants inherited from Field

Field::BASE_FIELD_TYPES

Instance Method Summary collapse

Methods inherited from Field

#collection_string, #collection_string=, #column_type, field_types, #input_options, lookup_class, register, #render, #render_value

Instance Method Details

#available_asObject



64
65
66
67
68
# File 'app/models/fields/custom_field.rb', line 64

def available_as
  Field.field_types.reject do |new_type, _params|
    db_transition_safety(as, new_type) == :unsafe
  end
end

#custom_validator(obj) ⇒ Object

Extra validation that is called on this field when validation happens obj is reference to parent object




73
74
75
76
77
78
# File 'app/models/fields/custom_field.rb', line 73

def custom_validator(obj)
  attr = name.to_sym
  obj.errors.add(attr, ::I18n.t('activerecord.errors.models.custom_field.required', field: label)) if required? && obj.send(attr).blank?
  obj.errors.add(attr, ::I18n.t('activerecord.errors.models.custom_field.minlength', field: label)) if (minlength.to_i > 0) && (obj.send(attr).to_s.length < minlength.to_i)
  obj.errors.add(attr, ::I18n.t('activerecord.errors.models.custom_field.maxlength', field: label)) if (maxlength.to_i > 0) && (obj.send(attr).to_s.length > maxlength.to_i)
end