diff --git a/ActiveRecord.php b/ActiveRecord.php
index 084587547..bbfb62bad 100644
--- a/ActiveRecord.php
+++ b/ActiveRecord.php
@@ -1,50 +1,60 @@
-<?php
-if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300)
-	die('PHP ActiveRecord requires PHP 5.3 or higher');
-
-define('PHP_ACTIVERECORD_VERSION_ID','1.0');
-
-if (!defined('PHP_ACTIVERECORD_AUTOLOAD_PREPEND'))
-	define('PHP_ACTIVERECORD_AUTOLOAD_PREPEND',true);
-
-require __DIR__.'/lib/Singleton.php';
-require __DIR__.'/lib/Config.php';
-require __DIR__.'/lib/Utils.php';
-require __DIR__.'/lib/DateTimeInterface.php';
-require __DIR__.'/lib/DateTime.php';
-require __DIR__.'/lib/Model.php';
-require __DIR__.'/lib/Table.php';
-require __DIR__.'/lib/ConnectionManager.php';
-require __DIR__.'/lib/Connection.php';
-require __DIR__.'/lib/Serialization.php';
-require __DIR__.'/lib/SQLBuilder.php';
-require __DIR__.'/lib/Reflections.php';
-require __DIR__.'/lib/Inflector.php';
-require __DIR__.'/lib/CallBack.php';
-require __DIR__.'/lib/Exceptions.php';
-require __DIR__.'/lib/Cache.php';
-
-if (!defined('PHP_ACTIVERECORD_AUTOLOAD_DISABLE'))
-	spl_autoload_register('activerecord_autoload',false,PHP_ACTIVERECORD_AUTOLOAD_PREPEND);
-
-function activerecord_autoload($class_name)
-{
-	$path = ActiveRecord\Config::instance()->get_model_directory();
-	$root = realpath(isset($path) ? $path : '.');
-
-	if (($namespaces = ActiveRecord\get_namespaces($class_name)))
-	{
-		$class_name = array_pop($namespaces);
-		$directories = array();
-
-		foreach ($namespaces as $directory)
-			$directories[] = $directory;
-
-		$root .= DIRECTORY_SEPARATOR . implode($directories, DIRECTORY_SEPARATOR);
-	}
-
-	$file = "$root/$class_name.php";
-
-	if (file_exists($file))
-		require_once $file;
-}
\ No newline at end of file
+<?php
+if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300)
+	die('PHP ActiveRecord requires PHP 5.3 or higher');
+
+define('PHP_ACTIVERECORD_VERSION_ID','1.0');
+
+if (!defined('PHP_ACTIVERECORD_AUTOLOAD_PREPEND'))
+	define('PHP_ACTIVERECORD_AUTOLOAD_PREPEND',true);
+
+require __DIR__.'/lib/Singleton.php';
+require __DIR__.'/lib/Config.php';
+require __DIR__.'/lib/Utils.php';
+require __DIR__.'/lib/DateTimeInterface.php';
+require __DIR__.'/lib/DateTime.php';
+require __DIR__.'/lib/Model.php';
+require __DIR__.'/lib/Table.php';
+require __DIR__.'/lib/ConnectionManager.php';
+require __DIR__.'/lib/Connection.php';
+require __DIR__.'/lib/ConnectionInfo.php';
+require __DIR__.'/lib/Serialization.php';
+require __DIR__.'/lib/Expressions.php';
+require __DIR__.'/lib/SQLBuilder.php';
+require __DIR__.'/lib/Reflections.php';
+require __DIR__.'/lib/Inflector.php';
+require __DIR__.'/lib/CallBack.php';
+require __DIR__.'/lib/Exceptions.php';
+require __DIR__.'/lib/Cache.php';
+require __DIR__.'/lib/StrongParameters.php';
+
+if (!defined('PHP_ACTIVERECORD_AUTOLOAD_DISABLE'))
+	spl_autoload_register('activerecord_autoload',false,PHP_ACTIVERECORD_AUTOLOAD_PREPEND);
+
+function activerecord_autoload($class_name)
+{
+	$paths = ActiveRecord\Config::instance()->get_model_directories();
+	$namespace_directory = '';
+	if (($namespaces = ActiveRecord\get_namespaces($class_name)))
+	{
+		$class_name = array_pop($namespaces);
+		$directories = array();
+
+		foreach ($namespaces as $directory)
+			$directories[] = $directory;
+
+		$namespace_directory = DIRECTORY_SEPARATOR . implode($directories, DIRECTORY_SEPARATOR);
+	}
+	$paths = count($paths) ? $paths : array('.');
+	
+	foreach($paths as $path)
+	{
+		$root = realpath($path);
+		$file = "{$root}{$namespace_directory}/{$class_name}.php";
+		
+		if (file_exists($file))
+		{
+			require $file;
+			return;
+		}
+	}
+}
diff --git a/README.md b/README.md
index 9f7fe5a4a..04389392a 100755
--- a/README.md
+++ b/README.md
@@ -63,14 +63,17 @@ Example:
 ```php
 ActiveRecord\Config::initialize(function($cfg)
 {
-   $cfg->set_model_directory('/path/to/your/model_directory');
-   $cfg->set_connections(
-     array(
-       'development' => 'mysql://username:password@localhost/development_database_name',
-       'test' => 'mysql://username:password@localhost/test_database_name',
-       'production' => 'mysql://username:password@localhost/production_database_name'
-     )
-   );
+    $cfg->set_model_directories(array(
+      '/path/to/your/model_directory',
+      '/some/other/path/to/your/model_directory'
+    ));
+    $cfg->set_connections(
+      array(
+        'development' => 'mysql://username:password@localhost/development_database_name',
+        'test' => 'mysql://username:password@localhost/test_database_name',
+        'production' => 'mysql://username:password@localhost/production_database_name'
+      )
+    );
 });
 ```
 
diff --git a/examples/orders/models/Order.php b/examples/orders/models/Order.php
index 642943c4b..8c0464c28 100644
--- a/examples/orders/models/Order.php
+++ b/examples/orders/models/Order.php
@@ -34,4 +34,3 @@ public function apply_tax()
 		$this->tax = $this->price * $tax;
 	}
 }
-?>
diff --git a/examples/orders/models/Payment.php b/examples/orders/models/Payment.php
index faef5ef91..777f2b6a0 100644
--- a/examples/orders/models/Payment.php
+++ b/examples/orders/models/Payment.php
@@ -6,4 +6,3 @@ class Payment extends ActiveRecord\Model
 		array('person'),
 		array('order'));
 }
-?>
diff --git a/examples/orders/models/Person.php b/examples/orders/models/Person.php
index b2e586064..da8a6e550 100644
--- a/examples/orders/models/Person.php
+++ b/examples/orders/models/Person.php
@@ -10,4 +10,3 @@ class Person extends ActiveRecord\Model
 	static $validates_presence_of = array(
 		array('name'), array('state'));
 }
-?>
diff --git a/examples/orders/orders.php b/examples/orders/orders.php
index 8222ad448..44815b820 100644
--- a/examples/orders/orders.php
+++ b/examples/orders/orders.php
@@ -76,4 +76,4 @@
 
 foreach ($order->people as $person)
 	echo "  payment of $$person->amount by " . $person->name . "\n";
-?>
+
diff --git a/examples/simple/simple.php b/examples/simple/simple.php
index 0acff65d2..537919d4d 100644
--- a/examples/simple/simple.php
+++ b/examples/simple/simple.php
@@ -14,4 +14,3 @@ class Book extends ActiveRecord\Model { }
 });
 
 print_r(Book::first()->attributes());
-?>
diff --git a/examples/simple/simple_with_options.php b/examples/simple/simple_with_options.php
index 1056435f7..6e42fca92 100644
--- a/examples/simple/simple_with_options.php
+++ b/examples/simple/simple_with_options.php
@@ -29,4 +29,3 @@ class Book extends ActiveRecord\Model
 });
 
 print_r(Book::first()->attributes());
-?>
diff --git a/lib/Column.php b/lib/Column.php
index db6c0a3a6..3e583ef31 100644
--- a/lib/Column.php
+++ b/lib/Column.php
@@ -1,201 +1,205 @@
-<?php
-/**
- * @package ActiveRecord
- */
-namespace ActiveRecord;
-
-/**
- * Class for a table column.
- *
- * @package ActiveRecord
- */
-class Column
-{
-	// types for $type
-	const STRING	= 1;
-	const INTEGER	= 2;
-	const DECIMAL	= 3;
-	const DATETIME	= 4;
-	const DATE		= 5;
-	const TIME		= 6;
-
-	/**
-	 * Map a type to an column type.
-	 * @static
-	 * @var array
-	 */
-	static $TYPE_MAPPING = array(
-		'datetime'	=> self::DATETIME,
-		'timestamp'	=> self::DATETIME,
-		'date'		=> self::DATE,
-		'time'		=> self::TIME,
-
-		'tinyint'	=> self::INTEGER,
-		'smallint'	=> self::INTEGER,
-		'mediumint'	=> self::INTEGER,
-		'int'		=> self::INTEGER,
-		'bigint'	=> self::INTEGER,
-
-		'float'		=> self::DECIMAL,
-		'double'	=> self::DECIMAL,
-		'numeric'	=> self::DECIMAL,
-		'decimal'	=> self::DECIMAL,
-		'dec'		=> self::DECIMAL);
-
-	/**
-	 * The true name of this column.
-	 * @var string
-	 */
-	public $name;
-
-	/**
-	 * The inflected name of this columns .. hyphens/spaces will be => _.
-	 * @var string
-	 */
-	public $inflected_name;
-
-	/**
-	 * The type of this column: STRING, INTEGER, ...
-	 * @var integer
-	 */
-	public $type;
-
-	/**
-	 * The raw database specific type.
-	 * @var string
-	 */
-	public $raw_type;
-
-	/**
-	 * The maximum length of this column.
-	 * @var int
-	 */
-	public $length;
-
-	/**
-	 * True if this column allows null.
-	 * @var boolean
-	 */
-	public $nullable;
-
-	/**
-	 * True if this column is a primary key.
-	 * @var boolean
-	 */
-	public $pk;
-
-	/**
-	 * The default value of the column.
-	 * @var mixed
-	 */
-	public $default;
-
-	/**
-	 * True if this column is set to auto_increment.
-	 * @var boolean
-	 */
-	public $auto_increment;
-
-	/**
-	 * Name of the sequence to use for this column if any.
-	 * @var boolean
-	 */
-	public $sequence;
-
-	/**
-	 * Cast a value to an integer type safely
-	 *
-	 * This will attempt to cast a value to an integer,
-	 * unless its detected that the casting will cause
-	 * the number to overflow or lose precision, in which
-	 * case the number will be returned as a string, so
-	 * that large integers (BIGINTS, unsigned INTS, etc)
-	 * can still be stored without error
-	 *
-	 * This would ideally be done with bcmath or gmp, but
-	 * requiring a new PHP extension for a bug-fix is a
-	 * little ridiculous
-	 *
-	 * @param mixed $value The value to cast
-	 * @return int|string type-casted value
-	 */
-	public static function castIntegerSafely($value)
-	{
-		if (is_int($value))
-			return $value;
-
-		// Its just a decimal number
-		elseif (is_numeric($value) && floor($value) != $value)
-			return (int) $value;
-
-		// If adding 0 to a string causes a float conversion,
-		// we have a number over PHP_INT_MAX
-		elseif (is_string($value) && is_float($value + 0))
-			return (string) $value;
-
-		// If a float was passed and its greater than PHP_INT_MAX
-		// (which could be wrong due to floating point precision)
-		// We'll also check for equal to (>=) in case the precision
-		// loss creates an overflow on casting
-		elseif (is_float($value) && $value >= PHP_INT_MAX)
-			return number_format($value, 0, '', '');
-
-		return (int) $value;
-	}
-
-	/**
-	 * Casts a value to the column's type.
-	 *
-	 * @param mixed $value The value to cast
-	 * @param Connection $connection The Connection this column belongs to
-	 * @return mixed type-casted value
-	 */
-	public function cast($value, $connection)
-	{
-		if ($value === null)
-			return null;
-
-		switch ($this->type)
-		{
-			case self::STRING:	return (string)$value;
-			case self::INTEGER:	return static::castIntegerSafely($value);
-			case self::DECIMAL:	return (double)$value;
-			case self::DATETIME:
-			case self::DATE:
-				if (!$value)
-					return null;
-
-				$date_class = Config::instance()->get_date_class();
-
-				if ($value instanceof $date_class)
-					return $value;
-
-				if ($value instanceof \DateTime)
-					return $date_class::createFromFormat(
-						Connection::DATETIME_TRANSLATE_FORMAT,
-						$value->format(Connection::DATETIME_TRANSLATE_FORMAT),
-						$value->getTimezone()
-					);
-
-				return $connection->string_to_datetime($value);
-		}
-		return $value;
-	}
-
-	/**
-	 * Sets the $type member variable.
-	 * @return mixed
-	 */
-	public function map_raw_type()
-	{
-		if ($this->raw_type == 'integer')
-			$this->raw_type = 'int';
-
-		if (array_key_exists($this->raw_type,self::$TYPE_MAPPING))
-			$this->type = self::$TYPE_MAPPING[$this->raw_type];
-		else
-			$this->type = self::STRING;
-
-		return $this->type;
-	}
-}
+<?php
+/**
+ * @package ActiveRecord
+ */
+namespace ActiveRecord;
+
+/**
+ * Class for a table column.
+ *
+ * @package ActiveRecord
+ */
+class Column
+{
+	// types for $type
+	const STRING	= 1;
+	const INTEGER	= 2;
+	const DECIMAL	= 3;
+	const DATETIME	= 4;
+	const DATE		= 5;
+	const TIME		= 6;
+	const BOOLEAN	= 7;
+
+	/**
+	 * Map a type to an column type.
+	 * @static
+	 * @var array
+	 */
+	static $TYPE_MAPPING = array(
+		'datetime'	=> self::DATETIME,
+		'timestamp'	=> self::DATETIME,
+		'date'		=> self::DATE,
+		'time'		=> self::TIME,
+
+		'boolean'	=> self::BOOLEAN,
+
+		'tinyint'	=> self::INTEGER,
+		'smallint'	=> self::INTEGER,
+		'mediumint'	=> self::INTEGER,
+		'int'		=> self::INTEGER,
+		'bigint'	=> self::INTEGER,
+
+		'float'		=> self::DECIMAL,
+		'double'	=> self::DECIMAL,
+		'numeric'	=> self::DECIMAL,
+		'decimal'	=> self::DECIMAL,
+		'dec'		=> self::DECIMAL);
+
+	/**
+	 * The true name of this column.
+	 * @var string
+	 */
+	public $name;
+
+	/**
+	 * The inflected name of this columns .. hyphens/spaces will be => _.
+	 * @var string
+	 */
+	public $inflected_name;
+
+	/**
+	 * The type of this column: STRING, INTEGER, ...
+	 * @var integer
+	 */
+	public $type;
+
+	/**
+	 * The raw database specific type.
+	 * @var string
+	 */
+	public $raw_type;
+
+	/**
+	 * The maximum length of this column.
+	 * @var int
+	 */
+	public $length;
+
+	/**
+	 * True if this column allows null.
+	 * @var boolean
+	 */
+	public $nullable;
+
+	/**
+	 * True if this column is a primary key.
+	 * @var boolean
+	 */
+	public $pk;
+
+	/**
+	 * The default value of the column.
+	 * @var mixed
+	 */
+	public $default;
+
+	/**
+	 * True if this column is set to auto_increment.
+	 * @var boolean
+	 */
+	public $auto_increment;
+
+	/**
+	 * Name of the sequence to use for this column if any.
+	 * @var boolean
+	 */
+	public $sequence;
+
+	/**
+	 * Cast a value to an integer type safely
+	 *
+	 * This will attempt to cast a value to an integer,
+	 * unless its detected that the casting will cause
+	 * the number to overflow or lose precision, in which
+	 * case the number will be returned as a string, so
+	 * that large integers (BIGINTS, unsigned INTS, etc)
+	 * can still be stored without error
+	 *
+	 * This would ideally be done with bcmath or gmp, but
+	 * requiring a new PHP extension for a bug-fix is a
+	 * little ridiculous
+	 *
+	 * @param mixed $value The value to cast
+	 * @return int|string type-casted value
+	 */
+	public static function castIntegerSafely($value)
+	{
+		if (is_int($value))
+			return $value;
+
+		// Its just a decimal number
+		elseif (is_numeric($value) && floor($value) != $value)
+			return (int) $value;
+
+		// If adding 0 to a string causes a float conversion,
+		// we have a number over PHP_INT_MAX
+		elseif (is_string($value) && is_float($value + 0))
+			return (string) $value;
+
+		// If a float was passed and its greater than PHP_INT_MAX
+		// (which could be wrong due to floating point precision)
+		// We'll also check for equal to (>=) in case the precision
+		// loss creates an overflow on casting
+		elseif (is_float($value) && $value >= PHP_INT_MAX)
+			return number_format($value, 0, '', '');
+
+		return (int) $value;
+	}
+
+	/**
+	 * Casts a value to the column's type.
+	 *
+	 * @param mixed $value The value to cast
+	 * @param Connection $connection The Connection this column belongs to
+	 * @return mixed type-casted value
+	 */
+	public function cast($value, $connection)
+	{
+		if ($value === null)
+			return null;
+
+		switch ($this->type)
+		{
+			case self::STRING:	return (string)$value;
+			case self::INTEGER:	return static::castIntegerSafely($value);
+			case self::DECIMAL:	return (double)$value;
+			case self::BOOLEAN:	return $connection->boolean_to_string($value);
+			case self::DATETIME:
+			case self::DATE:
+				if (!$value)
+					return null;
+
+				$date_class = Config::instance()->get_date_class();
+
+				if ($value instanceof $date_class)
+					return $value;
+
+				if ($value instanceof \DateTime)
+					return $date_class::createFromFormat(
+						Connection::DATETIME_TRANSLATE_FORMAT,
+						$value->format(Connection::DATETIME_TRANSLATE_FORMAT),
+						$value->getTimezone()
+					);
+
+				return $connection->string_to_datetime($value);
+		}
+		return $value;
+	}
+
+	/**
+	 * Sets the $type member variable.
+	 * @return mixed
+	 */
+	public function map_raw_type()
+	{
+		if ($this->raw_type == 'integer')
+			$this->raw_type = 'int';
+
+		if (array_key_exists($this->raw_type,self::$TYPE_MAPPING))
+			$this->type = self::$TYPE_MAPPING[$this->raw_type];
+		else
+			$this->type = self::STRING;
+
+		return $this->type;
+	}
+}
diff --git a/lib/Config.php b/lib/Config.php
index 1be1eedee..376d2a27d 100644
--- a/lib/Config.php
+++ b/lib/Config.php
@@ -51,12 +51,12 @@ class Config extends Singleton
 	private $connections = array();
 
 	/**
-	 * Directory for the auto_loading of model classes.
+	 * Array of directories for the auto_loading of model classes.
 	 *
 	 * @see activerecord_autoload
-	 * @var string
+	 * @var array
 	 */
-	private $model_directory;
+	private $model_directories = array();
 
 	/**
 	 * Switch for logging.
@@ -87,6 +87,13 @@ class Config extends Singleton
 	 */
 	private $date_format = \DateTime::ISO8601;
 
+	/**
+	 * Switch for use of StrongParameters
+	 *
+	 * @var bool
+	 */
+	private $require_strong_parameters = false;
+
 	/**
 	 * Allows config initialization using a closure.
 	 *
@@ -118,13 +125,25 @@ public static function initialize(Closure $initializer)
 	}
 
 	/**
-	 * Sets the list of database connection strings.
+	 * Sets the list of database connections. Can be an array of connection strings or an array of arrays.
 	 *
 	 * <code>
 	 * $config->set_connections(array(
 	 *     'development' => 'mysql://username:password@127.0.0.1/database_name'));
 	 * </code>
 	 *
+	 * <code>
+	 * $config->set_connections(array(
+	 *     'development' => array(
+	 *         'adapter' => 'mysql',
+	 *         'host' => '127.0.0.1',
+	 *         'database' => 'database_name',
+	 *         'username' => 'username',
+	 *         'password' => 'password'
+	 *     )
+	 * ));
+	 * </code>
+	 *
 	 * @param array $connections Array of connections
 	 * @param string $default_connection Optionally specify the default_connection
 	 * @return void
@@ -138,7 +157,16 @@ public function set_connections($connections, $default_connection=null)
 		if ($default_connection)
 			$this->set_default_connection($default_connection);
 
-		$this->connections = $connections;
+		$this->connections = array_map(function($connection){
+			if(is_string($connection))
+			{
+				return ConnectionInfo::from_connection_url($connection);
+			}
+			else
+			{
+				return new ConnectionInfo($connection);
+			}
+		}, $connections);
 	}
 
 	/**
@@ -157,7 +185,7 @@ public function get_connections()
 	 * @param string $name Name of connection to retrieve
 	 * @return string connection info for specified connection name
 	 */
