app/models/extended_field.rb

Summary

Maintainability
A
2 hrs
Test Coverage
class ExtendedField < ActiveRecord::Base
  include ExtendedFieldsHelpers

  # Choices/enumerations
  has_many :choice_mappings, as: :field
  has_many :choices, through: :choice_mappings

  # find an extended field based on params[:extended_field]
  def self.from_id_or_label(id_or_label)
    where('UPPER(label) = ?', id_or_label.upcase.tr('_', ' ')).first || find_by_id(id_or_label)
  end

  # James - 2008-12-05
  # Ensure attributes that when changed could be potentially destructive on existing data cannot
  # be changed after the initial save.
  # When sufficient testing and conversion code as been added to handle all events relating to changing
  # these fields, this attributes can be made writeable again.
  attr_readonly :label, :ftype, :multiple

  def setting(name, *args)
    ExtendedFieldSettings.get(name, *args)
  end

  # some input mechanisms for different languages can add whitespace
  # which messes with our label_for_params, etc.
  before_save :strip_extra_spaces_from_label

  after_save :store_topic_type

  def topic_type
    @topic_type ||= setting(:topic_type)
  end

  def topic_type=(value)
    @topic_type = value
  end

  def store_topic_type
    set_setting(:topic_type, @topic_type) unless @topic_type.blank?
  end

  after_save :store_circa

  def circa
    @circa ||= setting(:circa)
  end

  def circa?
    circa && circa.param_to_obj_equiv
  end

  def circa=(value)
    @circa = value
  end

  def store_circa
    set_setting(:circa, @circa) unless @circa.blank?
  end

  after_save :set_base_url

  def base_url
    # @base_url ||= self.setting(:base_url)
    # ROB:  Turning this off as it doesn't make sense to use absolute links (kete.co.nz/...)
    #       instead of relative onves (/...)
    ''
  end

  def base_url=(value)
    @base_url = value
  end

  def set_base_url
    set_setting(:base_url, @base_url) unless @base_url.blank?
  end

  def pseudo_choices
    choices.collect { |c| [c.label, c.id] }
  end

  def pseudo_choices=(array_of_ids)
    logger.debug "ARRAY_OF_IDS = #{array_of_ids.inspect}"
    self.choices = []
    self.choices = array_of_ids.collect { |id| Choice.find(id) }
  end

  has_many :topic_type_to_field_mappings, dependent: :destroy
  # if we ever use this association, we'll want to add a test for it
  has_many :topic_type_forms, through: :topic_type_to_field_mappings, source: :topic_type, order: 'position'

  has_many :content_type_to_field_mappings, dependent: :destroy
  # if we ever use this association, we'll want to add a test for it
  has_many :content_type_forms, through: :content_type_to_field_mappings, source: :content_type, order: 'position'

  validates_presence_of :label
  validates_uniqueness_of :label, case_sensitive: false

  # don't allow special characters in label that will break our xml
  validates_format_of :label, with: /^[^\'\":<>\&,\/\\\?\.\-]*$/, message: lambda { I18n.t('extended_field_model.invalid_chars', invalid_chars: ": \', \\, /, &, \", ?, <, >, -, and .") }

  # don't allow spaces
  validates_format_of :xml_element_name, :xsi_type, with: /^[^\s]*$/, message: lambda { I18n.t('extended_field_model.no_spaces') }

  # TODO: add validation that prevents adding xsi_type without xml_element_name

  # don't allow topic or content base attributes: title, description
  invalid_label_names = TopicType.column_names + ContentType.column_names
  validates_exclusion_of :label, in: invalid_label_names, message: lambda { I18n.t('extended_field_model.already_used', invalid_label_names: invalid_label_names.join(', ')) }

  # TODO: might want to reconsider using subselects here
  def self.find_available_fields(type, type_of)
    if type_of == 'TopicType'
      # exclude ancestor's fields as well
      topic_types_to_exclude = type.ancestors + [type]
      where('id not in (select extended_field_id from topic_type_to_field_mappings where topic_type_id in (?))', topic_types_to_exclude).readonly(false).all
    elsif type_of == 'ContentType'
      where('id not in (select extended_field_id from content_type_to_field_mappings where content_type_id = ?)', type).readonly(false).all
    else
      # TODO: this is an error, say something meaningful
    end
  end

  def add_checkbox
    # used by a form of available fields where 0 is always going to be the starting value
    0
  end

  alias required_checkbox add_checkbox

  def label_for_params
    label.downcase.tr(' ', '_')
  end

  def self.clauses_for_has_label_that_matches(params_key)
    params_key_words = params_key.to_s.tr('_', ' ').split(' ')

    match_keyword = 'LIKE'
    label_sql = 'LOWER(label)'

    if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
      match_keyword = 'ILIKE'
      label_sql = 'label'
    end

    clauses = []
    if params_key_words.size == 1
      clauses << "#{label_sql} #{match_keyword} '#{params_key_words.first}'"
    else
      # could make this sensitive to first and last
      # and adjust use of % accordingly
      # but "and" seems sufficient
      clauses =
        params_key_words.collect do |w|
          "#{label_sql} #{match_keyword} '%#{w}%'"
        end
    end

    clauses.join(' AND ')
  end

  def self.params_to_label(params_key)
    where(clauses_for_has_label_that_matches(params_key)).first.label
  end

  def is_a_choice?
    ['autocomplete', 'choice'].include?(ftype)
  end

  def link_choice_values
    dont_link_choice_values.nil? || !dont_link_choice_values
  end
  alias link_choice_values? link_choice_values

  def link_choice_values=(value)
    self.dont_link_choice_values = !value.param_to_obj_equiv
  end

  def is_required?(controller, topic_type_id = nil)
    raise 'ERROR: You must specify a topic type id since controller is topics' if controller == 'topics' && topic_type_id.nil?
    if controller == 'topics'
      # we have to check the submitted topic_type or its ancestors
      topic_type = TopicType.find(topic_type_id)
      all_possible_topic_types = topic_type.ancestors + [topic_type]
      ef_mapping = topic_type_to_field_mappings.find_by_topic_type_id(all_possible_topic_types)
    else
      content_type = ContentType.find_by_controller(controller)
      ef_mapping = ContentTypeToFieldMapping.find_by_content_type_id_and_extended_field_id(content_type, self)
    end
    ef_mapping.required?
  end

  # turn pretty urls on or off here
  include FriendlyUrls
  alias to_param format_for_friendly_unicode_urls

  protected

  def strip_extra_spaces_from_label
    self.label = label.strip
  end

  def validate
    errors.add('label', I18n.t('extended_field_model.label_cant_have')) if label && label.strip =~ /^(form|input|script)$/i
  end
end