#-- # WontoMedia - a wontology web application # Copyright (C) 2011 - Glen E. Ivey # www.wontology.com # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License version # 3 as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program in the file COPYING and/or LICENSE. If not, # see . #++ # This module contains methods for navigating through (walking) the # inheritence hierarchy between the properties of connections. They # allow callers to make queries about a property, and receive answers # that take into account the 'sub_property_of' relationships between # the immediately queried property and the property inheritance tree. module TrippleNavigation # This method finds all existing PropertyItem's that are super # properties of the PropertyItem identified by +predicate_id+. The # immediate super properties of an item are the objects of any # connection of the form "item sub_property_of object". The set of # super properties found by this method includes immediate super # properties and all of _their_ super properties. This method # accepts a block, and iteratively yields _its original argument_ # and the _IDs_ of the argument property's super properties to that # block. def self.relation_and_all_superproperties(predicate_id, &block) yield predicate_id spo_id = Item.find_by_name("sub_property_of").id connections = Connection.all( :conditions => [ "subject_id = ? AND predicate_id = ?", predicate_id, spo_id ]) connections.each do |e| relation_and_all_superproperties(e.obj_id, &block) end end # This method finds all existing PropertyItem's that are sub # properties of the PropertyItem identified by +predicate_id+. The # immediate sub properties of an item are the subjects of any # connection of the form "subject sub_property_of item". The set of # sub properties found by this method includes immediate sub # properties and all of _their_ sub properties. This method accepts # a block, and iteratively yields _its original argument_ and the # _IDs_ of the argument property's sub properties to that block. def self.relation_and_all_subproperties(predicate_id, &block) yield predicate_id spo_id = Item.find_by_name("sub_property_of").id connections = Connection.all( :conditions => [ "predicate_id = ? AND obj_id = ?", spo_id, predicate_id ]) connections.each do |e| relation_and_all_subproperties(e.subject_id, &block) end end # This method tests for chains of relationships between # PropertyItem's. Its parameters are packaged in a single hash # argument. The value of each entry in the hash is the _ID_ of an # Item in the database. It returns a boolean that answers the # "question" phrased in the parameter hash. # # Currently, there are two supported combinations of argument # symbols: # # * :does xxxx :inherit_from xxxx :via xxxx # * :does xxxx :link_to xxxx :through_children_of xxxx # # Both of these combinations form a question, the answer to which is # in the boolean returned by this method (+true+ for yes). The # symbols used to index the argument hash are: # # [:does] The value in the hash associated with :does is the ID of the Item for which the caller is querying the connections stored in the database. # [:inherit_from] The value in the hash associated with :inherit_from is the ID of a PropertyItem (different from the :does Item) in the database. check_properties() will test for the presence of specific connections between this PropertyItem and the one specified by :does. # [:via] The value in the hash associated with :via is the ID of the PropertyItem that must be present as the predicate of a connection for that connection to "count" toward establishing a chain of connections from the :does item to the :inherit_from item. Note that :via is directional: When :inherit_from is used, only connections where the :does item is the subject, the :inherit_from is the object, and all intervening connections "point" in that same direction will be tested, and this method will return true only when a chain of connections using the :via PropertyItem as their predicates can be found. # [:link_to] The value in the hash associated with :link_to is the ID of another Item, to which check_properties will try to find a chain of connections from the :does Item. Note that unlike calls to check_properties using :inherit_from, when :link_to is used the starting and ending items can be of any type, not just PropertyItems. # [:through_children_of] The value in the hash associated with :through_children_of is the ID of a PropertyItem. check_properties attempts to find a chain of connections between the other two Item parameters (in either direction) where the connections use the :through_children_of PropertyItem _or_ any PropertyItem that is a _child_ of the specified PropertyItem through the built-in sub_property_of relationship. Unlike calls using :inherit_from, when checking :link_to connections present in either direction between the items found in a chain can produce a true result. def self.check_properties(hash_of_params) unless hash_of_params.has_key?(:does) raise ArgumentError, "Expected :does in input hash" end if hash_of_params.has_key?(:inherit_from) # TODO: ':via' is *almost* always a reference to # sub_property_of. Change this method and refactor callers so # that when :via isn't present, it defaults to s_p_o. (If we # keep asking ActiveRecord to find_by_name(s_p_o), will it be # cached or a huge performance hit? Experiment and if # A.R. really goes to the database each time, have a static # variable here so that we only have to fetch from the db the # first time.) unless hash_of_params.has_key?(:via) raise ArgumentError, "Expected :via in input hash" end child_id = hash_of_params[:does] parent_id = hash_of_params[:inherit_from] via_property_id = hash_of_params[:via] # treat "self" as a form of inheritence if child_id == parent_id return true end connections = Connection.all( :conditions => [ "subject_id = ? AND predicate_id = ?", child_id, via_property_id ]) connections.each do |e| if check_properties( :does => e.obj_id, :inherit_from => parent_id, :via => via_property_id ) return true end end elsif hash_of_params.has_key?(:link_to) unless hash_of_params.has_key?(:through_children_of) raise ArgumentError, "Expected :through_children_of in input hash" end from_item_id = hash_of_params[:does] to_item_id = hash_of_params[:link_to] kind_of_path = hash_of_params[:through_children_of] spo_id = Item.find_by_name("sub_property_of").id connections = Connection.all( :conditions => [ "subject_id = ?", from_item_id ]) connections.each do |e| if check_properties( :does => e.predicate_id, :inherit_from => kind_of_path, :via => spo_id ) if e.obj_id == to_item_id return true else if check_properties( :does => e.obj_id, :link_to => to_item_id, :through_children_of => kind_of_path ) return true end end end end else raise ArgumentError, "Expected :inherit_from or :link_to in input hash" end return false end # This method find a property's inverse property (if it exists). It # returns the inverse property's ID or nil. def self.propertys_inverse( original_property_id ) spo_id = Item.find_by_name("sub_property_of").id inverse_id = Item.find_by_name("inverse_relationship").id # Note: We require both "A-to-B" and "B-to-A" connections to be # present in the database to define two properties as the inverse # of each other, so we only need to gather the connections that # refer to original_property as their subject. possible_inverse_defining_connections = Connection.all( :conditions => [ "subject_id = ?", original_property_id ]) possible_inverse_defining_connections.each do |connection| if TrippleNavigation.check_properties( :does => connection.predicate_id, :inherit_from => inverse_id, :via => spo_id ) return connection.obj_id end end nil end end