3scale/porta

View on GitHub
app/models/fields_definition.rb

Summary

Maintainability
A
0 mins
Test Coverage
class FieldsDefinition < ApplicationRecord

  DEFAULT_LABELS = {
    "org_name" => "Organization/Group Name",
    "org_legaladdress" => "Legal Address"
  }.freeze


  @targets = []
  class << self
    attr_reader :targets
  end

  def self.push_target(klass)
    @targets << klass.to_s
  end

  serialize :choices, Array

  belongs_to :account, :inverse_of => :fields_definitions
  acts_as_list :scope => %i[account_id target], :column => :pos

  scope :by_provider,  ->(provider) { where(['account_id = ?', provider.id]) }
  scope :by_target, ->(class_name) { where(['target = ?', class_name])}
  scope :by_name, ->(name) { where(['name = ?', name])}
  scope :required, -> { where({ :required => true })}

  def self.editable_by(user)
    select{ |fd| fd.editable_by?(user) }
  end

  before_validation :set_required_if_required_field_on_target

  validates :label, :target, :name, presence: true, length: { maximum: 255 }
  validates :name, format: { :with =>/\A[A-Za-z][A-Za-z\d_-]*\z/, :message => "Name should start with letters, and can contain numbers, - and _" }
  validates :name, uniqueness: { scope: [:account_id, :target], case_sensitive: true }
  validates :name, :exclusion => Fields::ExtraField::InputField.excludes
  validates :choices, :hint, length: { maximum: 65535 }

  validate :allowed_fields_definition_name, :check_base_validations, :read_only_billing_address, if: :target?
  validate :only_choices_for_text, if: %i[target? choices?]

  before_create :set_last_position_in_target_scope

  before_destroy :avoid_destroy_required_field_on_target

  default_scope { by_position }
  scope :by_position, -> { order(:pos) }

  attr_protected :account_id, :tenant_id
  attr_readonly :account_id, :tenant_id, :target, :name

  alias_attribute :position, :pos

  # This smells of :reek:NestedIterators: FieldsDefinition#self.create_defaults! contains iterators nested 2 deep
  def self.create_defaults!(account)
    targets.each do |target|
      klass = target.constantize
      klass.default_fields.each do |field|
        label = DEFAULT_LABELS[field] || field.humanize
        required = klass.required_fields.include? field
        account.fields_definitions.create!({target: target, name: field, label: label, required: required })
      end
    end
  end

  def editable_by?(user)
    acc = user.try!(:account)
    # if self.account == acc
    if acc && !acc.buyer?
      true
    else
      not read_only? and not hidden?
    end
  end

  def targets
    self.class.targets
  end

  def target=(t)
    self[:target] = t if targets.include?(t)
  end

  def visible_for?(user)
    if self.account != user.account
      !self.hidden?
    else
      true
    end
  end

  def target_class
    return if target.nil?

    target.constantize
  end

  def required_field_on_target?
    target_class.required_fields.include?(name)
  end

  def choices_for_views=(values)
    values = values.to_s
    split_values = values.split(values.include?("\n") ? /\n/ : /\,/)
    self[:choices] = split_values.map(&:strip).presence
  end

  def choices_for_views
    views_choices = Array(self[:choices])
    join_by = views_choices.any? { |c| c.include?(',') } ? "\n" : ", "
    views_choices.join(join_by).presence
  end

  private

  def target?
    target.present?
  end

  def choices?
    choices.any?
  end

  def set_last_position_in_target_scope
    fields_for_target = self.account.fields_definitions.where(target: target)
    max_position = fields_for_target.maximum(:pos) || 0
    self.position = max_position + 1
  end

  def allowed_fields_definition_name
    return if target.nil? # the target presence validation already caught this

    forbidden = ( (target_class.new.attributes.keys| target_class.column_names) -
                  target_class.builtin_fields)
    if forbidden.include?(name)
      errors.add(:name, "Field name is not allowed")
    end
  end

  def check_base_validations
    if self.required? && (self.hidden? || self.read_only?)
      errors.add(:required,  "Fields cannot be required AND hidden/read_only")
    end
  end

  def only_choices_for_text
    return unless target_class.builtin_fields.include?(name) # It's allowed by 3scale and in DB

    column_name_attribute = target_class.columns_hash[name]
    return if %i[string text].include?(column_name_attribute&.type)

    # It's not a text attribute.
    errors.add(:choices,  "are not allowed for #{column_name_attribute&.type.presence || 'this type of'} fields")
  end

  def set_required_if_required_field_on_target
    return unless target?

    if required_field_on_target?
      self.required  = true
      self.hidden    = false
      self.read_only = false
    end

    true
  end

  def read_only_billing_address
    if target_class == Account && name == 'billing_address' && !self.read_only?
      errors.add(:read_only, "billing_address has to be read_only")
    end
  end

  def avoid_destroy_required_field_on_target
    throw :abort if required_field_on_target?
  end

end