-	public function get_connection($name)
+	public function get_connection_info($name)
 	{
 		if (array_key_exists($name, $this->connections))
 			return $this->connections[$name];
@@ -170,7 +198,7 @@ public function get_connection($name)
 	 *
 	 * @return string
 	 */
-	public function get_default_connection_string()
+	public function get_default_connection_info()
 	{
 		return array_key_exists($this->default_connection,$this->connections) ?
 			$this->connections[$this->default_connection] : null;
@@ -200,26 +228,53 @@ public function set_default_connection($name)
 	/**
 	 * Sets the directory where models are located.
 	 *
-	 * @param string $dir Directory path containing your models
+	 * @param string $directory Directory path containing your models
 	 * @return void
 	 */
-	public function set_model_directory($dir)
+	public function set_model_directory($directory)
 	{
-		$this->model_directory = $dir;
+		$this->set_model_directories(array($directory));
 	}
-
+	
 	/**
-	 * Returns the model directory.
+	 * Returns the first model directory.
 	 *
 	 * @return string
-	 * @throws ConfigException if specified directory was not found
 	 */
 	public function get_model_directory()
 	{
-		if ($this->model_directory && !file_exists($this->model_directory))
-			throw new ConfigException('Invalid or non-existent directory: '.$this->model_directory);
+		$model_directories = $this->get_model_directories();
+		return array_shift($model_directories);
+	}
+	
+	/**
+	 * Sets the directories where models are located.
+	 *
+	 * @param array $directories Array with directory paths containing your models
+	 * @return void
+	 * @throws ConfigException if one of the model directories was not found
+	 */
+	public function set_model_directories($directories)
+	{
+		if (!is_array($directories))
+			throw new ConfigException("Directories must be an array");
+		
+		foreach($directories as $directory)
+		{
+			if(!file_exists($directory) || !is_dir($directory))
+				throw new ConfigException('Invalid or non-existent directory: '. $directory);
+		}
+		$this->model_directories = $directories;
+	}
 
-		return $this->model_directory;
+	/**
+	 * Returns the array of model directories.
+	 *
+	 * @return array
+	 */
+	public function get_model_directories()
+	{
+		return $this->model_directories;
 	}
 
 	/**
@@ -330,4 +385,26 @@ public function set_cache($url, $options=array())
 	{
 		Cache::initialize($url,$options);
 	}
-}
\ No newline at end of file
+
+	/**
+	 * Enable or disable use of StrongParameters
+	 *
+	 * @param bool $flag
+	 * @return void
+	 */
+	public function set_require_strong_parameters($flag)
+	{
+		$this->require_strong_parameters = $flag;
+	}
+
+	/**
+	 * Returns whether or not to require StrongParameters
+	 *
+	 * @return bool
+	 */
+	public function get_require_strong_parameters()
+	{
+		return $this->require_strong_parameters;
+	}
+
+}
diff --git a/lib/Connection.php b/lib/Connection.php
index 69e8a1b20..fc71cc4f8 100644
--- a/lib/Connection.php
+++ b/lib/Connection.php
@@ -54,10 +54,10 @@ abstract class Connection
 	 */
 	private $logger;
 	/**
-	 * The name of the protocol that is used.
+	 * The name of the adapter that is used.
 	 * @var string
 	 */
-	public $protocol;
+	public $adapter;
 	/**
 	 * Database's date format
 	 * @var string
@@ -91,41 +91,49 @@ abstract class Connection
 	/**
 	 * Retrieve a database connection.
 	 *
-	 * @param string $connection_string_or_connection_name A database connection string (ex. mysql://user:pass@host[:port]/dbname)
-	 *   Everything after the protocol:// part is specific to the connection adapter.
+	 * @param string $connection_info_or_name A database connection string (ex. mysql://user:pass@host[:port]/dbname)
+	 *   Everything after the adapter:// part is specific to the connection adapter.
 	 *   OR
 	 *   A connection name that is set in ActiveRecord\Config
 	 *   If null it will use the default connection specified by ActiveRecord\Config->set_default_connection
 	 * @return Connection
-	 * @see parse_connection_url
+	 * @see ConnectionInfo::from_connection_url
 	 */
-	public static function instance($connection_string_or_connection_name=null)
+	public static function instance($connection_info=null)
 	{
 		$config = Config::instance();
 
-		if (strpos($connection_string_or_connection_name, '://') === false)
+		if (!($connection_info instanceof ConnectionInfo))
 		{
-			$connection_string = $connection_string_or_connection_name ?
-				$config->get_connection($connection_string_or_connection_name) :
-				$config->get_default_connection_string();
+			// Connection instantiation using a connection array
+			if (is_array($connection_info))
+			{
+				$connection_info = new ConnectionInfo($connection_info);
+			}
+			// Connection instantiation using a connection url
+			else if (is_string($connection_info) && strpos($connection_info, '://') !== false)
+			{
+				$connection_info = ConnectionInfo::from_connection_url($connection_info);
+			}
+			// Connection instantiation using a connection name
+			else
+			{
+				$connection_info = $connection_info ?
+					$config->get_connection_info($connection_info) :
+					$config->get_default_connection_info();
+			}
 		}
-		else
-			$connection_string = $connection_string_or_connection_name;
 
-		if (!$connection_string)
-			throw new DatabaseException("Empty connection string");
-
-		$info = static::parse_connection_url($connection_string);
-		$fqclass = static::load_adapter_class($info->protocol);
+		$fqclass = static::load_adapter_class($connection_info->adapter);
 
 		try {
-			$connection = new $fqclass($info);
-			$connection->protocol = $info->protocol;
+			$connection = new $fqclass($connection_info);
+			$connection->adapter = $connection_info->adapter;
 			$connection->logging = $config->get_logging();
 			$connection->logger = $connection->logging ? $config->get_logger() : null;
 
-			if (isset($info->charset))
-				$connection->set_encoding($info->charset);
+			if (isset($connection_info->charset))
+				$connection->set_encoding($connection_info->charset);
 		} catch (PDOException $e) {
 			throw new DatabaseException($e);
 		}
@@ -151,92 +159,6 @@ private static function load_adapter_class($adapter)
 		return $fqclass;
 	}
 
-	/**
-	 * Use this for any adapters that can take connection info in the form below
-	 * to set the adapters connection info.
-	 *
-	 * <code>
-	 * protocol://username:password@host[:port]/dbname
-	 * protocol://urlencoded%20username:urlencoded%20password@host[:port]/dbname?decode=true
-	 * protocol://username:password@unix(/some/file/path)/dbname
-	 * </code>
-	 *
-	 * Sqlite has a special syntax, as it does not need a database name or user authentication:
-	 *
-	 * <code>
-	 * sqlite://file.db
-	 * sqlite://../relative/path/to/file.db
-	 * sqlite://unix(/absolute/path/to/file.db)
-	 * sqlite://windows(c%2A/absolute/path/to/file.db)
-	 * </code>
-	 *
-	 * @param string $connection_url A connection URL
-	 * @return object the parsed URL as an object.
-	 */
-	public static function parse_connection_url($connection_url)
-	{
-		$url = @parse_url($connection_url);
-
-		if (!isset($url['host']))
-			throw new DatabaseException('Database host must be specified in the connection string. If you want to specify an absolute filename, use e.g. sqlite://unix(/path/to/file)');
-
-		$info = new \stdClass();
-		$info->protocol = $url['scheme'];
-		$info->host = $url['host'];
-		$info->db = isset($url['path']) ? substr($url['path'], 1) : null;
-		$info->user = isset($url['user']) ? $url['user'] : null;
-		$info->pass = isset($url['pass']) ? $url['pass'] : null;
-
-		$allow_blank_db = ($info->protocol == 'sqlite');
-
-		if ($info->host == 'unix(')
-		{
-			$socket_database = $info->host . '/' . $info->db;
-
-			if ($allow_blank_db)
-				$unix_regex = '/^unix\((.+)\)\/?().*$/';
-			else
-				$unix_regex = '/^unix\((.+)\)\/(.+)$/';
-
-			if (preg_match_all($unix_regex, $socket_database, $matches) > 0)
-			{
-				$info->host = $matches[1][0];
-				$info->db = $matches[2][0];
-			}
-		} elseif (substr($info->host, 0, 8) == 'windows(')
-		{
-			$info->host = urldecode(substr($info->host, 8) . '/' . substr($info->db, 0, -1));
-			$info->db = null;
-		}
-
-		if ($allow_blank_db && $info->db)
-			$info->host .= '/' . $info->db;
-
-		if (isset($url['port']))
-			$info->port = $url['port'];
-
-		if (strpos($connection_url, 'decode=true') !== false)
-		{
-			if ($info->user)
-				$info->user = urldecode($info->user);
-
-			if ($info->pass)
-				$info->pass = urldecode($info->pass);
-		}
-
-		if (isset($url['query']))
-		{
-			foreach (explode('/&/', $url['query']) as $pair) {
-				list($name, $value) = explode('=', $pair);
-
-				if ($name == 'charset')
-					$info->charset = $value;
-			}
-		}
-
-		return $info;
-	}
-
 	/**
 	 * Class Connection is a singleton. Access it via instance().
 	 *
@@ -257,7 +179,7 @@ protected function __construct($info)
 			else
 				$host = "unix_socket=$info->host";
 
-			$this->connection = new PDO("$info->protocol:$host;dbname=$info->db", $info->user, $info->pass, static::$PDO_OPTIONS);
+			$this->connection = new PDO("$info->adapter:$host;dbname=$info->database", $info->username, $info->password, static::$PDO_OPTIONS);
 		} catch (PDOException $e) {
 			throw new DatabaseException($e);
 		}
@@ -499,6 +421,22 @@ public function string_to_datetime($string)
 		);
 	}
 
+	/**
+	 * Converts a boolean value to a string representation.
+	 *
+	 * The converted string representation should be in a format acceptable by the
+	 * underlying database connection.
+	 *
+	 * @param mixed $value
+	 * @access public
+	 * @return string
+	 */
+	public function boolean_to_string($value)
+	{
+		$boolean = (boolean)$value;
+		return (string)$boolean;
+	}
+
 	/**
 	 * Adds a limit clause to the SQL query.
 	 *
diff --git a/lib/ConnectionInfo.php b/lib/ConnectionInfo.php
new file mode 100644
index 000000000..74081b2ea
--- /dev/null
+++ b/lib/ConnectionInfo.php
@@ -0,0 +1,152 @@
+<?php
+
+namespace ActiveRecord;
+
+class ConnectionInfo {
+
+	/**
+	 * The adapter to use for this connection
+	 *
+	 * @var string
+	 */
+	public $adapter = null;
+
+	/**
+	 * The host to use for this connection.
+	 *
+	 * @var string
+	 */
+	public $host = null;
+
+	/**
+	 * The port to use for this connection.
+	 *
+	 * @var int
+	 */
+	public $port = null;
+
+	/**
+	 * The database to use for this connection.
+	 *
+	 * @var string
+	 */
+	public $database = null;
+
+	/**
+	 * The username to use for this connection.
+	 *
+	 * @var string
+	 */
+	public $username = null;
+
+	/**
+	 * The password to use for this connection.
+	 *
+	 * @var string
+	 */
+	public $password = null;
+
+	/**
+	 * The charset to use for this connection.
+	 *
+	 * @var string
+	 */
+	public $charset = null;
+
+	public function __construct($input = array())
+	{
+		foreach($input as $prop => $value)
+		{
+			if(property_exists($this, $prop))
+			{
+				$this->{$prop} = $value;
+			}
+		}
+	}
+
+	/**
+	 * Parses a connection url and return a ConnectionInfo object
+	 *
+	 * Use this for any adapters that can take connection info in the form below
+	 * to set the adapters connection info.
+	 *
+	 * <code>
+	 * adapter://username:password@host[:port]/dbname
+	 * adapter://urlencoded%20username:urlencoded%20password@host[:port]/dbname?decode=true
+	 * adapter://username:password@unix(/some/file/path)/dbname
+	 * </code>
+	 *
+	 * Sqlite has a special syntax, as it does not need a database name or user authentication:
+	 *
+	 * <code>
+	 * sqlite://file.db
+	 * sqlite://../relative/path/to/file.db
+	 * sqlite://unix(/absolute/path/to/file.db)
+	 * sqlite://windows(c%2A/absolute/path/to/file.db)
+	 * </code>
+	 *
+	 * @param string $connection_url A connection URL
+	 * @return ConnectionInfo the parsed URL as an object.
+	 */
+	public static function from_connection_url($connection_url)
+	{
+		$url = @parse_url($connection_url);
+
+		if (!isset($url['host']))
+			throw new DatabaseException('Database host must be specified in the connection string. If you want to specify an absolute filename, use e.g. sqlite://unix(/path/to/file)');
+
+		$info = new self();
+		$info->adapter = $url['scheme'];
+		$info->host = $url['host'];
+
+		$decode = false;
+
+		if (isset($url['query']))
+		{
+			parse_str($url['query'], $params);
+
+			if(isset($params['charset']))
+				$info->charset = $params['charset'];
+
+			if(isset($params['decode']))
+				$decode = ($params['decode'] == 'true');
+		}
+
+		if(isset($url['path']))
+			$info->database = substr($url['path'], 1);
+
+		$allow_blank_db = ($info->adapter == 'sqlite');
+
+		if ($info->host == 'unix(')
+		{
+			$socket_database = $info->host . '/' . $info->database;
+
+			sscanf($socket_database, 'unix(%[^)])/%s', $host, $database);
+
+			$info->host = $host;
+			$info->database = $database;
+		}
+		elseif (substr($info->host, 0, 8) == 'windows(')
+		{
+			$info->host = urldecode(substr($info->host, 8) . '/' . substr($info->database, 0, -1));
+			$info->database = null;
+		}
+		else
+		{
+			if ($allow_blank_db && $info->database)
+				$info->host .= '/' . $info->database;
+		}
+
+		if (isset($url['port']))
+			$info->port = $url['port'];
+
+		if (isset($url['user']))
+			$info->username = $decode ? urldecode($url['user']) : $url['user'];
+
+		if (isset($url['pass']))
+			$info->password = $decode ? urldecode($url['pass']) : $url['pass'];
+
+		return $info;
+	}
+
+}
diff --git a/lib/ConnectionManager.php b/lib/ConnectionManager.php
index 727c33dac..11ed5705a 100644
--- a/lib/ConnectionManager.php
+++ b/lib/ConnectionManager.php
@@ -30,7 +30,7 @@ public static function get_connection($name=null)
 		$name = $name ? $name : $config->get_default_connection();
 
 		if (!isset(self::$connections[$name]) || !self::$connections[$name]->connection)
-			self::$connections[$name] = Connection::instance($config->get_connection($name));
+			self::$connections[$name] = Connection::instance($config->get_connection_info($name));
 
 		return self::$connections[$name];
 	}
diff --git a/lib/Exceptions.php b/lib/Exceptions.php
index d3de8ab22..d2ca7672b 100644
--- a/lib/Exceptions.php
+++ b/lib/Exceptions.php
@@ -141,3 +141,17 @@ class RelationshipException extends ActiveRecordException {}
  * @package ActiveRecord
  */
 class HasManyThroughAssociationException extends RelationshipException {}
