module PIM::Validations

Constants

DEFAULT_CONTEXT_VARIABLE_NAME

Public Instance Methods

all_rules_by_attribute(attribute, *tags) click to toggle source
# File pim.rb, line 7681
def all_rules_by_attribute attribute, *tags
  rule_names = []
  rule_names.concat(@module_rules_by_attribute[attribute]) if @module_rules_by_attribute
  add_parent_objects(rule_names) { |p| p.module_rules_by_attribute[attribute] }
  rule_names.empty? ? {} : filter_rules_by_tags(all_rules_by_names(*rule_names), *tags)
end
all_rules_by_dependent_attribute(attribute, *tags) click to toggle source
# File pim.rb, line 7688
def all_rules_by_dependent_attribute attribute, *tags
  rule_names = []
  rule_names.concat(@module_rules_by_dependent_attribute[attribute]) if @module_rules_by_dependent_attribute
  add_parent_objects(rule_names) { |p| p.module_rules_by_dependent_attribute[attribute] }
  rule_names.empty? ? {} : filter_rules_by_tags(all_rules_by_names(*rule_names), *tags)
end
all_rules_by_names(*names) click to toggle source
# File pim.rb, line 7708
def all_rules_by_names *names
  rules = {}
  rules.merge!(module_rules_by_names(*names))
  add_parent_objects(rules) { |p| p.module_rules_by_names(*names) }
  rules
end
all_rules_by_params(*params) click to toggle source
# File pim.rb, line 7701
def all_rules_by_params *params
  rules = {}
  rules.merge!(module_rules_by_params(*params))
  add_parent_objects(rules) { |p| p.module_rules_by_params(*params) }
  rules
end
attribute_rule(name, *attributes, &block) click to toggle source
# File pim.rb, line 7670
def attribute_rule name, *attributes, &block
  raise "No attributes specified for rule '#{name}'" if attributes.empty?
  validation name, *attributes, &block
end
create_validation_rule(validation_rule_hash) click to toggle source
# File pim.rb, line 7635
def create_validation_rule validation_rule_hash

  name = PIM.get_value(validation_rule_hash, :name).to_sym

  attributes = PIM.get_value(validation_rule_hash, :validated_attributes, :attributes)
  dependent_attributes = PIM.get_value(validation_rule_hash, :dependent_attributes)
  tags = PIM.get_value(validation_rule_hash, :tags)

  disabled_block = PIM.get_value(validation_rule_hash, :disabled_block)
  validation_block = PIM.get_value(validation_rule_hash, :validation_block)

  # Create validation_block, unless specified
  if validation_block.nil?

    context_variable_name = PIM.get_value(validation_rule_hash, :ruby_code_context_variable_name) || DEFAULT_CONTEXT_VARIABLE_NAME
    ruby_code = PIM.get_value(validation_rule_hash, :ruby_code)
    raise "Neither validation block nor ruby code specified for validation rule '#{name}'"  if ruby_code.nil?

    validation_block = eval("proc { |#{context_variable_name}| #{ruby_code} }")

  end

  opts = {
    attributes: attributes,
    dependent_attributes: dependent_attributes,
    tags: tags,
    disabled_block: disabled_block
  }
  validation_rule = PIM::ValidationRule.new(name, opts, &validation_block)
  add_validation_rule validation_rule

  validation_rule

end
exists_validation_rule?(validation_rule) click to toggle source
# File pim.rb, line 7631
def exists_validation_rule? validation_rule
  validation_rule and all_rules_by_names.has_key?(to_sym(validation_rule))
end
filter_rules_by_tags(rules, *tags) click to toggle source
# File pim.rb, line 7695
def filter_rules_by_tags rules, *tags
  return rules if tags.empty?
  tags = stringified_array(tags).to_set
  rules.select { |k, v| PIM.is_empty?(v.tags) || v.tags.any? { |t| tags.include?(t.to_s) } }
end
key_location(key_hash) click to toggle source
# File pim.rb, line 7961
def key_location key_hash
  result = ""
  key_hash.each_pair do |k, v|

    next if !v

    attribute = attribute(k)
    label = attribute.label

    result << ", " unless result.empty?
    result << label << " " << "'" << v.to_s << "'"

  end
  result
end
location_msg(path) click to toggle source
# File pim.rb, line 7927
def location_msg path

  if path.length > 1

    child_attribute = attribute(path.last)
    label = child_attribute.label
    location_msg = "In attribute '#{label}'"

    if path.length > 2

      parent_attribute = attribute(path.first)
      path_index = path[1]

      if parent_attribute.base_class == Array
        index = path_index.to_i
        location_msg << ", row #{index+1}"
      elsif parent_attribute.base_class == Hash
        key = path_index.to_s
        location_msg << ", key '#{key}'"
      elsif is_hash?(path_index)
        key_location = key_location(path_index)
        location_msg << ", #{key_location}"
      end

    end

    location_msg << ": "

  else
    ""
  end

