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 (