+
+/**
+ * Thrown for unsafe in attributes in mass assignment
+ *
+ * @package ActiveRecord
+ */
+class UnsafeParametersException extends ActiveRecordException {}
+
+/**
+ * Thrown for non existing parameter from requireParam
+ *
+ * @package ActiveRecord
+ */
+class ParameterMissingException extends ActiveRecordException {}
diff --git a/lib/Expressions.php b/lib/Expressions.php
index 3fa2962fa..881fa4afd 100644
--- a/lib/Expressions.php
+++ b/lib/Expressions.php
@@ -1,192 +1,192 @@
-<?php
-/**
- * @package ActiveRecord
- */
-namespace ActiveRecord;
-
-/**
- * Templating like class for building SQL statements.
- *
- * Examples:
- * 'name = :name AND author = :author'
- * 'id = IN(:ids)'
- * 'id IN(:subselect)'
- * 
- * @package ActiveRecord
- */
-class Expressions
-{
-	const ParameterMarker = '?';
-
-	private $expressions;
-	private $values = array();
-	private $connection;
-
-	public function __construct($connection, $expressions=null /* [, $values ... ] */)
-	{
-		$values = null;
-		$this->connection = $connection;
-
-		if (is_array($expressions))
-		{
-			$glue = func_num_args() > 2 ? func_get_arg(2) : ' AND ';
-			list($expressions,$values) = $this->build_sql_from_hash($expressions,$glue);
-		}
-
-		if ($expressions != '')
-		{
-			if (!$values)
-				$values = array_slice(func_get_args(),2);
-
-			$this->values = $values;
-			$this->expressions = $expressions;
-		}
-	}
-
-	/**
-	 * Bind a value to the specific one based index. There must be a bind marker
-	 * for each value bound or to_s() will throw an exception.
-	 */
-	public function bind($parameter_number, $value)
-	{
-		if ($parameter_number <= 0)
-			throw new ExpressionsException("Invalid parameter index: $parameter_number");
-
-		$this->values[$parameter_number-1] = $value;
-	}
-
-	public function bind_values($values)
-	{
-		$this->values = $values;
-	}
-
-	/**
-	 * Returns all the values currently bound.
-	 */
-	public function values()
-	{
-		return $this->values;
-	}
-
-	/**
-	 * Returns the connection object.
-	 */
-	public function get_connection()
-	{
-		return $this->connection;
-	}
-
-	/**
-	 * Sets the connection object. It is highly recommended to set this so we can
-	 * use the adapter's native escaping mechanism.
-	 *
-	 * @param string $connection a Connection instance
-	 */
-	public function set_connection($connection)
-	{
-		$this->connection = $connection;
-	}
-
-	public function to_s($substitute=false, &$options=null)
-	{
-		if (!$options) $options = array();
-		
-		$values = array_key_exists('values',$options) ? $options['values'] : $this->values;
-
-		$ret = "";
-		$replace = array();
-		$num_values = count($values);
-		$len = strlen($this->expressions);
-		$quotes = 0;
-
-		for ($i=0,$n=strlen($this->expressions),$j=0; $i<$n; ++$i)
-		{
-			$ch = $this->expressions[$i];
-
-			if ($ch == self::ParameterMarker)
-			{
-				if ($quotes % 2 == 0)
-				{
-					if ($j > $num_values-1)
-						throw new ExpressionsException("No bound parameter for index $j");
-
-					$ch = $this->substitute($values,$substitute,$i,$j++);
-				}
-			}
-			elseif ($ch == '\'' && $i > 0 && $this->expressions[$i-1] != '\\')
-				++$quotes;
-
-			$ret .= $ch;
-		}
-		return $ret;
-	}
-
-	private function build_sql_from_hash(&$hash, $glue)
-	{
-		$sql = $g = "";
-
-		foreach ($hash as $name => $value)
-		{
-			if ($this->connection)
-				$name = $this->connection->quote_name($name);
-
-			if (is_array($value))
-				$sql .= "$g$name IN(?)";
-			elseif (is_null($value))
-				$sql .= "$g$name IS ?";
-			else
-				$sql .= "$g$name=?";
-
-			$g = $glue;
-		}
-		return array($sql,array_values($hash));
-	}
-
-	private function substitute(&$values, $substitute, $pos, $parameter_index)
-	{
-		$value = $values[$parameter_index];
-
-		if (is_array($value))
-		{
-			$value_count = count($value);
-
-			if ($value_count === 0)
-				if ($substitute)
-					return 'NULL';
-				else
-					return self::ParameterMarker;
-
-			if ($substitute)
-			{
-				$ret = '';
-
-				for ($i=0, $n=$value_count; $i<$n; ++$i)
-					$ret .= ($i > 0 ? ',' : '') . $this->stringify_value($value[$i]);
-
-				return $ret;
-			}
-			return join(',',array_fill(0,$value_count,self::ParameterMarker));
-		}
-
-		if ($substitute)
-			return $this->stringify_value($value);
-
-		return $this->expressions[$pos];
-	}
-
-	private function stringify_value($value)
-	{
-		if (is_null($value))
-			return "NULL";
-
-		return is_string($value) ? $this->quote_string($value) : $value;
-	}
-
-	private function quote_string($value)
-	{
-		if ($this->connection)
-			return $this->connection->escape($value);
-
-		return "'" . str_replace("'","''",$value) . "'";
-	}
+<?php
+/**
+ * @package ActiveRecord
+ */
+namespace ActiveRecord;
+
+/**
+ * Templating like class for building SQL statements.
+ *
+ * Examples:
+ * 'name = :name AND author = :author'
+ * 'id = IN(:ids)'
+ * 'id IN(:subselect)'
+ * 
+ * @package ActiveRecord
+ */
+class Expressions
+{
+	const ParameterMarker = '?';
+
+	private $expressions;
+	private $values = array();
+	private $connection;
+
+	public function __construct($connection, $expressions=null /* [, $values ... ] */)
+	{
+		$values = null;
+		$this->connection = $connection;
+
+		if (is_array($expressions))
+		{
+			$glue = func_num_args() > 2 ? func_get_arg(2) : ' AND ';
+			list($expressions,$values) = $this->build_sql_from_hash($expressions,$glue);
+		}
+
+		if ($expressions != '')
+		{
+			if (!$values)
+				$values = array_slice(func_get_args(),2);
+
+			$this->values = $values;
+			$this->expressions = $expressions;
+		}
+	}
+
+	/**
+	 * Bind a value to the specific one based index. There must be a bind marker
+	 * for each value bound or to_s() will throw an exception.
+	 */
+	public function bind($parameter_number, $value)
+	{
+		if ($parameter_number <= 0)
+			throw new ExpressionsException("Invalid parameter index: $parameter_number");
+
+		$this->values[$parameter_number-1] = $value;
+	}
+
+	public function bind_values($values)
+	{
+		$this->values = $values;
+	}
+
+	/**
+	 * Returns all the values currently bound.
+	 */
+	public function values()
+	{
+		return $this->values;
+	}
+
+	/**
+	 * Returns the connection object.
+	 */
+	public function get_connection()
+	{
+		return $this->connection;
+	}
+
+	/**
+	 * Sets the connection object. It is highly recommended to set this so we can
+	 * use the adapter's native escaping mechanism.
+	 *
+	 * @param string $connection a Connection instance
+	 */
+	public function set_connection($connection)
+	{
+		$this->connection = $connection;
+	}
+
+	public function to_s($substitute=false, &$options=null)
+	{
+		if (!$options) $options = array();
+		
+		$values = array_key_exists('values',$options) ? $options['values'] : $this->values;
+
+		$ret = "";
+		$replace = array();
+		$num_values = count($values);
+		$len = strlen($this->expressions);
+		$quotes = 0;
+
+		for ($i=0,$n=strlen($this->expressions),$j=0; $i<$n; ++$i)
+		{
+			$ch = $this->expressions[$i];
+
+			if ($ch == self::ParameterMarker)
+			{
+				if ($quotes % 2 == 0)
+				{
+					if ($j > $num_values-1)
+						throw new ExpressionsException("No bound parameter for index $j");
+
+					$ch = $this->substitute($values,$substitute,$i,$j++);
+				}
+			}
+			elseif ($ch == '\'' && $i > 0 && $this->expressions[$i-1] != '\\')
+				++$quotes;
+
+			$ret .= $ch;
+		}
+		return $ret;
+	}
+
+	private function build_sql_from_hash(&$hash, $glue)
+	{
+		$sql = $g = "";
+
+		foreach ($hash as $name => $value)
+		{
+			if ($this->connection)
+				$name = $this->connection->quote_name($name);
+
+			if (is_array($value))
+				$sql .= "$g$name IN(?)";
+			elseif (is_null($value))
+				$sql .= "$g$name IS ?";
+			else
+				$sql .= "$g$name=?";
+
+			$g = $glue;
+		}
+		return array($sql,array_values($hash));
+	}
+
+	private function substitute(&$values, $substitute, $pos, $parameter_index)
+	{
+		$value = $values[$parameter_index];
+
+		if (is_array($value))
+		{
+			$value_count = count($value);
+
+			if ($value_count === 0)
+				if ($substitute)
+					return 'NULL';
+				else
+					return self::ParameterMarker;
+
+			if ($substitute)
+			{
+				$ret = '';
+
+				for ($i=0, $n=$value_count; $i<$n; ++$i)
+					$ret .= ($i > 0 ? ',' : '') . $this->stringify_value($value[$i]);
+
+				return $ret;
+			}
+			return join(',',array_fill(0,$value_count,self::ParameterMarker));
+		}
+
+		if ($substitute)
+			return $this->stringify_value($value);
+
+		return $this->expressions[$pos];
+	}
+
+	private function stringify_value($value)
+	{
+		if (is_null($value))
+			return "NULL";
+
+		return is_string($value) ? $this->quote_string($value) : $value;
+	}
+
+	private function quote_string($value)
+	{
+		if ($this->connection)
+			return $this->connection->escape($value);
+
+		return "'" . str_replace("'","''",$value) . "'";
+	}
 }
\ No newline at end of file
diff --git a/lib/Inflector.php b/lib/Inflector.php
index 732fb3422..cea9229c3 100644
--- a/lib/Inflector.php
+++ b/lib/Inflector.php
@@ -1,119 +1,119 @@
-<?php
-/**
- * @package ActiveRecord
- */
-namespace ActiveRecord;
-
-/**
- * @package ActiveRecord
- */
-abstract class Inflector
-{
-	/**
-	 * Get an instance of the {@link Inflector} class.
-	 *
-	 * @return object
-	 */
-	public static function instance()
-	{
-		return new StandardInflector();
-	}
-
-	/**
-	 * Turn a string into its camelized version.
-	 *
-	 * @param string $s string to convert
-	 * @return string
-	 */
-	public function camelize($s)
-	{
-		$s = preg_replace('/[_-]+/','_',trim($s));
-		$s = str_replace(' ', '_', $s);
-
-		$camelized = '';
-
-		for ($i=0,$n=strlen($s); $i<$n; ++$i)
-		{
-			if ($s[$i] == '_' && $i+1 < $n)
-				$camelized .= strtoupper($s[++$i]);
-			else
-				$camelized .= $s[$i];
-		}
-
-		$camelized = trim($camelized,' _');
-
-		if (strlen($camelized) > 0)
-			$camelized[0] = strtolower($camelized[0]);
-
-		return $camelized;
-	}
-
-	/**
-	 * Determines if a string contains all uppercase characters.
-	 *
-	 * @param string $s string to check
-	 * @return bool
-	 */
-	public static function is_upper($s)
-	{
-		return (strtoupper($s) === $s);
-	}
-
-	/**
-	 * Determines if a string contains all lowercase characters.
-	 *
-	 * @param string $s string to check
-	 * @return bool
-	 */
-	public static function is_lower($s)
-	{
-		return (strtolower($s) === $s);
-	}
-
-	/**
-	 * Convert a camelized string to a lowercase, underscored string.
-	 *
-	 * @param string $s string to convert
-	 * @return string
-	 */
-	public function uncamelize($s)
-	{
-		$normalized = '';
-
-		for ($i=0,$n=strlen($s); $i<$n; ++$i)
-		{
-			if (ctype_alpha($s[$i]) && self::is_upper($s[$i]))
-				$normalized .= '_' . strtolower($s[$i]);
-			else
-				$normalized .= $s[$i];
-		}
-		return trim($normalized,' _');
-	}
-
-	/**
-	 * Convert a string with space into a underscored equivalent.
-	 *
-	 * @param string $s string to convert
-	 * @return string
-	 */
-	public function underscorify($s)
-	{
-		return preg_replace(array('/[_\- ]+/','/([a-z])([A-Z])/'),array('_','\\1_\\2'),trim($s));
-	}
-
-	public function keyify($class_name)
-	{
-		return strtolower($this->underscorify(denamespace($class_name))) . '_id';
-	}
-
-	abstract function variablize($s);
-}
-
-/**
- * @package ActiveRecord
- */
-class StandardInflector extends Inflector
-{
-	public function tableize($s) { return Utils::pluralize(strtolower($this->underscorify($s))); }
-	public function variablize($s) { return str_replace(array('-',' '),array('_','_'),strtolower(trim($s))); }
+<?php
+/**
+ * @package ActiveRecord
+ */
+namespace ActiveRecord;
+
+/**
+ * @package ActiveRecord
+ */
+abstract class Inflector
+{
+	/**
+	 * Get an instance of the {@link Inflector} class.
+	 *
+	 * @return object
+	 */
+	public static function instance()
+	{
+		return new StandardInflector();
+	}
+
+	/**
+	 * Turn a string into its camelized version.
+	 *
+	 * @param string $s string to convert
+	 * @return string
+	 */
+	public function camelize($s)
+	{
+		$s = preg_replace('/[_-]+/','_',trim($s));
+		$s = str_replace(' ', '_', $s);
+
+		$camelized = '';
+
+		for ($i=0,$n=strlen($s); $i<$n; ++$i)
+		{
+			if ($s[$i] == '_' && $i+1 < $n)
+				$camelized .= strtoupper($s[++$i]);
+			else
+				$camelized .= $s[$i];
+		}
+
+		$camelized = trim($camelized,' _');
+
+		if (strlen($camelized) > 0)
+			$camelized[0] = strtolower($camelized[0]);
+
+		return $camelized;
+	}
+
+	/**
+	 * Determines if a string contains all uppercase characters.
+	 *
+	 * @param string $s string to check
+	 * @return bool
+	 */
+	public static function is_upper($s)
+	{
+		return (strtoupper($s) === $s);
+	}
+
+	/**
+	 * Determines if a string contains all lowercase characters.
+	 *
+	 * @param string $s string to check
+	 * @return bool
+	 */
+	public static function is_lower($s)
+	{
+		return (strtolower($s) === $s);
+	}
+
+	/**
+	 * Convert a camelized string to a lowercase, underscored string.
+	 *
+	 * @param string $s string to convert
+	 * @return string
+	 */
+	public function uncamelize($s)
+	{
+		$normalized = '';
+
+		for ($i=0,$n=strlen($s); $i<$n; ++$i)
+		{
+			if (ctype_alpha($s[$i]) && self::is_upper($s[$i]))
+				$normalized .= '_' . strtolower($s[$i]);
+			else
+				$normalized .= $s[$i];
+		}
+		return trim($normalized,' _');
+	}
+
+	/**
+	 * Convert a string with space into a underscored equivalent.
+	 *
+	 * @param string $s string to convert
+	 * @return string
+	 */
+	public function underscorify($s)
+	{
+		return preg_replace(array('/[_\- ]+/','/([a-z])([A-Z])/'),array('_','\\1_\\2'),trim($s));
+	}
+
+	public function keyify($class_name)
+	{
+		return strtolower($this->underscorify(denamespace($class_name))) . '_id';
+	}
+
+	abstract function variablize($s);
+}
+
+/**
+ * @package ActiveRecord
+ */
+class StandardInflector extends Inflector
+{
+	public function tableize($s) { return Utils::pluralize(strtolower($this->underscorify($s))); }
+	public function variablize($s) { return str_replace(array('-',' '),array('_','_'),strtolower(trim($s))); }
 }
\ No newline at end of file
diff --git a/lib/Model.php b/lib/Model.php
index 28bcd68bf..76a1e399a 100644
--- a/lib/Model.php
+++ b/lib/Model.php
@@ -87,6 +87,23 @@ class Model
 	 */
 	private $attributes = array();
 
+	/**
+	 * Contains changes made to model attributes as
+	 * $attribute_name => array($original_value, $current_value)
+	 *
+	 * @var array
+	 * @access private
+	 */
+	private $changed_attributes = array();
+
+	/**
+	 * Contains changes made to model attributes before it was saved
+	 *
+	 * @var array
+	 * @access private
+	 */
+	private $previously_changed = array();
+
 	/**
 	 * Flag whether or not this model's attributes have been modified since it will either be null or an array of column_names that have been modified
 	 *
@@ -115,6 +132,13 @@ class Model
 	 */
 	private $__new_record = true;
 
+	/**
+	 * Flag whether or not this model has been validated
+	 *
+	 * @var boolean
+	 */
+	private $__validated = false;
+
 	/**
 	 * Set to the name of the connection this {@link Model} should use.
 	 *
@@ -255,13 +279,14 @@ class Model
 	 * new Person(array('first_name' => 'Tito', 'last_name' => 'the Grief'));
 	 * </code>
 	 *
-	 * @param array $attributes Hash containing names and values to mass assign to the model
+	 * @param array $attributes Hash containing names and values to mass assign to the model or
+	 *  StrongParameters object
 	 * @param boolean $guard_attributes Set to true to guard protected/non-accessible attributes
 	 * @param boolean $instantiating_via_find Set to true if this model is being created from a find call
 	 * @param boolean $new_record Set to true if this should be considered a new record
 	 * @return Model
 	 */
-	public function __construct(array $attributes=array(), $guard_attributes=true, $instantiating_via_find=false, $new_record=true)
+	public function __construct($attributes=null, $guard_attributes=true, $instantiating_via_find=false, $new_record=true)
 	{
 		$this->__new_record = $new_record;
 
@@ -413,7 +438,7 @@ public function __set($name, $value)
 		if (array_key_exists($name, static::$alias_attribute))
 			$name = static::$alias_attribute[$name];
 
-		elseif (method_exists($this,"set_$name"))
+		if (method_exists($this,"set_$name"))
 		{
 			$name = "set_$name";
 			return $this->$name($value);
@@ -477,8 +502,17 @@ public function assign_attribute($name, $value)
 			// has the ability to flag this model as dirty if a field in the Date object changes.
 			$value->attribute_of($this,$name);
 
-		$this->attributes[$name] = $value;
-		$this->flag_dirty($name);
+		// only update the attribute if it isn't set or has changed
+		if (!isset($this->attributes[$name]) || ($this->attributes[$name] !== $value)) {
+			// track changes to the attribute
+			if (array_key_exists($name, $this->attributes) && !isset($this->changed_attributes[$name]))
+				$this->changed_attributes[$name] = $this->attributes[$name];
+
+			// set the attribute and flag as dirty
+			$this->attributes[$name] = $value;
+			$this->flag_dirty($name);
+			$this->reset_validated();
+		}
 		return $value;
 	}
 
@@ -498,8 +532,10 @@ public function &read_attribute($name)
 			$name = static::$alias_attribute[$name];
 
 		// check for attribute
-		if (array_key_exists($name,$this->attributes))
-			return $this->attributes[$name];
+		if (array_key_exists($name,$this->attributes)) {
+			$value = $this->attributes[$name];
+			return $value;
+		}
 
 		// check relationships if no attribute
 		if (array_key_exists($name,$this->__relationships))
@@ -589,6 +625,52 @@ public function attributes()
 		return $this->attributes;
 	}
 
+	/**
+	 * Returns a copy of the model's changed attributes hash with the
+	 * attribute name and the original value.
+	 *
+	 * @return array A copy of the model's changed attribute data
+	 */
+	public function changed_attributes()
+	{
+		return $this->changed_attributes;
+	}
+
+	/**
+	 * Returns a copy of the model's changed attributes as a hash
+	 * in the form $attribute => array($original_value, $current_value)
+	 *
+	 * @return array A copy of the model's attribute changes
+	 */
+	public function changes()
+	{
+		$changes = array();
+		$attributes = array_intersect_key($this->attributes, $this->changed_attributes);
+		foreach($attributes as $name => $value) {
+			$changes[$name] = array($this->changed_attributes[$name],$value);
+		}
+		return $changes;
+	}
+
+	/**
+	 * Returns a copy of the model's changed attributes before it was saved
+	 *
+	 * @return array A copy of the model's changed attribute before a save
+	 */
+	public function previous_changes()
+	{
+		return $this->previously_changed;
+	}
+
+	/**
+	 * Returns the value of an attribute before it was changed
+	 *
+	 * @return string The original value of an attribute
+	 */
+	public function attribute_was($name) {
+		return isset($this->changed_attributes[$name]) ? $this->changed_attributes[$name] : null;
+	}
+
 	/**
 	 * Retrieve the primary key name.
 	 *
@@ -810,7 +892,7 @@ private function insert($validate=true)
 	{
 		$this->verify_not_readonly('insert');
 
-		if (($validate && !$this->_validate() || !$this->invoke_callback('before_create',false)))
+		if (($validate && !$this->_validate()) || !$this->invoke_callback('before_create',false))
 			return false;
 
 		$table = static::table();
@@ -1081,8 +1163,12 @@ private function _validate()
 	{
 		require_once 'Validations.php';
 
+		$this->flag_validated();
+
 		$validator = new Validations($this);
-		$validation_on = 'validation_on_' . ($this->is_new_record() ? 'create' : 'update');
+		$validation_mode = $this->is_new_record() ? 'create' : 'update';
+		$validation_on = 'validation_on_' . $validation_mode;
+		$validator->set_validation_mode($validation_mode);
 
 		foreach (array('before_validation', "before_$validation_on") as $callback)
 		{
@@ -1103,6 +1189,36 @@ private function _validate()
 		return true;
 	}
 
+	/**
+	 * Returns true if the model has been validated.
+	 *
+	 * @return boolean true if validated
+	 */
+	private function is_validated()
+	{
+		return $this->__validated;
+	}
+
+	/**
+	 * Flag model as validated
+	 *
+	 * @return void
+	 */
+	private function flag_validated()
+	{
+		$this->__validated = true;
+	}
+
+	/**
+	 * Resets the validated flag.
+	 *
+	 * @return void
+	 */
+	private function reset_validated()
+	{
+		$this->__validated = false;
+	}
+
 	/**
 	 * Returns true if the model has been modified.
 	 *
@@ -1114,25 +1230,30 @@ public function is_dirty()
 	}
 
 	/**
-	 * Run validations on model and returns whether or not model passed validation.
+	 * Returns true if the model is valid.
 	 *
+	 * @param boolean $force_validation If true, will always run validations.
 	 * @see is_invalid
 	 * @return boolean
 	 */
-	public function is_valid()
+	public function is_valid($force_validation = false)
 	{
-		return $this->_validate();
+		if( $force_validation || !$this->is_validated() )
+			return $this->_validate();
+
+		return $this->errors->is_empty();
 	}
 
 	/**
-	 * Runs validations and returns true if invalid.
+	 * Returns true if the model is invalid.
 	 *
+	 * @param boolean $force_validation If true, will always run validations.
 	 * @see is_valid
 	 * @return boolean
 	 */
-	public function is_invalid()
+	public function is_invalid($force_validation = false)
 	{
-		return !$this->_validate();
+		return !$this->is_valid($force_validation);
 	}
 
 	/**
@@ -1150,9 +1271,10 @@ public function set_timestamps()
 	}
 
 	/**
-	 * Mass update the model with an array of attribute data and saves to the database.
+	 * Mass update the model with attribute data and saves to the database.
 	 *
-	 * @param array $attributes An attribute data array in the form array(name => value, ...)
+	 * @param StrongParameters|array $attributes An StrongParameters object or array
+	 *  containing data to update in the form array(name => value, ...)
 	 * @return boolean True if successfully updated and saved otherwise false
 	 */
 	public function update_attributes($attributes)
@@ -1175,15 +1297,16 @@ public function update_attribute($name, $value)
 	}
 
 	/**
-	 * Mass update the model with data from an attributes hash.
+	 * Mass update the model with data from an attributes hash or object.
 	 *
 	 * Unlike update_attributes() this method only updates the model's data
 	 * but DOES NOT save it to the database.
 	 *
 	 * @see update_attributes
-	 * @param array $attributes An array containing data to update in the form array(name => value, ...)
+	 * @param StrongParameters|array $attributes An StrongParameters object or array
+	 *  containing data to update in the form array(name => value, ...)
 	 */
-	public function set_attributes(array $attributes)
+	public function set_attributes($attributes)
 	{
 		$this->set_attributes_via_mass_assignment($attributes, true);
 	}
@@ -1192,35 +1315,48 @@ public function set_attributes(array $attributes)
 	 * Passing $guard_attributes as true will throw an exception if an attribute does not exist.
 	 *
 	 * @throws \ActiveRecord\UndefinedPropertyException
-	 * @param array $attributes An array in the form array(name => value, ...)
+	 * @param StrongParameters|array $attributes An StrongParameters object or array
+	 *  containing data to update in the form array(name => value, ...)
 	 * @param boolean $guard_attributes Flag of whether or not protected/non-accessible attributes should be guarded
 	 */
-	private function set_attributes_via_mass_assignment(array &$attributes, $guard_attributes)
+	private function set_attributes_via_mass_assignment(&$attributes, $guard_attributes)
 	{
-		//access uninflected columns since that is what we would have in result set
+		// return fast when no attributes are given
+		if(empty($attributes)) return;
+
+		$require_strong_parameters = Config::instance()->get_require_strong_parameters();
+		if($guard_attributes && $require_strong_parameters && !($attributes instanceof StrongParameters))
+		{
+			throw new UnsafeParametersException();
+		}
+
+		// Legacy support for attr_accessible/attr_protected
+		// Recommended way of protecting attributes is by using StrongParameters
+		if($guard_attributes && !($attributes instanceof StrongParameters))
+		{
+			if (!empty(static::$attr_accessible))
+				$attributes = array_intersect_key($attributes, array_flip(static::$attr_accessible));
+
+			if (!empty(static::$attr_protected))
+				$attributes = array_diff_key($attributes, array_flip(static::$attr_protected));
+		}
+
 		$table = static::table();
 		$exceptions = array();
-		$use_attr_accessible = !empty(static::$attr_accessible);
-		$use_attr_protected = !empty(static::$attr_protected);
 		$connection = static::connection();
 
+		// access uninflected columns since that is what we would have in result set
 		foreach ($attributes as $name => $value)
 		{
 			// is a normal field on the table
-			if (array_key_exists($name,$table->columns))
+			if (array_key_exists($name, $table->columns))
 			{
-				$value = $table->columns[$name]->cast($value,$connection);
+				$value = $table->columns[$name]->cast($value, $connection);
 				$name = $table->columns[$name]->inflected_name;
 			}
 
 			if ($guard_attributes)
 			{
-				if ($use_attr_accessible && !in_array($name,static::$attr_accessible))
-					continue;
-
-				if ($use_attr_protected && in_array($name,static::$attr_protected))
-					continue;
-
 				// set valid table data
 				try {
 					$this->$name = $value;
@@ -1235,7 +1371,7 @@ private function set_attributes_via_mass_assignment(array &$attributes, $guard_a
 					continue;
 
 				// set arbitrary data
-				$this->assign_attribute($name,$value);
+				$this->assign_attribute($name, $value);
 			}
 		}
 
@@ -1285,6 +1421,7 @@ public function reload()
 		$this->expire_cache();
 		$this->set_attributes_via_mass_assignment($this->find($pk)->attributes, false);
 		$this->reset_dirty();
+		$this->reset_validated();
 
 		return $this;
 	}
@@ -1293,6 +1430,7 @@ public function __clone()
 	{
 		$this->__relationships = array();
 		$this->reset_dirty();
+		$this->reset_validated();
 		return $this;
 	}
 
@@ -1301,9 +1439,11 @@ public function __clone()
 	 *
 	 * @see dirty_attributes
 	 */
-	public function reset_dirty()
+	public function reset_dirty($model_was_saved=false)
 	{
 		$this->__dirty = null;
+		$this->previously_changed = $model_was_saved ? $this->changes() : array();
+		$this->changed_attributes = array();
 	}
 
 	/**
@@ -1463,7 +1603,7 @@ public static function count(/* ... */)
 		$table = static::table();
 		$sql = $table->options_to_sql($options);
 		$values = $sql->get_where_values();
-		return static::connection()->query_and_fetch_one($sql->to_s(),$values);
+		return (int) static::connection()->query_and_fetch_one($sql->to_s(),$values);
 	}
 
 	/**
diff --git a/lib/Relationship.php b/lib/Relationship.php
index 9846b44a4..16423609e 100644
--- a/lib/Relationship.php
+++ b/lib/Relationship.php
@@ -496,7 +496,7 @@ public function load(Model $model)
 			if ($this->through)
 			{
 				// verify through is a belongs_to or has_many for access of keys
-				if (!($through_relationship = $this->get_table()->get_relationship($this->through)))
+				if (!($through_relationship = $model->table()->get_relationship($this->through)))
 					throw new HasManyThroughAssociationException("Could not find the association $this->through in model " . get_class($model));
 
 				if (!($through_relationship instanceof HasMany) && !($through_relationship instanceof BelongsTo))
@@ -508,8 +508,7 @@ public function load(Model $model)
 
 				$this->set_keys($this->get_table()->class->getName(), true);
 				
-				$class = $this->class_name;
-				$relation = $class::table()->get_relationship($this->through);
+				$relation = $model::table()->get_relationship($this->through);
 				$through_table = $relation->get_table();
 				$this->options['joins'] = $this->construct_inner_join_sql($through_table, true);
 
diff --git a/lib/SQLBuilder.php b/lib/SQLBuilder.php
index ef239ed87..39c4daafa 100644
--- a/lib/SQLBuilder.php
+++ b/lib/SQLBuilder.php
@@ -291,7 +291,6 @@ private function prepend_table_name_to_fields($hash=array())
 
 	private function apply_where_conditions($args)
 	{
-		require_once 'Expressions.php';
 		$num_args = count($args);
 
 		if ($num_args == 1 && is_hash($args[0]))
@@ -345,7 +344,6 @@ private function build_delete()
 
 	private function build_insert()
 	{
-		require_once 'Expressions.php';
 		$keys = join(',',$this->quoted_key_names());
 
 		if ($this->sequence)
diff --git a/lib/Serialization.php b/lib/Serialization.php
index 812ce5b55..b4b9b324e 100644
--- a/lib/Serialization.php
+++ b/lib/Serialization.php
@@ -136,10 +136,13 @@ private function check_methods()
 		{
 			$this->options_to_a('methods');
 
-			foreach ($this->options['methods'] as $method)
+			foreach ($this->options['methods'] as $method => $name)
 			{
+				if (is_numeric($method))
+					$method = $name;
+
 				if (method_exists($this->model, $method))
-					$this->attributes[$method] = $this->model->$method();
+					$this->attributes[$name] = $this->model->$method();
 			}
 		}
 	}
@@ -242,7 +245,7 @@ final public function __toString()
 	 * @return string
 	 */
 	abstract public function to_s();
-};
+}
 
 /**
  * Array serializer.
@@ -345,32 +348,32 @@ private function write($data, $tag=null)
  */
 class CsvSerializer extends Serialization
 {
-  public static $delimiter = ',';
-  public static $enclosure = '"';
-
-  public function to_s()
-  {
-    if (@$this->options['only_header'] == true) return $this->header();
-    return $this->row();
-  }
-
-  private function header()
-  {
-    return $this->to_csv(array_keys($this->to_a()));
-  }
-
-  private function row()
-  {
-    return $this->to_csv($this->to_a());
-  }
-
-  private function to_csv($arr)
-  {
-    $outstream = fopen('php://temp', 'w');
-    fputcsv($outstream, $arr, self::$delimiter, self::$enclosure);
-    rewind($outstream);
-    $buffer = trim(stream_get_contents($outstream));
-    fclose($outstream);
-    return $buffer;
-  }
-}
\ No newline at end of file
+	public static $delimiter = ',';
+	public static $enclosure = '"';
+
+	public function to_s()
+	{
+		if (@$this->options['only_header'] == true) return $this->header();
+		return $this->row();
+	}
+
+	private function header()
+	{
+		return $this->to_csv(array_keys($this->to_a()));
+	}
+
+	private function row()
+	{
+		return $this->to_csv($this->to_a());
+	}
+
+	private function to_csv($arr)
+	{
+		$outstream = fopen('php://temp', 'w');
+		fputcsv($outstream, $arr, self::$delimiter, self::$enclosure);
+		rewind($outstream);
+		$buffer = trim(stream_get_contents($outstream));
+		fclose($outstream);
+		return $buffer;
+	}
+}
diff --git a/lib/StrongParameters.php b/lib/StrongParameters.php
new file mode 100644
index 000000000..45f42161b
--- /dev/null
+++ b/lib/StrongParameters.php
@@ -0,0 +1,214 @@
+<?php
+
+namespace ActiveRecord;
+
+use IteratorAggregate;
+use ArrayIterator;
+use ArrayAccess;
+use InvalidArgumentException;
+
+/**
+ * The use of StrongParameters is implementation dependent.
+ *
+ * You probably want to integrate this somewhere in your application where the POST data is processed.
+ *
+ * For example, in your controller class you can process the request data:
+ *
+ *   public function beforeFilter()
+ *   {
+ *       $this->params = new ActiveRecord\StrongParameters(array_merge($_GET, $_POST));
+ *   }
+ *
+ * This example assumes that your request data is structured as follows;
+ *
+ *   array(
+ *       "id" => 1,
+ *       "user" => array(
+ *           "name" => "Foo Bar",
+ *           "bio" => "I'm Foo Bar",
+ *           "email" => "foo@bar.baz"
+ *       )
+ *   )
+ *
+ * And then to use the data in a controller:
+ *
+ *   public function update_profile()
+ *   {
+ *       $user = User::find($this->params['id']);
+ *       $user->update_attributes($this->user_params());
+ *       $this->redirect('back');
+ *   }
+ *
+ *   protected function user_params()
+ *   {
+ *       return $this->params->require_param('user')->permit('name', 'bio', 'email');
+ *   }
+ *
+ *
+ * @package ActiveRecord
+ */
+class StrongParameters implements IteratorAggregate, ArrayAccess
+{
+	/**
+	 * Array containing data
+	 *
+	 * @var array
+	 */
+	protected $data = array();
+
+	/**
+	 * Array containing permitted attributes
+	 *
+	 * @var array
+	 */
+	protected $permitted = array();
+
+	/**
+	 * Construct StrongParameters object with data
+	 *
+	 * @param array $data
+	 * @return void
+	 */
+	public function __construct(array $data)
+	{
+		$this->data = $this->parse($data);
+	}
+
+	/**
+	 * Recursively parse array into StrongParameter
+	 * @param array $data
+	 * @return array
+	 */
+	protected function parse(array $data)
+	{
+		return array_map(function($value)
+		{
+			if (!is_hash($value))
+			{
+				return $value;
+			}
+			return new StrongParameters($value);
+		}, $data);
+	}
+
+	/**
+	 * Permit the specified attributes to be returned for mass assignment.
+	 *
+	 * @param mixed $attrs,...
+	 * @return this
+	 */
+	public function permit($attrs = array()/* [, ...$attr] */)
+	{
+		if(func_num_args() > 1)
+		{
+			$attrs = func_get_args();
+		}
+		elseif(!is_array($attrs))
+		{
+			$attrs = array($attrs);
+		}
+		$this->permitted = $attrs;
+		return $this;
+	}
+
+	/**
+	 * Fetch a value from the data
+	 *
+	 * @param string $key
+	 * @return mixed
+	 */
+	public function fetch($key)
+	{
+		if (isset($this->data[$key])) {
+			return $this->data[$key];
+		}
+		return null;
+	}
+
+	/**
+	 * Fetch a required value from the data. If the param doesn't exist an error
+	 * is thrown
+	 *
+	 * @param string $key
+	 * @throws ActiveRecord\ParameterMissingException
+	 * @return mixed
+	 */
+	public function requireParam($key)
+	{
+		$param = $this->fetch($key);
+		if (empty($param)) {
+			throw new ParameterMissingException("Missing param '$key'");
+		}
+		return $param;
+	}
+
+	/**
+	 * @see requireParam
+	 */
+	public function require_param($key)
+	{
+		return $this->requireParam($key);
+	}
+
+	/**
+	 * Required method for IteratorAggregate interface.
+	 *
+	 * @return ArrayIterator iterator for permitted data.
+	 */
+	public function getIterator()
+	{
+		$permitted_data = array_intersect_key($this->data, array_flip($this->permitted));
+		return new ArrayIterator($permitted_data);
+	}
+
+	/**
+	 * Required method for ArrayAccess interface.
+	 *
+	 * @param string $offset
+	 * @return bool true if offset exists
+	 */
+	public function offsetExists($offset)
+	{
+		return isset($this->data[$offset]);
+	}
+
+	/**
+	 * Required method for ArrayAccess interface.
+	 *
+	 * @param string $offset
+	 * @return mixed
+	 * @see fetch
+	 */
+	public function offsetGet($offset)
+	{
+		return $this->fetch($offset);
+	}
+
+	/**
+	 * Required method for ArrayAccess interface.
+	 *
+	 * @param string $offset
+	 * @param mixed $value
+	 * @return void
+	 * @throws InvalidArgumentException when offset is null
+	 */
+	public function offsetSet($offset, $value)
+	{
+		if (is_null($offset)) {
+			throw new InvalidArgumentException('offset cannot be null');
+		}
+		$this->data[$offset] = $value;
+	}
+
+	/**
+	 * Required method for ArrayAccess interface.
+	 *
+	 * @param string $offset
+	 * @return void
+	 */
+	public function offsetUnset($offset)
+	{
+		unset($this->data[$offset]);
+	}
+
+}
diff --git a/lib/Table.php b/lib/Table.php
index 7434ff11c..ec6303459 100644
--- a/lib/Table.php
+++ b/lib/Table.php
@@ -98,7 +98,7 @@ public function __construct($class_name)
 
 		$this->callback = new CallBack($class_name);
 		$this->callback->register('before_save', function(Model $model) { $model->set_timestamps(); }, array('prepend' => true));
-		$this->callback->register('after_save', function(Model $model) { $model->reset_dirty(); }, array('prepend' => true));
+		$this->callback->register('after_save', function(Model $model) { $model->reset_dirty(true); }, array('prepend' => true));
 	}
 
 	public function reestablish_connection($close=true)
@@ -458,6 +458,8 @@ private function set_primary_key()
 					$this->pk[] = $c->inflected_name;
 			}
 		}
+		// make sure the primary key is stored in lowercase
+		$this->pk = array_map('strtolower', $this->pk);
 	}
 
 	private function set_table_name()
diff --git a/lib/Utils.php b/lib/Utils.php
index 105d28c3a..3251f776c 100644
--- a/lib/Utils.php
+++ b/lib/Utils.php
@@ -63,13 +63,15 @@ function array_flatten(array $array)
 /**
  * Somewhat naive way to determine if an array is a hash.
  */