end
module_rules_by_attribute() click to toggle source
# File pim.rb, line 7977
def module_rules_by_attribute
  @module_rules_by_attribute ||= Hash.new { |k,v| k[v] = [] }
end
module_rules_by_dependent_attribute() click to toggle source
# File pim.rb, line 7981
def module_rules_by_dependent_attribute
  @module_rules_by_dependent_attribute ||= Hash.new { |k,v| k[v] = [] }
end
module_rules_by_name() click to toggle source
# File pim.rb, line 8007
def module_rules_by_name
  @module_rules_by_name ||= {}
end
module_rules_by_names(*names) click to toggle source
# File pim.rb, line 7998
def module_rules_by_names *names
  if @module_rules_by_name
    rules = @module_rules_by_name.select { |k,v| names.empty? or names.include?(k) }
  else
    rules = {}
  end
  rules
end
module_rules_by_param() click to toggle source
# File pim.rb, line 7994
def module_rules_by_param
  @module_rules_by_param ||= {}
end
module_rules_by_params(*params) click to toggle source
# File pim.rb, line 7985
def module_rules_by_params *params
  if @module_rules_by_param
    rules = @module_rules_by_param.select { |k,v| params.empty? or params.include?(k) }
  else
    rules = {}
  end
  rules
end
param_rule(name, &block) click to toggle source
# File pim.rb, line 7675
def param_rule name, &block
  raise "Validation rule for parameter '#{name}' is already defined" if module_rules_by_param.has_key?(name)
  rule = PIM::ValidationRule.new(name, &block)
  module_rules_by_param[name] = rule
end
validate(old_item, new_item, *attributes) click to toggle source
# File pim.rb, line 7820
def validate old_item, new_item, *attributes

  PIM::Utils.timed(group: "validation",
                   key: "validate-initialize",
                   message: "Initialization point 'validation'") do
    initialization_point :validation
  end if has_initialization_point?(:validation)

  # Copy input values
  old_item = symbolized_hash(old_item) if old_item
  new_item = symbolized_hash(new_item) if new_item
  attributes = symbolized_array(attributes || []).sort

  result = PIM::ValidationResult.new

  # Determine the item's category
  category_value = (new_item[:category__]) if new_item
  if !category_value

    message = "The item does not have a category specified"
    if not data_module.has_option?(:ignore_no_category)
      result.fail PIM::ValidationRule.new(:no_category), PIM::ValidationLevel::ERROR, [:category__], message
    else
      PIM.log_debug(message)
    end
    category = nil

  else

    # Check if category exists
    category = category(category_value)
    if !category
      message = "The item's category '#{category_value}' is not defined in the data model"
      params = { :err_val => category_value }
      result.fail PIM::ValidationRule.new(:undefined_category), PIM::ValidationLevel::ERROR, [:category__], message, params
    end

  end

  # FIXME: No category and no attribute to validate => Validate all attributes in item

  # No category and no attributes specified => Nothing to do here
  if !category && is_empty?(attributes)
    return result
  end

  context = {
    category: category,
    old_values: old_item,
    unparsed_values: new_item,
    parsed_values: {},
    attributes: {},
    cache: {},
    additional_attribute_categories: {},
    parsed_attributes: Set.new(),
    updated_attributes: Set.new(attributes.sort),
    undefined_attributes: Set.new(attributes.sort)
  }

  # 'Super' hash to return parsed value or ensure value gets parsed first
  context[:values] = Hash.new do |hash, key|

    key = PIM.to_sym(key)

    # Ensure value is parsed
    parsed_values = context[:parsed_values]
    if parsed_values.key?(key)
      value = parsed_values[key]
    else
      if parse_attribute_value(result, key, context, check_regular: false)
        value = parsed_values[key]
      end
    end
    value

  end

  # Determine tags of service validations to load and apply depending on category
  unless category.nil?
    validation_tags_context = PIM::ValidationTagsContext.new(self, result, context)
    validation_tags = category.calculate_validation_tags(validation_tags_context)
    context[:validation_tags] = validation_tags unless validation_tags.nil? or validation_tags.empty?
  end

  PIM::Utils.timed(group: "validation",
                   key: "execute_module_validation-#{data_module}",
                   message: "Execute full validation on data module #{data_module}") do
    execute_module_validation result, data_module, context
  end

  # Add validation error for all "undefined" attributes, i.e. attributes not defined in any data model!
  undefined_attributes = context[:undefined_attributes]
  undefined_attributes.sort.each do |attribute_name|
    next if is_meta_attribute?(attribute_name)
    message = "Attribute '#{attribute_name}' is not defined in module '#{data_module.name}' and will not be validated"
    if not data_module.has_option?(:ignore_undefined_attributes)
      params = { :err_val => attribute_name }
      result.fail PIM::ValidationRule.new(:undefined_attribute), PIM::ValidationLevel::ERROR, [attribute_name], message, params
    else
      PIM.log_debug(message)
    end
  end

  return result

