#-- # 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 <http://www.gnu.org/licenses/>. #++ # Model for the representation of "items" (things that can be related # to each other through "connections") in WontoMedia's database. The # schema listing the data fields for an Item is, of course, in # db/schema.rb. And Rails' automatically-provided model methods are # based on the field names there. # # WontoMedia uses Rails' "single-table inheritance" to create several # specialized types of Item objects, all of which are stored in the # same database table together. The type of object a particular table # row represents is determined by that object's <tt>sti_type</tt> # field. There are a number of constants and methods in the module # ItemHelper that can be used load or evaluate +sti_type+ and to # perform generic operations on Item instances that correctly preserve # their child class. # # A great deal of this model's behavior is provided through Rails # validation methods (<tt>validates_...</tt>), see the source for # details. class Item < 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 # support sub-classes for Individual, Category, Property, Qualified self.inheritance_column = "sti_type" # name validates_presence_of :name, :message => "Item's name cannot be blank." validates_length_of :name, :maximum => 80, :message => "Item name must be 80 characters or less." validates_each :name do |record, attr, value| if !(value =~ /^[a-zA-Z][a-zA-Z0-9._:-]*$/m) || (value =~ /\n/m) record.errors.add attr, "must start with a letter, and can contain only"\ "letters, numbers, and/or the period, colon, dash, and underscore." end end validates_uniqueness_of :name, :message => "There is already a item with the same name." # title validates_presence_of :title, :message => "Item's title cannot be blank." validates_length_of :title, :maximum => 255, :message => "Item title must be 255 characters or less." validates_each :title do |record, attr, value| if value =~ /[\n\t]/m record.errors.add attr, "should not be multiple lines." end end validates_length_of :description, :maximum => 65000, :allow_nil => true, :allow_blank => true, :message => "Item description must be 65,000 characters or less." # 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 @class_item = nil @class_has_been_set = false after_save :after_save_callback # update class-defining connection after # main object saves; rollback still works def class_item if @class_has_been_set @class_item # ignore db content if this has been set # explicitly. This will eventually be # saved or discarded else iio_item = Item.find_by_name('is_instance_of') class_assignment = Connection.first( :conditions => [ "subject_id = ? AND predicate_id = ?", id, iio_item.id ] ) if class_assignment.nil? or class_assignment.kind_of_obj != Connection::OBJECT_KIND_ITEM return nil else return class_assignment.obj end end end def class_item_id item = class_item return item.nil? ? nil : item.id end def class_item=( new_class_item ) @class_item = new_class_item @class_has_been_set = true end def class_item_id=( new_class_id ) @class_item = new_class_id.nil? ? nil : Item.find_by_id( new_class_id ) @class_has_been_set = true end def is_class? return( !( Connection.first( :conditions => [ "predicate_id = ? AND obj_id = ?", Item.find_by_name('is_instance_of').id, id ] ).nil? ) or !( Connection.first( :conditions => [ "predicate_id = ? AND (subject_id = ? OR obj_id = ?)", Item.find_by_name('sub_class_of').id, id, id ] ).nil? ) ) end def superclass_of defining_connection = Connection.first( :conditions => [ "subject_id = ? AND predicate_id = ?", id, Item.find_by_name('sub_class_of').id ] ) return defining_connection.obj unless defining_connection.nil? return nil end def instance_of connection = Connection.first( :conditions => [ "subject_id = ? AND predicate_id = ?", id, Item.find_by_name('is_instance_of').id ] ) return nil if connection == nil return connection.obj end private def after_save_callback if @class_has_been_set iio_item = Item.find_by_name('is_instance_of') class_assignment = Connection.first( :conditions => [ "subject_id = ? AND predicate_id = ?", id, iio_item.id ] ) if class_assignment && @class_item == class_assignment.obj return true # class-defining connection already in db -> done end if class_assignment # if we're here, current db is different class_assignment.destroy # so get rid of it end begin class_assignment = Connection.new( { :subject_id => id, :predicate_id => iio_item.id, :obj_id => @class_item.id, :kind_of_obj => Connection::OBJECT_KIND_ITEM } ) @class_has_been_set = false return class_assignment.save rescue return false end end return true end end