-function is_hash(&$array)
+function is_hash($array)
 {
-	if (!is_array($array))
+	if (!is_array($array)) {
 		return false;
+	}
 
 	$keys = array_keys($array);
-	return @is_string($keys[0]) ? true : false;
+
+	return isset($keys[0]) && is_string($keys[0]);
 }
 
 /**
@@ -173,7 +175,7 @@ public static function extract_options($options)
 		return is_array(end($options)) ? end($options) : array();
 	}
 
-	public static function add_condition(&$conditions=array(), $condition, $conjuction='AND')
+	public static function add_condition(&$conditions=array(), $condition, $conjunction='AND')
 	{
 		if (is_array($condition))
 		{
@@ -181,12 +183,12 @@ public static function add_condition(&$conditions=array(), $condition, $conjucti
 				$conditions = array_flatten($condition);
 			else
 			{
-				$conditions[0] .= " $conjuction " . array_shift($condition);
+				$conditions[0] = "({$conditions[0]}) $conjunction (" . array_shift($condition) . ")";
 				$conditions[] = array_flatten($condition);
 			}
 		}
 		elseif (is_string($condition))
-			$conditions[0] .= " $conjuction $condition";
+			$conditions[0] = "({$conditions[0]}) {$conjunction} ($condition)";
 
 		return $conditions;
 	}
@@ -367,4 +369,4 @@ public static function add_irregular($singular, $plural)
     {
         self::$irregular[$singular] = $plural;
     }
-}
\ No newline at end of file
+}
diff --git a/lib/Validations.php b/lib/Validations.php
index 64a58cfa5..c2cec72b3 100644
--- a/lib/Validations.php
+++ b/lib/Validations.php
@@ -45,6 +45,7 @@ class Validations
 	private $options = array();
 	private $validators = array();
 	private $record;
+	private $validation_mode = null;
 
 	private static $VALIDATION_FUNCTIONS = array(
 		'validates_presence_of',
@@ -179,7 +180,9 @@ public function validates_presence_of($attrs)
 		foreach ($attrs as $attr)
 		{
 			$options = array_merge($configuration, $attr);
-			$this->record->add_on_blank($options[0], $options['message']);
+			
+			if( $this->validation_mode_matches($options['on']) )
+				$this->record->add_on_blank($options[0], $options['message']);
 		}
 	}
 
@@ -249,11 +252,17 @@ public function validates_exclusion_of($attrs)
 	 */
 	public function validates_inclusion_or_exclusion_of($type, $attrs)
 	{
-		$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES[$type], 'on' => 'save'));
+		$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array(
+			'message' => Errors::$DEFAULT_ERROR_MESSAGES[$type]
+		));
 
 		foreach ($attrs as $attr)
 		{
 			$options = array_merge($configuration, $attr);
+			
+			if (!$this->validation_mode_matches($options['on']))
+				continue;
+			
 			$attribute = $options[0];
 			$var = $this->model->$attribute;
 
@@ -313,6 +322,10 @@ public function validates_numericality_of($attrs)
 		foreach ($attrs as $attr)
 		{
 			$options = array_merge($configuration, $attr);
+			
+			if (!$this->validation_mode_matches($options['on']))
+				continue;
+			
 			$attribute = $options[0];
 			$var = $this->model->$attribute;
 
@@ -414,11 +427,18 @@ public function validates_size_of($attrs)
 	 */
 	public function validates_format_of($attrs)
 	{
-		$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES['invalid'], 'on' => 'save', 'with' => null));
+		$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array(
+			'message' => Errors::$DEFAULT_ERROR_MESSAGES['invalid'],
+			'with' => null
+		));
 
 		foreach ($attrs as $attr)
 		{
 			$options = array_merge($configuration, $attr);
+			
+			if (!$this->validation_mode_matches($options['on']))
+				continue;
+			
 			$attribute = $options[0];
 			$var = $this->model->$attribute;
 
@@ -470,6 +490,10 @@ public function validates_length_of($attrs)
 		foreach ($attrs as $attr)
 		{
 			$options = array_merge($configuration, $attr);
+			
+			if (!$this->validation_mode_matches($options['on']))
+				continue;
+			
 			$range_options = array_intersect(array_keys(self::$ALL_RANGE_OPTIONS), array_keys($attr));
 			sort($range_options);
 
@@ -571,6 +595,10 @@ public function validates_uniqueness_of($attrs)
 		foreach ($attrs as $attr)
 		{
 			$options = array_merge($configuration, $attr);
+			
+			if (!$this->validation_mode_matches($options['on']))
+				continue;
+			
 			$pk = $this->model->get_primary_key();
 			$pk_value = $this->model->{$pk[0]};
 
@@ -610,6 +638,49 @@ public function validates_uniqueness_of($attrs)
 				$this->record->add($add_record, $options['message']);
 		}
 	}
+	
+	/**
+	 * Set the validation mode.
+	 *
+	 * The validation mode is used to check if a validation rule should be executed.
+	 * This is decided based on the "on" option for validation rules.
+	 *
+	 * The "on" option can have the following values:
+	 *
+	 * <ul>
+	 * <li><b>save:</b> Invoked before saving changes to either a new or an existing model.
+	 * <li><b>create:</b> Invoked only before inserting a new model.
+	 * <li><b>update:</b> Invoked only before updating an existing model.
+	 * <li><b>delete:</b> Invoked before deletion of a model (unimplemented).
+	 * </ul>
+	 *
+	 * @param string $mode The mode to set, one of "create", "update" or "delete".
+	 * @todo implement "delete" value for option "on"
+	 */
+	public function set_validation_mode($mode)
+	{
+		$this->validation_mode = $mode;
+	}
+	
+	/**
+	 * Check if the current validation mode matches the specified mode.
+	 *
+	 * See {@link set_validation_mode} for more information on validation modes.
+	 *
+	 * @param string $mode The mode to be matched, one of "create", "update" or "delete".
+	 * @return boolean
+	 */
+	private function validation_mode_matches($mode)
+	{
+		// The validation mode is not always set, run the validation anyway in that case
+		if( $this->validation_mode == null )
+			return true;
+		
+		if( $mode == 'save' )
+			return in_array($this->validation_mode, array('create', 'update'));
+		
+		return $mode == $this->validation_mode;
+	}
 
 	private function is_null_with_option($var, &$options)
 	{
diff --git a/lib/adapters/MysqlAdapter.php b/lib/adapters/MysqlAdapter.php
index 12a50db63..ddd545059 100644
--- a/lib/adapters/MysqlAdapter.php
+++ b/lib/adapters/MysqlAdapter.php
@@ -96,4 +96,3 @@ public function native_database_types()
 	}
 
 }
-?>
diff --git a/lib/adapters/OciAdapter.php b/lib/adapters/OciAdapter.php
index 353fffefa..5e97343e2 100644
--- a/lib/adapters/OciAdapter.php
+++ b/lib/adapters/OciAdapter.php
@@ -22,7 +22,7 @@ protected function __construct($info)
 	{
 		try {
 			$this->dsn_params = isset($info->charset) ? ";charset=$info->charset" : "";
-			$this->connection = new PDO("oci:dbname=//$info->host/$info->db$this->dsn_params",$info->user,$info->pass,static::$PDO_OPTIONS);
+			$this->connection = new PDO("oci:dbname=//$info->host/$info->database$this->dsn_params",$info->username,$info->password,static::$PDO_OPTIONS);
 		} catch (PDOException $e) {
 			throw new DatabaseException($e);
 		}
@@ -143,4 +143,3 @@ public function native_database_types()
 		);
 	}
 }
-?>
diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php
index 72da44182..17e7eb113 100644
--- a/lib/adapters/PgsqlAdapter.php
+++ b/lib/adapters/PgsqlAdapter.php
@@ -101,7 +101,7 @@ public function create_column(&$column)
 
 		$c->map_raw_type();
 
-		if ($column['default'])
+		if (!is_null($column['default']))
 		{
 			preg_match("/^nextval\('(.*)'\)$/",$column['default'],$matches);
 
@@ -131,9 +131,21 @@ public function native_database_types()
 			'time' => array('name' => 'time'),
 			'date' => array('name' => 'date'),
 			'binary' => array('name' => 'binary'),
-			'boolean' => array('name' => 'boolean')
+			'boolean' => array('name' => 'boolean'),
+			'bigint' => array('name' => 'integer'),
+			'smallint' => array('name' => 'integer'),
+			'real' => array('name' => 'float'),
+			'double precision' => array('name' => 'float'),
+			'numeric' => array('name' => 'float'),
+			'decimal' => array('name' => 'float')
 		);
 	}
 
+	public function boolean_to_string($value)
+	{
+		if (!$value || in_array(strtolower($value), array('f','false','n','no','off')))
+			return "0";
+		else
+			return "1";
+	}
 }
-?>
diff --git a/lib/adapters/SqliteAdapter.php b/lib/adapters/SqliteAdapter.php
index 0917043be..61eb257f2 100644
--- a/lib/adapters/SqliteAdapter.php
+++ b/lib/adapters/SqliteAdapter.php
@@ -110,4 +110,3 @@ public function native_database_types()
 	}
 
 }
-?>
\ No newline at end of file
diff --git a/test/ActiveRecordCacheTest.php b/test/ActiveRecordCacheTest.php
index 59749d613..6d43cca45 100644
--- a/test/ActiveRecordCacheTest.php
+++ b/test/ActiveRecordCacheTest.php
@@ -41,4 +41,4 @@ public function test_caches_column_meta_data()
 		$this->assert_true(is_array($value));
 	}
 }
-?>
+
diff --git a/test/ActiveRecordFindTest.php b/test/ActiveRecordFindTest.php
index 329eb9e67..b1e76a8ca 100644
--- a/test/ActiveRecordFindTest.php
+++ b/test/ActiveRecordFindTest.php
@@ -226,12 +226,12 @@ public function test_fetch_all()
 
 	public function test_count()
 	{
-		$this->assert_equals(1,Author::count(1));
-		$this->assert_equals(2,Author::count(array(1,2)));
+		$this->assert_same(1,Author::count(1));
+		$this->assert_same(2,Author::count(array(1,2)));
 		$this->assert_true(Author::count() > 1);
-		$this->assert_equals(0,Author::count(array('conditions' => 'author_id=99999999999999')));
-		$this->assert_equals(2,Author::count(array('conditions' => 'author_id=1 or author_id=2')));
-		$this->assert_equals(1,Author::count(array('name' => 'Tito', 'author_id' => 1)));
+		$this->assert_same(0,Author::count(array('conditions' => 'author_id=99999999999999')));
+		$this->assert_same(2,Author::count(array('conditions' => 'author_id=1 or author_id=2')));
+		$this->assert_same(1,Author::count(array('name' => 'Tito', 'author_id' => 1)));
 	}
 
 	public function test_gh149_empty_count()
@@ -476,5 +476,4 @@ public function test_find_by_datetime()
 		$this->assert_not_null(Author::find_by_created_at($now));
 		$this->assert_not_null(Author::find_by_created_at($arnow));
 	}
-};
-?>
+}
diff --git a/test/ActiveRecordTest.php b/test/ActiveRecordTest.php
index f0a0fd110..cc0eaf881 100644
--- a/test/ActiveRecordTest.php
+++ b/test/ActiveRecordTest.php
@@ -167,17 +167,39 @@ public function test_reload_protected_attribute()
 	public function test_active_record_model_home_not_set()
 	{
 		$home = ActiveRecord\Config::instance()->get_model_directory();
-		ActiveRecord\Config::instance()->set_model_directory(__FILE__);
+		ActiveRecord\Config::instance()->set_model_directory(__DIR__);
 		$this->assert_equals(false,class_exists('TestAutoload'));
 
 		ActiveRecord\Config::instance()->set_model_directory($home);
 	}
 
+	public function test_auto_load_with_model_in_secondary_model_directory(){
+		$home = ActiveRecord\Config::instance()->get_model_directory();
+		ActiveRecord\Config::instance()->set_model_directories(array(
+			realpath(__DIR__ . '/models'),
+			realpath(__DIR__ . '/backup-models'),
+		));
+		$this->assert_true(class_exists('Backup'));
+
+		ActiveRecord\Config::instance()->set_model_directory($home);
+	}
+
 	public function test_auto_load_with_namespaced_model()
 	{
 		$this->assert_true(class_exists('NamespaceTest\Book'));
 	}
 
+	public function test_auto_load_with_namespaced_model_in_secondary_model_directory(){
+		$home = ActiveRecord\Config::instance()->get_model_directory();
+		ActiveRecord\Config::instance()->set_model_directories(array(
+			realpath(__DIR__ . '/models'),
+			realpath(__DIR__ . '/backup-models'),
+		));
+		$this->assert_true(class_exists('NamespaceTest\Backup'));
+
+		ActiveRecord\Config::instance()->set_model_directory($home);
+	}
+
 	public function test_namespace_gets_stripped_from_table_name()
 	{
 		$model = new NamespaceTest\Book();
@@ -259,6 +281,22 @@ public function test_alias_attribute_setter()
 		$this->assert_equals($venue->marquee, $venue->name);
 	}
 
+	public function test_alias_attribute_custom_setter()
+	{
+		Venue::$use_custom_set_city_setter = true;
+		$venue = Venue::find(1);
+
+		$venue->mycity = 'cityname';
+		$this->assert_equals($venue->mycity, 'cityname#');
+		$this->assert_equals($venue->mycity, $venue->city);
+
+		$venue->city = 'anothercity';
+		$this->assert_equals($venue->city, 'anothercity#');
+		$this->assert_equals($venue->city, $venue->mycity);
+
+		Venue::$use_custom_set_city_setter = false;
+	}
+
 	public function test_alias_from_mass_attributes()
 	{
 		$venue = new Venue(array('marquee' => 'meme', 'id' => 123));
@@ -526,6 +564,102 @@ public function test_gh245_dirty_attribute_should_not_raise_php_notice_if_not_di
 		$this->assert_true($event->attribute_is_dirty('title'));
 	}
 
+	public function test_attribute_is_not_flagged_dirty_if_assigning_same_value() {
+		$event = Event::find(1);
+		$event->type = "Music";
+		$this->assert_false($event->attribute_is_dirty('type'));
+	}
+
+	public function test_changed_attributes() {
+		$event = Event::find(1);
+
+		$event->type = "Groovy Music";
+		$changed_attributes = $event->changed_attributes();
+		$this->assert_true(is_array($changed_attributes));
+		$this->assert_equals(1, count($changed_attributes));
+		$this->assert_true(isset($changed_attributes['type']));
+		$this->assert_equals("Music", $changed_attributes['type']);
+
+		$event->type = "Funky Music";
+		$changed_attributes = $event->changed_attributes();
+		$this->assert_true(is_array($changed_attributes));
+		$this->assert_equals(1, count($changed_attributes));
+		$this->assert_true(isset($changed_attributes['type']));
+		$this->assert_equals("Music", $changed_attributes['type']);
+	}
+
+	public function test_changes() {
+		$event = Event::find(1);
+
+		$event->type = "Groovy Music";
+		$changes = $event->changes();
+		$this->assert_true(is_array($changes));
+		$this->assert_equals(1, count($changes));
+		$this->assert_true(isset($changes['type']));
+		$this->assert_true(is_array($changes['type']));
+		$this->assert_equals("Music", $changes['type'][0]);
+		$this->assert_equals("Groovy Music", $changes['type'][1]);
+
+		$event->type = "Funky Music";
+		$changes = $event->changes();
+		$this->assert_true(is_array($changes));
+		$this->assert_equals(1, count($changes));
+		$this->assert_true(isset($changes['type']));
+		$this->assert_true(is_array($changes['type']));
+		$this->assert_equals("Music", $changes['type'][0]);
+		$this->assert_equals("Funky Music", $changes['type'][1]);
+	}
+
+	public function test_attribute_was() {
+		$event = Event::find(1);
+		$event->type = "Funky Music";
+		$this->assert_equals("Music", $event->attribute_was("type"));
+		$event->type = "Groovy Music";
+		$this->assert_equals("Music", $event->attribute_was("type"));
+	}
+
+	public function test_previous_changes() {
+		$event = Event::find(1);
+		$event->type = "Groovy Music";
+		$previous_changes = $event->previous_changes();
+		$this->assert_true(empty($previous_changes));
+		$event->save();
+		$previous_changes = $event->previous_changes();
+		$this->assert_true(is_array($previous_changes));
+		$this->assert_equals(1, count($previous_changes));
+		$this->assert_true(isset($previous_changes['type']));
+		$this->assert_true(is_array($previous_changes['type']));
+		$this->assert_equals("Music", $previous_changes['type'][0]);
+		$this->assert_equals("Groovy Music", $previous_changes['type'][1]);
+	}
+
+	public function test_save_resets_changed_attributes() {
+		$event = Event::find(1);
+		$event->type = "Groovy Music";
+		$event->save();
+		$changed_attributes = $event->changed_attributes();
+		$this->assert_true(empty($changed_attributes));
+	}
+
+	public function test_changing_datetime_attribute_tracks_change() {
+		$author = new Author();
+		$author->created_at = $original = new \DateTime("yesterday");
+		$author->created_at = $now = new \DateTime();
+		$changes = $author->changes();
+		$this->assert_true(isset($changes['created_at']));
+		$this->assert_datetime_equals($original, $changes['created_at'][0]);
+		$this->assert_datetime_equals($now, $changes['created_at'][1]);
+	}
+
+	public function test_changing_empty_attribute_value_tracks_change() {
+		$event = new Event();
+		$event->description = "The most fun";
+		$changes = $event->changes();
+		$this->assert_true(array_key_exists("description", $changes));
+		$this->assert_equals("", $changes['description'][0]);
+		$this->assert_equals("The most fun", $changes['description'][1]);
+	}
+
 	public function test_assigning_php_datetime_gets_converted_to_date_class_with_defaults()
 	{
 		$author = new Author();
@@ -571,5 +705,4 @@ public function test_query()
 		$row = Author::query('SELECT COUNT(*) AS n FROM authors WHERE name=?',array('Tito'))->fetch();
 		$this->assert_equals(array('n' => 1), $row);
 	}
-};
-?>
+}
diff --git a/test/ActiveRecordWriteTest.php b/test/ActiveRecordWriteTest.php
index 48c156c34..c0351413d 100644
--- a/test/ActiveRecordWriteTest.php
+++ b/test/ActiveRecordWriteTest.php
@@ -10,7 +10,7 @@ public function before_save()
 	{
 		$this->name = 'i saved';
 	}
-};
+}
 
 class AuthorWithoutSequence extends ActiveRecord\Model
 {
@@ -441,4 +441,4 @@ public function test_update_our_datetime()
 		$this->assert_true($our_datetime === $author->some_date);
 	}
 
-};
+}
diff --git a/test/CacheTest.php b/test/CacheTest.php
index 3e390a7cd..af5e5426c 100644
--- a/test/CacheTest.php
+++ b/test/CacheTest.php
@@ -88,4 +88,3 @@ public function test_exception_when_connect_fails()
 		Cache::initialize('memcache://127.0.0.1:1234');
 	}
 }
-?>
diff --git a/test/CallbackTest.php b/test/CallbackTest.php
index 00615b12d..9c1e46d29 100644
--- a/test/CallbackTest.php
+++ b/test/CallbackTest.php
@@ -290,5 +290,4 @@ public function test_before_validation_returned_false_halts_execution()
 		$this->assert_false($ret);
 		$this->assert_true(strpos(ActiveRecord\Table::load('VenueCB')->last_sql, 'UPDATE') === false);
 	}
-};
-?>
\ No newline at end of file
+}
diff --git a/test/ColumnTest.php b/test/ColumnTest.php
index 5330814ea..c36dceeca 100644
--- a/test/ColumnTest.php
+++ b/test/ColumnTest.php
@@ -95,6 +95,9 @@ public function test_cast()
 		// 64 bit
 		elseif (PHP_INT_SIZE === 8)
 			$this->assert_cast(Column::INTEGER,'9223372036854775808',(((float) PHP_INT_MAX) + 1));
+
+		$this->assert_cast(Column::BOOLEAN,$this->conn->boolean_to_string(true),true);
+		$this->assert_cast(Column::BOOLEAN,$this->conn->boolean_to_string(false),false);
 	}
 
 	public function test_cast_leave_null_alone()
@@ -104,7 +107,8 @@ public function test_cast_leave_null_alone()
 			Column::INTEGER,
 			Column::DECIMAL,
 			Column::DATETIME,
-			Column::DATE);
+			Column::DATE,
+			Column::BOOLEAN);
 
 		foreach ($types as $type) {
 			$this->assert_cast($type,null,null);
@@ -155,4 +159,3 @@ public function test_ar_date_time_attribute_copies_exact_tz()
 		$this->assert_equals($dt->getTimeZone()->getName(), $dt2->getTimeZone()->getName());
 	}
 }
-?>
diff --git a/test/ConfigTest.php b/test/ConfigTest.php
index f547d5197..f15510a27 100644
--- a/test/ConfigTest.php
+++ b/test/ConfigTest.php
@@ -24,7 +24,14 @@ class ConfigTest extends SnakeCase_PHPUnit_Framework_TestCase
 	public function set_up()
 	{
 		$this->config = new Config();
-		$this->connections = array('development' => 'mysql://blah/development', 'test' => 'mysql://blah/test');
+		$this->connections = array(
+			'development' => 'mysql://blah/development',
+			'test' => 'mysql://blah/test',
+			'production' => array(
+				'adapter' => 'mysql',
+				'host' => '127.0.0.1',
+				'database' => 'production'
+			));
 		$this->config->set_connections($this->connections);
 	}
 
@@ -38,36 +45,40 @@ public function test_set_connections_must_be_array()
 
 	public function test_get_connections()
 	{
-		$this->assert_equals($this->connections,$this->config->get_connections());
+		$connections = $this->config->get_connections();
+		$this->assert_instance_of('ActiveRecord\ConnectionInfo', $connections['development']);
+		$this->assert_equals('development', $connections['development']->database);
 	}
 
-	public function test_get_connection()
+	public function test_get_connection_info()
 	{
-		$this->assert_equals($this->connections['development'],$this->config->get_connection('development'));
+		$connection = $this->config->get_connection_info('development');
+		$this->assert_instance_of('ActiveRecord\ConnectionInfo', $connection);
+		$this->assert_equals('development', $connection->database);
 	}
 
 	public function test_get_invalid_connection()
 	{
-		$this->assert_null($this->config->get_connection('whiskey tango foxtrot'));
+		$this->assert_null($this->config->get_connection_info('whiskey tango foxtrot'));
 	}
 
 	public function test_get_default_connection_and_connection()
 	{
-		$this->config->set_default_connection('development');
-		$this->assert_equals('development',$this->config->get_default_connection());
-		$this->assert_equals($this->connections['development'],$this->config->get_default_connection_string());
+		$this->config->set_default_connection('test');
+		$this->assert_equals('test', $this->config->get_default_connection());
+		$this->assert_equals('test', $this->config->get_default_connection_info()->database);
 	}
 
-	public function test_get_default_connection_and_connection_string_defaults_to_development()
+	public function test_get_default_connection_and_connection_info_defaults_to_development()
 	{
-		$this->assert_equals('development',$this->config->get_default_connection());
-		$this->assert_equals($this->connections['development'],$this->config->get_default_connection_string());
+		$this->assert_equals('development', $this->config->get_default_connection());
+		$this->assert_equals('development', $this->config->get_default_connection_info()->database);
 	}
 
-	public function test_get_default_connection_string_when_connection_name_is_not_valid()
+	public function test_get_default_connection_info_when_connection_name_is_not_valid()
 	{
 		$this->config->set_default_connection('little mac');
-		$this->assert_null($this->config->get_default_connection_string());
+		$this->assert_null($this->config->get_default_connection_info());
 	}
 
 	public function test_default_connection_is_set_when_only_one_connection_is_present()
@@ -82,6 +93,14 @@ public function test_set_connections_with_default()
 		$this->assert_equals('test',$this->config->get_default_connection());
 	}
 
+	/**
+	 * @expectedException ActiveRecord\ConfigException
+	 */
+	public function test_set_model_directories_must_be_array()
+	{
+		$this->config->set_model_directories(null);
+	}
+
 	public function test_get_date_class_with_default()
 	{
 		$this->assert_equals('ActiveRecord\\DateTime', $this->config->get_date_class());
@@ -95,6 +114,41 @@ public function test_set_date_class_when_class_doesnt_exist()
 		$this->config->set_date_class('doesntexist');
 	}
 
+	/**
+	 * @expectedException ActiveRecord\ConfigException
+	 */
+	public function test_set_model_directories_directories_must_exist(){
+		$home = ActiveRecord\Config::instance()->get_model_directory();
+
+		$this->config->set_model_directories(array(
+			realpath(__DIR__ . '/models'),
+			'/some-non-existing-directory'
+		));
+
+		ActiveRecord\Config::instance()->set_model_directory($home);
+	}
+
+	public function test_set_model_directory_stores_as_array(){
+		$home = ActiveRecord\Config::instance()->get_model_directory();
+
+		$this->config->set_model_directory(realpath(__DIR__ . '/models'));
+		$this->assertInternalType('array', $this->config->get_model_directories());
+
+		ActiveRecord\Config::instance()->set_model_directory($home);
+	}
+
+	public function test_get_model_directory_returns_first_model_directory(){
+		$home = ActiveRecord\Config::instance()->get_model_directory();
+
+		$this->config->set_model_directories(array(
+			realpath(__DIR__ . '/models'),
+			realpath(__DIR__ . '/backup-models'),
+		));
+		$this->assert_equals(realpath(__DIR__ . '/models'), $this->config->get_model_directory());
+
+		ActiveRecord\Config::instance()->set_model_directory($home);
+	}
+
 	/**
 	 * @expectedException ActiveRecord\ConfigException
 	 */
