root/tags/0.1_RC3/ignitedrecord.php

User picture

Author: m4rw3r

Revision: 279 («Previous)


File Size: 64.7 KB

(June 27, 2008 18:39 UTC) Almost 4 years ago


  

 

Showing without highlighting since it looks like a big file and may slow your browser - show with highlighting

Show/hide line numbers
<?php 
/*
 * Created on 2008 Mar 28
 * by Martin Wernstahl <m4rw3r@gmail.com>
 */

/**
 * @page BSD_LICENSE BSD License
 * @code
 * 
 * Copyright (c) 2008, Martin Wernstahl
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * The name of Martin Wernstahl may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Martin Wernstahl ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Martin Wernstahl BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * @endcode
 */

/**
 * @addtogroup IgnitedRecord
 * @{
 * An Object-Relational-Mapper model similar to Ruby on Rails ActiveRecord class.
 * 
 * @version 0.1
 * @author Martin Wernstahl <m4rw3r@gmail.com>
 * @par Copyright
 * Copyright (c) 2008, Martin Wernstahl <m4rw3r@gmail.com>
 * @par License:
 * Released under the BSD Lisence: @ref BSD_LICENSE
 * 
 * @par PHP Version
 * PHP 4 or greater
 */
/**
 * @page IgnitedRecord_Change_Log Change Log
 * @anchor IgnitedRecord_Change_Log
 * <hr>
 * @section sec Version History
 * @section sec2 0.1 RC 2
 * <ul>
 *     <li>Finally a name for this model, IgnitedRecord!</li>
 *     <li>The data is now sanitized before insert/update, it removes all properties that doesn't match the field names</li>
 *     <li>Added the <dfn>get()</dfn> method</li>
 *     <li>Added the <dfn>factory</dfn> method, creates a new model instance from supplied settings</li>
 *     <li>Added the <dfn>in_db()</dfn> method (IgnitedRecord_record)</li>
 *     <li>Added Act_As, to enable easier customization of the IgnitedRecord class.</li>
 *     <li>Added a Child_class_helper array which contains all helper class names to be assigned to the child classes.</li>
 *     <li>Implemented a basic hook system.</li>
 *     <li>Added a lot of hooks into the main IgnitedRecord class.</li>
 *     <li>Added the ability to name relations, this enables the use of relations referring to self and has_many, belongs_to,  has_one and has_and_belongs_to_many can relate to the same table without them overwriting eachother (useful for the Adjacency tree).</li>
 *     <li>Created a MPTtree behaviour, IgnitedRecord_tree (requires <a href="http://www.cibase.info/index.php/code/details/11">MPTtree</a>)</li>
 * </ul>
 * @section sec1 0.1 RC 1
 * <ul>
 *    <li><b>Initial Release</b></li>
 * </ul>
 */

/**
 * An ORM object representing a database record, extend to add extra functionality.
 * 
 * Interacts closely with an IgnitedRecord model
 * 
 * @author Martin Wernstahl <m4rw3r@gmail.com>
 * @par Copyright
 * Copyright (c) 2008, Martin Wernstahl <m4rw3r@gmail.com>
 * 
 * @todo Add a property which contains the original data, and compare to this one on save (if not modified, do not save)
 */
class IgnitedRecord_record{
	/**
	 * The IgnitedRecord instance that manages this record.
	 */
	var $__instance;
	/**
	 * The table used storing this data.
	 */
	var $__table;
	/**
	 * The corresponding database id for this objectt's row.
	 * 
	 * null if no corresponding row exists
	 */
	var $__id = null;
	/**
	 * An asociative array containing all loaded relationships.
	 * 
	 * Tablename is key in the array
	 */
	var $__relations;
	/**
	 * Stores the names of the properties storing relations.
	 */
	var $__relation_properties;
	/**
	 * Constructor.
	 * 
	 * @param $instance The IgnitedRecord instance
	 * @param $data The data this object shall be created from (only data from database)
	 */
	function IgnitedRecord_record(&$instance,$data = false){
		$this->__instance =& $instance;
		$this->__table = $instance->__table;
		if($data != false){
			$this->__id = $data[$instance->__id_col];
			unset($data[$instance->__id_col]);
			foreach ($data as $key => $value) {
				$this->$key = $value;
			}
		}
		else{
			$this->__id = null;
		}
	}
	/**
	 * Returns true if this row is in the database, false otherwise.
	 */
	function in_db(){
		return isset($this->__id);
	}
	/**
	 * Saves this object in the database using the IgnitedRecord instance.
	 * 
	 * If this object does not exists in the database, an insert is performed,
	 * otherwise an update is performed.
	 */
	function save(){
		if(isset($this->__instance))
			$this->__instance->save($this);
		else
			log_message('error','IgnitedRecord_record: No model is associated with the table '.$this->__table);
	}
	/**
	 * Updates the data in this object.
	 * 
	 * Not to be confused with the SQL update, this function
	 * refreshes the data in this object.
	 * 
	 * @note All loaded relations will need to be reloaded after an update
	 * (if you want to use them).
	 */
	function update(){
		if(isset($this->__instance))
			$this->__instance->update($this);
		else
			log_message('error','IgnitedRecord_record: No model is associated with the table '.$this->__table);
	}
	/**
	 * Deletes this object from the database.
	 * 
	 * Removes all relations and bindings to the database.
	 * Can then be inserted into the table again, using save().
	 * 
	 * @note No data in this object (apart from relations and database id) are lost.
	 */
	function delete(){
		if(isset($this->__instance) && $this->__id != null)
			$this->__instance->delete($this);
		else
			log_message('error','IgnitedRecord_record: No model is associated with the table '.$this->__table);
	}
	/**
	 * Loads all associated objects/rows in relationships.
	 * 
	 * @param $name Set to the name of the relation to load, omit if to load all relationships
	 */
	function load_rel($name = false){
		if(isset($this->__instance) && $this->__id != null)
			$this->__instance->load_rel($this,$name);
		else
			log_message('error','IgnitedRecord_record: No model is associated with the table '.$this->__table);
	}
	/**
	 * Loads the associated IgnitedRecord_records from the table $table.
	 * 
	 * @param $name The name of the relation to load
	 */
	function load_related($name = false){
		$this->load_rel($name);
	}
	/**
	 * Returns the related $table objects.
	 * 
	 * Depending on if it is a Belongs To, Has Many, Has One or Has And Belongs To Many,
	 * the result can be an array or an IgnitedRecord_record. \n
	 * If the relation is not loaded, it gets loaded.
	 * 
	 * @param $table The tablename to get related objects from
	 * @return An array or an IgnitedRecord_record, false if no relation is found
	 */
	function &get_related($table){
		if(!isset($this->__relations[$table]))
			$this->load_rel($table);
		if(isset($this->__relations[$table]))
			return $this->__relations[$table];
		$false = false;
		return $false;
	}
	/**
	 * Establishes a relationship with the supplied IgnitedRecord_record object.
	 * 
	 * Depending on the settings of the IgnitedRecord instance tide to this object,
	 * the type of relation can vary. \n
	 * Ex. \n
	 * If you have defined two relations, one to "posts" with Has Many
	 * and one to "moderators" with Has And Belongs To Many,
	 * and you pass an object of the type "posts", a Has Many relation
	 * will be established. \n
	 * If there are no relation defined, no relationship will be formed.
	 * 
	 * @param $object An IgnitedRecord_record
	 * @param $rel_name The name of the relation to add the object through, if omitted, IgnitedRecord will try to figure it out
	 */
	function add_relationship(&$object, $rel_name = false){
		if(isset($this->__instance))
			$this->__instance->establish_relationship($this, $object, $rel_name);
		else
			log_message('error','IgnitedRecord_record: No model is associated with the table '.$this->__table);
	}
	/**
	 * Somewhat of a shorthand for add_relationship(), but needs the relationship name.
	 * 
	 * Example:
	 * @code
	 * $obj->add('child',$object);
	 * @endcode
	 * 
	 * @param $rel_name The name of the relationship
	 * @param $object An IgnitedRecord_record to establish a relation with
	 */
	function add($rel_name, &$object){
		$this->add_relationship($object, $rel_name);
	}
	/**
	 * Removes the relationship between $this and $object.
	 * 
	 * If a relationship exists between the two, it is removed.
	 * 
	 * @param $object An IgnitedRecord_record
	 * @param $rel_name The name of the relation to remove the object from
	 */
	function remove_relationship(&$object, $rel_name = false){
		if(isset($this->__instance))
			$this->__instance->remove_relationship($this, $object, $rel_name);
		else
			log_message('error','IgnitedRecord_record: No model is associated with the table '.$this->__table);
	}
	/**
	 * Somewhat of a shorthand for remove_relationship(), but needs the relationship name.
	 * 
	 * Example:
	 * @code
	 * $obj->remove('child',$object);
	 * @endcode
	 * 
	 * @param $rel_name The name of the relationship
	 * @param $object An IgnitedRecord_record to break a relation with
	 */
	function remove($rel_name, &$object){
		$this->remove_relationship($object, $rel_name);
	}
	/**
	 * Aggregates the child helpers into this object, only PHP 5.
	 * 
	 * @param $method The method called
	 * @param $args The argument array sent to the method
	 * 
	 * @return Whatever the helper method returns
	 */
	function __call($method,$args){
		foreach(array_keys($this->__instance->__child_class_helpers) as $property){
			if(method_exists($this->$property, $method))
				return call_user_func_array(array($this->$property, $method),$args);
		}
	}
}
/**
 * A model for ORM, extend to use.
 * 
 * Uses the CodeIgniter inflector helper to pluralize / singularize modelname / tablename.
 * 
 * Hooks:
 * @code
 * Hooks for the IgnitedRecord class:
 * name						args
 * ---------------------    ---------------------
 * 
 * pre_find					find id
 * post_find				find id
 * 							reference to generated object
 * pre_get
 * post_get					reference to generated object
 * pre_find_by				reference to the where array
 * post_find_by				where array
 * 							reference t generated object
 * pre_find_by_sql			reference to sql string
 * post_find_by_sql			sql string
 * 							reference to generated object
 * pre_find_all
 * post_find_all			reference to return array
 * pre_find_all_by			reference to where array
 * post_find_all_by			where array
 * 							reference to return array
 * pre_find_all_by_sql		reference to sql string
 * post_find_all_by_sql		sql string
 * 							reference to return array
 * post_new_record			reference to generated object
 * save_pre_strip_data		reference to object to save
 * save_post_strip_data		reference to data array to be saved
 * save_pre_insert			reference to data array to be saved
 * save_post_insert			reference to object which has been inserted
 * save_pre_update			reference to data array to be saved
 * save_post_update			reference to object which has been updated
 * post_save				reference to object which has been updated
 * pre_update				reference to object to refreh
 * post_update				reference to newly created object
 * pre_delete				reference to object to be deleted
 * pre_delete_query			reference to object to be deleted
 * post_delete				reference to deleted object
 * @endcode
 * 
 * @author Martin Wernstahl <m4rw3r@gmail.com>
 * @par Copyright
 * Copyright (c) 2008, Martin Wernstahl <m4rw3r@gmail.com>
 * 
 * @todo Make __auto_load_relations use joins instead, then we are loading two or more records at the same time
 * @todo Maybe instead of auto_load_relations, make separate functions which utilize JOINs
 * @todo Replace the call to CI's db_result::result() with calls to mysql_fetch_object(conn_id,'IgnitedRecord_record',array(&$this)), look in CI's DB_result and in PHP manual for mysql_fetch_object()
 */