end
validate_items(*items) click to toggle source
# File pim.rb, line 7715
def validate_items *items

  validation_results = []
  return validation_results if items.empty?

  PIM::Utils.timed(group: "validation",
                   key: "validate-initialize",
                   message: "Initialization point 'validation'") do
    initialization_point :validation
  end if has_initialization_point?(:validation)

  # Get json item cache and ensure the size is big enough for a reasonable number of more items
  item_cache = data_module.item_cache(type: :json)
  item_cache.min_size = items.length * 1.5

  # Ensure items are in a proper JSON format, i.e. NOT symbolize
  items = items.map { |item| PIM.unsymbolized_hash(item) }

  # Add all items to local cache
  item_primary_keys = Set.new
  items.each do |item|
    primary_key = get_value(item, :primaryKey__)
    unless primary_key.nil?
      item_primary_keys << primary_key
      item_cache[primary_key] = item
    end
  end

  # Check if more items should be pre-loaded
  if data_module.has_option?(:preload_referenced_items)

    # Ensure cache is big enough
    max_number_of_items =
      data_module.get_option(:preload_referenced_items_size,
                             PIM::DataModel::PRELOAD_REFERENCED_ITEMS_DEFAULT_SIZE)
    max_number_of_items = max_number_of_items.clamp(0, PIM::DataModel::PRELOAD_REFERENCED_ITEMS_MAX_SIZE)
    item_cache.min_size = (items.length * 1.5) + max_number_of_items

    # Set format to preload items in
    item_format = data_module.get_option(:preload_referenced_items_format, :json)

    # Load queried items ONLY if format is NOT json, because json items are already loaded and cached!
    load_queried_items = item_format.to_s != 'json'

    # Preload referenced items for specified or all hierarchies
    hierarchy_names = data_module.get_option(:preload_referenced_items_hierarchy_names, nil)
    preloaded_items = PIM::Services::ItemService.get_items_referenced_by_item_hierarchies(*item_primary_keys,
                                                                                          format: item_format,
                                                                                          hierarchy_names: hierarchy_names,
                                                                                          load_queried_items: load_queried_items,
                                                                                          max_number_of_items: max_number_of_items)
    PIM.log_debug "Preloaded #{preloaded_items.size} #{item_format} item(s) into cache"

  end

  items.each do |item|

    # Validate item
    validation_result = validate(item, item)
    validation_results << validation_result

    # Set attribute states
    attribute_states = []
    validation_result.states_by_attribute_name.each_pair do |attribute, states|
      states.each do |state|
        attribute_states << "#{attribute}:#{state}"
      end
    end
    item['attributeStates__'] = attribute_states

    # Set validation errors and warnings
    validation_errors = []
    validation_warnings = []
    validation_result.messages_by_attribute_name.each_pair do |attribute, messages|
      messages.each do |message|
        validation = "#{attribute}:#{message.name}"
        if message.level == ValidationLevel::ERROR or message.level == ValidationLevel::FAILURE
          validation_errors << validation
        elsif message.level == ValidationLevel::WARNING
          validation_warnings << validation
        end
      end
    end
    item['validation__'] = validation_errors
    item['validation_warnings__'] = validation_warnings

    # Set calculated values
    validation_result.calculated_values_by_attribute_name.each do |attribute, value|
      item[attribute.to_s] = PIM::Utils.as_json(value)
    end

    # Update other item validation system properties
    # NOTE: We are NOT updating checksum__, updatedAt__, updatedBy__, validatedAt__ or history__!
    item['validation_dirty__'] = false
    item['compliant__'] = PIM.is_empty?(item['validation__'])

    # Update item in local cache
    primary_key = item['primaryKey__']
    item_cache[primary_key] = item unless primary_key.nil?

  end

  return validation_results
end
validation(name, *attributes, &block) click to toggle source
# File pim.rb, line 7612
def validation name, *attributes, &block

  name = to_sym(name)
  if not block.nil?

    validation_rule = PIM::ValidationRuleBuilder.build_validation_rule(data_module, name, *attributes, &block)
    add_validation_rule validation_rule

  elsif not attributes.empty?
    raise "No block specified for validation rule '#{name}'"
  else
    validation_rule = all_rules_by_names(name).first
  end

  validation_rule

end
Also aliased as: validation_rule
validation_rule(name, *attributes, &block)
Alias for: validation