class Onoma::Nomenclature

This class represents a nomenclature

Attributes

forest_right[RW]
items[R]
name[R]
notions[RW]
properties[R]
property_natures[R]
translateable[RW]

Public Class Methods

harvest(element, options = {}) click to toggle source
# File lib/onoma/nomenclature.rb, line 24
def harvest(element, options = {})
  notions = element.attr('notions').to_s.split(/\s*\,\s*/).map(&:to_sym)
  options[:notions] = notions if notions.any?
  options[:translateable] = element.attr('translateable').to_s != 'false'
  name = element.attr('name').to_s
  nomenclature = new(name, options)
  element.xpath('xmlns:properties/xmlns:property').each do |property|
    nomenclature.harvest_property(property)
  end
  element.xpath('xmlns:items/xmlns:item').each do |item|
    nomenclature.harvest_item(item)
  end
  nomenclature.list.each(&:fetch_parent)
  nomenclature.rebuild_tree!
  nomenclature
end
new(name, options = {}) click to toggle source

Instanciate a new nomenclature

# File lib/onoma/nomenclature.rb, line 12
def initialize(name, options = {})
  @name = name.to_sym
  @set = options.delete(:set)
  @items = HashWithIndifferentAccess.new
  @forest_right = 0
  @roots = []
  @properties = {}.with_indifferent_access
  @translateable = !options[:translateable].is_a?(FalseClass)
  @notions = options[:notions] || []
end

Public Instance Methods

<=>(other) click to toggle source
# File lib/onoma/nomenclature.rb, line 372
def <=>(other)
  name <=> other.name
end
[](item_name) click to toggle source

Returns the given item

# File lib/onoma/nomenclature.rb, line 353
def [](item_name)
  @items[item_name]
end
add_item(name, attributes = {}, options = {}) click to toggle source

Add an item to the nomenclature

# File lib/onoma/nomenclature.rb, line 161
def add_item(name, attributes = {}, options = {})
  i = Item.new(self, name, attributes)
  if @items[i.name]
    raise "Item #{i.name} is already defined in nomenclature #{@name}"
  end

  @items[i.name] = i
  @roots << i unless i.parent?
  i.rebuild_tree! unless options[:rebuild].is_a?(FalseClass)
  i
end
add_property(name, type, options = {}) click to toggle source

Add an property to the nomenclature

# File lib/onoma/nomenclature.rb, line 250
def add_property(name, type, options = {})
  p = PropertyNature.new(self, name, type, options)
  if @properties[p.name]
    raise "Property #{p.name} is already defined in nomenclature #{@name}"
  end

  @properties[p.name] = p
  @references = nil
  p
end
all(item_name = nil)
Alias for: to_a
best_match(property_name, searched_item) click to toggle source

Returns the best match on nomenclature properties

# File lib/onoma/nomenclature.rb, line 511
def best_match(property_name, searched_item)
  items = []
  begin
    list.select do |item|
      items << item if item.property(property_name) == searched_item.name
    end
    break if items.any?

    searched_item = searched_item.parent
  end while searched_item
  items
end
cascade_item_renaming(name, new_name) click to toggle source

name and new_name are Symbol

# File lib/onoma/nomenclature.rb, line 210
def cascade_item_renaming(name, new_name)
  @set.references.each do |reference|
    next unless reference.foreign_nomenclature == self

    p = reference.property
    if p.list?
      reference.nomenclature.find_each do |item|
        v = item.property(p.name)
        if v && v.include?(name)
          l = v.map do |n|
            n == name ? new_name : n
          end
          item.set(p.name, l)
        end
      end
    else
      reference.nomenclature.find_each do |item|
        v = item.property(p.name)
        item.set(p.name, new_name) if v == name
      end
    end
  end
end
cast_options(options) click to toggle source
# File lib/onoma/nomenclature.rb, line 529
def cast_options(options)
  return {} if options.nil?

  options.each_with_object({}) do |(k, v), h|
    h[k.to_sym] = if properties[k]
                    cast_property(k, v.to_s)
                  else
                    v
                  end
  end