class IgnitedRecord extends Model{
	/**
	 * The table that stores all database rows.
	 * 
	 * Default is plural of classname (using CodeIgniters Inflector helper) \n
	 * Override to specifically define the tablename
	 */
	var $__table = null;
	/**
	 * The id column in database.
	 * 
	 * Expected to be an auto_incremental unsigned int, IgnitedRecord never sets this column.
	 */
	var $__id_col = 'id';
	/**
	 * The classname of the classes produced by this factory.
	 * 
	 * Override normally to use another class as IgnitedRecord_record (preferably a descendant class)
	 */
	var $__child_class = 'IgnitedRecord_record';
	/**
	 * Set to true to load all relationships automaticly when an IgnitedRecord_record is instantiated.
	 * 
	 * Warning, can cause endless loops because of a related table having $__auto_load_relationships
	 * switched on and relates back to this table.
	 */
	var $__auto_load_relationships = false;
	/**
	 * Defines how the database should behave.
	 * 
	 * Packaged with IgnitedRecord by default: tree (list, rev (revision handling) are not finished)
	 * 
	 * Set to empty array (default) if to not use any special behaviour.
	 */
	var $__act_as = array();
	/**
	 * Data for a Belongs To relationships.
	 * 
	 * There are three types of values that can be entered:
	 * 
	 * @arg Tablename, as string or multiple in array
	 * @arg An array where the key is the tablename and value is the column with tablename_id
	 * (multiple entries are supported)
	 * @arg An array where the key is the tablename and value is an array with this structure:
	 * @code
	 * var $__belongs_to = array('tablename' => array('name' => 'property_name', (the name this reference)
	 *                                                'model' => 'modelname',
	 *                                                'col' => 'foreign_key_column'));
	 * @endcode
	 * This one can also contain multiple entries.
	 * 
	 * Default values are the following:
	 * @arg tablename => as specified
	 * @arg name => modelname or singular(tablename)
	 * @arg model => tablename or singular(tablename), if a model with that name exists
	 * @arg col => tablename_id
	 * 
	 * @note The foreign key is stored in this table, with default column name othertablename_id
	 * @note Needs to be defined before constructor kicks in.
	 */
	var $__belongs_to;
	/**
	 * Data for a Has Many relationships.
	 * 
	 * There are three types of values that can be entered:
	 * 
	 * @arg Tablename, as string or multiple in array
	 * @arg An array where the key is the tablename and value is the column with tablename_id
	 * (multiple entries are supported)
	 * @arg An array where the key is the tablename and value is an array with this structure:
	 * @code
	 * var $__has_many = array('tablename' => array('name' => 'property_name', (the name this reference)
	 *                                              'model' => 'modelname',
	 *                                              'col' => 'foreign_key_column'));
	 * @endcode
	 * This one can also contain multiple entries.
	 * 
	 * Default values are the following:
	 * @arg tablename => as specified
	 * @arg name => tablename
	 * @arg model => tablename or singular(tablename), if a model with that name exists
	 * @arg col => {$this->__table}_id
	 * 
	 * @note The foreign key is stored in the other table, with default column name thistablename_id
	 * @note Needs to be defined before constructor kicks in.
	 */
	var $__has_many;
	/**
	 * Data for a Has One relationships.
	 * 
	 * There are three types of values that can be entered:
	 * 
	 * @arg Tablename, as string or multiple in array
	 * @arg An array where the key is the tablename and value is the column with tablename_id
	 * (multiple entries are supported)
	 * @arg An array where the key is the tablename and value is an array with this structure:
	 * @code
	 * var $__has_one = array('tablename' => array('name' => 'property_name', (the name this reference)
	 *                                             'model' => 'modelname',
	 *                                             'col' => 'foreign_key_column'));
	 * @endcode
	 * This one can also contain multiple entries.
	 * 
	 * Default values are the following:
	 * @arg tablename => as specified
	 * @arg name => modelname or singular(tablename)
	 * @arg model => tablename or singular(tablename), if a model with that name exists
	 * @arg col => {$this->__table}_id
	 * 
	 * @note The foreign key is stored in the other table, with default column name thistablename_id
	 * @note Needs to be defined before constructor kicks in.
	 */
	var $__has_one;
	/**
	 * Data for a Has And Belongs To many relationships.
	 * 
	 * There are three types of values that can be entered:
	 * 
	 * @arg Tablename, as string or multiple in array
	 * @arg An array where the key is the tablename and value is the name of the relations table
	 * (multiple entries are supported)
	 * @arg An array where the key is the tablename and value is an array with this structure:
	 * @code
	 * var $__has_and_belongs_to_many = array('othertablename' => array('name' => 'relation_name',
	 *                                        'table' => 'relation_tablename',
	 *                                        'this_col' => 'this_table_foreign_key_column',
	 *                                        'other_col' => 'other_table_foreign_key_column',
	 *                                        'model' => 'modelname_for_other_table'
	 *                                       ));
	 * @endcode
	 * This one can also contain multiple entries.
	 * 
	 * Default values are the following:
	 * @arg name => tablename
	 * @arg table => tablename_{$this->__table} or {$this->__table}_tablename (arranged in alphabetical order)
	 * @arg this_col => {$this->__table}_id
	 * @arg other_col => tablename_id
	 * @arg model => tablename or singular(tablename), if a model with that name exists
	 * 
	 * @note Needs to be defined before constructor kicks in.
	 */
	var $__has_and_belongs_to_many;
	/**
	 * Shorthand for $__has_and_belongs_to_many.
	 * 
	 * @access private
	 */
	var $__habtm;
	/**
	 * Stores the names of the related tables and their relation name.
	 * 
	 * @access private
	 */
	var $__related_tables;
	/**
	 * Cache for the table field metadata.
	 * 
	 * Stores the names of the columns.
	 * If you define this by hand, you save one query
	 */
	var $__columns;
	/**
	 * Child class helpers.
	 * 
	 * These are assigned to properties of the child class.
	 * They should be a class whose constructor takes the IgnitedRecord_record.
	 * 
	 * Is assigned by behaviours.
	 * key => propertyname, value => classname
	 * 
	 * @access private
	 * (accessed by Behaviours)
	 */
	var $__child_class_helpers = array();
	/**
	 * Stores the names of the loaded acts
	 * 
	 * @access private
	 */
	var $__loaded_acts;
	/**
	 * Stores all the registered hooks.
	 * 
	 * @access private
	 */
	var $hooks;
	/**
	 * Constructor.
	 * 
	 * Sets the tablename if not already set and normalizes relationship data.
	 * Also loads the CodeIgniter inflector helper (if not loaded).
	 */
	function IgnitedRecord(){
		parent::Model();
		$this->_load_acts();
		$this->load->helper('inflector');
		if($this->__table == null){
			$class = get_class($this);
			if(strtolower(substr($class,-6)) == '_model')
				$class = substr($class,0,-6); // remove _model from classname if it exists
			$this->__table = plural($class);
		}
		// normalize relationship data
		$this->_normalize_rel_belong($this->__belongs_to);
		$this->_normalize_rel($this->__has_many,false);
		$this->_normalize_rel($this->__has_one,true);
		$this->_normalize_rel_habtm($this->__has_and_belongs_to_many);
		// shorthand
		$this->__habtm =& $this->__has_and_belongs_to_many;
	}
	/**
	 * Aggregates the acts into this object, only PHP 5.
	 * 
	 * @param $method The method called
	 * @param $args The argument array sent to the method
	 * 
	 * @return Whatever the helper method returns
	 */
	function __call($method,$args){
		foreach($this->__loaded_acts as $property){
			if(method_exists($this->$property, $method))
				return call_user_func_array(array($this->$property, $method),$args);
		}
	}
	/**
	 * Adds a hook to the specified name and priority.
	 * 
	 * @param $name The name of the hook
	 * @param $function The function to be called, or the array(classobj,function) to be called
	 * @param $priority The priority of the function lower = higher priority
	 * 
	 * @return void
	 */
	function add_hook($name, $function, $priority = 10){
		$this->hooks[$name][$priority][] = $function;
	}
	/**
	 * Runs the hook(s) registred with the name $name.
	 * 
	 * The return values will be in the order of their priority and
	 * false retuns will be skipped.
	 * 
	 * @param $name The name of the hook to be called
	 * @param $data The array containing the data to pass on to registered functions
	 * 
	 * @return empty array if no hook was run or if all hooks returned false,
	 * otherwise an array containing the value(s) from the registred function(s) is returned
	 * 
	 * @todo Is the return values really needed?
	 */
	function hook($name,$data){
		// Have we got a hook for this specific event?
		if (!isset($this->hooks[$name])) {
			// No, do nothing
			return array();
		}
		else{
			// Yes, sort the list by priority
			ksort($this->hooks[$name]);
		}
		$ret = array();
		foreach($this->hooks[$name] as $priority => $functions){
			if(is_array($functions)){
				foreach($functions as $func){
					$tmp =& call_user_func_array($func,$data);
					if($tmp !== false || $tmp != null)
						$ret[] = $tmp;
				}
			}
		}
		return $ret;
	}
	/**
	 * Loads the acts specified in $__act_as.
	 * 
	 * Expects the behaviours to be localized in files whith the names like:
	 * ignitedrecord_behaviourname.php and to have the class name linke this: IgnitedRecord_behaviourname
	 * If the behaviour class already exists, no loading of file takes place.
	 * The behaviourname is always lowercase
	 */
	function _load_acts(){
		$this->__loaded_acts = array();
		foreach((Array) $this->__act_as as $key => $act){
			if(!is_numeric($key)){
				$opt = $act;
				$act = $key;
			}
			else{
				$opt = array();
			}
			$act = strtolower($act);
			$exists = false;
			$class_name = 'IgnitedRecord_'.$act;
			if(!class_exists($class_name)){
				if(file_exists(APPPATH.'models/ignitedrecord_'.$act.'.php')){
					include_once(APPPATH.'models/ignitedrecord_'.$act.'.php');
					if(class_exists($class_name))
						$exists = true;
				}
				else{
					log_message('warning',"IgnitedRecord: Behaviour file IgnitedRecord_$act.php does not exists in the models dir. Cannot load $act.");
				}
			}
			else{
				$exists = true;
			}
			if($exists == true){
				// Check if a reference to a library, model or anything else
				// exists in $this, all properties in this class are
				// prefixed by '__' so there are no risk of overwriting them
				if(isset($this->$act)){
					// unset removes reference, prevents overwrite of the original data
					unset($this->$act);
				}
				// load behaviour
				$this->$act = new $class_name($this,$opt); // PHP 4: $this->$act =& new $class_name($this,$opt);
				$this->__loaded_acts[] = $act;
			}
			else{
				log_message('warning',"IgnitedRecord: Behaviour class IgnitedRecord_$act does not exists. Cannot load $act.");
			}
		}
	}
	/**
	 * Creates a new model from the parameters sent.
	 * 
	 * uses eval() and (un)serialize()
	 * 
	 * @param $name The name of the generated model.
	 * @param $settings The settings of this model (stored as propertyname as key and value as value)
	 * @param $str_return If to return the string generating that class, defaut: false
	 * 
	 * @return A new derived class, or a string capable of creating the derived class
	 */
	function &factory($name, $settings = array(), $str_return = false){
		$str = serialize($settings);
		$data = "class {$name} extends IgnitedRecord{
			function {$name}(){
				\$settings = unserialize('{$str}');
				foreach(\$settings as \$key => \$value){
					\$this->\$key = \$value;
				}
				parent::IgnitedRecord();
			}
		}";
		if($str_return)
			return $data;
		eval($data);
		$model = new $name(); // PHP 4: $model =& new $name();
		return $model;
	}
	
	//////////////////////////////////////////////
	//    Update and Find methods
	//////////////////////////////////////////////
	
	/**
	 * Fetches an IgnitedRecord_record from the db.
	 * 
	 * To be used with CodeIgniter's ActiveRecord class to sort and filter the query.
	 * This method makes only one call, which fetches one row of data:
	 * @code
	 * $this->db->get($this->__table,1);
	 * @endcode
	 *
	 * @return A populated IgnitedRecord_record if result is found, false otherwise
	 */
	function &get(){
		$this->hook('pre_get');
		$query = $this->db->get($this->__table,1);
		if(!$query->num_rows()){
			$false = false;
			return $false;
		}
		$obj =& $this->_dbobj2ORM($query->row_array());
		$this->hook('post_get',array(&$obj));
		return $obj;
	}
	/**
	 * Fetches the IgnitedRecord_record object with the id $id.
	 * 
	 * @param $id The id of the row
	 * 
	 * @return A populated IgnitedRecord_record if result is found, false otherwise
	 */
	function &find($id){
		$this->hook('pre_find',array($id));
		$query = $this->db->get_where($this->__table,array($this->__id_col => $id),1);
		if(!$query->num_rows()){
			$false = false;
			return $false;
		}
		$obj =& $this->_dbobj2ORM($query->row_array());
		$this->hook('post_find',array($id,&$obj));
		return $obj;
	}
	/**
	 * Fetches the IgnitedRecord_record object where the $column contains $data.
	 * 
	 * This function merges the $column and $data into one array,
	 * where the $column becomes the key(s) in the array and
	 * the $data becomes the value(s), using _array_combine(). \n
	 * This array is then passed into CodeIgniter's db::get_where()
	 * method as the where clause.
	 * 
	 * @param $column The column name(s) to match, typecasted to array if not already
	 * @param $data The data value(s) to match, typecasted to array if not already
	 * 
	 * @return A populated IgnitedRecord_record if result is found, false otherwise
	 */
	function &find_by($column, $data){
		if(($where = $this->_array_combine((Array)$column, (Array)$data)) === false){
			log_message('error','IgnitedRecord: Number of columns and number of data argumnents does not match in call to find_by.');
			$false = false;
			return $false;
		}
		$this->hook('pre_find_by',array(&$where));
		$query = $this->db->get_where($this->__table, $where, 1);
		if(!$query->num_rows()){
			$false = false;
			return $false;
		}
		$obj =& $this->_dbobj2ORM($query->row_array());
		$this->hook('post_find_by',array($where,&$obj));
		return $obj;
	}
	/**
	 * Fetches the IgnitedRecord_record object with the sql supplied.
	 * 
	 * @param $sql The sql query, needs escaping of values (must start with SELECT * or equivalent)
	 * 
	 * @return A populated IgnitedRecord_record if a result is found, false otherwise
	 */
	function &find_by_sql($sql){
		$this->hook('pre_find_by_sql',array(&$sql));
		$query = $this->db->query($sql);
		if(!$query->num_rows()){
			$false = false;
			return $false;
		}
		$obj =& $this->_dbobj2ORM($query->row_array());
		$this->hook('post_find_by_sql',array($sql,&$obj));
		return $obj;
	}
	/**
	 * Fetches all IgnitedRecord_record objects in the database.
	 * 
	 * @return An array with populated IgnitedRecord_records, empty array if table is empty
	 */
	function &find_all(){
		$arr = array();
		$this->hook('pre_find_all');
		$query = $this->db->get($this->__table);
		foreach($query->result_array() as $row){
			$arr[] =& $this->_dbobj2ORM($row);
		}
		$this->hook('post_find_all',array(&$arr));
		return $arr;
	}
	/**
	 * Fetches all IgnitedRecord_record objects where the $column contains $data.
	 * 
	 * This function merges the $column and $data into one array,
	 * where the $column becomes the key(s) in the array and
	 * the $data becomes the value(s), using _array_combine(). \n
	 * This array is then passed into CodeIgniter's db::get_where()
	 * method as the where clause.
	 * 
	 * @param $column The column name(s) to match
	 * @param $data The data which will be used to match
	 * 
	 * @return An array with populated IgnitedRecord_records if results are found, empty array otherwise
	 */
	function &find_all_by($column, $data){
		$arr = array();
		if(($where = $this->_array_combine((Array)$column, (Array)$data)) === false){
			log_message('error','IgnitedRecord: Number of columns and number of data argumnents does not match in call to find_by.');
			return $arr;
		}
		$this->hook('pre_find_by',array(&$where));
		$query = $this->db->get_where($this->__table, $where, 1);
		foreach($query->result_array() as $row){
			$arr[] =& $this->_dbobj2ORM($row);
		}
		$this->hook('post_find_all_by',array($where,&$obj));
		return $arr;
	}
	/**
	 * Fetches all IgnitedRecord_record objects which match the sql supplied.
	 * 
	 * @param $sql The sql query, needs escaping of values (must start with SELECT * or equivalent)
	 * 
	 * @return An array with populated IgnitedRecord_records if resultas are found, false otherwise
	 */
	function &find_all_by_sql($sql){
		$this->hook('pre_find_by_sql',array(&$sql));
		$arr = array();
		$query = $this->query($sql);
		foreach($query->result_array() as $row){
			$arr[] =& $this->_dbobj2ORM($row);
		}
		$this->hook('post_find_all_by',array($sql,&$arr));
		return $arr;
	}
	/**
	 * Creates a new empty IgnitedRecord_record object.
	 * 
	 * @param $data The data of the new object, is assigned to the new object before it is returned
	 * 
	 * @return A new IgnitedRecord_record
	 */
	function &new_record($data = array()){
		$ref =& $this->_dbobj2ORM($data);
		$this->hook('post_new_record',array(&$ref));
		return $ref;
	}
	/**
	 * Saves the supplied object in the databse.
	 * 
	 * Takes a reference of the object (edits the object after insert)'
	 * 
	 * @note Can only save the $object if it belongs to the same table as this model
	 * 
	 * @param $object The object to be inserted or updated, passed by reference
	 */
	function save(&$object){
		if($this->__table == $object->__table){
			$this->hook('save_pre_strip_data',array(&$object));
			// remove some values that shall not belong in the database
			$data =& $this->_strip_data($object);
			$this->hook('save_post_strip_data',array(&$data));
			
			// check if row exists
			if($object->__id == null){
				// no row exists in database, insert
				$this->hook('save_pre_insert',array(&$data));
				$this->db->insert($this->__table,$data);
				$object->__id = $this->db->insert_id(); // grabs the id of the inserted row
				$this->hook('save_post_insert',array(&$object));
			}
			else{
				// update
				$this->hook('save_pre_update',array(&$data));
				$this->db->where($this->__id_col,$object->__id);
				$this->db->update($this->__table,$data);
				$this->hook('save_post_update',array(&$object));
			}
			$this->hook('post_save',array(&$object));
		}
		else{
			show_error('Incompatible object supplied to '.classname($this).', tables does not match');
		}
	}
	/**
	 * Updates the data in the supplied object.
	 * 
	 * @note Can only perform an update if the $object belongs to the same table as this instance
	 * @note Replaces the object with a clean one, losing all loaded relations
	 * (you have to load them again with load_rel() if you need them)
	 * 
	 * @param $object The object to be updated, passed by reference
	 */
	function update(&$object){
		if($this->__table == $object->__instance->__table){
			// check if row exists
			if($object->__id != null){
				$this->hook('pre_update',array(&$object));
				$query = $this->db->get_where($this->__table,array($this->__id_col => $object->__id),1);
				$object =& $this->_dbobj2ORM($query->row_array());
				$this->hook('post_update',array(&$object));
			}
		}
	}
	/**
	 * Deletes the $object from the database if it exists in database.
	 * 
	 * Also clears all relations with other rows.
	 * 
	 * @param $object The IgnitedRecord_record to remove from database
	 */
	function delete(&$object){
		if(isset($object->__id)){
			$this->hook('pre_delete',array(&$object));
			// remove relationships
			foreach($object->__relation_properties as $name){
				unset($object->$name);
			}
			// no need to do anything for Belongs To, the data is stored in this table
			foreach($this->__has_many as $table => $data){
				$this->db->set($data['col'],null);
				$this->db->where($data['col'], $obj->__id);
				$this->db->update($table);
			}
			foreach($this->__has_one as $table => $data){
				$this->db->set($data['col'],null);
				$this->db->where($data['col'], $obj->__id);
				$this->db->update($table);
			}
			foreach($this->__habtm as $table => $data){
				$this->db->delete($data['table'],array($data['this_col'] => $object->__id));
			}
			unset($object->__relations);
			unset($object->__relation_properties);
			$this->hook('pre_delete_query',array(&$object));
			$this->db->delete($this->__table, array($this->__id_col => $object->__id));
			unset($object->__id);
			$this->hook('post_delete',array(&$object));
		}
	}
	/**
	 * Removes all data from the associated table, and also all relationships.
	 * 
	 * @attention All Data Will Be Lost! Including all relations with other tables!
	 */
	function delete_all(){
		$this->db->empty_table($this->__table);
		// Belongs to relations are stored in this table, no need to do anything
		foreach($this->__has_many as $table => $data){
			$this->db->set($data['col'],null);
			$this->db->update($table);
		}
		foreach($this->__has_one as $table => $data){
			$this->db->set($data['col'],null);
			$this->db->update($table);
		}
		foreach($this->__habtm as $table => $data){
			// remove all rows where this_col is not null
			// which is enabling the use of a relations table for more than two tables
			$this->db->delete($data['table'],array($data['this_col'].' !=' => null));
		}
	}
	
	//////////////////////////////////////////////
	//    Relationship methods
	//////////////////////////////////////////////
	
	// These methods are called by objects this class creates,
	// so I recommend that you only call these methods if you know what you are doing.
	// Please use the methods in IgnitedRecord_record instead
	
	/**
	 * Loads all associated relationships for the object $obj.
	 * Primary storage of the loaded relationships are in the $obj->__relations array
	 * with the relationship name as key. \n
	 * 
	 * Secondary storage is $obj->$rel_name
	 * 
	 * @note If any of the secondary storage properties are already occupied an overwrite will not be performed
	 * 
	 * @param $obj The object to load rrelationships for
	 * @param $rel_name Set to the tablename to init relationships for only this table, omit if to load all relationships
	 */
	function load_rel(&$obj,$rel_name = false){
		if(!isset($obj->__id))
			return; // no id = not stored in database = no relationships
		// establish relationships
		$CI =& get_instance();
		// Belongs To relationship
		// Stores the data in obj->modelname (or singular of tablename) as default
		if($rel_name == false || in_array($rel_name,array_keys($this->__belongs_to))){
			foreach($this->__belongs_to as $name => $data){
				if($rel_name != false && $name != $rel_name || isset($obj->__relations[$name]))
					continue; // skip if tablename is not false and don't match, or if we already have loaded that one
				$table =& $data['table'];
				if(isset($obj->$data['col']) && $obj->$data['col'] != ''){
					if($data['model'] != ''){
						// we have a model, load the specific model
						if(!in_array($data['model'],get_object_vars($CI)))
							$CI->load->model($data['model']);
						// use that model to instantiate the objects
						$model_inst =& $CI->$data['model'];
						$obj->__relations[$name] =& $model_inst->find($obj->$data['col']);
						if(!isset($obj->$name) && $obj->__relations[$name] != false){
							$obj->$data['model'] =& $obj->__relations[$name];
							$obj->__relation_properties[] = $data['model'];
						}
					}
					else{
						// no model = normal IgnitedRecord_record OBJ
						$model = singular($name);
						$query = $this->db->get_where($table,array('id' => $obj->$data['col']),1);
						$obj->__relations[$name] =& $this->_dbobj2ORM($query->row_array(),false);
						if(!isset($obj->$model) && $obj->__relations[$name] != false){
							$obj->$name =& $obj->__relations[$name];
							$obj->__relation_properties[] = $name;
						}
						$obj->__relations[$name]->__table = $table;
						unset($obj->__relations[$name]->__instance);
					}
				}
				else{
					log_message('warning','IgnitedRecord: no foreign key column specified in belongs to relationship between '.$this->__table.' and '.$data['table']);
				}
			}
		}
		// Has Many relationship
		// Stores the data in an array in obj->tablename (tablename is plural) as default
		if($rel_name == false || in_array($rel_name,array_keys($this->__has_many))){
			foreach($this->__has_many as $name => $data){
				if($rel_name != false && $name != $rel_name || isset($obj->__relations[$name]))
					continue; // skip if tablename is not false and don't match, or if we already have loaded that one
				$table =& $data['table'];
				if($data['model'] != ''){
					// we have a model, load the specific model
					if(!in_array($data['model'],get_object_vars($CI)))
						$CI->load->model($data['model']);
					// use that model to instantiate the objects
					$model_inst =& $CI->$data['model'];
					// use tablename (plural), because it has 'many'
					$obj->__relations[$name] =& $model_inst->find_all_by($data['col'], $obj->__id);
					if(!isset($obj->$name) && count($obj->__relations[$name])){
						$obj->$name =& $obj->__relations[$name];
						$obj->__relation_properties[] = $name;
					}
				}
				else{
					// no model = normal orm OBJ
					// use tablename (plural), because it has 'many'
					$obj->__relations[$name] = array();
					$query = $this->db->get_where($table,array($data['col'] => $obj->__id));
					foreach($query->result_array() as $row){
						$ORM =& $this->_dbobj2ORM($row,false);
						$ORM->__table = $table;
						unset($ORM->__instance);
						$obj->__relations[$name][] =& $ORM;
					}
					if(!isset($obj->$name) && count($obj->__relations[$name])){
						$obj->$name =& $obj->__relations[$name];
						$obj->__relation_properties[] = $name;
					}
				}
			}
		}
		// Has One relationship
		// Stores the data in obj->modelname (or singular of tablename) as default
		if($rel_name == false || in_array($rel_name,array_keys($this->__has_one))){
			foreach($this->__has_one as $name => $data) {
				if($rel_name != false && $name != $rel_name || isset($obj->__relations[$name]))
					continue; // skip if tablename is not false and don't match, or if we already have loaded that one
				$table =& $data['table'];
				if($data['model'] != ''){
					// we have a model, load the specific model
					if(!in_array($data['model'],get_object_vars($CI)))
						$CI->load->model($data['model']);
					// use that model to instantiate the objects
					$model_inst =& $CI->$data['model'];
					$obj->__relations[$name] =& $model_inst->find_by($data['col'],$obj->__id);
					if(!isset($obj->$data['model'])){
						$obj->$data['model'] =& $obj->__relations[$name];
						$obj->__relation_properties[] = $data['model'];
					}
				}
				else{
					// no model = normal orm OBJ
					$query = $this->db->get_where($table,array($data['col'] => $obj->__id),1);
					$obj->__relations[$name] =& $this->_dbobj2ORM($query->row_array(),false);
					$obj->__relations[$name]->__table = $table;
					unset($obj->__relations[$name]->__instance);
					if(!isset($obj->$model) && $obj->__relations[$name] != false){
						$obj->$name =& $obj->__relations[$name];
						$obj->__relation_properties[] = $name;
					}
				}
			}
		}
		// Has And Belongs To Many relationship
		// Stores the data in an array in obj->tablename (tablename is plural)
		if($rel_name == false || in_array($rel_name,array_keys($this->__habtm))){
			foreach($this->__habtm as $name => $data){
				if($rel_name != false && $name != $rel_name || isset($obj->__relations[$name]))
					continue; // skip if tablename is not false and don't match, or if we already have loaded that one
				// get data from relations table
				$query = $this->db->get_where($data['table'],array($data['this_col'] => $obj->__id));
				if($data['model'] != ''){
					// we have a model, load the specific model
					if(!in_array($data['model'],get_object_vars($CI)))
						$CI->load->model($data['model']);
					// use that model to instantiate the objects
					$model_inst =& $CI->$data['model'];
					// use tablename (plural), because it has 'many'
					$obj->__relations[$name] = array();
					// iterate through and load all rows into objects
					foreach($query->result_array() as $row){
						$orm =& $model_inst->find($row[$data['other_col']]);
						if($orm != false)
							$obj->__relations[$name][] =& $orm;
					}
					if(!isset($obj->$name) && count($obj->__relations[$name])){
						$obj->$name =& $obj->__relations[$name];
						$obj->__relation_properties[] = $name;
					}
				}
				else{
					// no model = normal orm OBJ
					$ids = array();
					// create id array
					foreach($query->result_array() as $row){
						$ids[] = $row[$data['other_col']];
					}
					if(count($ids)){
						// assumes that id is the unique id
						$this->db->where_in('id',$ids);
						$query = $this->db->get($data['other_table']);
						// use tablename (plural), because it has 'many'
						$obj->__relations[$name] = array();
						foreach($query->result_array() as $row){
							$ORM =& $this->_dbobj2ORM($row,false);
							$ORM->__table = $data['other_table'];
							unset($ORM->__instance);
							$obj->__relations[$name][] =& $ORM;
						}
						if(!isset($obj->$name) && count($obj->__relations[$name])){
							$obj->$name =& $obj->__relations[$name];
							$obj->__relation_properties[] = $name;
						}
					}
				}
			}
		}
	}
	/**
	 * Establishes a relationship between the two supplied objects.
	 * 
	 * Determines which method that shall be used; Belongs To, Has Many, Has One or Has And Belongs To Many. \n
	 * Performs some checking of input
	 * 
	 * @param $child An IgnitedRecord_record created by this IgnitedRecord model
	 * @param $object Another IgnitedRecord_record (must have an instance tied to it)
	 * @param $rel_name The name of the relationship, leave blank if to auto determine
	 */
	function establish_relationship(&$child, &$object, $rel_name = false){
		if(!isset($child->__instance) || $child->instance->__table != $this->__table){
			log_message('warning','An incompatible object was supplied to an IgnitedRecord object belonging to the table '. $child->__table);
			return;
		}
		if(!isset($object->__instance)){
			log_message('warning','An incompatible object was supplied to an IgnitedRecord object belonging to the table '. $object->__table);
			return;
		}
		if($rel_name == false){
			$rel_name = $this->__related_tables[$object->__table];
		}
		// Belongs To
		if(in_array($rel_name, array_keys($child->__instance->__belongs_to)))
			$this->establish_belongs_to_relationship($child->__instance->__belongs_to[$rel_name],$child,$object);
		// Has Many
		elseif(in_array($rel_name, array_keys($child->__instance->__has_many)))
			$this->establish_has_many_relationship($child->__instance->__has_many[$rel_name], $child,$object);
		// Has One
		elseif(in_array($rel_name, array_keys($child->__instance->__has_one)))
			$this->establish_has_one_relationship($child->__instance->__has_one[$rel_name], $child,$object);
		// Has And Belongs To Many
		elseif(in_array($rel_name, array_keys($child->__instance->__habtm)))
			$this->establish_habtm_relationship($child->__instance->__habtm[$rel_name],$child,$object);
	}
	/**
	 * Establishes a Belongs To relationship between $child and $object.
	 * 
	 * @note No checking if a relationship are defined between the two.
	 * 
	 * @param $rel The relationship data array
	 * @param $child An IgnitedRecord_record created by this IgnitedRecord model
	 * @param $object Another IgnitedRecord_record (must have an instance tied to it)
	 */
	function establish_belongs_to_relationship($rel, &$child, &$object){
		// $child Belongs To $object
		if($rel['table'] != $object->__table){
			// no matching table
			log_message('error','IgnitedRecord: The table '.$child->__table.' has not got a relation with the table '.$object->__table);
			return;
		}
		$column = $rel['col'];
		if(!isset($object->__id)){
			// object does not exist in database, save it
			$object->save();
		}
		// object now exists in database
		$child->$column = $object->__id;
		$child->save();
	}
	/**
	 * Establishes a Has Many relationship between $child and $object.
	 * 
	 * @note No checking if a relationship are defined between the two.
	 * 
	 * @param $rel The relationship data array
	 * @param $child An IgnitedRecord_record created by this IgnitedRecord model
	 * @param $object Another IgnitedRecord_record (must have an instance tied to it)
	 */
	function establish_has_many_relationship($rel, &$child, &$object){
		// $child Has Many of $object type
		// Like an inverted Belongs To relationship
		if($rel['table'] != $object->__table){
			// no matching table
			log_message('error','IgnitedRecord: The table '.$child->__table.' has not got a relation with the table '.$object->__table);
			return;
		}
		$column = $rel['col'];
		if(!isset($child->__id)){
			// this object does not exist in database, save it
			$child->save();
		}
		// this object now exists in database
		$object->$column = $child->__id;
		$object->save();
	}
	/**
	 * Establishes a Has One relationship between $child and $object.
	 * 
	 * @note No checking if a relationship are defined between the two.
	 * 
	 * @param $rel The relationship data array
	 * @param $child An IgnitedRecord_record created by this IgnitedRecord model
	 * @param $object Another IgnitedRecord_record (must have an instance tied to it)
	 */
	function establish_has_one_relationship($rel, &$child, &$object){
		// $child Has One of $object type
		if($rel['table'] != $object->__table){
			// no matching table
			log_message('error','IgnitedRecord: The table '.$child->__table.' has not got a relation with the table '.$object->__table);
			return;
		}
		$column = $rel['col'];
		if(!isset($child->__id)){
			$child->save();
		}
		else{
			$related =& $object->__instance->find_all_by($column,$child->__id);
			// remove all relationships to related objects in that table
			foreach($related as $r){
				$r->$column = null;
				$r->save();
			}
		}
		$object->$column = $child->__id;
		$object->save();
	}
	/**
	 * Establishes a Has And Belongs To Many relationship between $child and $object.
	 * 
	 * @param $rel The relationship data array
	 * @param $child An IgnitedRecord_record created by this IgnitedRecord model
	 * @param $object Another IgnitedRecord_record (must have an instance tied to it)
	 */
	function establish_habtm_relationship($rel, &$child, &$object){
		if($rel['other_table'] != $object->__table){
			// no matching table
			log_message('error','IgnitedRecord: The table '.$child->__table.' has not got a relation with the table '.$object->__table);
			return;
		}
		if(!isset($child->__id)){
			$child->save();
		}
		if(!isset($object->__id)){
			$object->save();
		}
		$this->db->where($rel['this_col'],$child->__id);
		$this->db->where($rel['other_col'],$object->__id);
		$query = $this->db->get($rel['table']);
		if(!$query->num_rows()){
			// no relationship established
			$data[$rel['this_col']] = $child->__id;
			$data[$rel['other_col']] = $object->__id;
			$this->db->insert($rel['table'],$data);
		}
	}
	/**
	 * Removes the relationship between the two supplied objects if it exists.
	 * 
	 * Determines which method that shall be used; Belongs To, Has Many, Has One or Has And Belongs To Many. \n
	 * Performs some checking of input
	 * 
	 * @param $child An IgnitedRecord_record created by this IgnitedRecord model
	 * @param $object Another IgnitedRecord_record (must have an instance tied to it)
	 * @param $rel_name The name of the relationship, leave blank if to auto determine
	 */
	function remove_relationship(&$child, &$object, $rel_name = false){
		if(!isset($child->__instance) || $child->instance->__table != $this->__table){
			log_message('warning','An incompatible object was supplied to an IgnitedRecord object belonging to the table '. $child->__table);
			return;
		}
		if(!isset($object->__instance)){
			log_message('warning','An incompatible object was supplied to an IgnitedRecord object belonging to the table '. $object->__table);
			return;
		}
		if($rel_name == false){
			$rel_name = $this->__related_tables[$object->__table];
		}
		// Belongs To
		if(in_array($rel_name, array_keys($child->__instance->__belongs_to)))
			$this->remove_belongs_to_relationship($child->__instance->__belongs_to[$rel_name],$child,$object);
		// Has Many
		elseif(in_array($rel_name, array_keys($child->__instance->__has_many)))
			$this->remove_has_many_relationship($child->__instance->__has_many[$rel_name],$child,$object);
		// Has One
		elseif(in_array($rel_name, array_keys($child->__instance->__has_one)))
			$this->remove_has_one_relationship($child->__instance->__has_one[$rel_name],$child,$object);
		elseif(in_array($object->__table, array_keys($child->__instance->__habtm)))
			$this->remove_habtm_relationship($child->__instance->__habtm[$rel_name],$child,$object);
	}
	/**
	 * Removes a Belongs To relationship between $child and $object, if it exists.
	 * 
	 * @note No checking if a relationship are defined between the two.
	 * 
	 * @param $rel The relationship data array
	 * @param $child An IgnitedRecord_record created by this IgnitedRecord model
	 * @param $object Another IgnitedRecord_record (must have an instance tied to it)
	 */
	function remove_belongs_to_relationship($rel, &$child, &$object){
		// $child Belongs To $object
		if($rel['table'] != $object->__table){
			// no matching table
			log_message('error','IgnitedRecord: The table '.$child->__table.' has not got a relation with the table '.$object->__table);
			return;
		}
		$column = $rel['col'];
		if(!isset($object->__id) || !isset($child->$column))
			return;
		if($child->$column == $object->__id){
			$child->$column = null;
			$child->save();
		}
	}
	/**
	 * Removes a Has Many relationship between $child and $object, if it exists.
	 * 
	 * @note No checking if a relationship are defined between the two.
	 * 
	 * @param $rel The relationship data array
	 * @param $child An IgnitedRecord_record created by this IgnitedRecord model
	 * @param $object Another IgnitedRecord_record (must have an instance tied to it)
	 */
	function remove_has_many_relationship($rel, &$child, &$object){
		// $child Has Many of $object type
		// like an inverted Belongs To relationship
		if($rel['table'] != $object->__table){
			// no matching table
			log_message('error','IgnitedRecord: The table '.$child->__table.' has not got a relation with the table '.$object->__table);
			return;
		}
		$column = $rel['col'];
		if(!isset($child->__id) || !isset($object->$column))
			return;
		if($object->$column == $child->__id){
			$object->$column = null;
			$object->save();
		}
	}
	/**
	 * Removes a Has One relationship between $child and $object, if it exists.
	 * 
	 * @note No checking if a relationship are defined between the two.
	 * 
	 * @param $rel The relationship data array
	 * @param $child An IgnitedRecord_record created by this IgnitedRecord model
	 * @param $object Another IgnitedRecord_record (must have an instance tied to it)
	 */
	function remove_has_one_relationship($rel, &$child, &$object){
		// $child Has One of $object type
		if($rel['table'] != $object->__table){
			// no matching table
			log_message('error','IgnitedRecord: The table '.$child->__table.' has not got a relation with the table '.$object->__table);
			return;
		}
		$column = $rel['col'];
		// works exactly like Has Many (only when establishing a relationship is it different)
		if(!isset($child->__id) || !isset($object->$column))
			return;
		if($object->$column == $child->__id){
			$object->$column = null;
			$object->save();
		}
	}
	/**
	 * Removes a Has And Belongs To Many relationship between $child and $object, if it exists.
	 * 
	 * @note No checking if a relationship are defined between the two.
	 * 
	 * @param $rel The relationship data array
	 * @param $child An IgnitedRecord_record created by this IgnitedRecord model
	 * @param $object Another IgnitedRecord_record (must have an instance tied to it)
	 */
	function remove_habtm_relationship($rel, &$child, &$object){
		if($rel['other_table'] != $object->__table){
			// no matching table
			log_message('error','IgnitedRecord: The table '.$child->__table.' has not got a relation with the table '.$object->__table);
			return;
		}
		if(!isset($child->__id) || !isset($object->__id))
			return;
		$this->db->where($rel['this_col'],$child->__id);
		$this->db->where($rel['other_col'],$object->__id);
		$this->db->delete($rel['table']);
	}
	
	//////////////////////////////////////////////
	//    Private methods
	//////////////////////////////////////////////
	
	/**
	 * Creates an IgnitedRecord_record object from the supplied db row.
	 * 
	 * @param $data The data to load from (array)
	 * @param $dynamic If to load the object dynamically, true uses the user defined class otherwise it uses the IgnitedRecord_record
	 * 
	 * @return A populated IgnitedRecord_record (or descendant of it)
	 */
	function &_dbobj2ORM($data,$dynamic = true){
		$class = $this->__child_class;
		if($dynamic)
			$obj = new $class($this,$data); // PHP 4: $obj =& new $class($this,$data);
		else
			$obj = new IgnitedRecord_record($this,$data); // PHP 4: $obj =& new IgnitedRecord_record($this,$data);
		if($this->__auto_load_relationships)
			$obj->load_rel();
		foreach($this->__child_class_helpers as $name => $hclass){
			$obj->$name = new $hclass($obj); // PHP 4: $obj->$name =& new $hclass($obj);
		}
		return $obj;
	}
	/**
	 * Removes all properties that are not data.
	 * 
	 * Skips the relation properties, the child helpers
	 * instance variables (tied to IgnitedRecord and other classes)
	 * and also the id column.
	 * 
	 * @param $object The object to be cleaned
	 * 
	 * @return An associative array containing references to the data in the object,
	 * key is property name and value is value of the property
	 */
	function &_strip_data(&$object){
		$columns = $this->db->list_fields($this->__table);
		$data = array();
		foreach(get_object_vars($object) as $property => $value){
			if(in_array($property, $columns))
				$data[$property] = $value;
		}
		return $data;
	}
	/**
	 * Normalizes the relationship data structure in the $data to a standard for belong to relationships.
	 * 
	 * By reference.\n
	 * Normalizes belong to relationship data into this form:
	 * @code
	 * array('relation_name' => array('table' => 'tablename', 
	 *                                'model' => 'modelname',
	 *                                'col' => 'foreign_key_column_name'));
	 * @endcode
	 * 
	 * If value is not an array, it is assumed that it is the column name for the foreign key
	 * 
	 * @param $data The data property
	 * @param $is_singular If the relationship name shall be singular (ie for has one relations)
	 */
	function _normalize_rel(&$data,$is_singular){
		if(is_string($data)){
			$data = array($this->_rel_name($data,$is_singular) => array('table' => $data, 'model' => $this->_get_modelname($data), 'col' => $this->__table.'_id'));
		}
		elseif(is_array($data)){
			$newar = array();
			foreach($data as $key => $value) {
				if(is_numeric($key)){
					// we have a numeric index, no options supplied
					$newar[$this->_rel_name($value,$is_singular)] = array('table' => $value, 'model' => $this->_get_modelname($key), 'col' => $this->__table.'_id');
				}
				elseif(!is_array($value)){
					$newar[$this->_rel_name($key,$is_singular)] = array('table' => $key, 'model' => $this->_get_modelname($key), 'col' => $value);
				}
				else{
					if(!isset($value['name'])){
						$rel_name = $this->_rel_name($key,$is_singular);
					}
					else{
						$rel_name = $value['name'];
					}
					if(!isset($value['model'])){
						$newar[$rel_name]['model'] = $this->_get_modelname($key);
					}
					else{
						$newar[$rel_name]['model'] = $value['model'];
					}
					if(!isset($value['col'])){
						$newar[$rel_name]['col'] = $this->__table.'_id';
					}
					else{
						$newar[$rel_name]['col'] = $value['col'];
					}
					$newar[$rel_name]['table'] = $key;
				}
			}
			$data = $newar;
		}
		else{
			$data = array();
		}
		// add the tablenames and their relation name so we can figure out it easy if we need to
		foreach($data as $name => $value){
			$this->__related_tables[$value['table']] = $name;
		}
	}
	/**
	 * Normalizes the relationship data structure in the $data to a standard for has- relationships.
	 * 
	 * By reference.\n
	 * Normalizes has relationship data into this form:
	 * @code
	 * array('relation_name' => array('table' => 'tablename', 
	 *                                'model' => 'modelname',
	 *                                'col' => 'foreign_key_column_name'));
	 * @endcode
	 * The difference to has relationships is that belong to relationships stores the foreign key in this table
	 * (and hence the foreign key column differs).
	 * 
	 * If value is not an array, it is assumed that it is the column name for the foreign key
	 * 
	 * @param $data The data property
	 */
	function _normalize_rel_belong(&$data){
		if(is_string($data)){
			$data = array($this->_rel_name($data,true) => array('table' => $data, 'model' => $this->_get_modelname($data), 'col' => $data.'_id'));
		}
		elseif(is_array($data)){
			$newar = array();
			foreach($data as $key => $value) {
				if(is_numeric($key)){
					// we have a numeric index, no options supplied
					$newar[$this->_rel_name($value,true)] = array('table' => $value, 'model' => $this->_get_modelname($key), 'col' => $value.'_id');
				}
				elseif(!is_array($value)){
					$newar[$this->_rel_name($key,true)] = array('table' => $key, 'model' => $this->_get_modelname($key), 'col' => $value);
				}
				else{
					if(!isset($value['name'])){
						$rel_name = $this->rel_name2($key,true);
					}
					else{
						$rel_name = $value['name'];
					}
					if(!isset($value['model'])){
						$newar[$rel_name]['model'] = $this->_get_modelname($key);
					}
					else{
						$newar[$rel_name]['model'] = $value['model'];
					}
					if(!isset($value['col'])){
						$newar[$rel_name]['col'] = $key.'_id';
					}
					else{
						$newar[$rel_name]['col'] = $value['col'];
					}
					$newar[$rel_name]['table'] = $key;
				}
			}
			$data = $newar;
		}
		else{
			$data = array();
		}
		// add the tablenames and their relation name so we can figure out it easy if we need to
		foreach($data as $name => $value){
			$this->__related_tables[$value['table']] = $name;
		}
	}
	/**
	 * Normalizes Has And Belongs To Many relationship data.
	 * 
	 * By reference.\n
	 * Normalizes has relationship data into this form:
	 * @code
	 * array('relation_name' => array('other_table' => 'othertablename',
	 *                                'table' => 'relation_tablename',
	 *                                'this_col' => 'this_table_foreign_key_column',
	 *                                'other_col' => 'other_table_foreign_key_column',
	 *                                'model' => 'modelname_for_other_table'
	 *                               ));
	 * @endcode
	 * 
	 * If value is not an array, it is assumed that it is the table name for the table storing the relations
	 * 
	 * @param $data The property holding Has And Belongs To Many data
	 */
	function _normalize_rel_habtm(&$data){
		if(is_string($data)){
			$table = strcmp($this->__table, $data) < 0 ?
					 $this->__table.'_'.$data :
					 $data.'_'.$this->__table;
			$data = array($this->_rel_name($data,false)    // the other table
				  => array('table' => $table, // the tablename
				  		   'other_table' => $data,
						   'this_col' => $this->__table.'_id', // the foreign key column referring to this table
						   'other_col' => $data.'_id', // the foreign key column referring to the other table
						   'model' => $this->_get_modelname($data) // the model for the other table
						  ));
		}
		elseif(is_array($data)){
			$newar = array();
			foreach($data as $key => $value){
				if(is_numeric($key)){
					$table = strcmp($this->__table, $value) < 0 ?
									$this->__table.'_'.$value :
									$value.'_'.$this->__table;
					// we have a numeric index
					$newar[$this->_rel_name($value,false)]
					  = array('table' => $table,
							  'other_table' => $value,
							  'this_col' => $this->__table.'_id',
							  'other_col' => $value.'_id',
							  'model' => $this->_get_modelname($value)
							 );
				}
				elseif(!is_array($value)){
					// we have a relation table name as value
					$newar[$this->_rel_name($key,false)]
					  = array('table' => $value,
							  'other_table' => $key,
							  'this_col' => $this->__table.'_id',
							  'other_col' => $key.'_id',
							  'model' => $this->_get_modelname($key)
							 );
				}
				else{
					if(!isset($value['name'])){
						$rel_name = $this->_rel_name($key,false);
					}
					else{
						$rel_name = $value['name'];
					}
					if(!isset($value['table'])){
						$newar[$rel_name]['table'] = strcmp($this->__table, $key) < 0 ?
									$this->__table.'_'.$key :
									$key.'_'.$this->__table;
					}
					else{
						$newar[$rel_name]['table'] = $value['table'];
					}
					if(!isset($value['this_col'])){
						$newar[$rel_name]['this_col'] = $this->__table.'_id';
					}
					else{
						$newar[$rel_name]['this_col'] = $value['this_col'];
					}
					if(!isset($value['other_col'])){
						$newar[$rel_name]['other_col'] = $key.'_id';
					}
					else{
						$newar[$rel_name]['other_col'] = $value['other_col'];
					}
					if(!isset($value['model'])){
						$newar[$rel_name]['model'] = $this->_get_modelname($key);
					}
					else{
						$newar[$rel_name]['model'] = $value['model'];
					}
					$newar[$rel_name]['other_table'] = $key;
				}
			}
			$data = $newar;
		}
		else{
			$data = array();
		}
		// add the tablenames and their relation name so we can figure out it easy if we need to
		foreach($data as $name => $value){
			$this->__related_tables[$value['other_table']] = $name;
		}
	}
	/**
	 * Returns the modelname of the tablename.
	 * 
	 * Tries if a model with $tablename exists, then tries with singular form of the $tablename. \n
	 * Called if no model is defined for a table.
	 * 
	 * @param $tablename The tablename to find a modelname of.
	 * 
	 * @return The modelname or '' if no model is found
	 */
	function _get_modelname($tablename){
		$tablename = strtolower($tablename);
		$model = '';
		if(file_exists(APPPATH.'/models/'.$tablename.EXT)){
			$model = $tablename;
		}
		elseif(file_exists(APPPATH.'/models/'.singular($tablename).EXT)){
			$model = singular($tablename);
		}
		return $model;
	}
	/**
	 * Returns the default name of the relationship.
	 * 
	 * @param $name The tablename
	 * @param $is_singular If the relationship relates to a sigle node
	 */
	function _rel_name($name,$is_singular){
		if($is_singular)
			return $this->_get_modelname($name) != '' ? $this->_get_modelname($name) : singular($name);
		else
			return $name;
	}
	/**
	 * Combines two non asociative arrays to one asociative.
	 * 
	 * Exists in PHP 5, but not in 4
	 * 
	 * @param $keys The array with the keys
	 * @param $values The array with the values
	 * 
	 * @return An asociative array with the keys from $keys and the values from $values
	 */
	function _array_combine($keys,$values) {
		if(function_exists('array_combine'))
			return array_combine($keys,$values); // PHP 5
		$out = array();
		foreach($keys as $key1 => $value1){
			$out[$value1] = $values[$key1];
		}
		return $out;
	}
}
/**
 * @}
 */
?>