Class: CustomField
- 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
Constant Summary collapse
- SAFE_DB_TRANSITIONS =
{ any: [%w[date time timestamp], %w[integer float]], one: { 'string' => 'text' } }
Constants inherited from Field
Instance Method Summary collapse
- #available_as ⇒ Object
-
#custom_validator(obj) ⇒ Object
Extra validation that is called on this field when validation happens obj is reference to parent object ——————————————————————————.
Methods inherited from Field
#collection_string, #collection_string=, #column_type, field_types, #input_options, lookup_class, register, #render, #render_value
Instance Method Details
#available_as ⇒ Object
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 |