end
cast_property(name, value) click to toggle source
# File lib/onoma/nomenclature.rb, line 541
def cast_property(name, value)
  value = value.to_s
  if property = properties[name]
    if property.type == :choice || property.type == :item
      if value =~ /\,/
        raise InvalidPropertyNature.new('A property nature of choice type cannot contain commas')
      end

      value = value.strip.to_sym
    elsif property.list?
      value = value.strip.split(/[[:space:]]*\,[[:space:]]*/).map(&:to_sym)
    elsif property.type == :boolean
      value = if value == 'true'
                true
              elsif value == 'false'
                false
              else
                nil
              end
    elsif property.type == :decimal
      value = BigDecimal(value)
    elsif property.type == :integer
      value = value.to_i
    elsif property.type == :date
      value = (value.blank? ? nil : Date.parse(value))
    elsif property.type == :symbol
      unless value =~ /\A\w+\z/
        raise InvalidPropertyNature.new("A property '#{name}' must contains a symbol. /[a-z0-9_]/ accepted. No spaces. Got #{value.inspect}")
      end

      value = value.to_sym
    end
  elsif !%w[name parent aliases].include?(name.to_s)
    raise ArgumentError.new("Undefined property '#{name}' in #{@name}")
  end
  value
end
change_item(name, changes = {}) click to toggle source

Add an item to the nomenclature

# File lib/onoma/nomenclature.rb, line 174
def change_item(name, changes = {})
  i = find!(name)
  has_parent = changes.key?(:parent)
  new_parent = changes[:parent]
  new_name = changes[:name]
  changes.each do |k, v|
    next if %i[parent name].include? k

    i.set(k, v)
  end
  if has_parent
    @roots << i if i.parent? && new_parent.nil?
    @roots.delete(i) if i.root? && new_parent
    i.parent = new_parent
  end
  i = rename_item(name, new_name) if new_name
  i
end
change_property(name, updates = {}) click to toggle source
# File lib/onoma/nomenclature.rb, line 261
def change_property(name, updates = {})
  property = property_natures[name]

  unless property
    raise "Property #{p.name} doesn't exist in nomenclature #{@name}"
  end

  updates.each do |k, v|
    begin
      property.send("#{k}=", v)
    rescue NoMethodError => e
      "#{k} attribute doesn't exist for property nature"
    end
  end
