module CustomAttributes module ActsAsCustomField extend ActiveSupport::Concern included do end module ClassMethods def acts_as_custom_field(_options = {}) include CustomAttributes::ActsAsCustomField::InstanceMethods scope :sorted, -> { order(:position) } serialize :possible_values attr_accessor :edit_tag_style validates_numericality_of :min_length, :only_integer => true, :greater_than_or_equal_to => 0 validates_numericality_of :max_length, :only_integer => true, :greater_than_or_equal_to => 0 validates_presence_of :name, :field_type validates_uniqueness_of :name, scope: :model_type validates_length_of :name, maximum: 30 validates_inclusion_of :field_type, in: proc { CustomAttributes::FieldType.available_types.map { |ft| ft.name.gsub(/CustomAttributes::|FieldType/, '') } } validate :validate_custom_field before_create do |field| field.set_slug end before_save do |field| field.type.before_custom_field_save(field) end end end module InstanceMethods def type @type ||= CustomAttributes::FieldType.find(field_type) end # Called upon Customizable Model validation # Actual validation handled by FieldType def validate_custom_value(custom_value) value = custom_value.value errs = type.validate_custom_value(custom_value) unless errs.any? if value.is_a?(Array) errs << ::I18n.t('activerecord.errors.messages.invalid') unless multiple? if is_required? && value.detect(&:present?).nil? errs << ::I18n.t('activerecord.errors.messages.blank') end else if is_required? && value.blank? errs << ::I18n.t('activerecord.errors.messages.blank') end end end errs end # Returns possible *options* that are determined by the *FieldType* # Don't mistake for possible_values, which is a CustomField specific dynamic setting def possible_custom_value_options(custom_value) type.possible_custom_value_options(custom_value) end # Validate the CustomField according to type rules and check if the selected default value # is indeed a valid value for this field def validate_custom_field if type.nil? errors.add :default, ::I18n.t('activerecord.errors.messages.invalid_type') return end type.validate_custom_field(self).each do |attribute, message| errors.add attribute, message end if default.present? validate_field_value(default).each do |message| errors.add :default, message end end end # Helper function used in validate_custom_field def validate_field_value(value) validate_custom_value(CustomAttributes::CustomFieldValue.new(custom_field: self, value: value)) end # Helper function to check if a value is a valid value for this field def valid_field_value?(value) validate_field_value(value).empty? end # Used to set the value of CustomFieldValue. No database persistance happening. # A convenient way to override how values are being parsed via FieldType def set_custom_field_value(custom_field_value, value) type.set_custom_field_value(self, custom_field_value, value) end # Called after CustomValue has been saved # Overrideable through FieldType def after_save_custom_value(custom_value) type.after_save_custom_value(self, custom_value) end # Serializer for possible values attribute def possible_values values = read_attribute(:possible_values) if values.is_a?(Array) values.each do |value| value.to_s.force_encoding('UTF-8') end values else [] end end # Returns the value in type specific form (Integer, Float, ...) def cast_value(value) type.cast_value(self, value) end # Finds a value in a field that has predefined possible values. # Returns array of values if the field supports multiple values # Comma delimited keywords possible def value_from_keyword(keyword, customized) type.value_from_keyword(self, keyword, customized) end def field_type=(arg) # cannot change type of a saved custom field if new_record? @type = nil super end end def possible_values=(arg) if arg.is_a?(Array) values = arg.compact.map { |a| a.to_s.strip }.reject(&:blank?) write_attribute(:possible_values, values) else self.possible_values = arg.to_s.split(/[\n\r]+/) end end protected def set_slug self.slug = create_slug end def create_slug(iterator = 0) new_slug = name.strip.gsub(/([^A-Za-z0-9])+/) { '_' }.downcase new_slug = "#{new_slug}_#{iterator}" unless iterator == 0 new_slug = create_slug(iterator += 1) unless CustomField.where(slug: new_slug).count == 0 new_slug end end end end