@@ -138,4 +192,3 @@ public function test_logger_object_must_implement_log_method()
 		}
 	}
 }
-?>
diff --git a/test/ConnectionInfoTest.php b/test/ConnectionInfoTest.php
new file mode 100644
index 000000000..cea868509
--- /dev/null
+++ b/test/ConnectionInfoTest.php
@@ -0,0 +1,95 @@
+<?php
+
+use ActiveRecord\ConnectionInfo;
+
+class ConnectionInfoTest extends SnakeCase_PHPUnit_Framework_TestCase
+{
+	/**
+	 * @expectedException ActiveRecord\DatabaseException
+	 */
+	public function test_connection_info_from_should_throw_exception_when_no_host()
+	{
+		ConnectionInfo::from_connection_url('mysql://user:pass@');
+	}
+
+	public function test_connection_info()
+	{
+		$info = ConnectionInfo::from_connection_url('mysql://user:pass@127.0.0.1:3306/dbname');
+		$this->assert_equals('mysql', $info->adapter);
+		$this->assert_equals('user', $info->username);
+		$this->assert_equals('pass', $info->password);
+		$this->assert_equals('127.0.0.1', $info->host);
+		$this->assert_equals(3306, $info->port);
+		$this->assert_equals('dbname', $info->database);
+	}
+	
+	public function test_gh_103_sqlite_connection_string_relative()
+	{
+		$info = ConnectionInfo::from_connection_url('sqlite://../some/path/to/file.db');
+		$this->assert_equals('../some/path/to/file.db', $info->host);
+	}
+
+	/**
+	 * @expectedException ActiveRecord\DatabaseException
+	 */
+	public function test_gh_103_sqlite_connection_string_absolute()
+	{
+		$info = ConnectionInfo::from_connection_url('sqlite:///some/path/to/file.db');
+	}
+
+	public function test_gh_103_sqlite_connection_string_unix()
+	{
+		$info = ConnectionInfo::from_connection_url('sqlite://unix(/some/path/to/file.db)');
+		$this->assert_equals('/some/path/to/file.db', $info->host);
+
+		$info = ConnectionInfo::from_connection_url('sqlite://unix(/some/path/to/file.db)/');
+		$this->assert_equals('/some/path/to/file.db', $info->host);
+
+		$info = ConnectionInfo::from_connection_url('sqlite://unix(/some/path/to/file.db)/dummy');
+		$this->assert_equals('/some/path/to/file.db', $info->host);
+	}
+
+	public function test_gh_103_sqlite_connection_string_windows()
+	{
+		$info = ConnectionInfo::from_connection_url('sqlite://windows(c%3A/some/path/to/file.db)');
+		$this->assert_equals('c:/some/path/to/file.db', $info->host);
+	}
+
+	public function test_parse_connection_url_with_unix_sockets()
+	{
+		$info = ConnectionInfo::from_connection_url('mysql://user:password@unix(/tmp/mysql.sock)/database');
+		$this->assert_equals('/tmp/mysql.sock', $info->host);
+		$this->assert_equals('database', $info->database);
+	}
+
+	public function test_parse_connection_url_with_decode_option()
+	{
+		$info = ConnectionInfo::from_connection_url('mysql://h%20az:h%40i@127.0.0.1/test?decode=true');
+		$this->assert_equals('h az', $info->username);
+		$this->assert_equals('h@i', $info->password);
+	}
+
+	public function test_encoding()
+	{
+		$info = ConnectionInfo::from_connection_url('mysql://test:test@127.0.0.1/test?charset=utf8');
+		$this->assert_equals('utf8', $info->charset);
+	}
+
+	public function test_connection_info_from_array(){
+		$info = new ConnectionInfo(array(
+			'adapter' => 'mysql',
+			'host' => '127.0.0.1',
+			'port' => 3306,
+			'database' => 'dbname',
+			'username' => 'user',
+			'password' => 'pass'
+		));
+		$this->assert_equals('mysql', $info->adapter);
+		$this->assert_equals('user', $info->username);
+		$this->assert_equals('pass', $info->password);
+		$this->assert_equals('127.0.0.1', $info->host);
+		$this->assert_equals(3306, $info->port);
+		$this->assert_equals('dbname', $info->database);
+	}
+
+}
diff --git a/test/ConnectionTest.php b/test/ConnectionTest.php
deleted file mode 100644
index 59f91c7c6..000000000
--- a/test/ConnectionTest.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-use ActiveRecord\Connection;
-
-
-// Only use this to test static methods in Connection that are not specific
-// to any database adapter.
-
-class ConnectionTest extends SnakeCase_PHPUnit_Framework_TestCase
-{
-	/**
-	 * @expectedException ActiveRecord\DatabaseException
-	 */
-	public function test_connection_info_from_should_throw_exception_when_no_host()
-	{
-		ActiveRecord\Connection::parse_connection_url('mysql://user:pass@');
-	}
-
-	public function test_connection_info()
-	{
-		$info = ActiveRecord\Connection::parse_connection_url('mysql://user:pass@127.0.0.1:3306/dbname');
-		$this->assert_equals('mysql',$info->protocol);
-		$this->assert_equals('user',$info->user);
-		$this->assert_equals('pass',$info->pass);
-		$this->assert_equals('127.0.0.1',$info->host);
-		$this->assert_equals(3306,$info->port);
-		$this->assert_equals('dbname',$info->db);
-	}
-	
-	public function test_gh_103_sqlite_connection_string_relative()
-	{
-		$info = ActiveRecord\Connection::parse_connection_url('sqlite://../some/path/to/file.db');
-		$this->assert_equals('../some/path/to/file.db', $info->host);
-	}
-
-	/**
-	 * @expectedException ActiveRecord\DatabaseException
-	 */
-	public function test_gh_103_sqlite_connection_string_absolute()
-	{
-		$info = ActiveRecord\Connection::parse_connection_url('sqlite:///some/path/to/file.db');
-	}
-
-	public function test_gh_103_sqlite_connection_string_unix()
-	{
-		$info = ActiveRecord\Connection::parse_connection_url('sqlite://unix(/some/path/to/file.db)');
-		$this->assert_equals('/some/path/to/file.db', $info->host);
-       	
-		$info = ActiveRecord\Connection::parse_connection_url('sqlite://unix(/some/path/to/file.db)/');
-		$this->assert_equals('/some/path/to/file.db', $info->host);
-    	
-		$info = ActiveRecord\Connection::parse_connection_url('sqlite://unix(/some/path/to/file.db)/dummy');
-		$this->assert_equals('/some/path/to/file.db', $info->host);
-	}
-
-	public function test_gh_103_sqlite_connection_string_windows()
-	{
-		$info = ActiveRecord\Connection::parse_connection_url('sqlite://windows(c%3A/some/path/to/file.db)');
-		$this->assert_equals('c:/some/path/to/file.db', $info->host);
-	}
-
-	public function test_parse_connection_url_with_unix_sockets()
-	{
-		$info = ActiveRecord\Connection::parse_connection_url('mysql://user:password@unix(/tmp/mysql.sock)/database');
-		$this->assert_equals('/tmp/mysql.sock',$info->host);
-	}
-
-	public function test_parse_connection_url_with_decode_option()
-	{
-		$info = ActiveRecord\Connection::parse_connection_url('mysql://h%20az:h%40i@127.0.0.1/test?decode=true');
-		$this->assert_equals('h az',$info->user);
-		$this->assert_equals('h@i',$info->pass);
-	}
-
-	public function test_encoding()
-	{
-		$info = ActiveRecord\Connection::parse_connection_url('mysql://test:test@127.0.0.1/test?charset=utf8');
-		$this->assert_equals('utf8', $info->charset);
-	}
-}
-?>
diff --git a/test/DateFormatTest.php b/test/DateFormatTest.php
index ab857e90a..b18faffd7 100644
--- a/test/DateFormatTest.php
+++ b/test/DateFormatTest.php
@@ -14,5 +14,4 @@ public function test_datefield_gets_converted_to_ar_datetime()
 		$this->assert_is_a("ActiveRecord\\DateTime",$author->some_date);
 	}
 
-};
-?>
+}
diff --git a/test/DateTimeTest.php b/test/DateTimeTest.php
index 882328e5a..14839da16 100644
--- a/test/DateTimeTest.php
+++ b/test/DateTimeTest.php
@@ -224,4 +224,3 @@ public function test_clone()
 		$this->assert_not_same($datetime, $cloned_datetime);
 	}
 }
-?>
diff --git a/test/ExpressionsTest.php b/test/ExpressionsTest.php
index fdddb57ea..c1d90aab3 100644
--- a/test/ExpressionsTest.php
+++ b/test/ExpressionsTest.php
@@ -1,5 +1,4 @@
 <?php
-require_once __DIR__ . '/../lib/Expressions.php';
 
 use ActiveRecord\Expressions;
 use ActiveRecord\ConnectionManager;
@@ -205,4 +204,3 @@ public function test_hash_with_array()
 		$this->assert_equals('id=? AND name IN(?,?)',$a->to_s());
 	}
 }
-?>
diff --git a/test/HasManyThroughTest.php b/test/HasManyThroughTest.php
index ff9586cc1..1dcbdf3c3 100644
--- a/test/HasManyThroughTest.php
+++ b/test/HasManyThroughTest.php
@@ -55,6 +55,41 @@ public function test_gh107_has_many_though_include_eager_with_namespace()
 		$this->assert_equals(1, $user->id);
 		$this->assert_equals(1, $user->newsletters[0]->id);
 	}
+
+	public function test_gh68_has_many_through_with_missing_associations()
+	{
+		Property::$has_many = array(
+			array('amenities', 'through' => 'property_amenities')
+		);
+		Amenity::$has_many = array(
+			array('properties', 'through' => 'property_amenities')
+		);
+
+		$property = Property::find('first');
+		try{
+			$property->amenities;
+			$this->fail('Should trigger exception');
+		}catch(ActiveRecord\HasManyThroughAssociationException $e){
+			$this->assertEquals('Could not find the association property_amenities in model Property', $e->getMessage());
+		}
+	}
+
+	public function test_gh68_has_many_through_with_missing_association()
+	{
+		Property::$has_many = array(
+			'property_amenities',
+			array('amenities', 'through' => 'property_amenities')
+		);
+		Amenity::$has_many = array(
+			array('properties', 'through' => 'property_amenities')
+		);
+
+		$amenity = Amenity::find('first');
+		try{
+			$amenity->properties;
+			$this->fail('Should trigger exception');
+		}catch(ActiveRecord\HasManyThroughAssociationException $e){
+			$this->assertEquals('Could not find the association property_amenities in model Amenity', $e->getMessage());
+		}
+	}
 }
-# vim: noet ts=4 nobinary
-?>
diff --git a/test/InflectorTest.php b/test/InflectorTest.php
index 7726592f0..d93f40b38 100644
--- a/test/InflectorTest.php
+++ b/test/InflectorTest.php
@@ -24,5 +24,4 @@ public function test_keyify()
 	{
 		$this->assert_equals('building_type_id', $this->inflector->keyify('BuildingType'));
 	}
-};
-?>
\ No newline at end of file
+}
diff --git a/test/ModelCallbackTest.php b/test/ModelCallbackTest.php
index 949706385..b9ffd7466 100644
--- a/test/ModelCallbackTest.php
+++ b/test/ModelCallbackTest.php
@@ -94,4 +94,3 @@ public function test_destroy()
 			function($model) { $model->delete(); });
 	}
 }
-?>
diff --git a/test/MysqlAdapterTest.php b/test/MysqlAdapterTest.php
index 2a93570e6..f7153eea8 100644
--- a/test/MysqlAdapterTest.php
+++ b/test/MysqlAdapterTest.php
@@ -20,9 +20,10 @@ public function test_enum()
 
 	public function test_set_charset()
 	{
-		$connection_string = ActiveRecord\Config::instance()->get_connection($this->connection_name);
-		$conn = ActiveRecord\Connection::instance($connection_string . '?charset=utf8');
-		$this->assert_equals('SET NAMES ?',$conn->last_query);
+		$connection_info = ActiveRecord\Config::instance()->get_connection_info($this->connection_name);
+		$connection_info->charset = 'utf8';
+		$conn = ActiveRecord\Connection::instance($connection_info);
+		$this->assert_equals('SET NAMES ?', $conn->last_query);
 	}
 
 	public function test_limit_with_null_offset_does_not_contain_offset()
@@ -34,4 +35,3 @@ public function test_limit_with_null_offset_does_not_contain_offset()
 		$this->assert_true(strpos($this->conn->last_query, 'LIMIT 1') !== false);
 	}
 }
-?>
diff --git a/test/OciAdapterTest.php b/test/OciAdapterTest.php
index d820c0fd5..2a8fa7376 100644
--- a/test/OciAdapterTest.php
+++ b/test/OciAdapterTest.php
@@ -38,9 +38,9 @@ public function test_columns_sequence() {}
 
 	public function test_set_charset()
 	{
-		$connection_string = ActiveRecord\Config::instance()->get_connection($this->connection_name);
-		$conn = ActiveRecord\Connection::instance($connection_string . '?charset=utf8');
+		$connection_info = ActiveRecord\Config::instance()->get_connection_info($this->connection_name);
+		$connection_info->charset = 'utf8';
+		$conn = ActiveRecord\Connection::instance($connection_info);
 		$this->assert_equals(';charset=utf8', $conn->dsn_params);
 	}
 }
-?>
diff --git a/test/PgsqlAdapterTest.php b/test/PgsqlAdapterTest.php
index 348b11e9f..c7d23a594 100644
--- a/test/PgsqlAdapterTest.php
+++ b/test/PgsqlAdapterTest.php
@@ -31,8 +31,9 @@ public function test_insert_id_should_return_explicitly_inserted_id()
 
 	public function test_set_charset()
 	{
-		$connection_string = ActiveRecord\Config::instance()->get_connection($this->connection_name);
-		$conn = ActiveRecord\Connection::instance($connection_string . '?charset=utf8');
+		$connection_info = ActiveRecord\Config::instance()->get_connection_info($this->connection_name);
+		$connection_info->charset = 'utf8';
+		$conn = ActiveRecord\Connection::instance($connection_info);
 		$this->assert_equals("SET NAMES 'utf8'",$conn->last_query);
 	}
 
@@ -40,5 +41,24 @@ public function test_gh96_columns_not_duplicated_by_index()
 	{
 		$this->assert_equals(3,$this->conn->query_column_info("user_newsletters")->rowCount());
 	}
+
+	public function test_boolean_to_string()
+	{
+		// false values
+		$this->assert_equals("0", $this->conn->boolean_to_string(false));
+		$this->assert_equals("0", $this->conn->boolean_to_string('0'));
+		$this->assert_equals("0", $this->conn->boolean_to_string('f'));
+		$this->assert_equals("0", $this->conn->boolean_to_string('false'));
+		$this->assert_equals("0", $this->conn->boolean_to_string('n'));
+		$this->assert_equals("0", $this->conn->boolean_to_string('no'));
+		$this->assert_equals("0", $this->conn->boolean_to_string('off'));
+		// true values
+		$this->assert_equals("1", $this->conn->boolean_to_string(true));
+		$this->assert_equals("1", $this->conn->boolean_to_string('1'));
+		$this->assert_equals("1", $this->conn->boolean_to_string('t'));
+		$this->assert_equals("1", $this->conn->boolean_to_string('true'));
+		$this->assert_equals("1", $this->conn->boolean_to_string('y'));
+		$this->assert_equals("1", $this->conn->boolean_to_string('yes'));
+		$this->assert_equals("1", $this->conn->boolean_to_string('on'));
+	}
 }
-?>
diff --git a/test/RelationshipTest.php b/test/RelationshipTest.php
index d8b99b090..6adce031d 100644
--- a/test/RelationshipTest.php
+++ b/test/RelationshipTest.php
@@ -1,6 +1,6 @@
 <?php
 
-class NotModel {};
+class NotModel {}
 
 class AuthorWithNonModelRelationship extends ActiveRecord\Model
 {
@@ -341,6 +341,14 @@ public function test_has_many_through()
 
 	public function test_gh27_has_many_through_with_explicit_keys()
 	{
+		Property::$has_many = array(
+			'property_amenities',
+			array('amenities', 'through' => 'property_amenities')
+		);
+		Amenity::$has_many = array(
+			'property_amenities'
+		);
+		
 		$property = Property::first();
 
 		$this->assert_equals(1, $property->amenities[0]->amenity_id);
@@ -548,7 +556,7 @@ public function test_gh93_and_gh100_eager_loading_respects_association_options()
 		Venue::$has_many = array(array('events', 'class_name' => 'Event', 'order' => 'id asc', 'conditions' => array('length(title) = ?', 14)));
 		$venues = Venue::find(array(2, 6), array('include' => 'events'));
 
-		$this->assert_sql_has("WHERE length(title) = ? AND venue_id IN(?,?) ORDER BY id asc",ActiveRecord\Table::load('Event')->last_sql);
+		$this->assert_sql_has("WHERE (length(title) = ?) AND (venue_id IN(?,?)) ORDER BY id asc",ActiveRecord\Table::load('Event')->last_sql);
 		$this->assert_equals(1, count($venues[0]->events));
     }
 
@@ -740,5 +748,4 @@ public function test_dont_attempt_eager_load_when_record_does_not_exist()
 	{
 		Author::find(999999, array('include' => array('books')));
 	}
-};
-?>
+}
diff --git a/test/SQLBuilderTest.php b/test/SQLBuilderTest.php
index 603b60b6b..b44ca1c41 100644
--- a/test/SQLBuilderTest.php
+++ b/test/SQLBuilderTest.php
@@ -278,5 +278,4 @@ public function test_where_with_joins_prepends_table_name_to_fields()
 
 		$this->assert_sql_has("SELECT * FROM authors $joins WHERE authors.id=? AND authors.name=?",(string)$this->sql);
 	}
-};
-?>
\ No newline at end of file
+}
diff --git a/test/SerializationTest.php b/test/SerializationTest.php
index e8ef9cebd..562e24d39 100644
--- a/test/SerializationTest.php
+++ b/test/SerializationTest.php
@@ -64,14 +64,21 @@ public function test_methods_takes_a_string()
 		$this->assert_equals('ANCIENT ART OF MAIN TANKING', $a['upper_name']);
 	}
 
-	// methods added last should we shuld have value of the method in our json
-	// rather than the regular attribute value
+	// methods should take precedence over attributes
 	public function test_methods_method_same_as_attribute()
 	{
 		$a = $this->_a(array('methods' => 'name'));
 		$this->assert_equals('ancient art of main tanking', $a['name']);
 	}
 
+	public function test_methods_method_alias()
+	{
+		$a = $this->_a(array('methods' => array('name' => 'alias_name')));
+		$this->assert_equals('ancient art of main tanking', $a['alias_name']);
+		$a = $this->_a(array('methods' => array('upper_name' => 'name')));
+		$this->assert_equals('ANCIENT ART OF MAIN TANKING', $a['name']);
+	}
+
 	public function test_include()
 	{
 		$a = $this->_a(array('include' => array('author')));
@@ -123,30 +130,30 @@ public function test_to_xml()
 		$this->assert_equals($book->attributes(),get_object_vars(new SimpleXMLElement($book->to_xml())));
 	}
 
-  public function test_to_array()
-  {
- 		$book = Book::find(1);
+	public function test_to_array()
+	{
+		$book = Book::find(1);
 		$array = $book->to_array();
 		$this->assert_equals($book->attributes(), $array);
-  }
+	}
 
-  public function test_to_array_include_root()
-  {
+	public function test_to_array_include_root()
+	{
 		ActiveRecord\ArraySerializer::$include_root = true;
- 		$book = Book::find(1);
+		$book = Book::find(1);
 		$array = $book->to_array();
-    $book_attributes = array('book' => $book->attributes());
+		$book_attributes = array('book' => $book->attributes());
 		$this->assert_equals($book_attributes, $array);
-  }
+	}
 
-  public function test_to_array_except()
-  {
- 		$book = Book::find(1);
+	public function test_to_array_except()
+	{
+		$book = Book::find(1);
 		$array = $book->to_array(array('except' => array('special')));
 		$book_attributes = $book->attributes();
 		unset($book_attributes['special']);
 		$this->assert_equals($book_attributes, $array);
-  }
+	}
 
 	public function test_works_with_datetime()
 	{
@@ -166,50 +173,49 @@ public function test_only_method()
 		$this->assert_contains('<sharks>lasers</sharks>', Author::first()->to_xml(array('only_method' => 'return_something')));
 	}
 
-  public function test_to_csv()
-  {
-    $book = Book::find(1);
-    $this->assert_equals('1,1,2,"Ancient Art of Main Tanking",0,0',$book->to_csv());
-  }
-
-  public function test_to_csv_only_header()
-  {
-    $book = Book::find(1);
-    $this->assert_equals('book_id,author_id,secondary_author_id,name,numeric_test,special',
-                         $book->to_csv(array('only_header'=>true))
-                         );
-  }
-
-  public function test_to_csv_only_method()
-  {
-    $book = Book::find(1);
-    $this->assert_equals('2,"Ancient Art of Main Tanking"',
-                         $book->to_csv(array('only'=>array('name','secondary_author_id')))
-                         );
-  }
-
-  public function test_to_csv_only_method_on_header()
-  {
-    $book = Book::find(1);
-    $this->assert_equals('secondary_author_id,name',
-                         $book->to_csv(array('only'=>array('secondary_author_id','name'),
-                                             'only_header'=>true))
-                         );
-  }
-
-  public function test_to_csv_with_custom_delimiter()
-  {
-    $book = Book::find(1);
-    ActiveRecord\CsvSerializer::$delimiter=';';
-    $this->assert_equals('1;1;2;"Ancient Art of Main Tanking";0;0',$book->to_csv());
-  }
-
-  public function test_to_csv_with_custom_enclosure()
-  {
-    $book = Book::find(1);
-    ActiveRecord\CsvSerializer::$delimiter=',';
-    ActiveRecord\CsvSerializer::$enclosure="'";
-    $this->assert_equals("1,1,2,'Ancient Art of Main Tanking',0,0",$book->to_csv());
-  }
-};
-?>
+	public function test_to_csv()
+	{
+		$book = Book::find(1);
+		$this->assert_equals('1,1,2,"Ancient Art of Main Tanking",0,0',$book->to_csv());
+	}
+
+	public function test_to_csv_only_header()
+	{
+		$book = Book::find(1);
+		$this->assert_equals('book_id,author_id,secondary_author_id,name,numeric_test,special',
+			$book->to_csv(array('only_header'=>true))
+		);
+	}
+
+	public function test_to_csv_only_method()
+	{
+		$book = Book::find(1);
+		$this->assert_equals('2,"Ancient Art of Main Tanking"',
+			$book->to_csv(array('only'=>array('name','secondary_author_id')))
+		);
+	}
+
+	public function test_to_csv_only_method_on_header()
+	{
+		$book = Book::find(1);
+		$this->assert_equals('secondary_author_id,name',
+		$book->to_csv(array('only'=>array('secondary_author_id','name'),
+			'only_header'=>true))
+		);
+	}
+
+	public function test_to_csv_with_custom_delimiter()
+	{
+		$book = Book::find(1);
+		ActiveRecord\CsvSerializer::$delimiter=';';
+		$this->assert_equals('1;1;2;"Ancient Art of Main Tanking";0;0',$book->to_csv());
+	}
+
+	public function test_to_csv_with_custom_enclosure()
+	{
+		$book = Book::find(1);
+		ActiveRecord\CsvSerializer::$delimiter=',';
+		ActiveRecord\CsvSerializer::$enclosure="'";
+		$this->assert_equals("1,1,2,'Ancient Art of Main Tanking',0,0",$book->to_csv());
+	}
+}
diff --git a/test/SqliteAdapterTest.php b/test/SqliteAdapterTest.php
index 0490c8137..146152cf8 100644
--- a/test/SqliteAdapterTest.php
+++ b/test/SqliteAdapterTest.php
@@ -79,4 +79,3 @@ public function test_date_to_string()
 	// not supported
 	public function test_connect_with_port() {}
 }