end
check!() click to toggle source
# File lib/onoma/nomenclature.rb, line 281
def check!
  # Check properties
  @properties.values.each do |property|
    if property.choices_nomenclature && !property.inline_choices? && !Onoma[property.choices_nomenclature.to_s]
      raise InvalidPropertyNature.new("[#{name}] #{property.name} nomenclature property must refer to an existing nomenclature. Got #{property.choices_nomenclature.inspect}. Expecting: #{Onoma.names.inspect}")
    end
    next unless property.type == :choice && property.default
    unless property.choices.include?(property.default)
      raise InvalidPropertyNature.new("The default choice #{property.default.inspect} is invalid (in #{name}##{property.name}). Pick one from #{property.choices.sort.inspect}.")
    end
  end

  # Check items
  list.each do |item|
    @properties.values.each do |property|
      choices = property.choices
      if item.property(property.name) && property.type == :choice
        # Cleans for parametric reference
        name = item.property(property.name).to_s.split(/\(/).first.to_sym
        unless choices.include?(name)
          raise InvalidProperty.new("The given choice #{name.inspect} is invalid (in #{self.name}##{item.name}). Pick one from #{choices.sort.inspect}.")
        end
      elsif item.property(property.name) && property.type == :list && property.choices_nomenclature
        (item.property(property.name) || []).each { |name|
          # Cleans for parametric reference
          name = name.to_s.split(/\(/).first.to_sym
          unless choices.include?(name)
            raise InvalidProperty.new("The given choice #{name.inspect} is invalid (in #{self.name}##{item.name}). Pick one from #{choices.sort.inspect}.")
          end
        }
      end
    end
  end

  # Default return
  true
end
default(item_name = nil) click to toggle source

Return the default item name

# File lib/onoma/nomenclature.rb, line 426
def default(item_name = nil)
  first(item_name)
end
degree_of_kinship(a, b) click to toggle source
# File lib/onoma/nomenclature.rb, line 416
def degree_of_kinship(a, b)
  a.degree_of_kinship_with(b)
end
dependency_index() click to toggle source
# File lib/onoma/nomenclature.rb, line 376
def dependency_index
  unless @dependency_index
    @dependency_index = 0
    properties.each do |_n, p|
      if p.choices_nomenclature && !p.inline_choices?
        @dependency_index += 1 + Onoma[p.choices_nomenclature].dependency_index
      end
    end
  end
  @dependency_index
end
exists?(item) click to toggle source

Returns true if an item exists in the nomenclature that matches the name, or false otherwise. The argument can take two forms:

* String/Symbol - Find an item with this primary name
* Onoma::Item - Find an item with the same name of the item
# File lib/onoma/nomenclature.rb, line 450
def exists?(item)
  @items[item.respond_to?(:name) ? item.name : item].present?
end
Also aliased as: include?
find(item_name) click to toggle source

Return the Item for the given name. Returns nil if no item found

# File lib/onoma/nomenclature.rb, line 431
def find(item_name)
  @items[item_name]
end
Also aliased as: item
find!(item_name) click to toggle source

Return the Item for the given name. Raises Onoma::ItemNotFound if no item found in nomenclature

# File lib/onoma/nomenclature.rb, line 439
def find!(item_name)
  i = find(item_name)
  raise ItemNotFound.new("Cannot find item #{item_name.inspect} in #{name}") unless i

  i
end
find_by(properties) click to toggle source
# File lib/onoma/nomenclature.rb, line 503
def find_by(properties)
  items = where(properties)
  return nil unless items.any?

  items.first
end
find_each(&block) click to toggle source

Iterates on items

# File lib/onoma/nomenclature.rb, line 466
def find_each(&block)
  list.each(&block)
end
first(item_name = nil) click to toggle source

Return first item name

# File lib/onoma/nomenclature.rb, line 421
def first(item_name = nil)
  all(item_name).first
end
harvest_item(element, attributes = {}) click to toggle source

Add an item to the nomenclature from an XML element

# File lib/onoma/nomenclature.rb, line 107
def harvest_item(element, attributes = {})
  name = element.attr('name').to_s
  parent = attributes[:parent] || (element.key?('parent') ? element['parent'] : nil)
  attributes = element.attributes.each_with_object(HashWithIndifferentAccess.new) do |(k, v), h|
    next if %w[name parent].include?(k)

    h[k] = cast_property(k, v.to_s)
  end
  attributes[:parent] = parent if parent
  item = add_item(name, attributes, rebuild: false)
  item
end
harvest_property(element) click to toggle source

Add an property to the nomenclature from an XML element

# File lib/onoma/nomenclature.rb, line 121
def harvest_property(element)
  name = element.attr('name').to_sym
  type = element.attr('type').to_sym
  options = {}
  if element.has_attribute?('fallbacks')
    options[:fallbacks] = element.attr('fallbacks').to_s.strip.split(/[[:space:]]*\,[[:space:]]*/).map(&:to_sym)
  end
  if element.has_attribute?('default')
    options[:default] = element.attr('default').to_sym
  end
  options[:required] = element.attr('required').to_s == 'true'
  # options[:inherit]  = !!(element.attr('inherit').to_s == 'true')
  if type == :list
    type = element.has_attribute?('nomenclature') ? :item_list : :choice_list
  elsif type == :choice
    type = :item if element.has_attribute?('nomenclature')
  end
  if type == :choice || type == :choice_list
    if element.has_attribute?('choices')
      options[:choices] = element.attr('choices').to_s.strip.split(/[[:space:]]*\,[[:space:]]*/).map(&:to_sym)
    else
      type = :string_list
    end
  elsif type == :item || type == :item_list
    if element.has_attribute?('choices')
      options[:choices] = element.attr('choices').to_s.strip.to_sym
    elsif element.has_attribute?('nomenclature')
      options[:choices] = element.attr('nomenclature').to_s.strip.to_sym
    else
      raise MissingChoices.new("[#{@name}] Property #{name} must have nomenclature as choices")
    end
  end
  unless Onoma::PROPERTY_TYPES.include?(type)
    raise ArgumentError.new("Property #{name} type is unknown: #{type.inspect}")
  end

  add_property(name, type, options)
end
human_name(options = {}) click to toggle source

Return human name

# File lib/onoma/nomenclature.rb, line 337
def human_name(options = {})
  I18n.t("nomenclatures.#{Onoma.escape_key(name)}.name", options.merge(default: ["labels.#{Onoma.escape_key(name)}".to_sym, name.to_s.humanize]))
end
Also aliased as: humanize
humanize(options = {})
Alias for: human_name
include?(item)
Alias for: exists?
inspect() click to toggle source
# File lib/onoma/nomenclature.rb, line 319
def inspect
  "Onoma::#{name.to_s.classify}"
end
item(item_name)
Alias for: find
list() click to toggle source

Returns list of items as an Array

# File lib/onoma/nomenclature.rb, line 461
def list
  Onoma::Relation.new(self, @items.values)
end
merge_item(name, into) click to toggle source
# File lib/onoma/nomenclature.rb, line 234
def merge_item(name, into)
  i = find!(name)
  dest = find!(into)
  i.children.each do |child|
    child.parent = dest
  end
  cascade_item_renaming(name.to_sym, into.to_sym)
  @items.delete(name)
end
method_missing(method_name, *args) click to toggle source

Returns property nature

Calls superclass method
# File lib/onoma/nomenclature.rb, line 525
def method_missing(method_name, *args)
  @properties[method_name] || super
end
name=(value) click to toggle source
# File lib/onoma/nomenclature.rb, line 46
def name=(value)
  @name = value.to_sym
end
new_boundaries(count = 2) click to toggle source
# File lib/onoma/nomenclature.rb, line 343
def new_boundaries(count = 2)
  boundaries = []
  count.times do
    @forest_right += 1
    boundaries << @forest_right
  end
  boundaries
end
property(property_name) click to toggle source
# File lib/onoma/nomenclature.rb, line 456
def property(property_name)
  @properties[property_name]
end
rebuild_tree!() click to toggle source

Build a nested set index on items Returns last right value

# File lib/onoma/nomenclature.rb, line 101
def rebuild_tree!
  @forest_right = 0
  roots.each(&:rebuild_tree!)
end
references() click to toggle source
# File lib/onoma/nomenclature.rb, line 56
def references
  unless @references
    @references = []
    properties.each do |_p, property|
      if property.item_reference?
        @references << Onoma::Reference.new(@set, property, @set.find(property.source), property.item_list? ? :array : :key)
      end
    end
  end
  @references
end
remove_item(name) click to toggle source
# File lib/onoma/nomenclature.rb, line 244
def remove_item(name)
  find!(name)
  @items.delete(name)
end
rename_item(name, new_name) click to toggle source
# File lib/onoma/nomenclature.rb, line 193
def rename_item(name, new_name)
  if @items[new_name]
    raise "Item #{new_name} is already defined in nomenclature #{@name}. Use merging instead."
  end

  i = find!(name)
  i.children.each do |child|
    child.parent_name = new_name
  end
  cascade_item_renaming(name.to_sym, new_name.to_sym)
  i = @items.delete(i.name)
  i.name = new_name
  @items[new_name] = i
  i
end
roots() click to toggle source
# File lib/onoma/nomenclature.rb, line 42
def roots
  @items.values.select(&:root?)
end
select(&block) click to toggle source
# File lib/onoma/nomenclature.rb, line 357
def select(&block)
  list.select(&block)
end
select_without(already_imported) click to toggle source

Returns a list for select, without specified items

# File lib/onoma/nomenclature.rb, line 403
def select_without(already_imported)
  ActiveSupport::Deprecation.warn 'Onoma::Nomenclature#select_without method is deprecated. Please use Onoma::Nomenclature#without method instead.'

  select_options = @items.values.collect do |item|
    [item.human_name, item.name.to_s] unless already_imported[item.name.to_s]
  end
  select_options.compact!
  select_options.sort! do |a, b|
    a.first <=> b.first
  end
  select_options
end
selection(item_name = nil) click to toggle source

Returns a list for select as an array of pair (array)

# File lib/onoma/nomenclature.rb, line 389
def selection(item_name = nil)
  items = (item_name ? find!(item_name).self_and_children : @items.values)
  items.map { |item| [item.human_name, item.name.to_s] }
       .sort { |a, b| a.first.lower_ascii <=> b.first.lower_ascii }
end
selection_hash(item_name = nil) click to toggle source

Returns a list for select as an array of pair (hash)

# File lib/onoma/nomenclature.rb, line 396
def selection_hash(item_name = nil)
  items = (item_name ? find!(item_name).self_and_children : @items.values)
  items.map { |item| { label: item.human_name, value: item.name } }
       .sort { |a, b| a[:label].lower_ascii <=> b[:label].lower_ascii }
end
sibling(name) click to toggle source
# File lib/onoma/nomenclature.rb, line 277
def sibling(name)
  @set.find(name)
end
table_name() click to toggle source
# File lib/onoma/nomenclature.rb, line 323
def table_name
  @name.to_s.pluralize
end
to_a(item_name = nil) click to toggle source

List all item names. Can filter on a given item name and its children

# File lib/onoma/nomenclature.rb, line 362
def to_a(item_name = nil)
  if item_name.present? && @items[item_name]
    @items[item_name].self_and_children.map(&:name)
  else
    @items.keys.sort
  end
end
Also aliased as: all
to_csv(file, _options = {}) click to toggle source

Write CSV file with all items an there properties

# File lib/onoma/nomenclature.rb, line 69
def to_csv(file, _options = {})
  properties = @properties.values
  CSV.open(file, 'wb') do |csv|
    csv << [:name] + properties.map do |p|
      suffix = if p.decimal?
                 ' D'
               elsif p.integer?
                 ' I'
               elsif p.boolean?
                 ' B'
               elsif p.item?
                 " R(#{p.choices_nomenclature})"
               else
                 ''
               end

      "#{p.name}#{suffix}"
    end
    @items.values.each do |i|
      csv << [i.name] + properties.map { |p| i.attributes[p.name] }
    end
  end
end
to_xml_attrs() click to toggle source
# File lib/onoma/nomenclature.rb, line 93
def to_xml_attrs
  attrs = { name: name, translateable: translateable.to_s }
  attrs[:notions] = @notions.join(', ') if @notions.any?
  attrs
end
translateable?() click to toggle source
# File lib/onoma/nomenclature.rb, line 332
def translateable?
  @translateable
end
tree() click to toggle source

Returns hash with items in tree: {a => nil, b => {c => nil}}

# File lib/onoma/nomenclature.rb, line 328
def tree
  @roots.collect(&:tree).join
end
update_attributes(attributes = {}) click to toggle source
# File lib/onoma/nomenclature.rb, line 50
def update_attributes(attributes = {})
  attributes.each do |attribute, value|
    send("#{attribute}=", value)
  end
end
where(properties, collection = list) click to toggle source

List items with properties filtering

# File lib/onoma/nomenclature.rb, line 471
def where(properties, collection = list)
  collection.select do |item|
    valid = true
    properties.each do |name, value|
      item_value = item.property(name)
      if value.is_a?(Array)
        one_found = false
        value.each do |val|
          if val.is_a?(Onoma::Item)
            one_found = true if item_value == val.name.to_sym
          elsif item_value == val
            one_found = true
          end
        end
        valid = false unless one_found
      elsif value.is_a?(Onoma::Item)
        valid = false unless item_value == value.name.to_sym
      else
        valid = false unless item_value == value
      end
    end
    valid
  end
end
without(*names) click to toggle source
# File lib/onoma/nomenclature.rb, line 496
def without(*names)
  excluded = names.flatten.compact.map(&:to_sym)
  list.reject do |item|
    excluded.include?(item.name.to_sym)
  end
end