#--
# 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 .
#++
ActiveSupport::Dependencies.require_or_load Rails.root.join(
'lib', 'helpers', 'item_helper')
ActiveSupport::Dependencies.require_or_load Rails.root.join(
'lib', 'helpers', 'tripple_navigation')
# Model for the representation of "connections" (which are used to
# make statements about the relationships between "item") in
# WontoMedia's database. The schema listing the data fields for a
# Connection is, of course, in db/schema.rb. And Rails'
# automatically-provided model methods are based on the field names
# there.
#
# A great deal of this model's behavior is provided by Rails
# belongs_to relations between Connection object fields and
# Item objects (see source).
class Connection < ActiveRecord::Base
# This constant is a bit mask for Item.flags. A non-zero value
# indicates that the Item instance should not be user-modifiable.
DATA_IS_UNALTERABLE = 1
OBJECT_KIND_ITEM = 'item'
OBJECT_KIND_SCALAR = 'scalar'
# convenience attribute allows item representing a connection's object's
# type (if specified by a relationship somewhere in the database) to
# be attached to the connection once it has been looked up once
attr_accessor :type_item
before_validation :complex_validations
# validates_presence_of :subject, :predicate
#explicitly do the equivalent of the above in complex_validations because
#c_v has to be a "before" callback (so it's return value is checked), which
#means other validations aren't run yet, so can't count on these IDs to
#be present/valid. Ugh.
# validates_inclusion_of :kind_of_obj, :in => %w(item scalar)
# validates_uniqueness_of :subject_id, :scope => [:predicate_id, :obj_id]
#these work, but are subsumed by checks in c_v, so eliminated duplication
belongs_to :subject, :class_name => "Item"
belongs_to :predicate, :class_name => "Item"
belongs_to :obj, :class_name => "Item"
belongs_to :connection_desc, :class_name => "Item"
# This method is a hack to provide a legitimate default value for
# the +flags+ field of an Item that hasn't been initialized yet.
# Alternative at http://blog.phusion.nl/2008/10/03/47/
def flags #:nodoc:
# Note that the default value returned here must/does match the
# column default specified in the database schema
self[:flags] or 0
end
private
# This method performs checks to determine whether an individual
# Connection, if placed in the context of the rest of WontoMedia's
# database, would violate any rules about the structure of the
# relationship network. It is automatically invoked using Rails'
# before_validation hook. In the event that one or more
# checks fail, this method returns +false+ and places the
# appropriate message(s) in the +errors+ flash.
def complex_validations() #:doc:
if kind_of_obj == OBJECT_KIND_ITEM
[ [subject_id, :subject], [predicate_id, :predicate],
[obj_id, :obj] ].each do |tocheck|
field, symbol = *tocheck
if field.nil?
errors.add symbol, "can't be blank."
return false
end
end
# is there an existing connection with a predicate that is a
# sub-property or super-property of the current predicate?
# check for duplicate connection and all super-properties
TrippleNavigation.relation_and_all_superproperties(
predicate_id) do |super_prop|
unless (Connection.all( :conditions => [
"subject_id = ? AND predicate_id = ? AND obj_id = ?",
subject_id, super_prop, obj_id ] ).empty? )
errors.add :subject, 'relationship (or equivalent) already exists.'
return false
end
end
# check for sub-properties
TrippleNavigation.relation_and_all_subproperties(
predicate_id) do |sub_prop|
unless (Connection.all( :conditions => [
"subject_id = ? AND predicate_id = ? AND obj_id = ?",
subject_id, sub_prop, obj_id ] ).empty? )
errors.add :subject, 'equivalent relationship already exists.'
return false
end
end
# is there an existing implied opposing connection?
# find all connections back-connecting our subject and object, determine
# if any of their predicates has an (inherited)
# inverse_relationship to current predicate
if connections = Connection.all( :conditions => [
"subject_id = ? AND obj_id = ?", obj_id, subject_id ] )
inverse_id = Item.find_by_name("inverse_relationship").id
connections.each do |e|
TrippleNavigation.relation_and_all_superproperties(
predicate_id) do |proposed_rel|
TrippleNavigation.relation_and_all_superproperties(
e.predicate_id) do |existing_rel|
if Connection.all( :conditions => [
"subject_id = ? AND predicate_id = ? AND obj_id = ?",
proposed_rel, inverse_id, existing_rel ] ).length > 0
errors.add :subject, 'already has this relationship implied.'
return false
end
end
end
end
end
#used in many subsequent tests
spo_id = Item.find_by_name("sub_property_of").id
# would this create an "individual parent_of category" relationship?
subItem = Item.find(subject_id)
objItem = Item.find(obj_id)
# check for "individual parent_of category"
if subItem.sti_type == ItemHelper::ITEM_INDIVIDUAL_CLASS_NAME &&
objItem.sti_type == ItemHelper::ITEM_CATEGORY_CLASS_NAME
if TrippleNavigation.check_properties(
:does => predicate_id,
:inherit_from => Item.find_by_name("parent_of").id,
:via => spo_id)
errors.add :predicate,
'an individual cannot be the parent of a category.'
return false
end
end
# check for "category child_of individual"
if subItem.sti_type == ItemHelper::ITEM_CATEGORY_CLASS_NAME &&
objItem.sti_type == ItemHelper::ITEM_INDIVIDUAL_CLASS_NAME
if TrippleNavigation.check_properties(
:does => predicate_id,
:inherit_from => Item.find_by_name("child_of").id,
:via => spo_id)
errors.add :predicate,
'a category cannot be the child of an individual.'
return false
end
end
# would this create a loop (including connection-to-self) of
# hierarchical or ordered relationships?
# if this is the kind of property we have to worry about?
[
Item.find_by_name("parent_of").id,
Item.find_by_name("child_of").id,
Item.find_by_name("sub_property_of").id,
Item.find_by_name("predecessor_of").id,
Item.find_by_name("successor_of").id
].each do |prop_id|
if TrippleNavigation.check_properties(
:does => predicate_id, :inherit_from => prop_id, :via => spo_id)
if TrippleNavigation.check_properties(
:does => obj_id, :link_to => subject_id,
:through_children_of => prop_id )
errors.add :subject, 'this relationship would create a loop.'
return false
end
end
end
# is this a "bad" connection-to-self?
if subject_id == obj_id
[ "inverse_relationship", "hierarchical_relationship",
"ordered_relationship" ].each do |relation_name|
if TrippleNavigation.check_properties(
:does => predicate_id,
:inherit_from => Item.find_by_name(relation_name).id,
:via => spo_id )
errors.add :subject,
'cannot create an ordered/hierarchical relationship from an item to itself'
return false
end
end
end
elsif kind_of_obj == OBJECT_KIND_SCALAR
[ [subject_id, :subject], [predicate_id, :predicate],
[scalar_obj, :scalar_obj] ].each do |tocheck|
field, symbol = *tocheck
if field.nil?
errors.add symbol, "can't be blank."
return false
elsif field =~ /^\s*$/
errors.add symbol, "can't be blank."
return false
end
end
else
errors.add :kind_of_obj, "isn't valid (#{kind_of_obj})"
return false
end
true
end
end