-?>
\ No newline at end of file
diff --git a/test/StrongParametersModelTest.php b/test/StrongParametersModelTest.php
new file mode 100644
index 000000000..b2972cca4
--- /dev/null
+++ b/test/StrongParametersModelTest.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace ActiveRecord;
+
+use Book;
+
+class StrongParametersModelTest extends \DatabaseTest
+{
+
+	private static $require_strong_parameters;
+
+	public static function setUpBeforeClass()
+	{
+		$config = Config::instance();
+		self::$require_strong_parameters = $config->get_require_strong_parameters();
+		$config->set_require_strong_parameters(true);
+	}
+
+	public static function tearDownAfterClass()
+	{
+		$config = Config::instance();
+		$config->set_require_strong_parameters(self::$require_strong_parameters);
+	}
+
+	public function testConstructWithStrongParameters()
+	{
+		$params = new StrongParameters(array(
+			'name' => 'Foo',
+		));
+		$book = new Book($params);
+		$this->assertNull($book->name);
+
+		$params->permit('name');
+		$book = new Book($params);
+		$this->assertEquals('Foo', $book->name);
+	}
+
+	/**
+	 * @expectedException ActiveRecord\UnsafeParametersException
+	 */
+	public function testConstructWithoutStrongParametersThrowsException()
+	{
+		$config = Config::instance();
+		$require_strong_parameters = $config->get_require_strong_parameters();
+		$config->set_require_strong_parameters(true);
+
+		$params = array('name' => 'Foo');
+		$book = new Book($params);
+	}
+
+	public function testUpdateAttributesWithStrongParameters()
+	{
+		$params = new StrongParameters(array(
+			'name' => 'Foo',
+		));
+		$book = new Book();
+		$book->update_attributes($params);
+		$this->assertNull($book->name);
+
+		$params->permit('name');
+		$book->update_attributes($params);
+		$this->assertEquals('Foo', $book->name);
+	}
+
+	/**
+	 * @expectedException ActiveRecord\UnsafeParametersException
+	 */
+	public function testUpdateAttributesWithoutStrongParameters()
+	{
+		$config = Config::instance();
+		$require_strong_parameters = $config->get_require_strong_parameters();
+		$config->set_require_strong_parameters(true);
+
+		$params = array('name' => 'Foo');
+		$book = new Book();
+		$book->update_attributes($params);
+	}
+
+
+}
diff --git a/test/StrongParametersTest.php b/test/StrongParametersTest.php
new file mode 100644
index 000000000..750ba6c60
--- /dev/null
+++ b/test/StrongParametersTest.php
@@ -0,0 +1,158 @@
+<?php
+
+namespace ActiveRecord;
+
+class StrongParametersTest extends \PHPUnit_Framework_TestCase
+{
+
+	public function setUp(){
+		$this->params = new StrongParameters(array(
+			'user' => array(
+				'name' => 'Foo Bar',
+				'email' => 'foo@bar.baz',
+				'bio' => 'I am Foo Bar',
+				'is_admin' => true,
+			),
+		));
+	}
+
+	public function testConstruct()
+	{
+		$params = new StrongParameters(array('name' => 'Foo Bar'));
+		$this->assertInstanceOf('ActiveRecord\StrongParameters', $params);
+	}
+
+	public function testPermit()
+	{
+		$params = new StrongParameters(array(
+			'name' => 'Foo Bar',
+			'email' => 'foo@bar.baz',
+			'bio' => 'I am Foo Bar',
+			'is_admin' => true,
+		));
+		$params->permit();
+		$expected = array();
+		$actual = iterator_to_array($params);
+		$this->assertEquals($expected, $actual);
+
+		$params->permit(array('name', 'email'));
+		$expected = array('name' => 'Foo Bar', 'email' => 'foo@bar.baz');
+		$actual = iterator_to_array($params);
+		$this->assertEquals($expected, $actual);
+
+		$params->permit('name', 'bio');
+		$expected = array('name' => 'Foo Bar', 'bio' => 'I am Foo Bar');
+		$actual = iterator_to_array($params);
+		$this->assertEquals($expected, $actual);
+	}
+
+	public function testPermitReturnsSelf()
+	{
+		$params = new StrongParameters(array(
+			'name' => 'Foo Bar',
+			'email' => 'foo@bar.baz',
+			'bio' => 'I am Foo Bar',
+			'is_admin' => true,
+		));
+
+		$expected = array('name' => 'Foo Bar');
+		$actual = iterator_to_array($params->permit('name'));
+		$this->assertEquals($expected, $actual);
+
+		$expected = array('name' => 'Foo Bar', 'bio' => 'I am Foo Bar');
+		$actual = iterator_to_array($params->permit('name', 'bio'));
+		$this->assertEquals($expected, $actual);
+	}
+
+	public function testRequireParam()
+	{
+		$this->assertInstanceOf('ActiveRecord\StrongParameters', $this->params->requireParam('user'));
+	}
+
+	/**
+	 * @expectedException ActiveRecord\ParameterMissingException
+	 */
+	public function testRequireParamThrowsOnMissingParam()
+	{
+		$this->params->requireParam('nonexisting');
+	}
+
+	public function testFetch()
+	{
+		$params = new StrongParameters(array('name' => 'Foo Bar'));
+		$this->assertEquals('Foo Bar', $params->fetch('name'));
+
+		$this->assertInstanceOf('ActiveRecord\StrongParameters', $this->params->fetch('user'));
+		
+		$this->assertEquals('Foo Bar', $this->params->fetch('user')->fetch('name'));
+	}
+
+	public function testGetIterator()
+	{
+		$this->assertInstanceOf('ArrayIterator', $this->params->getIterator());
+	}
+
+	public function testArrayAccessOffsetExists()
+	{
+		$params = new StrongParameters(array('name' => 'Foo Bar'));
+		$this->assertTrue(isset($params['name']));
+		$this->assertFalse(isset($params['undefined']));
+	}
+
+	public function testArrayAccessOffsetGet()
+	{
+		$params = new StrongParameters(array('name' => 'Foo Bar'));
+		$this->assertEquals('Foo Bar', $params['name']);
+
+		$this->assertEquals('Foo Bar', $this->params['user']->fetch('name'));
+		$this->assertEquals('Foo Bar', $this->params['user']['name']);
+	}
+
+	public function testArrayAccessOffsetSet()
+	{
+		$params = new StrongParameters(array('name' => 'Foo Bar'));
+		$params['test'] = 'foobar';
+		$params->permit('test', 'name');
+		$this->assertEquals(array('name' => 'Foo Bar', 'test' => 'foobar'), iterator_to_array($params));
+	}
+
+	/**
+	 * @expectedException InvalidArgumentException
+	 * @expectedExceptionMessage offset cannot be null
+	 */
+	public function testArrayAccessOffsetSetThrowsOnNull()
+	{
+		$params = new StrongParameters(array('name' => 'Foo Bar'));
+		$params[] = 'foobar';
+	}
+
+	public function testArrayAccessOffsetUnset()
+	{
+		$params = new StrongParameters(array('name' => 'Foo Bar'));
+		$params['test'] = 'foobar';
+		$params->permit('test', 'name');
+		$this->assertEquals(array('name' => 'Foo Bar', 'test' => 'foobar'), iterator_to_array($params));
+		unset($params['test']);
+		$this->assertEquals(array('name' => 'Foo Bar'), iterator_to_array($params));
+		unset($params['name']);
+		$this->assertEquals(array(), iterator_to_array($params));
+	}
+
+	public function testArrayAccessOffsetUnsetNested()
+	{
+		$user = $this->params->fetch('user');
+		$user->permit('name', 'email');
+
+		$this->assertEquals(array(
+			'name' => 'Foo Bar',
+			'email' => 'foo@bar.baz',
+		), iterator_to_array($user));
+
+		unset($this->params['user']['name']);
+		$this->assertEquals(array(
+			'email' => 'foo@bar.baz',
+		), iterator_to_array($user));
+	}
+
+}
+
diff --git a/test/UtilsTest.php b/test/UtilsTest.php
index 9205a6b8e..37d8bf151 100644
--- a/test/UtilsTest.php
+++ b/test/UtilsTest.php
@@ -103,5 +103,19 @@ public function test_wrap_strings_in_arrays()
 		$x = '1';
 		$this->assert_equals(array(array('1')),ActiveRecord\wrap_strings_in_arrays($x));
 	}
+
+	public function test_is_hash()
+	{
+		$hash = array('key' => 'value');
+		$this->assert_true(ActiveRecord\is_hash($hash));
+		
+		$notHash = array(0 => 'value');
+		$this->assert_false(ActiveRecord\is_hash($notHash));
+	}
+	
+	public function test_is_hash_empty_array()
+	{
+		$notHash = array();
+		$this->assert_false(ActiveRecord\is_hash($notHash));
+	}
 };
