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