lib/onoma/item.rb in onoma-0.0.0 vs lib/onoma/item.rb in onoma-0.1.0
- old
+ new
@@ -1,11 +1,11 @@
module Onoma
# An item of a nomenclature is the core data.
class Item
attr_reader :nomenclature, :left, :right, :depth, :aliases, :parent_name
attr_accessor :name, :attributes
- alias_method :properties, :attributes
+ alias properties attributes
# New item
def initialize(nomenclature, name, options = {})
@nomenclature = nomenclature
@name = name.to_s
@@ -16,10 +16,11 @@
@parent_name = parent.to_s
else
self.parent = parent
end
@attributes = ActiveSupport::HashWithIndifferentAccess.new
+ @children = Set.new
options.each do |k, v|
set(k, v)
end
end
@@ -27,29 +28,42 @@
!parent
end
def parent=(item)
old_parent_name = @parent_name
+ old_parent = @parent
if item.nil?
@parent = nil
@parent_name = nil
else
if item.is_a?(Symbol) || item.is_a?(String)
item = nomenclature.find!(item.to_s)
end
if item.nomenclature != nomenclature
- fail 'Item must come from same nomenclature'
+ raise 'Item must come from same nomenclature'
end
- if item.parents.include?(self) || item == self
- fail 'Circular dependency. Item can be parent of itself.'
+ if item.parents.any? { |p| self == p } || self == item
+ raise 'Circular dependency. Item can be parent of itself.'
end
@parent = item
@parent_name = @parent.name.to_s
end
- @nomenclature.rebuild_tree! if old_parent_name != @parent_name
+ if old_parent_name != @parent_name
+ old_parent.delete_child(self) if old_parent
+ @parent.add_child(self) if @parent
+ @nomenclature.rebuild_tree!
+ end
end
+ def add_child(item)
+ @children << item
+ end
+
+ def delete_child(item)
+ @children.delete(item)
+ end
+
# Changes parent without rebuilding
def parent_name=(name)
@parent = nil
@parent_name = name.to_s
end
@@ -57,48 +71,67 @@
def parent?
parent.present?
end
def parent
- @parent ||= @nomenclature.find(@parent_name)
+ return @parent if @parent
+ @parent = find_parent
+ @parent.add_child(self) if @parent
+ @parent
end
+ alias fetch_parent parent
+ def find_parent
+ @nomenclature.find(@parent_name)
+ end
+
+ def degree_of_kinship_with(other)
+ other_item = item_for_comparison(other)
+ a = self_and_parents.reverse
+ b = other_item.self_and_parents.reverse
+ return nil if a.first != b.first
+ common_lineage = 0
+ a.size.times do |index|
+ break if a[index].nil? || b[index].nil? || a[index] != b[index]
+ common_lineage += 1
+ end
+ a.size + b.size - 2 * common_lineage
+ end
+
def original_nomenclature_name
return parent.name.to_sym unless root?
nil
end
# Returns children recursively by default
def children(options = {})
if options[:index].is_a?(FalseClass)
if options[:recursively].is_a?(FalseClass)
- return nomenclature.list.select do |item|
- (item.parent == self)
- end
+ @children.to_a
else
- return children(index: false, recursive: false).each_with_object([]) do |item, list|
+ children(index: false, recursive: false).each_with_object([]) do |item, list|
list << item
list += item.children(index: false, recursive: true)
list
end
end
else
if options[:recursively].is_a?(FalseClass)
- return nomenclature.list.select do |item|
+ nomenclature.list.select do |item|
@left < item.left && item.right < @right && item.depth == @depth + 1
end
else
# @children ||=
- return nomenclature.list.select do |item|
+ nomenclature.list.select do |item|
@left < item.left && item.right < @right
end
end
end
end
def root
- self.parent? ? parent.root : self
+ parent? ? parent.root : self
end
# Returns direct parents from the closest to the farthest
def parents
@parents ||= (parent.nil? ? [] : [parent] + parent.parents)
@@ -110,10 +143,16 @@
def self_and_parents
[self] + parents
end
+ def rise(&block)
+ result = yield(self)
+ return result if result
+ parent ? parent.rise(&block) : nil
+ end
+
# Computes left/right value for nested set
# Returns right index
def rebuild_tree!
@nomenclature.forest_right = rebuild_tree(@nomenclature.forest_right + 1)
end
@@ -137,11 +176,13 @@
# Return human name of item
def human_name(options = {})
"nomenclatures.#{nomenclature.name}.items.#{name}".t(options.merge(default: ["items.#{name}".to_sym, "enumerize.#{nomenclature.name}.#{name}".to_sym, "labels.#{name}".to_sym, name.humanize]))
end
- alias_method :humanize, :human_name
+ alias humanize human_name
+ alias localize human_name
+ alias l localize
def human_notion_name(notion_name, options = {})
"nomenclatures.#{nomenclature.name}.notions.#{notion_name}.#{name}".t(options.merge(default: ["labels.#{name}".to_sym]))
end
@@ -188,11 +229,11 @@
end
def to_xml_attrs
attrs = {}
attrs[:name] = name
- attrs[:parent] = @parent_name if self.parent?
+ attrs[:parent] = @parent_name if parent?
properties.each do |pname, pvalue|
if p = nomenclature.properties[pname.to_s]
if p.type == :decimal
pvalue = pvalue.to_s.to_f
elsif p.list?
@@ -204,15 +245,16 @@
attrs
end
# Returns property value
def property(name)
+ return @name.to_sym if name == :name
property = @nomenclature.properties[name]
value = @attributes[name]
if property
if value.nil? && property.fallbacks
- for fallback in property.fallbacks
+ property.fallbacks.each do |fallback|
value ||= @attributes[fallback]
break if value
end
end
value ||= cast_property(name, property.default) if property.default
@@ -225,15 +267,19 @@
if property.list?
return property(name).collect do |i|
["nomenclatures.#{@nomenclature.name}.item_lists.#{self.name}.#{name}.#{i}".t, i]
end
elsif property.nomenclature?
- return Onoma[property(name)].list.collect do |i|
+ target_nomenclature = Onoma.find(property(name).to_sym)
+ unless target_nomenclature
+ raise "Cannot find nomenclature: for #{property(name).inspect}"
+ end
+ return target_nomenclature.list.collect do |i|
[i.human_name, i.name]
end
else
- fail StandardError, 'Cannot call selection for a non-list property'
+ raise StandardError, 'Cannot call selection for a non-list property'
end
end
# Checks if item has property with given name
def has_property?(name)
@@ -245,16 +291,20 @@
return property(method_name) if has_property?(method_name)
super
end
def set(name, value)
- fail "Invalid property: #{name.inspect}" if [:name, :parent].include?(name.to_sym)
- # TODO: check format
- if property = nomenclature.properties[name]
- value ||= [] if property.list?
+ raise "Invalid property: #{name.inspect}" if %i[name parent].include?(name.to_sym)
+ # # TODO: check format
+ # if property = nomenclature.properties[name]
+ # value ||= [] if property.list?
+ # end
+ if value.nil?
+ @attributes.delete(name)
+ else
+ @attributes[name] = value
end
- @attributes[name] = value
end
private
def cast_property(name, value)
@@ -262,10 +312,10 @@
end
def item_for_comparison(other)
item = nomenclature[other.is_a?(Item) ? other.name : other]
unless item
- fail StandardError, "Invalid operand to compare: #{other.inspect} not in #{nomenclature.name}"
+ raise StandardError, "Invalid operand to compare: #{other.inspect} not in #{nomenclature.name}"
end
item
end
end
end