-?>
diff --git a/test/ValidatesFormatOfTest.php b/test/ValidatesFormatOfTest.php
index 76c52d5ee..0aaac2f34 100644
--- a/test/ValidatesFormatOfTest.php
+++ b/test/ValidatesFormatOfTest.php
@@ -1,111 +1,110 @@
-<?php
-
-class BookFormat extends ActiveRecord\Model
-{
-	static $table = 'books';
-	static $validates_format_of = array(
-		array('name')
-	);
-};
-
-class ValidatesFormatOfTest extends DatabaseTest
-{
-	public function set_up($connection_name=null)
-	{
-		parent::set_up($connection_name);
-		BookFormat::$validates_format_of[0] = array('name');
-	}
-
-	public function test_format()
-	{
-		BookFormat::$validates_format_of[0]['with'] = '/^[a-z\W]*$/';
-		$book = new BookFormat(array('author_id' => 1, 'name' => 'testing reg'));
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-
-		BookFormat::$validates_format_of[0]['with'] = '/[0-9]/';
-		$book = new BookFormat(array('author_id' => 1, 'name' => 12));
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_invalid_null()
-	{
-		BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
-		$book = new BookFormat;
-		$book->name = null;
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-	}
-
-	public function test_invalid_blank()
-	{
-		BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
-		$book = new BookFormat;
-		$book->name = '';
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-	}
-
-	public function test_valid_blank_andallow_blank()
-	{
-		BookFormat::$validates_format_of[0]['allow_blank'] = true;
-		BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
-		$book = new BookFormat(array('author_id' => 1, 'name' => ''));
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_valid_null_and_allow_null()
-	{
-		BookFormat::$validates_format_of[0]['allow_null'] = true;
-		BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
-		$book = new BookFormat();
-		$book->author_id = 1;
-		$book->name = null;
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	/**
-	 * @expectedException ActiveRecord\ValidationsArgumentError
-	 */
-	public function test_invalid_lack_of_with_key()
-	{
-		$book = new BookFormat;
-		$book->name = null;
-		$book->save();
-	}
-
-	/**
-	 * @expectedException ActiveRecord\ValidationsArgumentError
-	 */
-	public function test_invalid_with_expression_as_non_string()
-	{
-		BookFormat::$validates_format_of[0]['with'] = array('test');
-		$book = new BookFormat;
-		$book->name = null;
-		$book->save();
-	}
-
-	public function test_invalid_with_expression_as_non_regexp()
-	{
-		BookFormat::$validates_format_of[0]['with'] = 'blah';
-		$book = new BookFormat;
-		$book->name = 'blah';
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-	}
-
-	public function test_custom_message()
-	{
-		BookFormat::$validates_format_of[0]['message'] = 'is using a custom message.';
-		BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
-
-		$book = new BookFormat;
-		$book->name = null;
-		$book->save();
-		$this->assert_equals('is using a custom message.', $book->errors->on('name'));
-	}
-};
-?>
\ No newline at end of file
+<?php
+
+class BookFormat extends ActiveRecord\Model
+{
+	static $table = 'books';
+	static $validates_format_of = array(
+		array('name')
+	);
+}
+
+class ValidatesFormatOfTest extends DatabaseTest
+{
+	public function set_up($connection_name=null)
+	{
+		parent::set_up($connection_name);
+		BookFormat::$validates_format_of[0] = array('name');
+	}
+
+	public function test_format()
+	{
+		BookFormat::$validates_format_of[0]['with'] = '/^[a-z\W]*$/';
+		$book = new BookFormat(array('author_id' => 1, 'name' => 'testing reg'));
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+
+		BookFormat::$validates_format_of[0]['with'] = '/[0-9]/';
+		$book = new BookFormat(array('author_id' => 1, 'name' => 12));
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_invalid_null()
+	{
+		BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
+		$book = new BookFormat;
+		$book->name = null;
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+	}
+
+	public function test_invalid_blank()
+	{
+		BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
+		$book = new BookFormat;
+		$book->name = '';
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+	}
+
+	public function test_valid_blank_andallow_blank()
+	{
+		BookFormat::$validates_format_of[0]['allow_blank'] = true;
+		BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
+		$book = new BookFormat(array('author_id' => 1, 'name' => ''));
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_valid_null_and_allow_null()
+	{
+		BookFormat::$validates_format_of[0]['allow_null'] = true;
+		BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
+		$book = new BookFormat();
+		$book->author_id = 1;
+		$book->name = null;
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	/**
+	 * @expectedException ActiveRecord\ValidationsArgumentError
+	 */
+	public function test_invalid_lack_of_with_key()
+	{
+		$book = new BookFormat;
+		$book->name = null;
+		$book->save();
+	}
+
+	/**
+	 * @expectedException ActiveRecord\ValidationsArgumentError
+	 */
+	public function test_invalid_with_expression_as_non_string()
+	{
+		BookFormat::$validates_format_of[0]['with'] = array('test');
+		$book = new BookFormat;
+		$book->name = null;
+		$book->save();
+	}
+
+	public function test_invalid_with_expression_as_non_regexp()
+	{
+		BookFormat::$validates_format_of[0]['with'] = 'blah';
+		$book = new BookFormat;
+		$book->name = 'blah';
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+	}
+
+	public function test_custom_message()
+	{
+		BookFormat::$validates_format_of[0]['message'] = 'is using a custom message.';
+		BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
+
+		$book = new BookFormat;
+		$book->name = null;
+		$book->save();
+		$this->assert_equals('is using a custom message.', $book->errors->on('name'));
+	}
+}
diff --git a/test/ValidatesInclusionAndExclusionOfTest.php b/test/ValidatesInclusionAndExclusionOfTest.php
index ade280165..efa6a28f2 100644
--- a/test/ValidatesInclusionAndExclusionOfTest.php
+++ b/test/ValidatesInclusionAndExclusionOfTest.php
@@ -1,157 +1,156 @@
-<?php
-
-class BookExclusion extends ActiveRecord\Model
-{
-	static $table = 'books';
-	public static $validates_exclusion_of = array(
-		array('name', 'in' => array('blah', 'alpha', 'bravo'))
-	);
-};
-
-class BookInclusion extends ActiveRecord\Model
-{
-	static $table = 'books';
-	public static $validates_inclusion_of = array(
-		array('name', 'in' => array('blah', 'tanker', 'shark'))
-	);
-};
-
-class ValidatesInclusionAndExclusionOfTest extends DatabaseTest
-{
-	public function set_up($connection_name=null)
-	{
-		parent::set_up($connection_name);
-		BookInclusion::$validates_inclusion_of[0] = array('name', 'in' => array('blah', 'tanker', 'shark'));
-		BookExclusion::$validates_exclusion_of[0] = array('name', 'in' => array('blah', 'alpha', 'bravo'));
-	}
-
-	public function test_inclusion()
-	{
-		$book = new BookInclusion;
-		$book->name = 'blah';
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_exclusion()
-	{
-		$book = new BookExclusion;
-		$book->name = 'blahh';
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_invalid_inclusion()
-	{
-		$book = new BookInclusion;
-		$book->name = 'thanker';
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-		$book->name = 'alpha ';
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-	}
-
-	public function test_invalid_exclusion()
-	{
-		$book = new BookExclusion;
-		$book->name = 'alpha';
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-
-		$book = new BookExclusion;
-		$book->name = 'bravo';
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-	}
-
-	public function test_inclusion_with_numeric()
-	{
-		BookInclusion::$validates_inclusion_of[0]['in']= array(0, 1, 2);
-		$book = new BookInclusion;
-		$book->name = 2;
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_inclusion_with_boolean()
-	{
-		BookInclusion::$validates_inclusion_of[0]['in']= array(true);
-		$book = new BookInclusion;
-		$book->name = true;
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_inclusion_with_null()
-	{
-		BookInclusion::$validates_inclusion_of[0]['in']= array(null);
-		$book = new BookInclusion;
-		$book->name = null;
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_invalid_inclusion_with_numeric()
-	{
-		BookInclusion::$validates_inclusion_of[0]['in']= array(0, 1, 2);
-		$book = new BookInclusion;
-		$book->name = 5;
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-	}
-
-	public function tes_inclusion_within_option()
-	{
-		BookInclusion::$validates_inclusion_of[0] = array('name', 'within' => array('okay'));
-		$book = new BookInclusion;
-		$book->name = 'okay';
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function tes_inclusion_scalar_value()
-	{
-		BookInclusion::$validates_inclusion_of[0] = array('name', 'within' => 'okay');
-		$book = new BookInclusion;
-		$book->name = 'okay';
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_valid_null()
-	{
-		BookInclusion::$validates_inclusion_of[0]['allow_null'] = true;
-		$book = new BookInclusion;
-		$book->name = null;
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_valid_blank()
-	{
-		BookInclusion::$validates_inclusion_of[0]['allow_blank'] = true;
-		$book = new BookInclusion;
-		$book->name = '';
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_custom_message()
-	{
-		$msg = 'is using a custom message.';
-		BookInclusion::$validates_inclusion_of[0]['message'] = $msg;
-		BookExclusion::$validates_exclusion_of[0]['message'] = $msg;
-
-		$book = new BookInclusion;
-		$book->name = 'not included';
-		$book->save();
-		$this->assert_equals('is using a custom message.', $book->errors->on('name'));
-		$book = new BookExclusion;
-		$book->name = 'bravo';
-		$book->save();
-		$this->assert_equals('is using a custom message.', $book->errors->on('name'));
-	}
-
-};
-?>
\ No newline at end of file
+<?php
+
+class BookExclusion extends ActiveRecord\Model
+{
+	static $table = 'books';
+	public static $validates_exclusion_of = array(
+		array('name', 'in' => array('blah', 'alpha', 'bravo'))
+	);
+}
+
+class BookInclusion extends ActiveRecord\Model
+{
+	static $table = 'books';
+	public static $validates_inclusion_of = array(
+		array('name', 'in' => array('blah', 'tanker', 'shark'))
+	);
+}
+
+class ValidatesInclusionAndExclusionOfTest extends DatabaseTest
+{
+	public function set_up($connection_name=null)
+	{
+		parent::set_up($connection_name);
+		BookInclusion::$validates_inclusion_of[0] = array('name', 'in' => array('blah', 'tanker', 'shark'));
+		BookExclusion::$validates_exclusion_of[0] = array('name', 'in' => array('blah', 'alpha', 'bravo'));
+	}
+
+	public function test_inclusion()
+	{
+		$book = new BookInclusion;
+		$book->name = 'blah';
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_exclusion()
+	{
+		$book = new BookExclusion;
+		$book->name = 'blahh';
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_invalid_inclusion()
+	{
+		$book = new BookInclusion;
+		$book->name = 'thanker';
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+		$book->name = 'alpha ';
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+	}
+
+	public function test_invalid_exclusion()
+	{
+		$book = new BookExclusion;
+		$book->name = 'alpha';
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+
+		$book = new BookExclusion;
+		$book->name = 'bravo';
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+	}
+
+	public function test_inclusion_with_numeric()
+	{
+		BookInclusion::$validates_inclusion_of[0]['in']= array(0, 1, 2);
+		$book = new BookInclusion;
+		$book->name = 2;
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_inclusion_with_boolean()
+	{
+		BookInclusion::$validates_inclusion_of[0]['in']= array(true);
+		$book = new BookInclusion;
+		$book->name = true;
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_inclusion_with_null()
+	{
+		BookInclusion::$validates_inclusion_of[0]['in']= array(null);
+		$book = new BookInclusion;
+		$book->name = null;
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_invalid_inclusion_with_numeric()
+	{
+		BookInclusion::$validates_inclusion_of[0]['in']= array(0, 1, 2);
+		$book = new BookInclusion;
+		$book->name = 5;
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+	}
+
+	public function tes_inclusion_within_option()
+	{
+		BookInclusion::$validates_inclusion_of[0] = array('name', 'within' => array('okay'));
+		$book = new BookInclusion;
+		$book->name = 'okay';
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function tes_inclusion_scalar_value()
+	{
+		BookInclusion::$validates_inclusion_of[0] = array('name', 'within' => 'okay');
+		$book = new BookInclusion;
+		$book->name = 'okay';
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_valid_null()
+	{
+		BookInclusion::$validates_inclusion_of[0]['allow_null'] = true;
+		$book = new BookInclusion;
+		$book->name = null;
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_valid_blank()
+	{
+		BookInclusion::$validates_inclusion_of[0]['allow_blank'] = true;
+		$book = new BookInclusion;
+		$book->name = '';
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_custom_message()
+	{
+		$msg = 'is using a custom message.';
+		BookInclusion::$validates_inclusion_of[0]['message'] = $msg;
+		BookExclusion::$validates_exclusion_of[0]['message'] = $msg;
+
+		$book = new BookInclusion;
+		$book->name = 'not included';
+		$book->save();
+		$this->assert_equals('is using a custom message.', $book->errors->on('name'));
+		$book = new BookExclusion;
+		$book->name = 'bravo';
+		$book->save();
+		$this->assert_equals('is using a custom message.', $book->errors->on('name'));
+	}
+
+}
diff --git a/test/ValidatesLengthOfTest.php b/test/ValidatesLengthOfTest.php
index bd93a1d80..99ebe786c 100644
--- a/test/ValidatesLengthOfTest.php
+++ b/test/ValidatesLengthOfTest.php
@@ -1,360 +1,334 @@
-<?php
-
-class BookLength extends ActiveRecord\Model
-{
-	static $table = 'books';
-	static $validates_length_of = array();
-}
-
-class BookSize extends ActiveRecord\Model
-{
-	static $table = 'books';
-	static $validates_size_of = array();
-}
-
-class ValidatesLengthOfTest extends DatabaseTest
-{
-	public function set_up($connection_name=null)
-	{
-		parent::set_up($connection_name);
-		BookLength::$validates_length_of[0] = array('name', 'allow_blank' => false, 'allow_null' => false);
-	}
-	
-	public function test_within()
-	{
-		BookLength::$validates_length_of[0]['within'] = array(1, 5);
-		$book = new BookLength;
-		$book->name = '12345';
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_within_error_message()
-	{
-		BookLength::$validates_length_of[0]['within'] = array(2,5);
-		$book = new BookLength();
-		$book->name = '1';
-		$book->is_valid();
-		$this->assert_equals(array('Name is too short (minimum is 2 characters)'),$book->errors->full_messages());
-
-		$book->name = '123456';
-		$book->is_valid();
-		$this->assert_equals(array('Name is too long (maximum is 5 characters)'),$book->errors->full_messages());
-	}
-
-	public function test_within_custom_error_message()
-	{
-		BookLength::$validates_length_of[0]['within'] = array(2,5);
-		BookLength::$validates_length_of[0]['too_short'] = 'is too short';
-		BookLength::$validates_length_of[0]['message'] = 'is not between 2 and 5 characters';
-		$book = new BookLength();
-		$book->name = '1';
-		$book->is_valid();
-		$this->assert_equals(array('Name is not between 2 and 5 characters'),$book->errors->full_messages());
-
-		$book->name = '123456';
-		$book->is_valid();
-		$this->assert_equals(array('Name is not between 2 and 5 characters'),$book->errors->full_messages());
-	}
-	
-	public function test_valid_in()
-	{
-		BookLength::$validates_length_of[0]['in'] = array(1, 5);
-		$book = new BookLength;
-		$book->name = '12345';
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_aliased_size_of()
-	{
-		BookSize::$validates_size_of = BookLength::$validates_length_of;
-		BookSize::$validates_size_of[0]['within'] = array(1, 5);
-		$book = new BookSize;
-		$book->name = '12345';
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_invalid_within_and_in()
-	{
-		BookLength::$validates_length_of[0]['within'] = array(1, 3);
-		$book = new BookLength;
-		$book->name = 'four';
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-
-		$this->set_up();
-		BookLength::$validates_length_of[0]['in'] = array(1, 3);
-		$book = new BookLength;
-		$book->name = 'four';
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-	}
-
-	public function test_valid_null()
-	{
-		BookLength::$validates_length_of[0]['within'] = array(1, 3);
-		BookLength::$validates_length_of[0]['allow_null'] = true;
-
-		$book = new BookLength;
-		$book->name = null;
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_valid_blank()
-	{
-		BookLength::$validates_length_of[0]['within'] = array(1, 3);
-		BookLength::$validates_length_of[0]['allow_blank'] = true;
-
-		$book = new BookLength;
-		$book->name = '';
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_invalid_blank()
-	{
-		BookLength::$validates_length_of[0]['within'] = array(1, 3);
-
-		$book = new BookLength;
-		$book->name = '';
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-		$this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name'));
-	}
-
-	public function test_invalid_null_within()
-	{
-		BookLength::$validates_length_of[0]['within'] = array(1, 3);
-
-		$book = new BookLength;
-		$book->name = null;
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-		$this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name'));
-	}
-	
-	public function test_invalid_null_minimum()
-	{
-		BookLength::$validates_length_of[0]['minimum'] = 1;
-
-		$book = new BookLength;
-		$book->name = null;
-		$book->save();
-		$this->assert_true($book->errors->is_invalid('name'));
-		$this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name'));
-		
-	}
-	
-	public function test_valid_null_maximum()
-	{
-		BookLength::$validates_length_of[0]['maximum'] = 1;
-
-		$book = new BookLength;
-		$book->name = null;
-		$book->save();
-		$this->assert_false($book->errors->is_invalid('name'));
-	}
-
-	public function test_float_as_impossible_range_option()
-	{
-		BookLength::$validates_length_of[0]['within'] = array(1, 3.6);
-		$book = new BookLength;
-		$book->name = '123';
-		try {
-			$book->save();
-		} catch (ActiveRecord\ValidationsArgumentError $e) {
-			$this->assert_equals('maximum value cannot use a float for length.', $e->getMessage());
-		}
-
-		$this->set_up();
-		BookLength::$validates_length_of[0]['is'] = 1.8;
-		$book = new BookLength;
-		$book->name = '123';
-		try {
-			$book->save();
-		} catch (ActiveRecord\ValidationsArgumentError $e) {
-			$this->assert_equals('is value cannot use a float for length.', $e->getMessage());
-			return;
-		}
-
-		$this->fail('An expected exception has not be raised.');
-	}
-
-	public function test_signed_integer_as_impossible_within_option()
-	{
-		BookLength::$validates_length_of[0]['within'] = array(-1, 3);
-
-		$book = new BookLength;
-		$book->name = '123';
-		try {
-			$book->save();
-		} catch (ActiveRecord\ValidationsArgumentError $e) {
-			$this->assert_equals('minimum value cannot use a signed integer.', $e->getMessage());
-			return;
-		}
-
-		$this->fail('An expected exception has not be raised.');
-	}
-
-    public function test_not_array_as_impossible_range_option()
-    {
-        BookLength::$validates_length_of[0]['within'] = 'string';
-        $book = new BookLength;
-        $book->name = '123';
-        try {
-            $book->save();
-        } catch (ActiveRecord\ValidationsArgumentError $e) {
-            $this->assert_equals('within must be an array composing a range of numbers with key [0] being less than key [1]', $e->getMessage());
-        }
-
-        $this->set_up();
-        BookLength::$validates_length_of[0]['in'] = 'string';
-        $book = new BookLength;
-        $book->name = '123';
-        try {
-            $book->save();
-        } catch (ActiveRecord\ValidationsArgumentError $e) {
-            $this->assert_equals('in must be an array composing a range of numbers with key [0] being less than key [1]', $e->getMessage());
-            return;
-        }
-
-        $this->fail('An expected exception has not be raised.');
-    }
-
-	public function test_signed_integer_as_impossible_is_option()
-	{
-		BookLength::$validates_length_of[0]['is'] = -8;
-
-		$book = new BookLength;
-		$book->name = '123';
-		try {
-			$book->save();
-		} catch (ActiveRecord\ValidationsArgumentError $e) {
-			$this->assert_equals('is value cannot use a signed integer.', $e->getMessage());
-			return;
-		}
-
-		$this->fail('An expected exception has not be raised.');
-	}
-
-	public function test_lack_of_option()
-	{
-		try {
-			$book = new BookLength;
-			$book->name = null;
-			$book->save();
-		} catch (ActiveRecord\ValidationsArgumentError $e) {
-			$this->assert_equals('Range unspecified.  Specify the [within], [maximum], or [is] option.', $e->getMessage());
-			return;
-		}
-
-		$this->fail('An expected exception has not be raised.');
-	}
-
-	public function test_too_many_options()
-	{
-		BookLength::$validates_length_of[0]['within'] = array(1, 3);
-		BookLength::$validates_length_of[0]['in'] = array(1, 3);
-
-		try {
-			$book = new BookLength;
-			$book->name = null;
-			$book->save();
-		} catch (ActiveRecord\ValidationsArgumentError $e) {
-			$this->assert_equals('Too many range options specified.  Choose only one.', $e->getMessage());
-			return;
-		}
-
-		$this->fail('An expected exception has not be raised.');
-	}
-
-	public function test_too_many_options_with_different_option_types()
-	{
-		BookLength::$validates_length_of[0]['within'] = array(1, 3);
-		BookLength::$validates_length_of[0]['is'] = 3;
-
-		try {
-			$book = new BookLength;
-			$book->name = null;
-			$book->save();
-		} catch (ActiveRecord\ValidationsArgumentError $e) {
-			$this->assert_equals('Too many range options specified.  Choose only one.', $e->getMessage());
-			return;
-		}
-
-		$this->fail('An expected exception has not be raised.');
-	}
-
-	/**
-	 * @expectedException ActiveRecord\ValidationsArgumentError
-	 */
-	public function test_with_option_as_non_numeric()
-	{
-		BookLength::$validates_length_of[0]['with'] = array('test');
-
-		$book = new BookLength;
-		$book->name = null;
-		$book->save();
-	}
-
-	/**
-	 * @expectedException ActiveRecord\ValidationsArgumentError
-	 */
-	public function test_with_option_as_non_numeric_non_array()
-	{
-		BookLength::$validates_length_of[0]['with'] = 'test';
-
-		$book = new BookLength;
-		$book->name = null;
-		$book->save();
-	}
-
-	public function test_validates_length_of_maximum()
-	{
-		BookLength::$validates_length_of[0] = array('name', 'maximum' => 10);
-		$book = new BookLength(array('name' => '12345678901'));
-		$book->is_valid();
-		$this->assert_equals(array("Name is too long (maximum is 10 characters)"),$book->errors->full_messages());
-	}
-
-	public function test_validates_length_of_minimum()
-	{
-		BookLength::$validates_length_of[0] = array('name', 'minimum' => 2);
-		$book = new BookLength(array('name' => '1'));
-		$book->is_valid();
-		$this->assert_equals(array("Name is too short (minimum is 2 characters)"),$book->errors->full_messages());
-	}
-	
-	public function test_validates_length_of_min_max_custom_message()
-	{
-		BookLength::$validates_length_of[0] = array('name', 'maximum' => 10, 'message' => 'is far too long');
-		$book = new BookLength(array('name' => '12345678901'));
-		$book->is_valid();
-		$this->assert_equals(array("Name is far too long"),$book->errors->full_messages());
-
-		BookLength::$validates_length_of[0] = array('name', 'minimum' => 10, 'message' => 'is far too short');
-		$book = new BookLength(array('name' => '123456789'));
-		$book->is_valid();
-		$this->assert_equals(array("Name is far too short"),$book->errors->full_messages());
-	}
-	
-	public function test_validates_length_of_min_max_custom_message_overridden()
-	{
-		BookLength::$validates_length_of[0] = array('name', 'minimum' => 10, 'too_short' => 'is too short', 'message' => 'is custom message');
-		$book = new BookLength(array('name' => '123456789'));
-		$book->is_valid();
-		$this->assert_equals(array("Name is custom message"),$book->errors->full_messages());
-	}
-
-	public function test_validates_length_of_is()
-	{
-		BookLength::$validates_length_of[0] = array('name', 'is' => 2);
-		$book = new BookLength(array('name' => '123'));
-		$book->is_valid();
-		$this->assert_equals(array("Name is the wrong length (should be 2 characters)"),$book->errors->full_messages());
-	}
-};
-?>
\ No newline at end of file
+<?php
+
+class BookLength extends ActiveRecord\Model
+{
+	static $table = 'books';
+	static $validates_length_of = array();
+}
+
+class BookSize extends ActiveRecord\Model
+{
+	static $table = 'books';
+	static $validates_size_of = array();
+}
+
+class ValidatesLengthOfTest extends DatabaseTest
+{
+	public function set_up($connection_name=null)
+	{
+		parent::set_up($connection_name);
+		BookLength::$validates_length_of[0] = array('name', 'allow_blank' => false, 'allow_null' => false);
+	}
+	
+	public function test_within()
+	{
+		BookLength::$validates_length_of[0]['within'] = array(1, 5);
+		$book = new BookLength;
+		$book->name = '12345';
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_within_error_message()
+	{
+		BookLength::$validates_length_of[0]['within'] = array(2,5);
+		$book = new BookLength();
+		$book->name = '1';
+		$book->is_valid();
+		$this->assert_equals(array('Name is too short (minimum is 2 characters)'),$book->errors->full_messages());
+
+		$book->name = '123456';
+		$book->is_valid();
+		$this->assert_equals(array('Name is too long (maximum is 5 characters)'),$book->errors->full_messages());
+	}
+
+	public function test_within_custom_error_message()
+	{
+		BookLength::$validates_length_of[0]['within'] = array(2,5);
+		BookLength::$validates_length_of[0]['too_short'] = 'is too short';
+		BookLength::$validates_length_of[0]['message'] = 'is not between 2 and 5 characters';
+		$book = new BookLength();
+		$book->name = '1';
+		$book->is_valid();
+		$this->assert_equals(array('Name is not between 2 and 5 characters'),$book->errors->full_messages());
+
+		$book->name = '123456';
+		$book->is_valid();
+		$this->assert_equals(array('Name is not between 2 and 5 characters'),$book->errors->full_messages());
+	}
+	
+	public function test_valid_in()
+	{
+		BookLength::$validates_length_of[0]['in'] = array(1, 5);
+		$book = new BookLength;
+		$book->name = '12345';
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_aliased_size_of()
+	{
+		BookSize::$validates_size_of = BookLength::$validates_length_of;
+		BookSize::$validates_size_of[0]['within'] = array(1, 5);
+		$book = new BookSize;
+		$book->name = '12345';
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_invalid_within_and_in()
+	{
+		BookLength::$validates_length_of[0]['within'] = array(1, 3);
+		$book = new BookLength;
+		$book->name = 'four';
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+
+		$this->set_up();
+		BookLength::$validates_length_of[0]['in'] = array(1, 3);
+		$book = new BookLength;
+		$book->name = 'four';
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+	}
+
+	public function test_valid_null()
+	{
+		BookLength::$validates_length_of[0]['within'] = array(1, 3);
+		BookLength::$validates_length_of[0]['allow_null'] = true;
+
+		$book = new BookLength;
+		$book->name = null;
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_valid_blank()
+	{
+		BookLength::$validates_length_of[0]['within'] = array(1, 3);
+		BookLength::$validates_length_of[0]['allow_blank'] = true;
+
+		$book = new BookLength;
+		$book->name = '';
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_invalid_blank()
+	{
+		BookLength::$validates_length_of[0]['within'] = array(1, 3);
+
+		$book = new BookLength;
+		$book->name = '';
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+		$this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name'));
+	}
+
+	public function test_invalid_null_within()
+	{
+		BookLength::$validates_length_of[0]['within'] = array(1, 3);
+
+		$book = new BookLength;
+		$book->name = null;
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+		$this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name'));
+	}
+	
+	public function test_invalid_null_minimum()
+	{
+		BookLength::$validates_length_of[0]['minimum'] = 1;
+
+		$book = new BookLength;
+		$book->name = null;
+		$book->save();
+		$this->assert_true($book->errors->is_invalid('name'));
+		$this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name'));
+		
+	}
+	
+	public function test_valid_null_maximum()
+	{
+		BookLength::$validates_length_of[0]['maximum'] = 1;
+
+		$book = new BookLength;
+		$book->name = null;
+		$book->save();
+		$this->assert_false($book->errors->is_invalid('name'));
+	}
+
+	public function test_float_as_impossible_range_option()
+	{
+		BookLength::$validates_length_of[0]['within'] = array(1, 3.6);
+		$book = new BookLength;
+		$book->name = '123';
+		try {
+			$book->save();
+		} catch (ActiveRecord\ValidationsArgumentError $e) {
+			$this->assert_equals('maximum value cannot use a float for length.', $e->getMessage());
+		}
+
+		$this->set_up();
+		BookLength::$validates_length_of[0]['is'] = 1.8;
+		$book = new BookLength;
+		$book->name = '123';
+		try {
+			$book->save();
+		} catch (ActiveRecord\ValidationsArgumentError $e) {
+			$this->assert_equals('is value cannot use a float for length.', $e->getMessage());
+			return;
+		}
+
+		$this->fail('An expected exception has not be raised.');
+	}
+
+	public function test_signed_integer_as_impossible_within_option()
+	{
+		BookLength::$validates_length_of[0]['within'] = array(-1, 3);
+
+		$book = new BookLength;
+		$book->name = '123';
+		try {
+			$book->save();
+		} catch (ActiveRecord\ValidationsArgumentError $e) {
+			$this->assert_equals('minimum value cannot use a signed integer.', $e->getMessage());
+			return;
+		}
+
+		$this->fail('An expected exception has not be raised.');
+	}
+
+	public function test_signed_integer_as_impossible_is_option()
+	{
+		BookLength::$validates_length_of[0]['is'] = -8;
+
+		$book = new BookLength;
+		$book->name = '123';
+		try {
+			$book->save();
+		} catch (ActiveRecord\ValidationsArgumentError $e) {
+			$this->assert_equals('is value cannot use a signed integer.', $e->getMessage());
+			return;
+		}
+
+		$this->fail('An expected exception has not be raised.');
+	}
+
+	public function test_lack_of_option()
+	{
+		try {
+			$book = new BookLength;
+			$book->name = null;
+			$book->save();
+		} catch (ActiveRecord\ValidationsArgumentError $e) {
+			$this->assert_equals('Range unspecified.  Specify the [within], [maximum], or [is] option.', $e->getMessage());
+			return;
+		}
+
+		$this->fail('An expected exception has not be raised.');
+	}
+
+	public function test_too_many_options()
+	{
+		BookLength::$validates_length_of[0]['within'] = array(1, 3);
+		BookLength::$validates_length_of[0]['in'] = array(1, 3);
+
+		try {
+			$book = new BookLength;
+			$book->name = null;
+			$book->save();
+		} catch (ActiveRecord\ValidationsArgumentError $e) {
+			$this->assert_equals('Too many range options specified.  Choose only one.', $e->getMessage());
+			return;
+		}
+
+		$this->fail('An expected exception has not be raised.');
+	}
+
+	public function test_too_many_options_with_different_option_types()
+	{
+		BookLength::$validates_length_of[0]['within'] = array(1, 3);
+		BookLength::$validates_length_of[0]['is'] = 3;
+
+		try {
+			$book = new BookLength;
+			$book->name = null;
+			$book->save();
+		} catch (ActiveRecord\ValidationsArgumentError $e) {
+			$this->assert_equals('Too many range options specified.  Choose only one.', $e->getMessage());
+			return;
+		}
+
+		$this->fail('An expected exception has not be raised.');
+	}
+
+	/**
+	 * @expectedException ActiveRecord\ValidationsArgumentError
+	 */
+	public function test_with_option_as_non_numeric()
+	{
+		BookLength::$validates_length_of[0]['with'] = array('test');
+
+		$book = new BookLength;
+		$book->name = null;
+		$book->save();
+	}
+
+	/**
+	 * @expectedException ActiveRecord\ValidationsArgumentError
+	 */
+	public function test_with_option_as_non_numeric_non_array()
+	{
+		BookLength::$validates_length_of[0]['with'] = 'test';
+
+		$book = new BookLength;
+		$book->name = null;
+		$book->save();
+	}
+
+	public function test_validates_length_of_maximum()
+	{
+		BookLength::$validates_length_of[0] = array('name', 'maximum' => 10);
+		$book = new BookLength(array('name' => '12345678901'));
+		$book->is_valid();
+		$this->assert_equals(array("Name is too long (maximum is 10 characters)"),$book->errors->full_messages());
+	}
+
+	public function test_validates_length_of_minimum()
+	{
+		BookLength::$validates_length_of[0] = array('name', 'minimum' => 2);
+		$book = new BookLength(array('name' => '1'));
+		$book->is_valid();
+		$this->assert_equals(array("Name is too short (minimum is 2 characters)"),$book->errors->full_messages());
+	}
+	
+	public function test_validates_length_of_min_max_custom_message()
+	{
+		BookLength::$validates_length_of[0] = array('name', 'maximum' => 10, 'message' => 'is far too long');
+		$book = new BookLength(array('name' => '12345678901'));
+		$book->is_valid();
+		$this->assert_equals(array("Name is far too long"),$book->errors->full_messages());
+
+		BookLength::$validates_length_of[0] = array('name', 'minimum' => 10, 'message' => 'is far too short');
+		$book = new BookLength(array('name' => '123456789'));
+		$book->is_valid();
+		$this->assert_equals(array("Name is far too short"),$book->errors->full_messages());
+	}
+	
+	public function test_validates_length_of_min_max_custom_message_overridden()
+	{
+		BookLength::$validates_length_of[0] = array('name', 'minimum' => 10, 'too_short' => 'is too short', 'message' => 'is custom message');
+		$book = new BookLength(array('name' => '123456789'));
+		$book->is_valid();
+		$this->assert_equals(array("Name is custom message"),$book->errors->full_messages());
+	}
+
+	public function test_validates_length_of_is()
+	{
+		BookLength::$validates_length_of[0] = array('name', 'is' => 2);
+		$book = new BookLength(array('name' => '123'));
+		$book->is_valid();
+		$this->assert_equals(array("Name is the wrong length (should be 2 characters)"),$book->errors->full_messages());
+	}
+}
diff --git a/test/ValidatesNumericalityOfTest.php b/test/ValidatesNumericalityOfTest.php
index 08eb7f2ec..ac9d6e2e3 100644
--- a/test/ValidatesNumericalityOfTest.php
+++ b/test/ValidatesNumericalityOfTest.php
@@ -158,8 +158,7 @@ public function test_custom_message()
 		$book->is_valid();
 		$this->assert_equals(array('Numeric test Hello'),$book->errors->full_messages());
 	}
-};
+}
 
 array_merge(ValidatesNumericalityOfTest::$INTEGERS, ValidatesNumericalityOfTest::$INTEGER_STRINGS);
 array_merge(ValidatesNumericalityOfTest::$FLOATS, ValidatesNumericalityOfTest::$FLOAT_STRINGS);
-?>
diff --git a/test/ValidatesOnSaveCreateUpdateTest.php b/test/ValidatesOnSaveCreateUpdateTest.php
new file mode 100644
index 000000000..70f7a9c25
--- /dev/null
+++ b/test/ValidatesOnSaveCreateUpdateTest.php
@@ -0,0 +1,73 @@
+<?php
+
+class BookOn extends ActiveRecord\Model
+{
+	static $table_name = 'books';
+
+	static $validates_presence_of = array();
+}
+
+class ValidatesOnSaveCreateUpdateTest extends DatabaseTest{
+
+	public function test_validations_only_run_on_create()
+	{
+		BookOn::$validates_presence_of[0] = array('name', 'on' => 'create');
+		$book = new BookOn();
+		$this->assert_false($book->is_valid());
+		
+		$book->secondary_author_id = 1;
+		$this->assert_false( $book->save() );
+		
+		$book->name = 'Baz';
+		$this->assert_true( $book->save() );
+		
+		$book->name = null;
+		$this->assert_true( $book->save() );
+	}
+
+	public function test_validations_only_run_on_update()
+	{
+		BookOn::$validates_presence_of[0] = array('name', 'on' => 'update');
+		$book = new BookOn();
+		$this->assert_true($book->save());
+		
+		$book->name = null;
+		$this->assert_false( $book->save() );
+		
+		$book->name = 'Baz';
+		$this->assert_true( $book->save() );
+	}
+
+	public function test_validations_only_run_on_save()
+	{
+		BookOn::$validates_presence_of[0] = array('name', 'on' => 'save');
+		$book = new BookOn();
+		$this->assert_false($book->is_valid());
+		
+		$book->secondary_author_id = 1;
+		$this->assert_false( $book->save() );
+		
+		$book->name = 'Baz';
+		$this->assert_true( $book->save() );
+		
+		$book->name = null;
+		$this->assert_false( $book->save() );
+	}
+
+	public function test_validations_run_always_without_on()
+	{
+		BookOn::$validates_presence_of[0] = array('name');
+		$book = new BookOn();
+		$this->assert_false($book->is_valid());
+		
+		$book->secondary_author_id = 1;
+		$this->assert_false( $book->save() );
+		
+		$book->name = 'Baz';
+		$this->assert_true( $book->save() );
+		
+		$book->name = null;
+		$this->assert_false( $book->save() );
+	}
+	
+}
\ No newline at end of file
diff --git a/test/ValidatesPresenceOfTest.php b/test/ValidatesPresenceOfTest.php
index 745c55939..16c2354de 100644
--- a/test/ValidatesPresenceOfTest.php
+++ b/test/ValidatesPresenceOfTest.php
@@ -1,74 +1,73 @@
-<?php
-
-class BookPresence extends ActiveRecord\Model
-{
-	static $table_name = 'books';
-
-	static $validates_presence_of = array(
-		array('name')
-	);
-}
-
-class AuthorPresence extends ActiveRecord\Model
-{
-	static $table_name = 'authors';
-
-	static $validates_presence_of = array(
-		array('some_date')
-	);
-};
-
-class ValidatesPresenceOfTest extends DatabaseTest
-{
-	public function test_presence()
-	{
-		$book = new BookPresence(array('name' => 'blah'));
-		$this->assert_false($book->is_invalid());
-	}
-
-	public function test_presence_on_date_field_is_valid()
-	{
-		$author = new AuthorPresence(array('some_date' => '2010-01-01'));
-		$this->assert_true($author->is_valid());
-	}
-
-	public function test_presence_on_date_field_is_not_valid()
-	{
-		$author = new AuthorPresence();
-		$this->assert_false($author->is_valid());
-	}
-	
-	public function test_invalid_null()
-	{
-		$book = new BookPresence(array('name' => null));
-		$this->assert_true($book->is_invalid());
-	}
-
-	public function test_invalid_blank()
-	{
-		$book = new BookPresence(array('name' => ''));
-		$this->assert_true($book->is_invalid());
-	}
-
-	public function test_valid_white_space()
-	{
-		$book = new BookPresence(array('name' => ' '));
-		$this->assert_false($book->is_invalid());
-	}
-
-	public function test_custom_message()
-	{
-		BookPresence::$validates_presence_of[0]['message'] = 'is using a custom message.';
-
-		$book = new BookPresence(array('name' => null));
-		$book->is_valid();
-		$this->assert_equals('is using a custom message.', $book->errors->on('name'));
-	}
-
-	public function test_valid_zero()
-	{
-		$book = new BookPresence(array('name' => 0));
-		$this->assert_true($book->is_valid());
-	}
-};
-?>
\ No newline at end of file
+<?php
+
+class BookPresence extends ActiveRecord\Model
+{
+	static $table_name = 'books';
+
+	static $validates_presence_of = array(
+		array('name')
+	);
+}
+
+class AuthorPresence extends ActiveRecord\Model
+{
+	static $table_name = 'authors';
+
+	static $validates_presence_of = array(
+		array('some_date')
+	);
+}
+
+class ValidatesPresenceOfTest extends DatabaseTest
+{
+	public function test_presence()
+	{
+		$book = new BookPresence(array('name' => 'blah'));
+		$this->assert_false($book->is_invalid());
+	}
+
+	public function test_presence_on_date_field_is_valid()
+	{
+		$author = new AuthorPresence(array('some_date' => '2010-01-01'));
+		$this->assert_true($author->is_valid());
+	}
+
+	public function test_presence_on_date_field_is_not_valid()
+	{
+		$author = new AuthorPresence();
+		$this->assert_false($author->is_valid());
+	}
+	
+	public function test_invalid_null()
+	{
+		$book = new BookPresence(array('name' => null));
+		$this->assert_true($book->is_invalid());
+	}
+
+	public function test_invalid_blank()
+	{
+		$book = new BookPresence(array('name' => ''));
+		$this->assert_true($book->is_invalid());
+	}
+
+	public function test_valid_white_space()
+	{
+		$book = new BookPresence(array('name' => ' '));
+		$this->assert_false($book->is_invalid());
+	}
+
+	public function test_custom_message()
+	{
+		BookPresence::$validates_presence_of[0]['message'] = 'is using a custom message.';
+
+		$book = new BookPresence(array('name' => null));
+		$book->is_valid();
+		$this->assert_equals('is using a custom message.', $book->errors->on('name'));
+	}
+
+	public function test_valid_zero()
+	{
+		$book = new BookPresence(array('name' => 0));
+		$this->assert_true($book->is_valid());
+	}
+}
diff --git a/test/ValidationsTest.php b/test/ValidationsTest.php
index 3e0eef257..d34f9e07c 100644
--- a/test/ValidationsTest.php
+++ b/test/ValidationsTest.php
@@ -18,6 +18,33 @@ public function validate()
 	}
 }
 
+class UserValidations extends AR\Model
+{
+	static $table_name = 'users';
+
+	public $password_confirm;
+
+	// Only for test purpose. This will double encrypt pass from the DB!
+	public function set_password($pass)
+	{
+		$this->assign_attribute('password', static::encrypt($pass));
+	}
+
+	public function validate()
+	{
+		// Another BAD idea
+		$this->password_confirm = static::encrypt($this->password_confirm);
+		if($this->password_confirm !== $this->password)
+			$this->errors->add('password', 'Password Mismatch');
+	}
+
+	public static function encrypt($data)
+	{
+		return md5($data);
+	}
+	
+}
+
 class ValuestoreValidations extends ActiveRecord\Model
 {
 	static $table_name = 'valuestore';
@@ -68,6 +95,78 @@ public function test_is_invalid_is_true()
 		$this->assert_true($book->is_invalid());
 	}
 
+	public function test_is_valid_does_not_revalidate()
+	{
+		$attrs = array(
+			'password' => 'secret',
+			'password_confirm' => 'secret'
+		);
+
+		$user = new UserValidations($attrs);
+		/**
+		 * The `is_valid()` method will validate the User. In this test it will
+		 * be valid.
+		 * If `is_valid()` had revalidated it again, `password_confirm` would be
+		 * rehashed, becoming different from `password` and then the result
+		 * would be different from precedent (and also a bug).
+		 */
+		$this->assert_true($user->is_valid());
+		$this->assert_equals(!$user->is_valid(), $user->is_invalid());
+	}
+
+	public function test_is_valid_will_revalidate_if_attribute_changes()
+	{
+		$attrs = array(
+			'password' => 'bad',
+			'password_confirm' => 'secret'
+		);
+
+		$user = new UserValidations($attrs);
+		$this->assert_false($user->is_valid());
+
+		$user->password = 'secret';
+		// because custom validation is coded bad (on purpose), we have to
+		// reset password_confirm
+		$user->password_confirm = 'secret';
+		$this->assert_true($user->is_valid());
+	}
+
+	public function test_is_invalid_will_revalidate_if_attribute_changes()
+	{
+		$attrs = array(
+			'password' => 'bad',
+			'password_confirm' => 'secret'
+		);
+
+		$user = new UserValidations($attrs);
+		$this->assert_true($user->is_invalid());
+
+		$user->password = 'secret';
+		// because custom validation is coded bad (on purpose), we have to
+		// reset password_confirm
+		$user->password_confirm = 'secret';
+		$this->assert_false($user->is_invalid());
+	}
+
+	public function test_is_valid_must_be_forced_if_a_virtual_attribute_changes()
+	{
+		$attrs = array(
+			'password' => 'secret',
+			'password_confirm' => 'bad'
+		);
+
+		$user = new UserValidations($attrs);
+		$this->assert_false($user->is_valid());
+
+		$user->password_confirm = 'secret';
+		// Actually we check only attribute set by `__set` magic method.
+		$this->assert_false($user->is_valid());
+
+		// Passing `true` will force the validation of `user`, giving the
+		// right result.
+		$this->assert_true($user->is_valid(true));
+	}
+
 	public function test_is_iterable()
 	{
 		$book = new BookValidations();
@@ -178,5 +277,5 @@ public function test_gh131_custom_validation()
 		$this->assert_true($book->errors->is_invalid('name'));
 		$this->assert_equals(BookValidations::$custom_validator_error_msg, $book->errors->on('name'));
 	}
-};
-?>
+}
+
diff --git a/test/backup-models/Backup.php b/test/backup-models/Backup.php
new file mode 100644
index 000000000..e2a4bab40
--- /dev/null
+++ b/test/backup-models/Backup.php
@@ -0,0 +1,6 @@
+<?php 
+
+class Backup extends \ActiveRecord\Model
+{
+}
+?>
\ No newline at end of file
diff --git a/test/backup-models/NamespaceTest/Backup.php b/test/backup-models/NamespaceTest/Backup.php
new file mode 100644
index 000000000..48f77a37e
--- /dev/null
+++ b/test/backup-models/NamespaceTest/Backup.php
@@ -0,0 +1,7 @@
+<?php 
+namespace NamespaceTest;
+
+class Backup extends \ActiveRecord\Model
+{
+}
+?>
\ No newline at end of file
diff --git a/test/fixtures/users.csv b/test/fixtures/users.csv
index 3ff3deb87..5976d917a 100644
--- a/test/fixtures/users.csv
+++ b/test/fixtures/users.csv
@@ -1,2 +1,2 @@
-id
-1
+id,password
+1,hello
diff --git a/test/helpers/AdapterTest.php b/test/helpers/AdapterTest.php
index 97685f6bc..b4a156c26 100644
--- a/test/helpers/AdapterTest.php
+++ b/test/helpers/AdapterTest.php
@@ -8,7 +8,7 @@ class AdapterTest extends DatabaseTest
 	public function set_up($connection_name=null)
 	{
 		if (($connection_name && !in_array($connection_name, PDO::getAvailableDrivers())) ||
-			ActiveRecord\Config::instance()->get_connection($connection_name) == 'skip')
+			ActiveRecord\Config::instance()->get_connection_info($connection_name) == 'skip')
 			$this->mark_test_skipped($connection_name . ' drivers are not present');
 
 		parent::set_up($connection_name);
@@ -25,7 +25,7 @@ public function test_i_has_a_default_port_unless_im_sqlite()
 
 	public function test_should_set_adapter_variables()
 	{
-		$this->assert_not_null($this->conn->protocol);
+		$this->assert_not_null($this->conn->adapter);
 	}
 
 	public function test_null_connection_string_uses_default_connection()
@@ -38,7 +38,7 @@ public function test_null_connection_string_uses_default_connection()
 	/**
 	 * @expectedException ActiveRecord\DatabaseException
 	 */
-	public function test_invalid_connection_protocol()
+	public function test_invalid_connection_adapter()
 	{
 		ActiveRecord\Connection::instance('terribledb://user:pass@host/db');
 	}
@@ -51,7 +51,7 @@ public function test_no_host_connection()
 		if (!$GLOBALS['slow_tests'])
 			throw new ActiveRecord\DatabaseException("");
 
-		ActiveRecord\Connection::instance("{$this->conn->protocol}://user:pass");
+		ActiveRecord\Connection::instance("{$this->conn->adapter}://user:pass");
 	}
 
 	/**
@@ -62,7 +62,7 @@ public function test_connection_failed_invalid_host()
 		if (!$GLOBALS['slow_tests'])
 			throw new ActiveRecord\DatabaseException("");
 
-		ActiveRecord\Connection::instance("{$this->conn->protocol}://user:pass/1.1.1.1/db");
+		ActiveRecord\Connection::instance("{$this->conn->adapter}://user:pass/1.1.1.1/db");
 	}
 
 	/**
@@ -70,7 +70,7 @@ public function test_connection_failed_invalid_host()
 	 */
 	public function test_connection_failed()
 	{
-		ActiveRecord\Connection::instance("{$this->conn->protocol}://baduser:badpass@127.0.0.1/db");
+		ActiveRecord\Connection::instance("{$this->conn->adapter}://baduser:badpass@127.0.0.1/db");
 	}
 
 	/**
@@ -78,25 +78,32 @@ public function test_connection_failed()
 	 */
 	public function test_connect_failed()
 	{
-		ActiveRecord\Connection::instance("{$this->conn->protocol}://zzz:zzz@127.0.0.1/test");
+		ActiveRecord\Connection::instance("{$this->conn->adapter}://zzz:zzz@127.0.0.1/test");
 	}
 
 	public function test_connect_with_port()
 	{
 		$config = ActiveRecord\Config::instance();
-		$name = $config->get_default_connection();
-		$url = parse_url($config->get_connection($name));
+		$info = $config->get_default_connection_info();
+
 		$conn = $this->conn;
 		$port = $conn::$DEFAULT_PORT;
 
-		$connection_string = "{$url['scheme']}://{$url['user']}";
-		if(isset($url['pass'])){
-			$connection_string =  "{$connection_string}:{$url['pass']}";
-		}
-		$connection_string = "{$connection_string}@{$url['host']}:$port{$url['path']}";
+		$info->port = $port;
+
+		if ($this->conn->adapter != 'sqlite')
+			ActiveRecord\Connection::instance($info);
+	}
+
+	public function test_connect_with_array_connection_info()
+	{
+		$config = ActiveRecord\Config::instance();
+		$info = $config->get_default_connection_info();
+
+		$info_array = array_filter(get_object_vars($info));
 
-		if ($this->conn->protocol != 'sqlite')
-			ActiveRecord\Connection::instance($connection_string);
+		if ($this->conn->adapter != 'sqlite')
+			ActiveRecord\Connection::instance($info_array);
 	}
 
 	/**
@@ -104,7 +111,7 @@ public function test_connect_with_port()
 	 */
 	public function test_connect_to_invalid_database()
 	{
-		ActiveRecord\Connection::instance("{$this->conn->protocol}://test:test@127.0.0.1/" . self::InvalidDb);
+		ActiveRecord\Connection::instance("{$this->conn->adapter}://test:test@127.0.0.1/" . self::InvalidDb);
 	}
 
 	public function test_date_time_type()
@@ -408,4 +415,3 @@ public function test_date_to_string()
 		$this->assert_equals($datetime,$this->conn->date_to_string(date_create($datetime)));
 	}
 }
-?>
diff --git a/test/helpers/DatabaseLoader.php b/test/helpers/DatabaseLoader.php
index 8449e1bb2..e478d7e1b 100644
--- a/test/helpers/DatabaseLoader.php
+++ b/test/helpers/DatabaseLoader.php
@@ -8,14 +8,14 @@ public function __construct($db)
 	{
 		$this->db = $db;
 
-		if (!isset(static::$instances[$db->protocol]))
-			static::$instances[$db->protocol] = 0;
+		if (!isset(static::$instances[$db->adapter]))
+			static::$instances[$db->adapter] = 0;
 
-		if (static::$instances[$db->protocol]++ == 0)
+		if (static::$instances[$db->adapter]++ == 0)
 		{
 			// drop and re-create the tables one time only
 			$this->drop_tables();
-			$this->exec_sql_script($db->protocol);
+			$this->exec_sql_script($db->adapter);
 		}
 	}
 
@@ -23,14 +23,14 @@ public function reset_table_data()
 	{
 		foreach ($this->get_fixture_tables() as $table)
 		{
-			if ($this->db->protocol == 'oci' && $table == 'rm-bldg')
+			if ($this->db->adapter == 'oci' && $table == 'rm-bldg')
 				continue;
 
 			$this->db->query('DELETE FROM ' . $this->quote_name($table));
 			$this->load_fixture_data($table);
 		}
 
-		$after_fixtures = $this->db->protocol.'-after-fixtures';
+		$after_fixtures = $this->db->adapter.'-after-fixtures';
 		try {
 			$this->exec_sql_script($after_fixtures);
 		} catch (Exception $e) {
@@ -44,7 +44,7 @@ public function drop_tables()
 
 		foreach ($this->get_fixture_tables() as $table)
 		{
-			if ($this->db->protocol == 'oci')
+			if ($this->db->adapter == 'oci')
 			{
 				$table = strtoupper($table);
 
@@ -55,7 +55,7 @@ public function drop_tables()
 			if (in_array($table,$tables))
 				$this->db->query('DROP TABLE ' . $this->quote_name($table));
 
-			if ($this->db->protocol == 'oci')
+			if ($this->db->adapter == 'oci')
 			{
 				try {
 					$this->db->query("DROP SEQUENCE {$table}_seq");
@@ -121,10 +121,9 @@ public function load_fixture_data($table)
 
 	public function quote_name($name)
 	{
-		if ($this->db->protocol == 'oci')
+		if ($this->db->adapter == 'oci')
 			$name = strtoupper($name);
 
 		return $this->db->quote_name($name);
 	}
 }
-?>
diff --git a/test/helpers/DatabaseTest.php b/test/helpers/DatabaseTest.php
index c9c29045d..a597b768d 100644
--- a/test/helpers/DatabaseTest.php
+++ b/test/helpers/DatabaseTest.php
@@ -22,7 +22,8 @@ public function set_up($connection_name=null)
 		if ($connection_name == 'sqlite' || $config->get_default_connection() == 'sqlite')
 		{
 			// need to create the db. the adapter specifically does not create it for us.
-			static::$db = substr(ActiveRecord\Config::instance()->get_connection('sqlite'),9);
+			$info = ActiveRecord\Config::instance()->get_connection_info('sqlite');
+			static::$db = $info->host;
 			new SQLite3(static::$db);
 		}
 
@@ -82,4 +83,3 @@ public function assert_sql_doesnt_has($needle, $haystack)
 		return $this->assertNotContains($needle, $haystack);
 	}
 }
-?>
diff --git a/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php b/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php
index a5ac79c85..1016aa044 100644
--- a/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php
+++ b/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php
@@ -61,4 +61,3 @@ public function assert_datetime_equals($expected, $actual)
 		$this->assert_equals($expected->format(DateTime::ISO8601),$actual->format(DateTime::ISO8601));
 	}
 }
-?>
\ No newline at end of file
diff --git a/test/helpers/config.php b/test/helpers/config.php
index 49a488660..a71243e14 100644
--- a/test/helpers/config.php
+++ b/test/helpers/config.php
@@ -82,4 +82,3 @@
 });
 
 error_reporting(E_ALL | E_STRICT);
-?>
\ No newline at end of file
diff --git a/test/helpers/foo.php b/test/helpers/foo.php
index 1ed77ca51..49e3194eb 100644
--- a/test/helpers/foo.php
+++ b/test/helpers/foo.php
@@ -23,6 +23,3 @@ class UserNewsletter extends \ActiveRecord\Model {
 		array('newsletter'),
 	);
 }
-
-# vim: ts=4 noet nobinary
-?>
diff --git a/test/models/Amenity.php b/test/models/Amenity.php
index ce6e3f163..70f6e442b 100644
--- a/test/models/Amenity.php
+++ b/test/models/Amenity.php
@@ -7,5 +7,4 @@ class Amenity extends ActiveRecord\Model
 	static $has_many = array(
 		'property_amenities'
 	);
-};
-?>
+}
diff --git a/test/models/Author.php b/test/models/Author.php
index 321ffe9d1..16987facd 100644
--- a/test/models/Author.php
+++ b/test/models/Author.php
@@ -25,5 +25,4 @@ public function return_something()
 	{
 		return array("sharks" => "lasers");
 	}
-};
-?>
+}
diff --git a/test/models/AuthorAttrAccessible.php b/test/models/AuthorAttrAccessible.php
index 1443629c7..fb870c486 100644
--- a/test/models/AuthorAttrAccessible.php
+++ b/test/models/AuthorAttrAccessible.php
@@ -13,5 +13,4 @@ class AuthorAttrAccessible extends ActiveRecord\Model
 
 	// No attributes should be accessible
 	static $attr_accessible = array(null);
-};
-?>
+}
diff --git a/test/models/AwesomePerson.php b/test/models/AwesomePerson.php
index 33aeece65..3bf9d8b07 100644
--- a/test/models/AwesomePerson.php
+++ b/test/models/AwesomePerson.php
@@ -3,4 +3,3 @@ class AwesomePerson extends ActiveRecord\Model
 {
 	static $belongs_to = array('author');
 }
-?>
diff --git a/test/models/Book.php b/test/models/Book.php
index 88e35d403..d001b37d3 100644
--- a/test/models/Book.php
+++ b/test/models/Book.php
@@ -32,5 +32,4 @@ public function get_lower_name()
 	{
 		return strtolower($this->name);
 	}
-};
-?>
+}
diff --git a/test/models/BookAttrAccessible.php b/test/models/BookAttrAccessible.php
index 59d1efbcc..f9204d6fd 100644
--- a/test/models/BookAttrAccessible.php
+++ b/test/models/BookAttrAccessible.php
@@ -6,5 +6,4 @@ class BookAttrAccessible extends ActiveRecord\Model
 
 	static $attr_accessible = array('author_id');
 	static $attr_protected = array('book_id');
-};
-?>
\ No newline at end of file
+}
diff --git a/test/models/BookAttrProtected.php b/test/models/BookAttrProtected.php
index ab1305009..876925f9d 100644
--- a/test/models/BookAttrProtected.php
+++ b/test/models/BookAttrProtected.php
@@ -9,5 +9,5 @@ class BookAttrProtected extends ActiveRecord\Model
 
 	// No attributes should be accessible
 	static $attr_accessible = array(null);
-};
-?>
+}
+
diff --git a/test/models/Employee.php b/test/models/Employee.php
index dfdf2ccf6..606ccb6ad 100644
--- a/test/models/Employee.php
+++ b/test/models/Employee.php
@@ -2,5 +2,4 @@
 class Employee extends ActiveRecord\Model
 {
 	static $has_one;
-};
-?>
\ No newline at end of file
+}
diff --git a/test/models/Event.php b/test/models/Event.php
index ca3c13d74..5243cbda3 100644
--- a/test/models/Event.php
+++ b/test/models/Event.php
@@ -10,5 +10,5 @@ class Event extends ActiveRecord\Model
 		array('state', 'address', 'to' => 'venue'),
 		array('name', 'to' => 'host', 'prefix' => 'woot')
 	);
-};
-?>
+}
+
diff --git a/test/models/Host.php b/test/models/Host.php
index 7c66e3f08..fcc6a9626 100644
--- a/test/models/Host.php
+++ b/test/models/Host.php
@@ -6,4 +6,3 @@ class Host extends ActiveRecord\Model
 		array('venues', 'through' => 'events')
 	);
 }
-?>
diff --git a/test/models/JoinAuthor.php b/test/models/JoinAuthor.php
index d39fb873a..f2b250c43 100644
--- a/test/models/JoinAuthor.php
+++ b/test/models/JoinAuthor.php
@@ -3,5 +3,4 @@ class JoinAuthor extends ActiveRecord\Model
 {
 	static $table_name = 'authors';
 	static $pk = 'author_id';
-};
-?>
\ No newline at end of file
+}
diff --git a/test/models/JoinBook.php b/test/models/JoinBook.php
index be845534a..2b1199db5 100644
--- a/test/models/JoinBook.php
+++ b/test/models/JoinBook.php
@@ -4,5 +4,4 @@ class JoinBook extends ActiveRecord\Model
 	static $table_name = 'books';
 
 	static $belongs_to = array();
-};
-?>
\ No newline at end of file
+}
diff --git a/test/models/NamespaceTest/Book.php b/test/models/NamespaceTest/Book.php
index f2e237e5b..ee5456628 100644
--- a/test/models/NamespaceTest/Book.php
+++ b/test/models/NamespaceTest/Book.php
@@ -14,4 +14,3 @@ class Book extends \ActiveRecord\Model
 		array('pages_2', 'class_name' => 'SubNamespaceTest\Page'),
 	);
 }
-?>
diff --git a/test/models/NamespaceTest/SubNamespaceTest/Page.php b/test/models/NamespaceTest/SubNamespaceTest/Page.php
index 2d59f2c65..c0cd7f07e 100644
--- a/test/models/NamespaceTest/SubNamespaceTest/Page.php
+++ b/test/models/NamespaceTest/SubNamespaceTest/Page.php
@@ -7,4 +7,3 @@ class Page extends \ActiveRecord\Model
 		array('book', 'class_name' => '\NamespaceTest\Book'),
 	);
 }
-?>
diff --git a/test/models/Position.php b/test/models/Position.php
index ce409d815..9d98089c2 100644
--- a/test/models/Position.php
+++ b/test/models/Position.php
@@ -2,5 +2,4 @@
 class Position extends ActiveRecord\Model
 {
 	static $belongs_to;
-};
-?>
\ No newline at end of file
+}
diff --git a/test/models/Property.php b/test/models/Property.php
index e0f913cc9..9205c96d6 100644
--- a/test/models/Property.php
+++ b/test/models/Property.php
@@ -8,5 +8,4 @@ class Property extends ActiveRecord\Model
 		'property_amenities',
 		array('amenities', 'through' => 'property_amenities')
 	);
-};
-?>
+}
diff --git a/test/models/PropertyAmenity.php b/test/models/PropertyAmenity.php
index 47f80e966..6ab72f4b2 100644
--- a/test/models/PropertyAmenity.php
+++ b/test/models/PropertyAmenity.php
@@ -8,5 +8,4 @@ class PropertyAmenity extends ActiveRecord\Model
 		'amenity',
 		'property'
 	);
-};
-?>
+}
diff --git a/test/models/RmBldg.php b/test/models/RmBldg.php
index fbc9ce79d..b6fb38006 100644
--- a/test/models/RmBldg.php
+++ b/test/models/RmBldg.php
@@ -1,33 +1,32 @@
-<?php
-class RmBldg extends ActiveRecord\Model
-{
-	static $table = 'rm-bldg';
-
-	static $validates_presence_of = array(
-		array('space_out', 'message' => 'is missing!@#'),
-		array('rm_name')
-	);
-
-	static $validates_length_of = array(
-		array('space_out', 'within' => array(1, 5)),
-		array('space_out', 'minimum' => 9, 'too_short' => 'var is too short!! it should be at least %d long')
-	);
-
-	static $validates_inclusion_of = array(
-		array('space_out', 'in' => array('jpg', 'gif', 'png'), 'message' => 'extension %s is not included in the list'),
-	);
-
-	static $validates_exclusion_of = array(
-		array('space_out', 'in' => array('jpeg'))
-	);
-
-	static $validates_format_of = array(
-		array('space_out', 'with' => '/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i' )
-	);
-
-	static $validates_numericality_of = array(
-		array('space_out', 'less_than' => 9, 'greater_than' => '5'),
-		array('rm_id', 'less_than' => 10, 'odd' => null)
-	);
-}
-?>
+<?php
+class RmBldg extends ActiveRecord\Model
+{
+	static $table = 'rm-bldg';
+
+	static $validates_presence_of = array(
+		array('space_out', 'message' => 'is missing!@#'),
+		array('rm_name')
+	);
+
+	static $validates_length_of = array(
+		array('space_out', 'within' => array(1, 5)),
+		array('space_out', 'minimum' => 9, 'too_short' => 'var is too short!! it should be at least %d long')
+	);
+
+	static $validates_inclusion_of = array(
+		array('space_out', 'in' => array('jpg', 'gif', 'png'), 'message' => 'extension %s is not included in the list'),
+	);
+
+	static $validates_exclusion_of = array(
+		array('space_out', 'in' => array('jpeg'))
+	);
+
+	static $validates_format_of = array(
+		array('space_out', 'with' => '/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i' )
+	);
+
+	static $validates_numericality_of = array(
+		array('space_out', 'less_than' => 9, 'greater_than' => '5'),
+		array('rm_id', 'less_than' => 10, 'odd' => null)
+	);
+}
diff --git a/test/models/Venue.php b/test/models/Venue.php
index 38ad9241b..c1540375b 100644
--- a/test/models/Venue.php
+++ b/test/models/Venue.php
@@ -4,6 +4,7 @@ class Venue extends ActiveRecord\Model
 	static $use_custom_get_state_getter = false;
 	static $use_custom_set_state_setter = false;
 	
+	static $use_custom_set_city_setter = false;
 	
 	static $has_many = array(
 		'events',
@@ -32,6 +33,13 @@ public function set_state($value)
 		else
 			return $this->assign_attribute('state', $value);
 	}
+
+	public function set_city($value)
+	{
+		if (self::$use_custom_set_city_setter)
+			return $this->assign_attribute('city', $value . '#');
+		else
+			return $this->assign_attribute('city', $value);
+	}
 	
-};
-?>
+}
diff --git a/test/models/VenueCB.php b/test/models/VenueCB.php
index 7d96f2427..32255881a 100644
--- a/test/models/VenueCB.php
+++ b/test/models/VenueCB.php
@@ -41,4 +41,3 @@ public function before_validation_halt_execution()
 		return false;
 	}
 }
-?>
\ No newline at end of file
diff --git a/test/sql/mysql.sql b/test/sql/mysql.sql
index 60e529bc6..90dd82242 100644
--- a/test/sql/mysql.sql
+++ b/test/sql/mysql.sql
@@ -92,7 +92,8 @@ CREATE TABLE property_amenities(
 );
 
 CREATE TABLE users (
-    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
+    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+    password VARCHAR(255) NOT NULL
 ) ENGINE=InnoDB;
 
 CREATE TABLE newsletters (
@@ -109,4 +110,4 @@ CREATE TABLE valuestore (
   `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
   `key` varchar(20) NOT NULL DEFAULT '',
   `value` varchar(255) NOT NULL DEFAULT ''
-) ENGINE=InnoDB;
\ No newline at end of file
+) ENGINE=InnoDB;
diff --git a/test/sql/pgsql.sql b/test/sql/pgsql.sql
index edda21fae..fdc91dd80 100644
--- a/test/sql/pgsql.sql
+++ b/test/sql/pgsql.sql
@@ -91,7 +91,8 @@ CREATE TABLE property_amenities(
 );
 
 CREATE TABLE users(
-	id serial primary key
+	id serial primary key,
+	password varchar(255) not null
 );
 
 CREATE TABLE newsletters(
@@ -104,6 +105,17 @@ CREATE TABLE user_newsletters(
   newsletter_id int not null
 );
 
+CREATE TABLE services(
+  id serial primary key,
+  code varchar(255) not null
+);
+
+CREATE TABLE active_services (
+  id serial primary key,
+  user_id int not null,
+  serv_id int not null
+);
+
 CREATE TABLE valuestore (
   id serial primary key,
   key varchar(20) NOT NULL DEFAULT '',
diff --git a/test/sql/sqlite.sql b/test/sql/sqlite.sql
index b5abfeda0..35efdc6bf 100644
--- a/test/sql/sqlite.sql
+++ b/test/sql/sqlite.sql
@@ -91,7 +91,8 @@ CREATE TABLE property_amenities(
 );
 
 CREATE TABLE users (
-    id INTEGER NOT NULL PRIMARY KEY
+    id INTEGER NOT NULL PRIMARY KEY,
+    password VARCHAR(255) NOT NULL
 );
 
 CREATE TABLE newsletters (