[
  {
    "path": ".editorconfig",
    "content": "# editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[*.php]\nindent_style = tab\nindent_size = 4\n\n[*.{xml,xml.dist}]\nindent_size = 4\n"
  },
  {
    "path": ".gitignore",
    "content": ".project\n.buildpath\n.settings\n*.log\n*.db\n*.swp\nvendor/*\ncomposer.lock\nphpunit.xml\n"
  },
  {
    "path": ".travis.yml",
    "content": "install: composer install --prefer-source --dev\n\nservices:\n  - memcache\n\nenv: PHPAR_MYSQL=mysql://root@127.0.0.1/phpar_test PHPAR_PGSQL=pgsql://postgres@127.0.0.1/phpar_test\n\nlanguage: php\nphp:\n  - 5.3\n  - 5.4\n  - 5.5\n  - 7.0\n  - 7.1\n  - nightly\n\nmatrix:\n  include:\n    - php: hhvm\n      dist: trusty\n  allow_failures:\n    - php: hhvm\n    - php: nightly\n  fast_finish: true\n\nbefore_script:\n  - |\n    if [[ \"${TRAVIS_PHP_VERSION:0:1}\" == \"7\" ]]; then\n      curl -L https://github.com/websupport-sk/pecl-memcache/archive/NON_BLOCKING_IO_php7.tar.gz | tar xz;\n      (cd pecl-memcache-NON_BLOCKING_IO_php7 && phpize && ./configure && make && make install);\n    fi\n  - if [[ \"$TRAVIS_PHP_VERSION\" != \"hhvm\" ]]; then echo 'extension = \"memcache.so\"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi\n  \n  - mysql -e 'CREATE DATABASE phpar_test;'\n  - psql -c 'CREATE DATABASE phpar_test;' -U postgres\n\nscript: ./vendor/bin/phpunit\n"
  },
  {
    "path": "ActiveRecord.php",
    "content": "<?php\r\nif (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300)\r\n\tdie('PHP ActiveRecord requires PHP 5.3 or higher');\r\n\r\ndefine('PHP_ACTIVERECORD_VERSION_ID','1.0');\r\n\r\nif (!defined('PHP_ACTIVERECORD_AUTOLOAD_PREPEND'))\r\n\tdefine('PHP_ACTIVERECORD_AUTOLOAD_PREPEND',true);\r\n\r\nrequire __DIR__.'/lib/Singleton.php';\r\nrequire __DIR__.'/lib/Config.php';\r\nrequire __DIR__.'/lib/Utils.php';\r\nrequire __DIR__.'/lib/DateTimeInterface.php';\r\nrequire __DIR__.'/lib/DateTime.php';\r\nrequire __DIR__.'/lib/Model.php';\r\nrequire __DIR__.'/lib/Table.php';\r\nrequire __DIR__.'/lib/ConnectionManager.php';\r\nrequire __DIR__.'/lib/Connection.php';\r\nrequire __DIR__.'/lib/Serialization.php';\r\nrequire __DIR__.'/lib/SQLBuilder.php';\r\nrequire __DIR__.'/lib/Reflections.php';\r\nrequire __DIR__.'/lib/Inflector.php';\r\nrequire __DIR__.'/lib/CallBack.php';\r\nrequire __DIR__.'/lib/Exceptions.php';\r\nrequire __DIR__.'/lib/Cache.php';\r\n\r\nif (!defined('PHP_ACTIVERECORD_AUTOLOAD_DISABLE'))\r\n\tspl_autoload_register('activerecord_autoload',false,PHP_ACTIVERECORD_AUTOLOAD_PREPEND);\r\n\r\nfunction activerecord_autoload($class_name)\r\n{\r\n\t$path = ActiveRecord\\Config::instance()->get_model_directory();\r\n\t$root = realpath(isset($path) ? $path : '.');\r\n\r\n\tif (($namespaces = ActiveRecord\\get_namespaces($class_name)))\r\n\t{\r\n\t\t$class_name = array_pop($namespaces);\r\n\t\t$directories = array();\r\n\r\n\t\tforeach ($namespaces as $directory)\r\n\t\t\t$directories[] = $directory;\r\n\r\n\t\t$root .= DIRECTORY_SEPARATOR . implode($directories, DIRECTORY_SEPARATOR);\r\n\t}\r\n\r\n\t$file = \"$root/$class_name.php\";\r\n\r\n\tif (file_exists($file))\r\n\t\trequire_once $file;\r\n}"
  },
  {
    "path": "CHANGELOG",
    "content": "Version 1.0 - June 27, 2010\n\n- d2bed65 fixed an error with eager loading when no records exist\n- c225942 fixed set methods on DateTime objects to properly flag attributes as dirty\n- 46a1219 fixed a memory leak when using validations\n- c225942 fixed problem with some model functionality not working correctly after being deserialized \n- 3e26749 fixed validates_numericality_of to not ignore other options when only_integer is present and matches\n- 53ad5ec fixed ambiguous id error when finding by pk with a join option\n- 26e40f4 fixed conditions to accept DateTime values\n- 41e52fe changed serialization to serialize datetime fields as strings instead of the actual DateTime objects\n- dbee94b Model::transaction() now returns true if commit was successful otherwise false\n\nVersio 1.0 RC1 - May 7, 2010\n\n- support for Oracle\n- support for PostgreSQL\n- added delegators\n- added setters\n- added getters\n- added HAVING as a finder option\n- added ability to find using a hash\n- added find_or_create_by\n- added validates_uniqueness_of\n- added dynamic count_by\n- added eager loading\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to PHP ActiveRecord #\n\nWe always appreciate contributions to PHP ActiveRecord, but we are not always able to respond as quickly as we would like.\nPlease do not take delays personal and feel free to remind us by commenting on issues.\n\n### Testing ###\n\nPHP ActiveRecord has a full set of unit tests, which are run by PHPUnit.\n\nIn order to run these unit tests, you need to install the required packages using [Composer](https://getcomposer.org/):\n\n```sh\ncomposer install\n```\n\nAfter that you can run the tests by invoking the local PHPUnit\n\nTo run all test simply use:\n\n```sh\nvendor/bin/phpunit\n```\n\nOr run a single test file by specifying its path:\n\n```sh\nvendor/bin/phpunit test/InflectorTest.php\n```\n\n#### Skipped Tests ####\n\nYou might notice that some tests are marked as skipped. To obtain more information about skipped\ntests, pass the `--verbose` flag to PHPUnit:\n\n```sh\nvendor/bin/phpunit --verbose\n```\n\nSome common steps for fixing skipped tests are to:\n\n* Install `memcached` and the PHP memcached extension (e.g., `brew install php56-memcache memcached` on macOS)\n* Install the PDO drivers for PostgreSQL (e.g., `brew install php56-pdo-pgsql` on macOS)\n* Create a MySQL database and a PostgreSQL database. You can either create these such that they are available at the default locations of `mysql://test:test@127.0.0.1/test` and `pgsql://test:test@127.0.0.1/test` respectively. Alternatively, you can set the `PHPAR_MYSQL` and `PHPAR_PGSQL` environment variables to specify a different location for the MySQL and PostgreSQL databases.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2009\n\nAUTHORS:\nKien La\nJacques Fuentes\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# PHP ActiveRecord - Version 1.0 #\n\n[![Build Status](https://travis-ci.org/jpfuentes2/php-activerecord.png?branch=master)](https://travis-ci.org/jpfuentes2/php-activerecord)\n\nby \n\n* [@kla](https://github.com/kla) - Kien La\n* [@jpfuentes2](https://github.com/jpfuentes2) - Jacques Fuentes\n* [And these terrific Contributors](https://github.com/kla/php-activerecord/contributors)\n\n<http://www.phpactiverecord.org/> \n\n## Introduction ##\nA brief summarization of what ActiveRecord is:\n\n> Active record is an approach to access data in a database. A database table or view is wrapped into a class,\n> thus an object instance is tied to a single row in the table. After creation of an object, a new row is added to\n> the table upon save. Any object loaded gets its information from the database; when an object is updated, the\n> corresponding row in the table is also updated. The wrapper class implements accessor methods or properties for\n> each column in the table or view.\n\nMore details can be found [here](http://en.wikipedia.org/wiki/Active_record_pattern).\n\nThis implementation is inspired and thus borrows heavily from Ruby on Rails' ActiveRecord.\nWe have tried to maintain their conventions while deviating mainly because of convenience or necessity.\nOf course, there are some differences which will be obvious to the user if they are familiar with rails.\n\n## Minimum Requirements ##\n\n- PHP 5.3+\n- PDO driver for your respective database\n\n## Supported Databases ##\n\n- MySQL\n- SQLite\n- PostgreSQL\n- Oracle\n\n## Features ##\n\n- Finder methods\n- Dynamic finder methods\n- Writer methods\n- Relationships\n- Validations\n- Callbacks\n- Serializations (json/xml)\n- Transactions\n- Support for multiple adapters\n- Miscellaneous options such as: aliased/protected/accessible attributes\n\n## Installation ##\n\nSetup is very easy and straight-forward. There are essentially only three configuration points you must concern yourself with:\n\n1. Setting the model autoload directory.\n2. Configuring your database connections.\n3. Setting the database connection to use for your environment.\n\nExample:\n\n```php\nActiveRecord\\Config::initialize(function($cfg)\n{\n   $cfg->set_model_directory('/path/to/your/model_directory');\n   $cfg->set_connections(\n     array(\n       'development' => 'mysql://username:password@localhost/development_database_name',\n       'test' => 'mysql://username:password@localhost/test_database_name',\n       'production' => 'mysql://username:password@localhost/production_database_name'\n     )\n   );\n});\n```\n\nAlternatively (w/o the 5.3 closure):\n\n```php\n$cfg = ActiveRecord\\Config::instance();\n$cfg->set_model_directory('/path/to/your/model_directory');\n$cfg->set_connections(\n  array(\n    'development' => 'mysql://username:password@localhost/development_database_name',\n    'test' => 'mysql://username:password@localhost/test_database_name',\n    'production' => 'mysql://username:password@localhost/production_database_name'\n  )\n);\n```\n\nPHP ActiveRecord will default to use your development database. For testing or production, you simply set the default\nconnection according to your current environment ('test' or 'production'):\n\n```php\nActiveRecord\\Config::initialize(function($cfg)\n{\n  $cfg->set_default_connection(your_environment);\n});\n```\n\nOnce you have configured these three settings you are done. ActiveRecord takes care of the rest for you.\nIt does not require that you map your table schema to yaml/xml files. It will query the database for this information and\ncache it so that it does not make multiple calls to the database for a single schema.\n\n## Basic CRUD ##\n\n### Retrieve ###\nThese are your basic methods to find and retrieve records from your database.\nSee the *Finders* section for more details.\n\n```php\n$post = Post::find(1);\necho $post->title; # 'My first blog post!!'\necho $post->author_id; # 5\n\n# also the same since it is the first record in the db\n$post = Post::first();\n\n# finding using dynamic finders\n$post = Post::find_by_name('The Decider');\n$post = Post::find_by_name_and_id('The Bridge Builder',100);\n$post = Post::find_by_name_or_id('The Bridge Builder',100);\n\n# finding using a conditions array\n$posts = Post::find('all',array('conditions' => array('name=? or id > ?','The Bridge Builder',100)));\n```\n\n### Create ###\nHere we create a new post by instantiating a new object and then invoking the save() method.\n\n```php\n$post = new Post();\n$post->title = 'My first blog post!!';\n$post->author_id = 5;\n$post->save();\n# INSERT INTO `posts` (title,author_id) VALUES('My first blog post!!', 5)\n```\n\n### Update ###\nTo update you would just need to find a record first and then change one of its attributes.\nIt keeps an array of attributes that are \"dirty\" (that have been modified) and so our\nsql will only update the fields modified.\n\n```php\n$post = Post::find(1);\necho $post->title; # 'My first blog post!!'\n$post->title = 'Some real title';\n$post->save();\n# UPDATE `posts` SET title='Some real title' WHERE id=1\n\n$post->title = 'New real title';\n$post->author_id = 1;\n$post->save();\n# UPDATE `posts` SET title='New real title', author_id=1 WHERE id=1\n```\n\n### Delete ###\nDeleting a record will not *destroy* the object. This means that it will call sql to delete\nthe record in your database but you can still use the object if you need to.\n\n```php\n$post = Post::find(1);\n$post->delete();\n# DELETE FROM `posts` WHERE id=1\necho $post->title; # 'New real title'\n```\n\n## Contributing ##\n\nPlease refer to [CONTRIBUTING.md](https://github.com/jpfuentes2/php-activerecord/blob/master/CONTRIBUTING.md) for information on how to contribute to PHP ActiveRecord.\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"php-activerecord/php-activerecord\",\n    \"type\": \"library\",\n    \"description\": \"php-activerecord is an open source ORM library based on the ActiveRecord pattern.\",\n    \"keywords\": [\"activerecord\", \"orm\"],\n    \"homepage\": \"http://www.phpactiverecord.org/\",\n    \"license\": \"MIT\",\n    \"require\": {\n        \"php\": \">=5.3.0\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"4.*\",\n        \"pear/pear_exception\": \"1.0-beta1\",\n        \"pear/log\": \"~1.12\"\n    },\n    \"autoload\": {\n        \"files\": [ \"ActiveRecord.php\" ]\n    }\n}\n"
  },
  {
    "path": "examples/orders/models/Order.php",
    "content": "<?php\nclass Order extends ActiveRecord\\Model\n{\n\t// order belongs to a person\n\tstatic $belongs_to = array(\n\t\tarray('person'));\n\n\t// order can have many payments by many people\n\t// the conditions is just there as an example as it makes no logical sense\n\tstatic $has_many = array(\n\t\tarray('payments'),\n\t\tarray('people',\n\t\t\t'through'    => 'payments',\n\t\t\t'select'     => 'people.*, payments.amount',\n\t\t\t'conditions' => 'payments.amount < 200'));\n\n\t// order must have a price and tax > 0\n\tstatic $validates_numericality_of = array(\n\t\tarray('price', 'greater_than' => 0),\n\t\tarray('tax',   'greater_than' => 0));\n\n\t// setup a callback to automatically apply a tax\n\tstatic $before_validation_on_create = array('apply_tax');\n\n\tpublic function apply_tax()\n\t{\n\t\tif ($this->person->state == 'VA')\n\t\t\t$tax = 0.045;\n\t\telseif ($this->person->state == 'CA')\n\t\t\t$tax = 0.10;\n\t\telse\n\t\t\t$tax = 0.02;\n\n\t\t$this->tax = $this->price * $tax;\n\t}\n}\n?>\n"
  },
  {
    "path": "examples/orders/models/Payment.php",
    "content": "<?php\nclass Payment extends ActiveRecord\\Model\n{\n\t// payment belongs to a person\n\tstatic $belongs_to = array(\n\t\tarray('person'),\n\t\tarray('order'));\n}\n?>\n"
  },
  {
    "path": "examples/orders/models/Person.php",
    "content": "<?php\nclass Person extends ActiveRecord\\Model\n{\n\t// a person can have many orders and payments\n\tstatic $has_many = array(\n\t\tarray('orders'),\n\t\tarray('payments'));\n\n\t// must have a name and a state\n\tstatic $validates_presence_of = array(\n\t\tarray('name'), array('state'));\n}\n?>\n"
  },
  {
    "path": "examples/orders/orders.php",
    "content": "<?php\nrequire_once __DIR__ . '/../../ActiveRecord.php';\n\n// initialize ActiveRecord\nActiveRecord\\Config::initialize(function($cfg)\n{\n    $cfg->set_model_directory(__DIR__ . '/models');\n    $cfg->set_connections(array('development' => 'mysql://test:test@127.0.0.1/orders_test'));\n\n\t// you can change the default connection with the below\n    //$cfg->set_default_connection('production');\n});\n\n// create some people\n$jax = new Person(array('name' => 'Jax', 'state' => 'CA'));\n$jax->save();\n\n// compact way to create and save a model\n$tito = Person::create(array('name' => 'Tito', 'state' => 'VA'));\n\n// place orders. tax is automatically applied in a callback\n// create_orders will automatically place the created model into $tito->orders\n// even if it failed validation\n$pokemon = $tito->create_orders(array('item_name' => 'Live Pokemon', 'price' => 6999.99));\n$coal    = $tito->create_orders(array('item_name' => 'Lump of Coal', 'price' => 100.00));\n$freebie = $tito->create_orders(array('item_name' => 'Freebie', 'price' => -100.99));\n\nif (count($freebie->errors) > 0)\n\techo \"[FAILED] saving order $freebie->item_name: \" . join(', ',$freebie->errors->full_messages()) . \"\\n\\n\";\n\n// payments\n$pokemon->create_payments(array('amount' => 1.99, 'person_id' => $tito->id));\n$pokemon->create_payments(array('amount' => 4999.50, 'person_id' => $tito->id));\n$pokemon->create_payments(array('amount' => 2.50, 'person_id' => $jax->id));\n\n// reload since we don't want the freebie to show up (because it failed validation)\n$tito->reload();\n\necho \"$tito->name has \" . count($tito->orders) . \" orders for: \" . join(', ',ActiveRecord\\collect($tito->orders,'item_name')) . \"\\n\\n\";\n\n// get all orders placed by Tito\nforeach (Order::find_all_by_person_id($tito->id) as $order)\n{\n\techo \"Order #$order->id for $order->item_name ($$order->price + $$order->tax tax) ordered by \" . $order->person->name . \"\\n\";\n\n\tif (count($order->payments) > 0)\n\t{\n\t\t// display each payment for this order\n\t\tforeach ($order->payments as $payment)\n\t\t\techo \"  payment #$payment->id of $$payment->amount by \" . $payment->person->name . \"\\n\";\n\t}\n\telse\n\t\techo \"  no payments\\n\";\n\n\techo \"\\n\";\n}\n\n// display summary of all payments made by Tito and Jax\n$conditions = array(\n\t'conditions'\t=> array('id IN(?)',array($tito->id,$jax->id)),\n\t'order'\t\t\t=> 'name desc');\n\nforeach (Person::all($conditions) as $person)\n{\n\t$n = count($person->payments);\n\t$total = array_sum(ActiveRecord\\collect($person->payments,'amount'));\n\techo \"$person->name made $n payments for a total of $$total\\n\\n\";\n}\n\n// using order has_many people through payments with options\n// array('people', 'through' => 'payments', 'select' => 'people.*, payments.amount', 'conditions' => 'payments.amount < 200'));\n// this means our people in the loop below also has the payment information since it is part of an inner join\n// we will only see 2 of the people instead of 3 because 1 of the payments is greater than 200\n$order = Order::find($pokemon->id);\necho \"Order #$order->id for $order->item_name ($$order->price + $$order->tax tax)\\n\";\n\nforeach ($order->people as $person)\n\techo \"  payment of $$person->amount by \" . $person->name . \"\\n\";\n?>\n"
  },
  {
    "path": "examples/orders/orders.sql",
    "content": "-- written for mysql, not tested with any other db\n\ndrop table if exists people;\ncreate table people(\n  id int not null primary key auto_increment,\n  name varchar(50),\n  state char(2),\n  created_at datetime,\n  updated_at datetime\n);\n\ndrop table if exists orders;\ncreate table orders(\n  id int not null primary key auto_increment,\n  person_id int not null,\n  item_name varchar(50),\n  price decimal(10,2),\n  tax decimal(10,2),\n  created_at datetime\n);\n\ndrop table if exists payments;\ncreate table payments(\n  id int not null primary key auto_increment,\n  order_id int not null,\n  person_id int not null,\n  amount decimal(10,2),\n  created_at datetime\n);\n"
  },
  {
    "path": "examples/simple/simple.php",
    "content": "<?php\nrequire_once __DIR__ . '/../../ActiveRecord.php';\n\n// assumes a table named \"books\" with a pk named \"id\"\n// see simple.sql\nclass Book extends ActiveRecord\\Model { }\n\n// initialize ActiveRecord\n// change the connection settings to whatever is appropriate for your mysql server \nActiveRecord\\Config::initialize(function($cfg)\n{\n    $cfg->set_model_directory('.');\n    $cfg->set_connections(array('development' => 'mysql://test:test@127.0.0.1/test'));\n});\n\nprint_r(Book::first()->attributes());\n?>\n"
  },
  {
    "path": "examples/simple/simple.sql",
    "content": "create table books(\n  id int not null primary key auto_increment,\n  name varchar(50),\n  author varchar(50)\n);\n\ninsert into books(name,author) values('How to be Angry','Jax');\n"
  },
  {
    "path": "examples/simple/simple_with_options.php",
    "content": "<?php\nrequire_once __DIR__ . '/../../ActiveRecord.php';\n\nclass Book extends ActiveRecord\\Model\n{\n\t// explicit table name since our table is not \"books\"\n\tstatic $table_name = 'simple_book';\n\n\t// explicit pk since our pk is not \"id\"\n\tstatic $primary_key = 'book_id';\n\n\t// explicit connection name since we always want production with this model\n\tstatic $connection = 'production';\n\n\t// explicit database name will generate sql like so => db.table_name\n\tstatic $db = 'test';\n}\n\n$connections = array(\n\t'development' => 'mysql://invalid',\n\t'production' => 'mysql://test:test@127.0.0.1/test'\n);\n\n// initialize ActiveRecord\nActiveRecord\\Config::initialize(function($cfg) use ($connections)\n{\n    $cfg->set_model_directory('.');\n    $cfg->set_connections($connections);\n});\n\nprint_r(Book::first()->attributes());\n?>\n"
  },
  {
    "path": "examples/simple/simple_with_options.sql",
    "content": "create table simple_book(\n  book_id int not null primary key auto_increment,\n  name varchar(50)\n);\n\ninsert into simple_book (name) values ('simple w/ options!');\n"
  },
  {
    "path": "lib/Cache.php",
    "content": "<?php\nnamespace ActiveRecord;\nuse Closure;\n\n/**\n * Cache::get('the-cache-key', function() {\n *\t # this gets executed when cache is stale\n *\t return \"your cacheable datas\";\n * });\n */\nclass Cache\n{\n\tstatic $adapter = null;\n\tstatic $options = array();\n\n\t/**\n\t * Initializes the cache.\n\t *\n\t * With the $options array it's possible to define:\n\t * - expiration of the key, (time in seconds)\n\t * - a namespace for the key\n\t *\n\t * this last one is useful in the case two applications use\n\t * a shared key/store (for instance a shared Memcached db)\n\t *\n\t * Ex:\n\t * $cfg_ar = ActiveRecord\\Config::instance();\n\t * $cfg_ar->set_cache('memcache://localhost:11211',array('namespace' => 'my_cool_app',\n\t *\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t 'expire'\t\t => 120\n\t *\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t ));\n\t *\n\t * In the example above all the keys expire after 120 seconds, and the\n\t * all get a postfix 'my_cool_app'.\n\t *\n\t * (Note: expiring needs to be implemented in your cache store.)\n\t *\n\t * @param string $url URL to your cache server\n\t * @param array $options Specify additional options\n\t */\n\tpublic static function initialize($url, $options=array())\n\t{\n\t\tif ($url)\n\t\t{\n\t\t\t$url = parse_url($url);\n\t\t\t$file = ucwords(Inflector::instance()->camelize($url['scheme']));\n\t\t\t$class = \"ActiveRecord\\\\$file\";\n\t\t\trequire_once __DIR__ . \"/cache/$file.php\";\n\t\t\tstatic::$adapter = new $class($url);\n\t\t}\n\t\telse\n\t\t\tstatic::$adapter = null;\n\n\t\tstatic::$options = array_merge(array('expire' => 30, 'namespace' => ''),$options);\n\t}\n\n\tpublic static function flush()\n\t{\n\t\tif (static::$adapter)\n\t\t\tstatic::$adapter->flush();\n\t}\n\n\t/**\n\t * Attempt to retrieve a value from cache using a key. If the value is not found, then the closure method\n\t * will be invoked, and the result will be stored in cache using that key.\n\t * @param $key\n\t * @param $closure\n\t * @param $expire in seconds\n\t * @return mixed\n\t */\n\tpublic static function get($key, $closure, $expire=null)\n\t{\n\t\tif (!static::$adapter)\n\t\t\treturn $closure();\n\n\t\tif (is_null($expire))\n\t\t{\n\t\t\t$expire = static::$options['expire'];\n\t\t}\n\n\t\t$key = static::get_namespace() . $key;\n\n\t\tif (!($value = static::$adapter->read($key)))\n\t\t\tstatic::$adapter->write($key, ($value = $closure()), $expire);\n\n\t\treturn $value;\n\t}\n\n\tpublic static function set($key, $var, $expire=null)\n\t{\n\t\tif (!static::$adapter)\n\t\t\treturn;\n\n\t\tif (is_null($expire))\n\t\t{\n\t\t\t$expire = static::$options['expire'];\n\t\t}\n\n\t\t$key = static::get_namespace() . $key;\n\t\treturn static::$adapter->write($key, $var, $expire);\n\t}\n\n\tpublic static function delete($key)\n\t{\n\t\tif (!static::$adapter)\n\t\t\treturn;\n\n\t\t$key = static::get_namespace() . $key;\n\t\treturn static::$adapter->delete($key);\n\t}\n\n\tprivate static function get_namespace()\n\t{\n\t\treturn (isset(static::$options['namespace']) && strlen(static::$options['namespace']) > 0) ? (static::$options['namespace'] . \"::\") : \"\";\n\t}\n}\n"
  },
  {
    "path": "lib/CallBack.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\nuse Closure;\n\n/**\n * Callbacks allow the programmer to hook into the life cycle of a {@link Model}.\n *\n * You can control the state of your object by declaring certain methods to be\n * called before or after methods are invoked on your object inside of ActiveRecord.\n *\n * Valid callbacks are:\n * <ul>\n * <li><b>after_construct:</b> called after a model has been constructed</li>\n * <li><b>before_save:</b> called before a model is saved</li>\n * <li><b>after_save:</b> called after a model is saved</li>\n * <li><b>before_create:</b> called before a NEW model is to be inserted into the database</li>\n * <li><b>after_create:</b> called after a NEW model has been inserted into the database</li>\n * <li><b>before_update:</b> called before an existing model has been saved</li>\n * <li><b>after_update:</b> called after an existing model has been saved</li>\n * <li><b>before_validation:</b> called before running validators</li>\n * <li><b>after_validation:</b> called after running validators</li>\n * <li><b>before_validation_on_create:</b> called before validation on a NEW model being inserted</li>\n * <li><b>after_validation_on_create:</b> called after validation on a NEW model being inserted</li>\n * <li><b>before_validation_on_update:</b> see above except for an existing model being saved</li>\n * <li><b>after_validation_on_update:</b> ...</li>\n * <li><b>before_destroy:</b> called after a model has been deleted</li>\n * <li><b>after_destroy:</b> called after a model has been deleted</li>\n * </ul>\n *\n * This class isn't meant to be used directly. Callbacks are defined on your model like the example below:\n *\n * <code>\n * class Person extends ActiveRecord\\Model {\n *   static $before_save = array('make_name_uppercase');\n *   static $after_save = array('do_happy_dance');\n *\n *   public function make_name_uppercase() {\n *     $this->name = strtoupper($this->name);\n *   }\n *\n *   public function do_happy_dance() {\n *     happy_dance();\n *   }\n * }\n * </code>\n *\n * Available options for callbacks:\n *\n * <ul>\n * <li><b>prepend:</b> puts the callback at the top of the callback chain instead of the bottom</li>\n * </ul>\n *\n * @package ActiveRecord\n * @link http://www.phpactiverecord.org/guides/callbacks\n */\nclass CallBack\n{\n\t/**\n\t * List of available callbacks.\n\t *\n\t * @var array\n\t */\n\tstatic protected $VALID_CALLBACKS = array(\n\t\t'after_construct',\n\t\t'before_save',\n\t\t'after_save',\n\t\t'before_create',\n\t\t'after_create',\n\t\t'before_update',\n\t\t'after_update',\n\t\t'before_validation',\n\t\t'after_validation',\n\t\t'before_validation_on_create',\n\t\t'after_validation_on_create',\n\t\t'before_validation_on_update',\n\t\t'after_validation_on_update',\n\t\t'before_destroy',\n\t\t'after_destroy'\n\t);\n\n\t/**\n\t * Container for reflection class of given model\n\t *\n\t * @var object\n\t */\n\tprivate $klass;\n\n\t/**\n\t * List of public methods of the given model\n\t * @var array\n\t */\n\tprivate $publicMethods;\n\n\t/**\n\t * Holds data for registered callbacks.\n\t *\n\t * @var array\n\t */\n\tprivate $registry = array();\n\n\t/**\n\t * Creates a CallBack.\n\t *\n\t * @param string $model_class_name The name of a {@link Model} class\n\t * @return CallBack\n\t */\n\tpublic function __construct($model_class_name)\n\t{\n\t\t$this->klass = Reflections::instance()->get($model_class_name);\n\n\t\tforeach (static::$VALID_CALLBACKS as $name)\n\t\t{\n\t\t\t// look for explicitly defined static callback\n\t\t\tif (($definition = $this->klass->getStaticPropertyValue($name,null)))\n\t\t\t{\n\t\t\t\tif (!is_array($definition))\n\t\t\t\t\t$definition = array($definition);\n\n\t\t\t\tforeach ($definition as $method_name)\n\t\t\t\t\t$this->register($name,$method_name);\n\t\t\t}\n\n\t\t\t// implicit callbacks that don't need to have a static definition\n\t\t\t// simply define a method named the same as something in $VALID_CALLBACKS\n\t\t\t// and the callback is auto-registered\n\t\t\telseif ($this->klass->hasMethod($name))\n\t\t\t\t$this->register($name,$name);\n\t\t}\n\t}\n\n\t/**\n\t * Returns all the callbacks registered for a callback type.\n\t *\n\t * @param $name string Name of a callback (see {@link VALID_CALLBACKS $VALID_CALLBACKS})\n\t * @return array array of callbacks or null if invalid callback name.\n\t */\n\tpublic function get_callbacks($name)\n\t{\n\t\treturn isset($this->registry[$name]) ? $this->registry[$name] : null;\n\t}\n\n\t/**\n\t * Invokes a callback.\n\t *\n\t * @internal This is the only piece of the CallBack class that carries its own logic for the\n\t * model object. For (after|before)_(create|update) callbacks, it will merge with\n\t * a generic 'save' callback which is called first for the lease amount of precision.\n\t *\n\t * @param string $model Model to invoke the callback on.\n\t * @param string $name Name of the callback to invoke\n\t * @param boolean $must_exist Set to true to raise an exception if the callback does not exist.\n\t * @return mixed null if $name was not a valid callback type or false if a method was invoked\n\t * that was for a before_* callback and that method returned false. If this happens, execution\n\t * of any other callbacks after the offending callback will not occur.\n\t */\n\tpublic function invoke($model, $name, $must_exist=true)\n\t{\n\t\tif ($must_exist && !array_key_exists($name, $this->registry))\n\t\t\tthrow new ActiveRecordException(\"No callbacks were defined for: $name on \" . get_class($model));\n\n\t\t// if it doesn't exist it might be a /(after|before)_(create|update)/ so we still need to run the save\n\t\t// callback\n\t\tif (!array_key_exists($name, $this->registry))\n\t\t\t$registry = array();\n\t\telse\n\t\t\t$registry = $this->registry[$name];\n\n\t\t$first = substr($name,0,6);\n\n\t\t// starts with /(after|before)_(create|update)/\n\t\tif (($first == 'after_' || $first == 'before') && (($second = substr($name,7,5)) == 'creat' || $second == 'updat' || $second == 'reate' || $second == 'pdate'))\n\t\t{\n\t\t\t$temporal_save = str_replace(array('create', 'update'), 'save', $name);\n\n\t\t\tif (!isset($this->registry[$temporal_save]))\n\t\t\t\t$this->registry[$temporal_save] = array();\n\n\t\t\t$registry = array_merge($this->registry[$temporal_save], $registry ? $registry : array());\n\t\t}\n\n\t\tif ($registry)\n\t\t{\n\t\t\tforeach ($registry as $method)\n\t\t\t{\n\t\t\t\t$ret = ($method instanceof Closure ? $method($model) : $model->$method());\n\n\t\t\t\tif (false === $ret && $first === 'before')\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Register a new callback.\n\t *\n\t * The option array can contain the following parameters:\n\t * <ul>\n\t * <li><b>prepend:</b> Add this callback at the beginning of the existing callbacks (true) or at the end (false, default)</li>\n\t * </ul>\n\t *\n\t * @param string $name Name of callback type (see {@link VALID_CALLBACKS $VALID_CALLBACKS})\n\t * @param mixed $closure_or_method_name Either a closure or the name of a method on the {@link Model}\n\t * @param array $options Options array\n\t * @return void\n\t * @throws ActiveRecordException if invalid callback type or callback method was not found\n\t */\n\tpublic function register($name, $closure_or_method_name=null, $options=array())\n\t{\n\t\t$options = array_merge(array('prepend' => false), $options);\n\n\t\tif (!$closure_or_method_name)\n\t\t\t$closure_or_method_name = $name;\n\n\t\tif (!in_array($name,self::$VALID_CALLBACKS))\n\t\t\tthrow new ActiveRecordException(\"Invalid callback: $name\");\n\n\t\tif (!($closure_or_method_name instanceof Closure))\n\t\t{\n\t\t\tif (!isset($this->publicMethods))\n\t\t\t\t$this->publicMethods = get_class_methods($this->klass->getName());\n\n\t\t\tif (!in_array($closure_or_method_name, $this->publicMethods))\n\t\t\t{\n\t\t\t\tif ($this->klass->hasMethod($closure_or_method_name))\n\t\t\t\t{\n\t\t\t\t\t// Method is private or protected\n\t\t\t\t\tthrow new ActiveRecordException(\"Callback methods need to be public (or anonymous closures). \" .\n\t\t\t\t\t\t\"Please change the visibility of \" . $this->klass->getName() . \"->\" . $closure_or_method_name . \"()\");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// i'm a dirty ruby programmer\n\t\t\t\t\tthrow new ActiveRecordException(\"Unknown method for callback: $name\" .\n\t\t\t\t\t\t(is_string($closure_or_method_name) ? \": #$closure_or_method_name\" : \"\"));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!isset($this->registry[$name]))\n\t\t\t$this->registry[$name] = array();\n\n\t\tif ($options['prepend'])\n\t\t\tarray_unshift($this->registry[$name], $closure_or_method_name);\n\t\telse\n\t\t\t$this->registry[$name][] = $closure_or_method_name;\n\t}\n}"
  },
  {
    "path": "lib/Column.php",
    "content": "<?php\r\n/**\r\n * @package ActiveRecord\r\n */\r\nnamespace ActiveRecord;\r\n\r\n/**\r\n * Class for a table column.\r\n *\r\n * @package ActiveRecord\r\n */\r\nclass Column\r\n{\r\n\t// types for $type\r\n\tconst STRING\t= 1;\r\n\tconst INTEGER\t= 2;\r\n\tconst DECIMAL\t= 3;\r\n\tconst DATETIME\t= 4;\r\n\tconst DATE\t\t= 5;\r\n\tconst TIME\t\t= 6;\r\n\r\n\t/**\r\n\t * Map a type to an column type.\r\n\t * @static\r\n\t * @var array\r\n\t */\r\n\tstatic $TYPE_MAPPING = array(\r\n\t\t'datetime'\t=> self::DATETIME,\r\n\t\t'timestamp'\t=> self::DATETIME,\r\n\t\t'date'\t\t=> self::DATE,\r\n\t\t'time'\t\t=> self::TIME,\r\n\r\n\t\t'tinyint'\t=> self::INTEGER,\r\n\t\t'smallint'\t=> self::INTEGER,\r\n\t\t'mediumint'\t=> self::INTEGER,\r\n\t\t'int'\t\t=> self::INTEGER,\r\n\t\t'bigint'\t=> self::INTEGER,\r\n\r\n\t\t'float'\t\t=> self::DECIMAL,\r\n\t\t'double'\t=> self::DECIMAL,\r\n\t\t'numeric'\t=> self::DECIMAL,\r\n\t\t'decimal'\t=> self::DECIMAL,\r\n\t\t'dec'\t\t=> self::DECIMAL);\r\n\r\n\t/**\r\n\t * The true name of this column.\r\n\t * @var string\r\n\t */\r\n\tpublic $name;\r\n\r\n\t/**\r\n\t * The inflected name of this columns .. hyphens/spaces will be => _.\r\n\t * @var string\r\n\t */\r\n\tpublic $inflected_name;\r\n\r\n\t/**\r\n\t * The type of this column: STRING, INTEGER, ...\r\n\t * @var integer\r\n\t */\r\n\tpublic $type;\r\n\r\n\t/**\r\n\t * The raw database specific type.\r\n\t * @var string\r\n\t */\r\n\tpublic $raw_type;\r\n\r\n\t/**\r\n\t * The maximum length of this column.\r\n\t * @var int\r\n\t */\r\n\tpublic $length;\r\n\r\n\t/**\r\n\t * True if this column allows null.\r\n\t * @var boolean\r\n\t */\r\n\tpublic $nullable;\r\n\r\n\t/**\r\n\t * True if this column is a primary key.\r\n\t * @var boolean\r\n\t */\r\n\tpublic $pk;\r\n\r\n\t/**\r\n\t * The default value of the column.\r\n\t * @var mixed\r\n\t */\r\n\tpublic $default;\r\n\r\n\t/**\r\n\t * True if this column is set to auto_increment.\r\n\t * @var boolean\r\n\t */\r\n\tpublic $auto_increment;\r\n\r\n\t/**\r\n\t * Name of the sequence to use for this column if any.\r\n\t * @var boolean\r\n\t */\r\n\tpublic $sequence;\r\n\r\n\t/**\r\n\t * Cast a value to an integer type safely\r\n\t *\r\n\t * This will attempt to cast a value to an integer,\r\n\t * unless its detected that the casting will cause\r\n\t * the number to overflow or lose precision, in which\r\n\t * case the number will be returned as a string, so\r\n\t * that large integers (BIGINTS, unsigned INTS, etc)\r\n\t * can still be stored without error\r\n\t *\r\n\t * This would ideally be done with bcmath or gmp, but\r\n\t * requiring a new PHP extension for a bug-fix is a\r\n\t * little ridiculous\r\n\t *\r\n\t * @param mixed $value The value to cast\r\n\t * @return int|string type-casted value\r\n\t */\r\n\tpublic static function castIntegerSafely($value)\r\n\t{\r\n\t\tif (is_int($value))\r\n\t\t\treturn $value;\r\n\r\n\t\t// Its just a decimal number\r\n\t\telseif (is_numeric($value) && floor($value) != $value)\r\n\t\t\treturn (int) $value;\r\n\r\n\t\t// If adding 0 to a string causes a float conversion,\r\n\t\t// we have a number over PHP_INT_MAX\r\n\t\telseif (is_string($value) && is_float($value + 0))\r\n\t\t\treturn (string) $value;\r\n\r\n\t\t// If a float was passed and its greater than PHP_INT_MAX\r\n\t\t// (which could be wrong due to floating point precision)\r\n\t\t// We'll also check for equal to (>=) in case the precision\r\n\t\t// loss creates an overflow on casting\r\n\t\telseif (is_float($value) && $value >= PHP_INT_MAX)\r\n\t\t\treturn number_format($value, 0, '', '');\r\n\r\n\t\treturn (int) $value;\r\n\t}\r\n\r\n\t/**\r\n\t * Casts a value to the column's type.\r\n\t *\r\n\t * @param mixed $value The value to cast\r\n\t * @param Connection $connection The Connection this column belongs to\r\n\t * @return mixed type-casted value\r\n\t */\r\n\tpublic function cast($value, $connection)\r\n\t{\r\n\t\tif ($value === null)\r\n\t\t\treturn null;\r\n\r\n\t\tswitch ($this->type)\r\n\t\t{\r\n\t\t\tcase self::STRING:\treturn (string)$value;\r\n\t\t\tcase self::INTEGER:\treturn static::castIntegerSafely($value);\r\n\t\t\tcase self::DECIMAL:\treturn (double)$value;\r\n\t\t\tcase self::DATETIME:\r\n\t\t\tcase self::DATE:\r\n\t\t\t\tif (!$value)\r\n\t\t\t\t\treturn null;\r\n\r\n\t\t\t\t$date_class = Config::instance()->get_date_class();\r\n\r\n\t\t\t\tif ($value instanceof $date_class)\r\n\t\t\t\t\treturn $value;\r\n\r\n\t\t\t\tif ($value instanceof \\DateTime)\r\n\t\t\t\t\treturn $date_class::createFromFormat(\r\n\t\t\t\t\t\tConnection::DATETIME_TRANSLATE_FORMAT,\r\n\t\t\t\t\t\t$value->format(Connection::DATETIME_TRANSLATE_FORMAT),\r\n\t\t\t\t\t\t$value->getTimezone()\r\n\t\t\t\t\t);\r\n\r\n\t\t\t\treturn $connection->string_to_datetime($value);\r\n\t\t}\r\n\t\treturn $value;\r\n\t}\r\n\r\n\t/**\r\n\t * Sets the $type member variable.\r\n\t * @return mixed\r\n\t */\r\n\tpublic function map_raw_type()\r\n\t{\r\n\t\tif ($this->raw_type == 'integer')\r\n\t\t\t$this->raw_type = 'int';\r\n\r\n\t\tif (array_key_exists($this->raw_type,self::$TYPE_MAPPING))\r\n\t\t\t$this->type = self::$TYPE_MAPPING[$this->raw_type];\r\n\t\telse\r\n\t\t\t$this->type = self::STRING;\r\n\r\n\t\treturn $this->type;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "lib/Config.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\nuse Closure;\n\n/**\n * Manages configuration options for ActiveRecord.\n *\n * <code>\n * ActiveRecord::initialize(function($cfg) {\n *   $cfg->set_model_home('models');\n *   $cfg->set_connections(array(\n *     'development' => 'mysql://user:pass@development.com/awesome_development',\n *     'production' => 'mysql://user:pass@production.com/awesome_production'));\n * });\n * </code>\n *\n * @package ActiveRecord\n */\nclass Config extends Singleton\n{\n\t/**\n\t * Name of the connection to use by default.\n\t *\n\t * <code>\n\t * ActiveRecord\\Config::initialize(function($cfg) {\n\t *   $cfg->set_model_directory('/your/app/models');\n\t *   $cfg->set_connections(array(\n\t *     'development' => 'mysql://user:pass@development.com/awesome_development',\n\t *     'production' => 'mysql://user:pass@production.com/awesome_production'));\n\t * });\n\t * </code>\n\t *\n\t * This is a singleton class so you can retrieve the {@link Singleton} instance by doing:\n\t *\n\t * <code>\n\t * $config = ActiveRecord\\Config::instance();\n\t * </code>\n\t *\n\t * @var string\n\t */\n\tprivate $default_connection = 'development';\n\n\t/**\n\t * Contains the list of database connection strings.\n\t *\n\t * @var array\n\t */\n\tprivate $connections = array();\n\n\t/**\n\t * Directory for the auto_loading of model classes.\n\t *\n\t * @see activerecord_autoload\n\t * @var string\n\t */\n\tprivate $model_directory;\n\n\t/**\n\t * Switch for logging.\n\t *\n\t * @var bool\n\t */\n\tprivate $logging = false;\n\n\t/**\n\t * Contains a Logger object that must impelement a log() method.\n\t *\n\t * @var object\n\t */\n\tprivate $logger;\n\n\t/**\n\t * Contains the class name for the Date class to use. Must have a public format() method and a\n\t * public static createFromFormat($format, $time) method\n\t *\n\t * @var string\n\t */\n\tprivate $date_class = 'ActiveRecord\\\\DateTime';\n\n\t/**\n\t * The format to serialize DateTime values into.\n\t *\n\t * @var string\n\t */\n\tprivate $date_format = \\DateTime::ISO8601;\n\n\t/**\n\t * Allows config initialization using a closure.\n\t *\n\t * This method is just syntatic sugar.\n\t *\n\t * <code>\n\t * ActiveRecord\\Config::initialize(function($cfg) {\n\t *   $cfg->set_model_directory('/path/to/your/model_directory');\n\t *   $cfg->set_connections(array(\n\t *     'development' => 'mysql://username:password@127.0.0.1/database_name'));\n\t * });\n\t * </code>\n\t *\n\t * You can also initialize by grabbing the singleton object:\n\t *\n\t * <code>\n\t * $cfg = ActiveRecord\\Config::instance();\n\t * $cfg->set_model_directory('/path/to/your/model_directory');\n\t * $cfg->set_connections(array('development' =>\n  \t *   'mysql://username:password@localhost/database_name'));\n\t * </code>\n\t *\n\t * @param Closure $initializer A closure\n\t * @return void\n\t */\n\tpublic static function initialize(Closure $initializer)\n\t{\n\t\t$initializer(parent::instance());\n\t}\n\n\t/**\n\t * Sets the list of database connection strings.\n\t *\n\t * <code>\n\t * $config->set_connections(array(\n\t *     'development' => 'mysql://username:password@127.0.0.1/database_name'));\n\t * </code>\n\t *\n\t * @param array $connections Array of connections\n\t * @param string $default_connection Optionally specify the default_connection\n\t * @return void\n\t * @throws ActiveRecord\\ConfigException\n\t */\n\tpublic function set_connections($connections, $default_connection=null)\n\t{\n\t\tif (!is_array($connections))\n\t\t\tthrow new ConfigException(\"Connections must be an array\");\n\n\t\tif ($default_connection)\n\t\t\t$this->set_default_connection($default_connection);\n\n\t\t$this->connections = $connections;\n\t}\n\n\t/**\n\t * Returns the connection strings array.\n\t *\n\t * @return array\n\t */\n\tpublic function get_connections()\n\t{\n\t\treturn $this->connections;\n\t}\n\n\t/**\n\t * Returns a connection string if found otherwise null.\n\t *\n\t * @param string $name Name of connection to retrieve\n\t * @return string connection info for specified connection name\n\t */\n\tpublic function get_connection($name)\n\t{\n\t\tif (array_key_exists($name, $this->connections))\n\t\t\treturn $this->connections[$name];\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Returns the default connection string or null if there is none.\n\t *\n\t * @return string\n\t */\n\tpublic function get_default_connection_string()\n\t{\n\t\treturn array_key_exists($this->default_connection,$this->connections) ?\n\t\t\t$this->connections[$this->default_connection] : null;\n\t}\n\n\t/**\n\t * Returns the name of the default connection.\n\t *\n\t * @return string\n\t */\n\tpublic function get_default_connection()\n\t{\n\t\treturn $this->default_connection;\n\t}\n\n\t/**\n\t * Set the name of the default connection.\n\t *\n\t * @param string $name Name of a connection in the connections array\n\t * @return void\n\t */\n\tpublic function set_default_connection($name)\n\t{\n\t\t$this->default_connection = $name;\n\t}\n\n\t/**\n\t * Sets the directory where models are located.\n\t *\n\t * @param string $dir Directory path containing your models\n\t * @return void\n\t */\n\tpublic function set_model_directory($dir)\n\t{\n\t\t$this->model_directory = $dir;\n\t}\n\n\t/**\n\t * Returns the model directory.\n\t *\n\t * @return string\n\t * @throws ConfigException if specified directory was not found\n\t */\n\tpublic function get_model_directory()\n\t{\n\t\tif ($this->model_directory && !file_exists($this->model_directory))\n\t\t\tthrow new ConfigException('Invalid or non-existent directory: '.$this->model_directory);\n\n\t\treturn $this->model_directory;\n\t}\n\n\t/**\n\t * Turn on/off logging\n\t *\n\t * @param boolean $bool\n\t * @return void\n\t */\n\tpublic function set_logging($bool)\n\t{\n\t\t$this->logging = (bool)$bool;\n\t}\n\n\t/**\n\t * Sets the logger object for future SQL logging\n\t *\n\t * @param object $logger\n\t * @return void\n\t * @throws ConfigException if Logger objecct does not implement public log()\n\t */\n\tpublic function set_logger($logger)\n\t{\n\t\t$klass = Reflections::instance()->add($logger)->get($logger);\n\n\t\tif (!$klass->getMethod('log') || !$klass->getMethod('log')->isPublic())\n\t\t\tthrow new ConfigException(\"Logger object must implement a public log method\");\n\n\t\t$this->logger = $logger;\n\t}\n\n\t/**\n\t * Return whether or not logging is on\n\t *\n\t * @return boolean\n\t */\n\tpublic function get_logging()\n\t{\n\t\treturn $this->logging;\n\t}\n\n\t/**\n\t * Returns the logger\n\t *\n\t * @return object\n\t */\n\tpublic function get_logger()\n\t{\n\t\treturn $this->logger;\n\t}\n\n\tpublic function set_date_class($date_class)\n\t{\n\t\ttry {\n\t\t\t$klass = Reflections::instance()->add($date_class)->get($date_class);\n\t\t} catch (\\ReflectionException $e) {\n\t\t\tthrow new ConfigException(\"Cannot find date class\");\n\t\t}\n\n\t\tif (!$klass->hasMethod('format') || !$klass->getMethod('format')->isPublic())\n\t\t\tthrow new ConfigException('Given date class must have a \"public format($format = null)\" method');\n\n\t\tif (!$klass->hasMethod('createFromFormat') || !$klass->getMethod('createFromFormat')->isPublic())\n\t\t\tthrow new ConfigException('Given date class must have a \"public static createFromFormat($format, $time)\" method');\n\n\t\t$this->date_class = $date_class;\n\t}\n\n\tpublic function get_date_class()\n\t{\n\t\treturn $this->date_class;\n\t}\n\n\t/**\n\t * @deprecated\n\t */\n\tpublic function get_date_format()\n\t{\n\t\ttrigger_error('Use ActiveRecord\\Serialization::$DATETIME_FORMAT. Config::get_date_format() has been deprecated.', E_USER_DEPRECATED);\n\t\treturn Serialization::$DATETIME_FORMAT;\n\t}\n\n\t/**\n\t * @deprecated\n\t */\n\tpublic function set_date_format($format)\n\t{\n\t\ttrigger_error('Use ActiveRecord\\Serialization::$DATETIME_FORMAT. Config::set_date_format() has been deprecated.', E_USER_DEPRECATED);\n\t\tSerialization::$DATETIME_FORMAT = $format;\n\t}\n\n\t/**\n\t * Sets the url for the cache server to enable query caching.\n\t *\n\t * Only table schema queries are cached at the moment. A general query cache\n\t * will follow.\n\t *\n\t * Example:\n\t *\n\t * <code>\n\t * $config->set_cache(\"memcached://localhost\");\n\t * $config->set_cache(\"memcached://localhost\",array(\"expire\" => 60));\n\t * </code>\n\t *\n\t * @param string $url Url to your cache server.\n\t * @param array $options Array of options\n\t */\n\tpublic function set_cache($url, $options=array())\n\t{\n\t\tCache::initialize($url,$options);\n\t}\n}"
  },
  {
    "path": "lib/Connection.php",
    "content": "<?php\n\n/**\n * @package ActiveRecord\n */\n\nnamespace ActiveRecord;\n\nrequire_once 'Column.php';\n\nuse PDO;\nuse PDOException;\nuse Closure;\n\n/**\n * The base class for database connection adapters.\n *\n * @package ActiveRecord\n */\nabstract class Connection\n{\n\n\t/**\n\t * The DateTime format to use when translating other DateTime-compatible objects.\n\t *\n\t * NOTE!: The DateTime \"format\" used must not include a time-zone (name, abbreviation, etc) or offset.\n\t * Including one will cause PHP to ignore the passed in time-zone in the 3rd argument.\n\t * See bug: https://bugs.php.net/bug.php?id=61022\n\t *\n\t * @var string\n\t */\n\tconst DATETIME_TRANSLATE_FORMAT = 'Y-m-d\\TH:i:s';\n\n\t/**\n\t * The PDO connection object.\n\t * @var mixed\n\t */\n\tpublic $connection;\n\t/**\n\t * The last query run.\n\t * @var string\n\t */\n\tpublic $last_query;\n\t/**\n\t * Switch for logging.\n\t *\n\t * @var bool\n\t */\n\tprivate $logging = false;\n\t/**\n\t * Contains a Logger object that must impelement a log() method.\n\t *\n\t * @var object\n\t */\n\tprivate $logger;\n\t/**\n\t * The name of the protocol that is used.\n\t * @var string\n\t */\n\tpublic $protocol;\n\t/**\n\t * Database's date format\n\t * @var string\n\t */\n\tstatic $date_format = 'Y-m-d';\n\t/**\n\t * Database's datetime format\n\t * @var string\n\t */\n\tstatic $datetime_format = 'Y-m-d H:i:s T';\n\t/**\n\t * Default PDO options to set for each connection.\n\t * @var array\n\t */\n\tstatic $PDO_OPTIONS = array(\n\t\tPDO::ATTR_CASE => PDO::CASE_LOWER,\n\t\tPDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,\n\t\tPDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,\n\t\tPDO::ATTR_STRINGIFY_FETCHES => false);\n\t/**\n\t * The quote character for stuff like column and field names.\n\t * @var string\n\t */\n\tstatic $QUOTE_CHARACTER = '`';\n\t/**\n\t * Default port.\n\t * @var int\n\t */\n\tstatic $DEFAULT_PORT = 0;\n\n\t/**\n\t * Retrieve a database connection.\n\t *\n\t * @param string $connection_string_or_connection_name A database connection string (ex. mysql://user:pass@host[:port]/dbname)\n\t *   Everything after the protocol:// part is specific to the connection adapter.\n\t *   OR\n\t *   A connection name that is set in ActiveRecord\\Config\n\t *   If null it will use the default connection specified by ActiveRecord\\Config->set_default_connection\n\t * @return Connection\n\t * @see parse_connection_url\n\t */\n\tpublic static function instance($connection_string_or_connection_name=null)\n\t{\n\t\t$config = Config::instance();\n\n\t\tif (strpos($connection_string_or_connection_name, '://') === false)\n\t\t{\n\t\t\t$connection_string = $connection_string_or_connection_name ?\n\t\t\t\t$config->get_connection($connection_string_or_connection_name) :\n\t\t\t\t$config->get_default_connection_string();\n\t\t}\n\t\telse\n\t\t\t$connection_string = $connection_string_or_connection_name;\n\n\t\tif (!$connection_string)\n\t\t\tthrow new DatabaseException(\"Empty connection string\");\n\n\t\t$info = static::parse_connection_url($connection_string);\n\t\t$fqclass = static::load_adapter_class($info->protocol);\n\n\t\ttry {\n\t\t\t$connection = new $fqclass($info);\n\t\t\t$connection->protocol = $info->protocol;\n\t\t\t$connection->logging = $config->get_logging();\n\t\t\t$connection->logger = $connection->logging ? $config->get_logger() : null;\n\n\t\t\tif (isset($info->charset))\n\t\t\t\t$connection->set_encoding($info->charset);\n\t\t} catch (PDOException $e) {\n\t\t\tthrow new DatabaseException($e);\n\t\t}\n\t\treturn $connection;\n\t}\n\n\t/**\n\t * Loads the specified class for an adapter.\n\t *\n\t * @param string $adapter Name of the adapter.\n\t * @return string The full name of the class including namespace.\n\t */\n\tprivate static function load_adapter_class($adapter)\n\t{\n\t\t$class = ucwords($adapter) . 'Adapter';\n\t\t$fqclass = 'ActiveRecord\\\\' . $class;\n\t\t$source = __DIR__ . \"/adapters/$class.php\";\n\n\t\tif (!file_exists($source))\n\t\t\tthrow new DatabaseException(\"$fqclass not found!\");\n\n\t\trequire_once($source);\n\t\treturn $fqclass;\n\t}\n\n\t/**\n\t * Use this for any adapters that can take connection info in the form below\n\t * to set the adapters connection info.\n\t *\n\t * <code>\n\t * protocol://username:password@host[:port]/dbname\n\t * protocol://urlencoded%20username:urlencoded%20password@host[:port]/dbname?decode=true\n\t * protocol://username:password@unix(/some/file/path)/dbname\n\t * </code>\n\t *\n\t * Sqlite has a special syntax, as it does not need a database name or user authentication:\n\t *\n\t * <code>\n\t * sqlite://file.db\n\t * sqlite://../relative/path/to/file.db\n\t * sqlite://unix(/absolute/path/to/file.db)\n\t * sqlite://windows(c%2A/absolute/path/to/file.db)\n\t * </code>\n\t *\n\t * @param string $connection_url A connection URL\n\t * @return object the parsed URL as an object.\n\t */\n\tpublic static function parse_connection_url($connection_url)\n\t{\n\t\t$url = @parse_url($connection_url);\n\n\t\tif (!isset($url['host']))\n\t\t\tthrow 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)');\n\n\t\t$info = new \\stdClass();\n\t\t$info->protocol = $url['scheme'];\n\t\t$info->host = $url['host'];\n\t\t$info->db = isset($url['path']) ? substr($url['path'], 1) : null;\n\t\t$info->user = isset($url['user']) ? $url['user'] : null;\n\t\t$info->pass = isset($url['pass']) ? $url['pass'] : null;\n\n\t\t$allow_blank_db = ($info->protocol == 'sqlite');\n\n\t\tif ($info->host == 'unix(')\n\t\t{\n\t\t\t$socket_database = $info->host . '/' . $info->db;\n\n\t\t\tif ($allow_blank_db)\n\t\t\t\t$unix_regex = '/^unix\\((.+)\\)\\/?().*$/';\n\t\t\telse\n\t\t\t\t$unix_regex = '/^unix\\((.+)\\)\\/(.+)$/';\n\n\t\t\tif (preg_match_all($unix_regex, $socket_database, $matches) > 0)\n\t\t\t{\n\t\t\t\t$info->host = $matches[1][0];\n\t\t\t\t$info->db = $matches[2][0];\n\t\t\t}\n\t\t} elseif (substr($info->host, 0, 8) == 'windows(')\n\t\t{\n\t\t\t$info->host = urldecode(substr($info->host, 8) . '/' . substr($info->db, 0, -1));\n\t\t\t$info->db = null;\n\t\t}\n\n\t\tif ($allow_blank_db && $info->db)\n\t\t\t$info->host .= '/' . $info->db;\n\n\t\tif (isset($url['port']))\n\t\t\t$info->port = $url['port'];\n\n\t\tif (strpos($connection_url, 'decode=true') !== false)\n\t\t{\n\t\t\tif ($info->user)\n\t\t\t\t$info->user = urldecode($info->user);\n\n\t\t\tif ($info->pass)\n\t\t\t\t$info->pass = urldecode($info->pass);\n\t\t}\n\n\t\tif (isset($url['query']))\n\t\t{\n\t\t\tforeach (explode('/&/', $url['query']) as $pair) {\n\t\t\t\tlist($name, $value) = explode('=', $pair);\n\n\t\t\t\tif ($name == 'charset')\n\t\t\t\t\t$info->charset = $value;\n\t\t\t}\n\t\t}\n\n\t\treturn $info;\n\t}\n\n\t/**\n\t * Class Connection is a singleton. Access it via instance().\n\t *\n\t * @param array $info Array containing URL parts\n\t * @return Connection\n\t */\n\tprotected function __construct($info)\n\t{\n\t\ttry {\n\t\t\t// unix sockets start with a /\n\t\t\tif ($info->host[0] != '/')\n\t\t\t{\n\t\t\t\t$host = \"host=$info->host\";\n\n\t\t\t\tif (isset($info->port))\n\t\t\t\t\t$host .= \";port=$info->port\";\n\t\t\t}\n\t\t\telse\n\t\t\t\t$host = \"unix_socket=$info->host\";\n\n\t\t\t$this->connection = new PDO(\"$info->protocol:$host;dbname=$info->db\", $info->user, $info->pass, static::$PDO_OPTIONS);\n\t\t} catch (PDOException $e) {\n\t\t\tthrow new DatabaseException($e);\n\t\t}\n\t}\n\n\t/**\n\t * Retrieves column meta data for the specified table.\n\t *\n\t * @param string $table Name of a table\n\t * @return array An array of {@link Column} objects.\n\t */\n\tpublic function columns($table)\n\t{\n\t\t$columns = array();\n\t\t$sth = $this->query_column_info($table);\n\n\t\twhile (($row = $sth->fetch())) {\n\t\t\t$c = $this->create_column($row);\n\t\t\t$columns[$c->name] = $c;\n\t\t}\n\t\treturn $columns;\n\t}\n\n\t/**\n\t * Escapes quotes in a string.\n\t *\n\t * @param string $string The string to be quoted.\n\t * @return string The string with any quotes in it properly escaped.\n\t */\n\tpublic function escape($string)\n\t{\n\t\treturn $this->connection->quote($string);\n\t}\n\n\t/**\n\t * Retrieve the insert id of the last model saved.\n\t *\n\t * @param string $sequence Optional name of a sequence to use\n\t * @return int\n\t */\n\tpublic function insert_id($sequence=null)\n\t{\n\t\treturn $this->connection->lastInsertId($sequence);\n\t}\n\n\t/**\n\t * Execute a raw SQL query on the database.\n\t *\n\t * @param string $sql Raw SQL string to execute.\n\t * @param array &$values Optional array of bind values\n\t * @return mixed A result set object\n\t */\n\tpublic function query($sql, &$values=array())\n\t{\n\t\tif ($this->logging)\n\t\t{\n\t\t\t$this->logger->log($sql);\n\t\t\tif ( $values ) $this->logger->log($values);\n\t\t}\n\n\t\t$this->last_query = $sql;\n\n\t\ttry {\n\t\t\tif (!($sth = $this->connection->prepare($sql)))\n\t\t\t\tthrow new DatabaseException($this);\n\t\t} catch (PDOException $e) {\n\t\t\tthrow new DatabaseException($this);\n\t\t}\n\n\t\t$sth->setFetchMode(PDO::FETCH_ASSOC);\n\n\t\ttry {\n\t\t\tif (!$sth->execute($values))\n\t\t\t\tthrow new DatabaseException($this);\n\t\t} catch (PDOException $e) {\n\t\t\tthrow new DatabaseException($e);\n\t\t}\n\t\treturn $sth;\n\t}\n\n\t/**\n\t * Execute a query that returns maximum of one row with one field and return it.\n\t *\n\t * @param string $sql Raw SQL string to execute.\n\t * @param array &$values Optional array of values to bind to the query.\n\t * @return string\n\t */\n\tpublic function query_and_fetch_one($sql, &$values=array())\n\t{\n\t\t$sth = $this->query($sql, $values);\n\t\t$row = $sth->fetch(PDO::FETCH_NUM);\n\t\treturn $row[0];\n\t}\n\n\t/**\n\t * Execute a raw SQL query and fetch the results.\n\t *\n\t * @param string $sql Raw SQL string to execute.\n\t * @param Closure $handler Closure that will be passed the fetched results.\n\t */\n\tpublic function query_and_fetch($sql, Closure $handler)\n\t{\n\t\t$sth = $this->query($sql);\n\n\t\twhile (($row = $sth->fetch(PDO::FETCH_ASSOC)))\n\t\t\t$handler($row);\n\t}\n\n\t/**\n\t * Returns all tables for the current database.\n\t *\n\t * @return array Array containing table names.\n\t */\n\tpublic function tables()\n\t{\n\t\t$tables = array();\n\t\t$sth = $this->query_for_tables();\n\n\t\twhile (($row = $sth->fetch(PDO::FETCH_NUM)))\n\t\t\t$tables[] = $row[0];\n\n\t\treturn $tables;\n\t}\n\n\t/**\n\t * Starts a transaction.\n\t */\n\tpublic function transaction()\n\t{\n\t\tif (!$this->connection->beginTransaction())\n\t\t\tthrow new DatabaseException($this);\n\t}\n\n\t/**\n\t * Commits the current transaction.\n\t */\n\tpublic function commit()\n\t{\n\t\tif (!$this->connection->commit())\n\t\t\tthrow new DatabaseException($this);\n\t}\n\n\t/**\n\t * Rollback a transaction.\n\t */\n\tpublic function rollback()\n\t{\n\t\tif (!$this->connection->rollback())\n\t\t\tthrow new DatabaseException($this);\n\t}\n\n\t/**\n\t * Tells you if this adapter supports sequences or not.\n\t *\n\t * @return boolean\n\t */\n\tfunction supports_sequences()\n\t{\n\t\treturn false;\n\t}\n\n\t/**\n\t * Return a default sequence name for the specified table.\n\t *\n\t * @param string $table Name of a table\n\t * @param string $column_name Name of column sequence is for\n\t * @return string sequence name or null if not supported.\n\t */\n\tpublic function get_sequence_name($table, $column_name)\n\t{\n\t\treturn \"{$table}_seq\";\n\t}\n\n\t/**\n\t * Return SQL for getting the next value in a sequence.\n\t *\n\t * @param string $sequence_name Name of the sequence\n\t * @return string\n\t */\n\tpublic function next_sequence_value($sequence_name)\n\t{\n\t\treturn null;\n\t}\n\n\t/**\n\t * Quote a name like table names and field names.\n\t *\n\t * @param string $string String to quote.\n\t * @return string\n\t */\n\tpublic function quote_name($string)\n\t{\n\t\treturn $string[0] === static::$QUOTE_CHARACTER || $string[strlen($string) - 1] === static::$QUOTE_CHARACTER ?\n\t\t\t$string : static::$QUOTE_CHARACTER . $string . static::$QUOTE_CHARACTER;\n\t}\n\n\t/**\n\t * Return a date time formatted into the database's date format.\n\t *\n\t * @param DateTime $datetime The DateTime object\n\t * @return string\n\t */\n\tpublic function date_to_string($datetime)\n\t{\n\t\treturn $datetime->format(static::$date_format);\n\t}\n\n\t/**\n\t * Return a date time formatted into the database's datetime format.\n\t *\n\t * @param DateTime $datetime The DateTime object\n\t * @return string\n\t */\n\tpublic function datetime_to_string($datetime)\n\t{\n\t\treturn $datetime->format(static::$datetime_format);\n\t}\n\n\t/**\n\t * Converts a string representation of a datetime into a DateTime object.\n\t *\n\t * @param string $string A datetime in the form accepted by date_create()\n\t * @return object The date_class set in Config\n\t */\n\tpublic function string_to_datetime($string)\n\t{\n\t\t$date = date_create($string);\n\t\t$errors = \\DateTime::getLastErrors();\n\n\t\tif ($errors['warning_count'] > 0 || $errors['error_count'] > 0)\n\t\t\treturn null;\n\n\t\t$date_class = Config::instance()->get_date_class();\n\n\t\treturn $date_class::createFromFormat(\n\t\t\tstatic::DATETIME_TRANSLATE_FORMAT,\n\t\t\t$date->format(static::DATETIME_TRANSLATE_FORMAT),\n\t\t\t$date->getTimezone()\n\t\t);\n\t}\n\n\t/**\n\t * Adds a limit clause to the SQL query.\n\t *\n\t * @param string $sql The SQL statement.\n\t * @param int $offset Row offset to start at.\n\t * @param int $limit Maximum number of rows to return.\n\t * @return string The SQL query that will limit results to specified parameters\n\t */\n\tabstract function limit($sql, $offset, $limit);\n\n\t/**\n\t * Query for column meta info and return statement handle.\n\t *\n\t * @param string $table Name of a table\n\t * @return PDOStatement\n\t */\n\tabstract public function query_column_info($table);\n\n\t/**\n\t * Query for all tables in the current database. The result must only\n\t * contain one column which has the name of the table.\n\t *\n\t * @return PDOStatement\n\t */\n\tabstract function query_for_tables();\n\n\t/**\n\t * Executes query to specify the character set for this connection.\n\t */\n\tabstract function set_encoding($charset);\n\n\t/*\n\t * Returns an array mapping of native database types\n\t */\n\n\tabstract public function native_database_types();\n\n\t/**\n\t * Specifies whether or not adapter can use LIMIT/ORDER clauses with DELETE & UPDATE operations\n\t *\n\t * @internal\n\t * @returns boolean (FALSE by default)\n\t */\n\tpublic function accepts_limit_and_order_for_update_and_delete()\n\t{\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "lib/ConnectionManager.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Singleton to manage any and all database connections.\n *\n * @package ActiveRecord\n */\nclass ConnectionManager extends Singleton\n{\n\t/**\n\t * Array of {@link Connection} objects.\n\t * @var array\n\t */\n\tstatic private $connections = array();\n\n\t/**\n\t * If $name is null then the default connection will be returned.\n\t *\n\t * @see Config\n\t * @param string $name Optional name of a connection\n\t * @return Connection\n\t */\n\tpublic static function get_connection($name=null)\n\t{\n\t\t$config = Config::instance();\n\t\t$name = $name ? $name : $config->get_default_connection();\n\n\t\tif (!isset(self::$connections[$name]) || !self::$connections[$name]->connection)\n\t\t\tself::$connections[$name] = Connection::instance($config->get_connection($name));\n\n\t\treturn self::$connections[$name];\n\t}\n\n\t/**\n\t * Drops the connection from the connection manager. Does not actually close it since there\n\t * is no close method in PDO.\n\t *\n\t * If $name is null then the default connection will be returned.\n\t *\n\t * @param string $name Name of the connection to forget about\n\t */\n\tpublic static function drop_connection($name=null)\n\t{\n\t\t$config = Config::instance();\n\t\t$name = $name ? $name : $config->get_default_connection();\n\n\t\tif (isset(self::$connections[$name]))\n\t\t\tunset(self::$connections[$name]);\n\t}\n}"
  },
  {
    "path": "lib/DateTime.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * An extension of PHP's DateTime class to provide dirty flagging and easier formatting options.\n *\n * All date and datetime fields from your database will be created as instances of this class.\n *\n * Example of formatting and changing the default format:\n *\n * <code>\n * $now = new ActiveRecord\\DateTime('2010-01-02 03:04:05');\n * ActiveRecord\\DateTime::$DEFAULT_FORMAT = 'short';\n *\n * echo $now->format();         # 02 Jan 03:04\n * echo $now->format('atom');   # 2010-01-02T03:04:05-05:00\n * echo $now->format('Y-m-d');  # 2010-01-02\n *\n * # __toString() uses the default formatter\n * echo (string)$now;           # 02 Jan 03:04\n * </code>\n *\n * You can also add your own pre-defined friendly formatters:\n *\n * <code>\n * ActiveRecord\\DateTime::$FORMATS['awesome_format'] = 'H:i:s m/d/Y';\n * echo $now->format('awesome_format')  # 03:04:05 01/02/2010\n * </code>\n *\n * @package ActiveRecord\n * @see http://php.net/manual/en/class.datetime.php\n */\nclass DateTime extends \\DateTime implements DateTimeInterface\n{\n\t/**\n\t * Default format used for format() and __toString()\n\t */\n\tpublic static $DEFAULT_FORMAT = 'rfc2822';\n\n\t/**\n\t * Pre-defined format strings.\n\t */\n\tpublic static $FORMATS = array(\n\t\t'db'      => 'Y-m-d H:i:s',\n\t\t'number'  => 'YmdHis',\n\t\t'time'    => 'H:i',\n\t\t'short'   => 'd M H:i',\n\t\t'long'    => 'F d, Y H:i',\n\t\t'atom'    => \\DateTime::ATOM,\n\t\t'cookie'  => \\DateTime::COOKIE,\n\t\t'iso8601' => \\DateTime::ISO8601,\n\t\t'rfc822'  => \\DateTime::RFC822,\n\t\t'rfc850'  => \\DateTime::RFC850,\n\t\t'rfc1036' => \\DateTime::RFC1036,\n\t\t'rfc1123' => \\DateTime::RFC1123,\n\t\t'rfc2822' => \\DateTime::RFC2822,\n\t\t'rfc3339' => \\DateTime::RFC3339,\n\t\t'rss'     => \\DateTime::RSS,\n\t\t'w3c'     => \\DateTime::W3C);\n\n\tprivate $model;\n\tprivate $attribute_name;\n\n\tpublic function attribute_of($model, $attribute_name)\n\t{\n\t\t$this->model = $model;\n\t\t$this->attribute_name = $attribute_name;\n\t}\n\n\t/**\n\t * Formats the DateTime to the specified format.\n\t *\n\t * <code>\n\t * $datetime->format();         # uses the format defined in DateTime::$DEFAULT_FORMAT\n\t * $datetime->format('short');  # d M H:i\n\t * $datetime->format('Y-m-d');  # Y-m-d\n\t * </code>\n\t *\n\t * @see FORMATS\n\t * @see get_format\n\t * @param string $format A format string accepted by get_format()\n\t * @return string formatted date and time string\n\t */\n\tpublic function format($format=null)\n\t{\n\t\treturn parent::format(self::get_format($format));\n\t}\n\n\t/**\n\t * Returns the format string.\n\t *\n\t * If $format is a pre-defined format in $FORMATS it will return that otherwise\n\t * it will assume $format is a format string itself.\n\t *\n\t * @see FORMATS\n\t * @param string $format A pre-defined string format or a raw format string\n\t * @return string a format string\n\t */\n\tpublic static function get_format($format=null)\n\t{\n\t\t// use default format if no format specified\n\t\tif (!$format)\n\t\t\t$format = self::$DEFAULT_FORMAT;\n\n\t\t// format is a friendly\n\t\tif (array_key_exists($format, self::$FORMATS))\n\t\t\t return self::$FORMATS[$format];\n\n\t\t// raw format\n\t\treturn $format;\n\t}\n\n\t/**\n\t * This needs to be overriden so it returns an instance of this class instead of PHP's \\DateTime.\n\t * See http://php.net/manual/en/datetime.createfromformat.php\n\t */\n\tpublic static function createFromFormat($format, $time, $tz = null)\n\t{\n\t\t$phpDate = $tz ? parent::createFromFormat($format, $time, $tz) : parent::createFromFormat($format, $time);\n\t\tif (!$phpDate)\n\t\t\treturn false;\n\t\t// convert to this class using the timestamp\n\t\t$ourDate = new static(null, $phpDate->getTimezone());\n\t\t$ourDate->setTimestamp($phpDate->getTimestamp());\n\t\treturn $ourDate;\n\t}\n\n\tpublic function __toString()\n\t{\n\t\treturn $this->format();\n\t}\n\n\t/**\n\t * Handle PHP object `clone`.\n\t *\n\t * This makes sure that the object doesn't still flag an attached model as\n\t * dirty after cloning the DateTime object and making modifications to it.\n\t *\n\t * @return void\n\t */\n\tpublic function __clone()\n\t{\n\t\t$this->model = null;\n\t\t$this->attribute_name = null;\n\t}\n\n\tprivate function flag_dirty()\n\t{\n\t\tif ($this->model)\n\t\t\t$this->model->flag_dirty($this->attribute_name);\n\t}\n\n\tpublic function setDate($year, $month, $day)\n\t{\n\t\t$this->flag_dirty();\n\t\treturn parent::setDate($year, $month, $day);\n\t}\n\n\tpublic function setISODate($year, $week , $day = 1)\n\t{\n\t\t$this->flag_dirty();\n\t\treturn parent::setISODate($year, $week, $day);\n\t}\n\n\tpublic function setTime($hour, $minute, $second = 0, $microseconds = 0)\n\t{\n\t\t$this->flag_dirty();\n\t\treturn parent::setTime($hour, $minute, $second);\n\t}\n\n\tpublic function setTimestamp($unixtimestamp)\n\t{\n\t\t$this->flag_dirty();\n\t\treturn parent::setTimestamp($unixtimestamp);\n\t}\n\n\tpublic function setTimezone($timezone)\n\t{\n\t\t$this->flag_dirty();\n\t\treturn parent::setTimezone($timezone);\n\t}\n\t\n\tpublic function modify($modify)\n\t{\n\t\t$this->flag_dirty();\n\t\treturn parent::modify($modify);\n\t}\n\t\n\tpublic function add($interval)\n\t{\n\t\t$this->flag_dirty();\n\t\treturn parent::add($interval);\n\t}\n\n\tpublic function sub($interval)\n\t{\n\t\t$this->flag_dirty();\n\t\treturn parent::sub($interval);\n\t}\n\n}\n"
  },
  {
    "path": "lib/DateTimeInterface.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Interface for the ActiveRecord\\DateTime class so that ActiveRecord\\Model->assign_attribute() will\n * know to call attribute_of() on passed values. This is so the DateTime object can flag the model\n * as dirty via $model->flag_dirty() when one of its setters is called.\n *\n * @package ActiveRecord\n * @see http://php.net/manual/en/class.datetime.php\n */\ninterface DateTimeInterface\n{\n\t/**\n\t * Indicates this object is an attribute of the specified model, with the given attribute name.\n\t *\n\t * @param Model $model The model this object is an attribute of\n\t * @param string $attribute_name The attribute name \n\t * @return void\n\t */\n\tpublic function attribute_of($model, $attribute_name);\n\n\t/**\n\t * Formats the DateTime to the specified format.\n\t */\n\tpublic function format($format=null);\n\n\t/**\n\t * See http://php.net/manual/en/datetime.createfromformat.php\n\t */\n\tpublic static function createFromFormat($format, $time, $tz = null);\n}\n"
  },
  {
    "path": "lib/Exceptions.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Generic base exception for all ActiveRecord specific errors.\n *\n * @package ActiveRecord\n */\nclass ActiveRecordException extends \\Exception {}\n\n/**\n * Thrown when a record cannot be found.\n *\n * @package ActiveRecord\n */\nclass RecordNotFound extends ActiveRecordException {}\n\n/**\n * Thrown when there was an error performing a database operation.\n *\n * The error will be specific to whatever database you are running.\n *\n * @package ActiveRecord\n */\nclass DatabaseException extends ActiveRecordException\n{\n\tpublic function __construct($adapter_or_string_or_mystery)\n\t{\n\t\tif ($adapter_or_string_or_mystery instanceof Connection)\n\t\t{\n\t\t\tparent::__construct(\n\t\t\t\tjoin(\", \",$adapter_or_string_or_mystery->connection->errorInfo()),\n\t\t\t\tintval($adapter_or_string_or_mystery->connection->errorCode()));\n\t\t}\n\t\telseif ($adapter_or_string_or_mystery instanceof \\PDOStatement)\n\t\t{\n\t\t\tparent::__construct(\n\t\t\t\tjoin(\", \",$adapter_or_string_or_mystery->errorInfo()),\n\t\t\t\tintval($adapter_or_string_or_mystery->errorCode()));\n\t\t}\n\t\telse\n\t\t\tparent::__construct($adapter_or_string_or_mystery);\n\t}\n}\n\n/**\n * Thrown by {@link Model}.\n *\n * @package ActiveRecord\n */\nclass ModelException extends ActiveRecordException {}\n\n/**\n * Thrown by {@link Expressions}.\n *\n * @package ActiveRecord\n */\nclass ExpressionsException extends ActiveRecordException {}\n\n/**\n * Thrown for configuration problems.\n *\n * @package ActiveRecord\n */\nclass ConfigException extends ActiveRecordException {}\n\n/**\n * Thrown for cache problems.\n *\n * @package ActiveRecord\n */\nclass CacheException extends ActiveRecordException {}\n\n/**\n * Thrown when attempting to access an invalid property on a {@link Model}.\n *\n * @package ActiveRecord\n */\nclass UndefinedPropertyException extends ModelException\n{\n\t/**\n\t * Sets the exception message to show the undefined property's name.\n\t *\n\t * @param str $property_name name of undefined property\n\t * @return void\n\t */\n\tpublic function __construct($class_name, $property_name)\n\t{\n\t\tif (is_array($property_name))\n\t\t{\n\t\t\t$this->message = implode(\"\\r\\n\", $property_name);\n\t\t\treturn;\n\t\t}\n\n\t\t$this->message = \"Undefined property: {$class_name}->{$property_name} in {$this->file} on line {$this->line}\";\n\t\tparent::__construct();\n\t}\n}\n\n/**\n * Thrown when attempting to perform a write operation on a {@link Model} that is in read-only mode.\n *\n * @package ActiveRecord\n */\nclass ReadOnlyException extends ModelException\n{\n\t/**\n\t * Sets the exception message to show the undefined property's name.\n\t *\n\t * @param str $class_name name of the model that is read only\n\t * @param str $method_name name of method which attempted to modify the model\n\t * @return void\n\t */\n\tpublic function __construct($class_name, $method_name)\n\t{\n\t\t$this->message = \"{$class_name}::{$method_name}() cannot be invoked because this model is set to read only\";\n\t\tparent::__construct();\n\t}\n}\n\n/**\n * Thrown for validations exceptions.\n *\n * @package ActiveRecord\n */\nclass ValidationsArgumentError extends ActiveRecordException {}\n\n/**\n * Thrown for relationship exceptions.\n *\n * @package ActiveRecord\n */\nclass RelationshipException extends ActiveRecordException {}\n\n/**\n * Thrown for has many thru exceptions.\n *\n * @package ActiveRecord\n */\nclass HasManyThroughAssociationException extends RelationshipException {}\n"
  },
  {
    "path": "lib/Expressions.php",
    "content": "<?php\r\n/**\r\n * @package ActiveRecord\r\n */\r\nnamespace ActiveRecord;\r\n\r\n/**\r\n * Templating like class for building SQL statements.\r\n *\r\n * Examples:\r\n * 'name = :name AND author = :author'\r\n * 'id = IN(:ids)'\r\n * 'id IN(:subselect)'\r\n * \r\n * @package ActiveRecord\r\n */\r\nclass Expressions\r\n{\r\n\tconst ParameterMarker = '?';\r\n\r\n\tprivate $expressions;\r\n\tprivate $values = array();\r\n\tprivate $connection;\r\n\r\n\tpublic function __construct($connection, $expressions=null /* [, $values ... ] */)\r\n\t{\r\n\t\t$values = null;\r\n\t\t$this->connection = $connection;\r\n\r\n\t\tif (is_array($expressions))\r\n\t\t{\r\n\t\t\t$glue = func_num_args() > 2 ? func_get_arg(2) : ' AND ';\r\n\t\t\tlist($expressions,$values) = $this->build_sql_from_hash($expressions,$glue);\r\n\t\t}\r\n\r\n\t\tif ($expressions != '')\r\n\t\t{\r\n\t\t\tif (!$values)\r\n\t\t\t\t$values = array_slice(func_get_args(),2);\r\n\r\n\t\t\t$this->values = $values;\r\n\t\t\t$this->expressions = $expressions;\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Bind a value to the specific one based index. There must be a bind marker\r\n\t * for each value bound or to_s() will throw an exception.\r\n\t */\r\n\tpublic function bind($parameter_number, $value)\r\n\t{\r\n\t\tif ($parameter_number <= 0)\r\n\t\t\tthrow new ExpressionsException(\"Invalid parameter index: $parameter_number\");\r\n\r\n\t\t$this->values[$parameter_number-1] = $value;\r\n\t}\r\n\r\n\tpublic function bind_values($values)\r\n\t{\r\n\t\t$this->values = $values;\r\n\t}\r\n\r\n\t/**\r\n\t * Returns all the values currently bound.\r\n\t */\r\n\tpublic function values()\r\n\t{\r\n\t\treturn $this->values;\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the connection object.\r\n\t */\r\n\tpublic function get_connection()\r\n\t{\r\n\t\treturn $this->connection;\r\n\t}\r\n\r\n\t/**\r\n\t * Sets the connection object. It is highly recommended to set this so we can\r\n\t * use the adapter's native escaping mechanism.\r\n\t *\r\n\t * @param string $connection a Connection instance\r\n\t */\r\n\tpublic function set_connection($connection)\r\n\t{\r\n\t\t$this->connection = $connection;\r\n\t}\r\n\r\n\tpublic function to_s($substitute=false, &$options=null)\r\n\t{\r\n\t\tif (!$options) $options = array();\r\n\t\t\r\n\t\t$values = array_key_exists('values',$options) ? $options['values'] : $this->values;\r\n\r\n\t\t$ret = \"\";\r\n\t\t$replace = array();\r\n\t\t$num_values = count($values);\r\n\t\t$len = strlen($this->expressions);\r\n\t\t$quotes = 0;\r\n\r\n\t\tfor ($i=0,$n=strlen($this->expressions),$j=0; $i<$n; ++$i)\r\n\t\t{\r\n\t\t\t$ch = $this->expressions[$i];\r\n\r\n\t\t\tif ($ch == self::ParameterMarker)\r\n\t\t\t{\r\n\t\t\t\tif ($quotes % 2 == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tif ($j > $num_values-1)\r\n\t\t\t\t\t\tthrow new ExpressionsException(\"No bound parameter for index $j\");\r\n\r\n\t\t\t\t\t$ch = $this->substitute($values,$substitute,$i,$j++);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telseif ($ch == '\\'' && $i > 0 && $this->expressions[$i-1] != '\\\\')\r\n\t\t\t\t++$quotes;\r\n\r\n\t\t\t$ret .= $ch;\r\n\t\t}\r\n\t\treturn $ret;\r\n\t}\r\n\r\n\tprivate function build_sql_from_hash(&$hash, $glue)\r\n\t{\r\n\t\t$sql = $g = \"\";\r\n\r\n\t\tforeach ($hash as $name => $value)\r\n\t\t{\r\n\t\t\tif ($this->connection)\r\n\t\t\t\t$name = $this->connection->quote_name($name);\r\n\r\n\t\t\tif (is_array($value))\r\n\t\t\t\t$sql .= \"$g$name IN(?)\";\r\n\t\t\telseif (is_null($value))\r\n\t\t\t\t$sql .= \"$g$name IS ?\";\r\n\t\t\telse\r\n\t\t\t\t$sql .= \"$g$name=?\";\r\n\r\n\t\t\t$g = $glue;\r\n\t\t}\r\n\t\treturn array($sql,array_values($hash));\r\n\t}\r\n\r\n\tprivate function substitute(&$values, $substitute, $pos, $parameter_index)\r\n\t{\r\n\t\t$value = $values[$parameter_index];\r\n\r\n\t\tif (is_array($value))\r\n\t\t{\r\n\t\t\t$value_count = count($value);\r\n\r\n\t\t\tif ($value_count === 0)\r\n\t\t\t\tif ($substitute)\r\n\t\t\t\t\treturn 'NULL';\r\n\t\t\t\telse\r\n\t\t\t\t\treturn self::ParameterMarker;\r\n\r\n\t\t\tif ($substitute)\r\n\t\t\t{\r\n\t\t\t\t$ret = '';\r\n\r\n\t\t\t\tfor ($i=0, $n=$value_count; $i<$n; ++$i)\r\n\t\t\t\t\t$ret .= ($i > 0 ? ',' : '') . $this->stringify_value($value[$i]);\r\n\r\n\t\t\t\treturn $ret;\r\n\t\t\t}\r\n\t\t\treturn join(',',array_fill(0,$value_count,self::ParameterMarker));\r\n\t\t}\r\n\r\n\t\tif ($substitute)\r\n\t\t\treturn $this->stringify_value($value);\r\n\r\n\t\treturn $this->expressions[$pos];\r\n\t}\r\n\r\n\tprivate function stringify_value($value)\r\n\t{\r\n\t\tif (is_null($value))\r\n\t\t\treturn \"NULL\";\r\n\r\n\t\treturn is_string($value) ? $this->quote_string($value) : $value;\r\n\t}\r\n\r\n\tprivate function quote_string($value)\r\n\t{\r\n\t\tif ($this->connection)\r\n\t\t\treturn $this->connection->escape($value);\r\n\r\n\t\treturn \"'\" . str_replace(\"'\",\"''\",$value) . \"'\";\r\n\t}\r\n}"
  },
  {
    "path": "lib/Inflector.php",
    "content": "<?php\r\n/**\r\n * @package ActiveRecord\r\n */\r\nnamespace ActiveRecord;\r\n\r\n/**\r\n * @package ActiveRecord\r\n */\r\nabstract class Inflector\r\n{\r\n\t/**\r\n\t * Get an instance of the {@link Inflector} class.\r\n\t *\r\n\t * @return object\r\n\t */\r\n\tpublic static function instance()\r\n\t{\r\n\t\treturn new StandardInflector();\r\n\t}\r\n\r\n\t/**\r\n\t * Turn a string into its camelized version.\r\n\t *\r\n\t * @param string $s string to convert\r\n\t * @return string\r\n\t */\r\n\tpublic function camelize($s)\r\n\t{\r\n\t\t$s = preg_replace('/[_-]+/','_',trim($s));\r\n\t\t$s = str_replace(' ', '_', $s);\r\n\r\n\t\t$camelized = '';\r\n\r\n\t\tfor ($i=0,$n=strlen($s); $i<$n; ++$i)\r\n\t\t{\r\n\t\t\tif ($s[$i] == '_' && $i+1 < $n)\r\n\t\t\t\t$camelized .= strtoupper($s[++$i]);\r\n\t\t\telse\r\n\t\t\t\t$camelized .= $s[$i];\r\n\t\t}\r\n\r\n\t\t$camelized = trim($camelized,' _');\r\n\r\n\t\tif (strlen($camelized) > 0)\r\n\t\t\t$camelized[0] = strtolower($camelized[0]);\r\n\r\n\t\treturn $camelized;\r\n\t}\r\n\r\n\t/**\r\n\t * Determines if a string contains all uppercase characters.\r\n\t *\r\n\t * @param string $s string to check\r\n\t * @return bool\r\n\t */\r\n\tpublic static function is_upper($s)\r\n\t{\r\n\t\treturn (strtoupper($s) === $s);\r\n\t}\r\n\r\n\t/**\r\n\t * Determines if a string contains all lowercase characters.\r\n\t *\r\n\t * @param string $s string to check\r\n\t * @return bool\r\n\t */\r\n\tpublic static function is_lower($s)\r\n\t{\r\n\t\treturn (strtolower($s) === $s);\r\n\t}\r\n\r\n\t/**\r\n\t * Convert a camelized string to a lowercase, underscored string.\r\n\t *\r\n\t * @param string $s string to convert\r\n\t * @return string\r\n\t */\r\n\tpublic function uncamelize($s)\r\n\t{\r\n\t\t$normalized = '';\r\n\r\n\t\tfor ($i=0,$n=strlen($s); $i<$n; ++$i)\r\n\t\t{\r\n\t\t\tif (ctype_alpha($s[$i]) && self::is_upper($s[$i]))\r\n\t\t\t\t$normalized .= '_' . strtolower($s[$i]);\r\n\t\t\telse\r\n\t\t\t\t$normalized .= $s[$i];\r\n\t\t}\r\n\t\treturn trim($normalized,' _');\r\n\t}\r\n\r\n\t/**\r\n\t * Convert a string with space into a underscored equivalent.\r\n\t *\r\n\t * @param string $s string to convert\r\n\t * @return string\r\n\t */\r\n\tpublic function underscorify($s)\r\n\t{\r\n\t\treturn preg_replace(array('/[_\\- ]+/','/([a-z])([A-Z])/'),array('_','\\\\1_\\\\2'),trim($s));\r\n\t}\r\n\r\n\tpublic function keyify($class_name)\r\n\t{\r\n\t\treturn strtolower($this->underscorify(denamespace($class_name))) . '_id';\r\n\t}\r\n\r\n\tabstract function variablize($s);\r\n}\r\n\r\n/**\r\n * @package ActiveRecord\r\n */\r\nclass StandardInflector extends Inflector\r\n{\r\n\tpublic function tableize($s) { return Utils::pluralize(strtolower($this->underscorify($s))); }\r\n\tpublic function variablize($s) { return str_replace(array('-',' '),array('_','_'),strtolower(trim($s))); }\r\n}"
  },
  {
    "path": "lib/Model.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * The base class for your models.\n *\n * Defining an ActiveRecord model for a table called people and orders:\n *\n * <code>\n * CREATE TABLE people(\n *   id int primary key auto_increment,\n *   parent_id int,\n *   first_name varchar(50),\n *   last_name varchar(50)\n * );\n *\n * CREATE TABLE orders(\n *   id int primary key auto_increment,\n *   person_id int not null,\n *   cost decimal(10,2),\n *   total decimal(10,2)\n * );\n * </code>\n *\n * <code>\n * class Person extends ActiveRecord\\Model {\n *   static $belongs_to = array(\n *     array('parent', 'foreign_key' => 'parent_id', 'class_name' => 'Person')\n *   );\n *\n *   static $has_many = array(\n *     array('children', 'foreign_key' => 'parent_id', 'class_name' => 'Person'),\n *     array('orders')\n *   );\n *\n *   static $validates_length_of = array(\n *     array('first_name', 'within' => array(1,50)),\n *     array('last_name', 'within' => array(1,50))\n *   );\n * }\n *\n * class Order extends ActiveRecord\\Model {\n *   static $belongs_to = array(\n *     array('person')\n *   );\n *\n *   static $validates_numericality_of = array(\n *     array('cost', 'greater_than' => 0),\n *     array('total', 'greater_than' => 0)\n *   );\n *\n *   static $before_save = array('calculate_total_with_tax');\n *\n *   public function calculate_total_with_tax() {\n *     $this->total = $this->cost * 0.045;\n *   }\n * }\n * </code>\n *\n * For a more in-depth look at defining models, relationships, callbacks and many other things\n * please consult our {@link http://www.phpactiverecord.org/guides Guides}.\n *\n * @package ActiveRecord\n * @see BelongsTo\n * @see CallBack\n * @see HasMany\n * @see HasAndBelongsToMany\n * @see Serialization\n * @see Validations\n */\nclass Model\n{\n\t/**\n\t * An instance of {@link Errors} and will be instantiated once a write method is called.\n\t *\n\t * @var Errors\n\t */\n\tpublic $errors;\n\n\t/**\n\t * Contains model values as column_name => value\n\t *\n\t * @var array\n\t */\n\tprivate $attributes = array();\n\n\t/**\n\t * 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\n\t *\n\t * @var array\n\t */\n\tprivate $__dirty = null;\n\n\t/**\n\t * Flag that determines of this model can have a writer method invoked such as: save/update/insert/delete\n\t *\n\t * @var boolean\n\t */\n\tprivate $__readonly = false;\n\n\t/**\n\t * Array of relationship objects as model_attribute_name => relationship\n\t *\n\t * @var array\n\t */\n\tprivate $__relationships = array();\n\n\t/**\n\t * Flag that determines if a call to save() should issue an insert or an update sql statement\n\t *\n\t * @var boolean\n\t */\n\tprivate $__new_record = true;\n\n\t/**\n\t * Set to the name of the connection this {@link Model} should use.\n\t *\n\t * @var string\n\t */\n\tstatic $connection;\n\n\t/**\n\t * Set to the name of the database this Model's table is in.\n\t *\n\t * @var string\n\t */\n\tstatic $db;\n\n\t/**\n\t * Set this to explicitly specify the model's table name if different from inferred name.\n\t *\n\t * If your table doesn't follow our table name convention you can set this to the\n\t * name of your table to explicitly tell ActiveRecord what your table is called.\n\t *\n\t * @var string\n\t */\n\tstatic $table_name;\n\n\t/**\n\t * Set this to override the default primary key name if different from default name of \"id\".\n\t *\n\t * @var string\n\t */\n\tstatic $primary_key;\n\n\t/**\n\t * Set this to explicitly specify the sequence name for the table.\n\t *\n\t * @var string\n\t */\n\tstatic $sequence;\n\n\t/**\n\t * Set this to true in your subclass to use caching for this model.\n\t * Note that you must also configure a cache object.\n\t */\n\tstatic $cache = false;\n\n\t/**\n\t * Set this to specify an expiration period for this model.\n\t * If not set, the expire value you set in your cache options will be used.\n\t *\n\t * @var integer\n\t */\n\tstatic $cache_expire;\n\n\t/**\n\t * Allows you to create aliases for attributes.\n\t *\n\t * <code>\n\t * class Person extends ActiveRecord\\Model {\n\t *   static $alias_attribute = array(\n\t *     'alias_first_name' => 'first_name',\n\t *     'alias_last_name' => 'last_name');\n\t * }\n\t *\n\t * $person = Person::first();\n\t * $person->alias_first_name = 'Tito';\n\t * echo $person->alias_first_name;\n\t * </code>\n\t *\n\t * @var array\n\t */\n\tstatic $alias_attribute = array();\n\n\t/**\n\t * Whitelist of attributes that are checked from mass-assignment calls such as constructing a model or using update_attributes.\n\t *\n\t * This is the opposite of {@link attr_protected $attr_protected}.\n\t *\n\t * <code>\n\t * class Person extends ActiveRecord\\Model {\n\t *   static $attr_accessible = array('first_name','last_name');\n\t * }\n\t *\n\t * $person = new Person(array(\n\t *   'first_name' => 'Tito',\n\t *   'last_name' => 'the Grief',\n\t *   'id' => 11111));\n\t *\n\t * echo $person->id; # => null\n\t * </code>\n\t *\n\t * @var array\n\t */\n\tstatic $attr_accessible = array();\n\n\t/**\n\t * Blacklist of attributes that cannot be mass-assigned.\n\t *\n\t * This is the opposite of {@link attr_accessible $attr_accessible} and the format\n\t * for defining these are exactly the same.\n\t *\n\t * If the attribute is both accessible and protected, it is treated as protected.\n\t *\n\t * @var array\n\t */\n\tstatic $attr_protected = array();\n\n\t/**\n\t * Delegates calls to a relationship.\n\t *\n\t * <code>\n\t * class Person extends ActiveRecord\\Model {\n\t *   static $belongs_to = array(array('venue'),array('host'));\n\t *   static $delegate = array(\n\t *     array('name', 'state', 'to' => 'venue'),\n\t *     array('name', 'to' => 'host', 'prefix' => 'woot'));\n\t * }\n\t * </code>\n\t *\n\t * Can then do:\n\t *\n\t * <code>\n\t * $person->state     # same as calling $person->venue->state\n\t * $person->name      # same as calling $person->venue->name\n\t * $person->woot_name # same as calling $person->host->name\n\t * </code>\n\t *\n\t * @var array\n\t */\n\tstatic $delegate = array();\n\n\t/**\n\t * Constructs a model.\n\t *\n\t * When a user instantiates a new object (e.g.: it was not ActiveRecord that instantiated via a find)\n\t * then @var $attributes will be mapped according to the schema's defaults. Otherwise, the given\n\t * $attributes will be mapped via set_attributes_via_mass_assignment.\n\t *\n\t * <code>\n\t * new Person(array('first_name' => 'Tito', 'last_name' => 'the Grief'));\n\t * </code>\n\t *\n\t * @param array $attributes Hash containing names and values to mass assign to the model\n\t * @param boolean $guard_attributes Set to true to guard protected/non-accessible attributes\n\t * @param boolean $instantiating_via_find Set to true if this model is being created from a find call\n\t * @param boolean $new_record Set to true if this should be considered a new record\n\t * @return Model\n\t */\n\tpublic function __construct(array $attributes=array(), $guard_attributes=true, $instantiating_via_find=false, $new_record=true)\n\t{\n\t\t$this->__new_record = $new_record;\n\n\t\t// initialize attributes applying defaults\n\t\tif (!$instantiating_via_find)\n\t\t{\n\t\t\tforeach (static::table()->columns as $name => $meta)\n\t\t\t\t$this->attributes[$meta->inflected_name] = $meta->default;\n\t\t}\n\n\t\t$this->set_attributes_via_mass_assignment($attributes, $guard_attributes);\n\n\t\t// since all attribute assignment now goes thru assign_attributes() we want to reset\n\t\t// dirty if instantiating via find since nothing is really dirty when doing that\n\t\tif ($instantiating_via_find)\n\t\t\t$this->__dirty = array();\n\n\t\t$this->invoke_callback('after_construct',false);\n\t}\n\n\t/**\n\t * Magic method which delegates to read_attribute(). This handles firing off getter methods,\n\t * as they are not checked/invoked inside of read_attribute(). This circumvents the problem with\n\t * a getter being accessed with the same name as an actual attribute.\n\t *\n\t * You can also define customer getter methods for the model.\n\t *\n\t * EXAMPLE:\n\t * <code>\n\t * class User extends ActiveRecord\\Model {\n\t *\n\t *   # define custom getter methods. Note you must\n\t *   # prepend get_ to your method name:\n\t *   function get_middle_initial() {\n\t *     return $this->middle_name{0};\n\t *   }\n\t * }\n\t *\n\t * $user = new User();\n\t * echo $user->middle_name;  # will call $user->get_middle_name()\n\t * </code>\n\t *\n\t * If you define a custom getter with the same name as an attribute then you\n\t * will need to use read_attribute() to get the attribute's value.\n\t * This is necessary due to the way __get() works.\n\t *\n\t * For example, assume 'name' is a field on the table and we're defining a\n\t * custom getter for 'name':\n\t *\n\t * <code>\n\t * class User extends ActiveRecord\\Model {\n\t *\n\t *   # INCORRECT way to do it\n\t *   # function get_name() {\n\t *   #   return strtoupper($this->name);\n\t *   # }\n\t *\n\t *   function get_name() {\n\t *     return strtoupper($this->read_attribute('name'));\n\t *   }\n\t * }\n\t *\n\t * $user = new User();\n\t * $user->name = 'bob';\n\t * echo $user->name; # => BOB\n\t * </code>\n\t *\n\t *\n\t * @see read_attribute()\n\t * @param string $name Name of an attribute\n\t * @return mixed The value of the attribute\n\t */\n\tpublic function &__get($name)\n\t{\n\t\t// check for getter\n\t\tif (method_exists($this, \"get_$name\"))\n\t\t{\n\t\t\t$name = \"get_$name\";\n\t\t\t$value = $this->$name();\n\t\t\treturn $value;\n\t\t}\n\n\t\treturn $this->read_attribute($name);\n\t}\n\n\t/**\n\t * Determines if an attribute exists for this {@link Model}.\n\t *\n\t * @param string $attribute_name\n\t * @return boolean\n\t */\n\tpublic function __isset($attribute_name)\n\t{\n\t\treturn array_key_exists($attribute_name,$this->attributes) || array_key_exists($attribute_name,static::$alias_attribute);\n\t}\n\n\t/**\n\t * Magic allows un-defined attributes to set via $attributes.\n\t *\n\t * You can also define customer setter methods for the model.\n\t *\n\t * EXAMPLE:\n\t * <code>\n\t * class User extends ActiveRecord\\Model {\n\t *\n\t *   # define custom setter methods. Note you must\n\t *   # prepend set_ to your method name:\n\t *   function set_password($plaintext) {\n\t *     $this->encrypted_password = md5($plaintext);\n\t *   }\n\t * }\n\t *\n\t * $user = new User();\n\t * $user->password = 'plaintext';  # will call $user->set_password('plaintext')\n\t * </code>\n\t *\n\t * If you define a custom setter with the same name as an attribute then you\n\t * will need to use assign_attribute() to assign the value to the attribute.\n\t * This is necessary due to the way __set() works.\n\t *\n\t * For example, assume 'name' is a field on the table and we're defining a\n\t * custom setter for 'name':\n\t *\n\t * <code>\n\t * class User extends ActiveRecord\\Model {\n\t *\n\t *   # INCORRECT way to do it\n\t *   # function set_name($name) {\n\t *   #   $this->name = strtoupper($name);\n\t *   # }\n\t *\n\t *   function set_name($name) {\n\t *     $this->assign_attribute('name',strtoupper($name));\n\t *   }\n\t * }\n\t *\n\t * $user = new User();\n\t * $user->name = 'bob';\n\t * echo $user->name; # => BOB\n\t * </code>\n\t *\n\t * @throws {@link UndefinedPropertyException} if $name does not exist\n\t * @param string $name Name of attribute, relationship or other to set\n\t * @param mixed $value The value\n\t * @return mixed The value\n\t */\n\tpublic function __set($name, $value)\n\t{\n\t\tif (array_key_exists($name, static::$alias_attribute))\n\t\t\t$name = static::$alias_attribute[$name];\n\n\t\telseif (method_exists($this,\"set_$name\"))\n\t\t{\n\t\t\t$name = \"set_$name\";\n\t\t\treturn $this->$name($value);\n\t\t}\n\n\t\tif (array_key_exists($name,$this->attributes))\n\t\t\treturn $this->assign_attribute($name,$value);\n\n\t\tif ($name == 'id')\n\t\t\treturn $this->assign_attribute($this->get_primary_key(true),$value);\n\n\t\tforeach (static::$delegate as &$item)\n\t\t{\n\t\t\tif (($delegated_name = $this->is_delegated($name,$item)))\n\t\t\t\treturn $this->{$item['to']}->{$delegated_name} = $value;\n\t\t}\n\n\t\tthrow new UndefinedPropertyException(get_called_class(),$name);\n\t}\n\n\tpublic function __wakeup()\n\t{\n\t\t// make sure the models Table instance gets initialized when waking up\n\t\tstatic::table();\n\t}\n\n\t/**\n\t * Assign a value to an attribute.\n\t *\n\t * @param string $name Name of the attribute\n\t * @param mixed &$value Value of the attribute\n\t * @return mixed the attribute value\n\t */\n\tpublic function assign_attribute($name, $value)\n\t{\n\t\t$table = static::table();\n\t\tif (!is_object($value)) {\n\t\t\tif (array_key_exists($name, $table->columns)) {\n\t\t\t\t$value = $table->columns[$name]->cast($value, static::connection());\n\t\t\t} else {\n\t\t\t\t$col = $table->get_column_by_inflected_name($name);\n\t\t\t\tif (!is_null($col)){\n\t\t\t\t\t$value = $col->cast($value, static::connection());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// convert php's \\DateTime to ours\n\t\tif ($value instanceof \\DateTime) {\n\t\t\t$date_class = Config::instance()->get_date_class();\n\t\t\tif (!($value instanceof $date_class))\n\t\t\t\t$value = $date_class::createFromFormat(\n\t\t\t\t\tConnection::DATETIME_TRANSLATE_FORMAT,\n\t\t\t\t\t$value->format(Connection::DATETIME_TRANSLATE_FORMAT),\n\t\t\t\t\t$value->getTimezone()\n\t\t\t\t);\n\t\t}\n\n\t\tif ($value instanceof DateTimeInterface)\n\t\t\t// Tell the Date object that it's associated with this model and attribute. This is so it\n\t\t\t// has the ability to flag this model as dirty if a field in the Date object changes.\n\t\t\t$value->attribute_of($this,$name);\n\n\t\t$this->attributes[$name] = $value;\n\t\t$this->flag_dirty($name);\n\t\treturn $value;\n\t}\n\n\t/**\n\t * Retrieves an attribute's value or a relationship object based on the name passed. If the attribute\n\t * accessed is 'id' then it will return the model's primary key no matter what the actual attribute name is\n\t * for the primary key.\n\t *\n\t * @param string $name Name of an attribute\n\t * @return mixed The value of the attribute\n\t * @throws {@link UndefinedPropertyException} if name could not be resolved to an attribute, relationship, ...\n\t */\n\tpublic function &read_attribute($name)\n\t{\n\t\t// check for aliased attribute\n\t\tif (array_key_exists($name, static::$alias_attribute))\n\t\t\t$name = static::$alias_attribute[$name];\n\n\t\t// check for attribute\n\t\tif (array_key_exists($name,$this->attributes))\n\t\t\treturn $this->attributes[$name];\n\n\t\t// check relationships if no attribute\n\t\tif (array_key_exists($name,$this->__relationships))\n\t\t\treturn $this->__relationships[$name];\n\n\t\t$table = static::table();\n\n\t\t// this may be first access to the relationship so check Table\n\t\tif (($relationship = $table->get_relationship($name)))\n\t\t{\n\t\t\t$this->__relationships[$name] = $relationship->load($this);\n\t\t\treturn $this->__relationships[$name];\n\t\t}\n\n\t\tif ($name == 'id')\n\t\t{\n\t\t\t$pk = $this->get_primary_key(true);\n\t\t\tif (isset($this->attributes[$pk]))\n\t\t\t\treturn $this->attributes[$pk];\n\t\t}\n\n\t\t//do not remove - have to return null by reference in strict mode\n\t\t$null = null;\n\n\t\tforeach (static::$delegate as &$item)\n\t\t{\n\t\t\tif (($delegated_name = $this->is_delegated($name,$item)))\n\t\t\t{\n\t\t\t\t$to = $item['to'];\n\t\t\t\tif ($this->$to)\n\t\t\t\t{\n\t\t\t\t\t$val =& $this->$to->__get($delegated_name);\n\t\t\t\t\treturn $val;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\treturn $null;\n\t\t\t}\n\t\t}\n\n\t\tthrow new UndefinedPropertyException(get_called_class(),$name);\n\t}\n\n\t/**\n\t * Flags an attribute as dirty.\n\t *\n\t * @param string $name Attribute name\n\t */\n\tpublic function flag_dirty($name)\n\t{\n\t\tif (!$this->__dirty)\n\t\t\t$this->__dirty = array();\n\n\t\t$this->__dirty[$name] = true;\n\t}\n\n\t/**\n\t * Returns hash of attributes that have been modified since loading the model.\n\t *\n\t * @return mixed null if no dirty attributes otherwise returns array of dirty attributes.\n\t */\n\tpublic function dirty_attributes()\n\t{\n\t\tif (!$this->__dirty)\n\t\t\treturn null;\n\n\t\t$dirty = array_intersect_key($this->attributes,$this->__dirty);\n\t\treturn !empty($dirty) ? $dirty : null;\n\t}\n\n\t/**\n\t * Check if a particular attribute has been modified since loading the model.\n\t * @param string $attribute\tName of the attribute\n\t * @return boolean TRUE if it has been modified.\n\t */\n\tpublic function attribute_is_dirty($attribute)\n\t{\n\t\treturn $this->__dirty && isset($this->__dirty[$attribute]) && array_key_exists($attribute, $this->attributes);\n\t}\n\n\t/**\n\t * Returns a copy of the model's attributes hash.\n\t *\n\t * @return array A copy of the model's attribute data\n\t */\n\tpublic function attributes()\n\t{\n\t\treturn $this->attributes;\n\t}\n\n\t/**\n\t * Retrieve the primary key name.\n\t *\n\t * @param boolean Set to true to return the first value in the pk array only\n\t * @return string The primary key for the model\n\t */\n\tpublic function get_primary_key($first=false)\n\t{\n\t\t$pk = static::table()->pk;\n\t\treturn $first ? $pk[0] : $pk;\n\t}\n\n\t/**\n\t * Returns the actual attribute name if $name is aliased.\n\t *\n\t * @param string $name An attribute name\n\t * @return string\n\t */\n\tpublic function get_real_attribute_name($name)\n\t{\n\t\tif (array_key_exists($name,$this->attributes))\n\t\t\treturn $name;\n\n\t\tif (array_key_exists($name,static::$alias_attribute))\n\t\t\treturn static::$alias_attribute[$name];\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Returns array of validator data for this Model.\n\t *\n\t * Will return an array looking like:\n\t *\n\t * <code>\n\t * array(\n\t *   'name' => array(\n\t *     array('validator' => 'validates_presence_of'),\n\t *     array('validator' => 'validates_inclusion_of', 'in' => array('Bob','Joe','John')),\n\t *   'password' => array(\n\t *     array('validator' => 'validates_length_of', 'minimum' => 6))\n\t *   )\n\t * );\n\t * </code>\n\t *\n\t * @return array An array containing validator data for this model.\n\t */\n\tpublic function get_validation_rules()\n\t{\n\t\trequire_once 'Validations.php';\n\n\t\t$validator = new Validations($this);\n\t\treturn $validator->rules();\n\t}\n\n\t/**\n\t * Returns an associative array containing values for all the attributes in $attributes\n\t *\n\t * @param array $attributes Array containing attribute names\n\t * @return array A hash containing $name => $value\n\t */\n\tpublic function get_values_for($attributes)\n\t{\n\t\t$ret = array();\n\n\t\tforeach ($attributes as $name)\n\t\t{\n\t\t\tif (array_key_exists($name,$this->attributes))\n\t\t\t\t$ret[$name] = $this->attributes[$name];\n\t\t}\n\t\treturn $ret;\n\t}\n\n\t/**\n\t * Retrieves the name of the table for this Model.\n\t *\n\t * @return string\n\t */\n\tpublic static function table_name()\n\t{\n\t\treturn static::table()->table;\n\t}\n\n\t/**\n\t * Returns the attribute name on the delegated relationship if $name is\n\t * delegated or null if not delegated.\n\t *\n\t * @param string $name Name of an attribute\n\t * @param array $delegate An array containing delegate data\n\t * @return delegated attribute name or null\n\t */\n\tprivate function is_delegated($name, &$delegate)\n\t{\n\t\tif ($delegate['prefix'] != '')\n\t\t\t$name = substr($name,strlen($delegate['prefix'])+1);\n\n\t\tif (is_array($delegate) && in_array($name,$delegate['delegate']))\n\t\t\treturn $name;\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Determine if the model is in read-only mode.\n\t *\n\t * @return boolean\n\t */\n\tpublic function is_readonly()\n\t{\n\t\treturn $this->__readonly;\n\t}\n\n\t/**\n\t * Determine if the model is a new record.\n\t *\n\t * @return boolean\n\t */\n\tpublic function is_new_record()\n\t{\n\t\treturn $this->__new_record;\n\t}\n\n\t/**\n\t * Throws an exception if this model is set to readonly.\n\t *\n\t * @throws \\ActiveRecord\\ReadOnlyException\n\t * @param string $method_name Name of method that was invoked on model for exception message\n\t */\n\tprivate function verify_not_readonly($method_name)\n\t{\n\t\tif ($this->is_readonly())\n\t\t\tthrow new ReadOnlyException(get_class($this), $method_name);\n\t}\n\n\t/**\n\t * Flag model as readonly.\n\t *\n\t * @param boolean $readonly Set to true to put the model into readonly mode\n\t */\n\tpublic function readonly($readonly=true)\n\t{\n\t\t$this->__readonly = $readonly;\n\t}\n\n\t/**\n\t * Retrieve the connection for this model.\n\t *\n\t * @return Connection\n\t */\n\tpublic static function connection()\n\t{\n\t\treturn static::table()->conn;\n\t}\n\n\t/**\n\t * Re-establishes the database connection with a new connection.\n\t *\n\t * @return Connection\n\t */\n\tpublic static function reestablish_connection()\n\t{\n\t\treturn static::table()->reestablish_connection();\n\t}\n\n\t/**\n\t * Returns the {@link Table} object for this model.\n\t *\n\t * Be sure to call in static scoping: static::table()\n\t *\n\t * @return Table\n\t */\n\tpublic static function table()\n\t{\n\t\treturn Table::load(get_called_class());\n\t}\n\n\t/**\n\t * Creates a model and saves it to the database.\n\t *\n\t * @param array $attributes Array of the models attributes\n\t * @param boolean $validate True if the validators should be run\n\t * @param boolean $guard_attributes Set to true to guard protected/non-accessible attributes\n\t * @return Model\n\t */\n\tpublic static function create($attributes, $validate=true, $guard_attributes=true)\n\t{\n\t\t$class_name = get_called_class();\n\t\t$model = new $class_name($attributes, $guard_attributes);\n\t\t$model->save($validate);\n\t\treturn $model;\n\t}\n\n\t/**\n\t * Save the model to the database.\n\t *\n\t * This function will automatically determine if an INSERT or UPDATE needs to occur.\n\t * If a validation or a callback for this model returns false, then the model will\n\t * not be saved and this will return false.\n\t *\n\t * If saving an existing model only data that has changed will be saved.\n\t *\n\t * @param boolean $validate Set to true or false depending on if you want the validators to run or not\n\t * @return boolean True if the model was saved to the database otherwise false\n\t */\n\tpublic function save($validate=true)\n\t{\n\t\t$this->verify_not_readonly('save');\n\t\treturn $this->is_new_record() ? $this->insert($validate) : $this->update($validate);\n\t}\n\n\t/**\n\t * Issue an INSERT sql statement for this model's attribute.\n\t *\n\t * @see save\n\t * @param boolean $validate Set to true or false depending on if you want the validators to run or not\n\t * @return boolean True if the model was saved to the database otherwise false\n\t */\n\tprivate function insert($validate=true)\n\t{\n\t\t$this->verify_not_readonly('insert');\n\n\t\tif (($validate && !$this->_validate() || !$this->invoke_callback('before_create',false)))\n\t\t\treturn false;\n\n\t\t$table = static::table();\n\n\t\tif (!($attributes = $this->dirty_attributes()))\n\t\t\t$attributes = $this->attributes;\n\n\t\t$pk = $this->get_primary_key(true);\n\t\t$use_sequence = false;\n\n\t\tif ($table->sequence && !isset($attributes[$pk]))\n\t\t{\n\t\t\tif (($conn = static::connection()) instanceof OciAdapter)\n\t\t\t{\n\t\t\t\t// terrible oracle makes us select the nextval first\n\t\t\t\t$attributes[$pk] = $conn->get_next_sequence_value($table->sequence);\n\t\t\t\t$table->insert($attributes);\n\t\t\t\t$this->attributes[$pk] = $attributes[$pk];\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// unset pk that was set to null\n\t\t\t\tif (array_key_exists($pk,$attributes))\n\t\t\t\t\tunset($attributes[$pk]);\n\n\t\t\t\t$table->insert($attributes,$pk,$table->sequence);\n\t\t\t\t$use_sequence = true;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\t$table->insert($attributes);\n\n\t\t// if we've got an autoincrementing/sequenced pk set it\n\t\t// don't need this check until the day comes that we decide to support composite pks\n\t\t// if (count($pk) == 1)\n\t\t{\n\t\t\t$column = $table->get_column_by_inflected_name($pk);\n\n\t\t\tif ($column->auto_increment || $use_sequence)\n\t\t\t\t$this->attributes[$pk] = static::connection()->insert_id($table->sequence);\n\t\t}\n\n\t\t$this->__new_record = false;\n\t\t$this->invoke_callback('after_create',false);\n\t\t$this->expire_cache();\n\t\treturn true;\n\t}\n\n\t/**\n\t * Issue an UPDATE sql statement for this model's dirty attributes.\n\t *\n\t * @see save\n\t * @param boolean $validate Set to true or false depending on if you want the validators to run or not\n\t * @return boolean True if the model was saved to the database otherwise false\n\t */\n\tprivate function update($validate=true)\n\t{\n\t\t$this->verify_not_readonly('update');\n\n\t\tif ($validate && !$this->_validate())\n\t\t\treturn false;\n\n\t\tif ($this->is_dirty())\n\t\t{\n\t\t\t$pk = $this->values_for_pk();\n\n\t\t\tif (empty($pk))\n\t\t\t\tthrow new ActiveRecordException(\"Cannot update, no primary key defined for: \" . get_called_class());\n\n\t\t\tif (!$this->invoke_callback('before_update',false))\n\t\t\t\treturn false;\n\n\t\t\t$dirty = $this->dirty_attributes();\n\t\t\tstatic::table()->update($dirty,$pk);\n\t\t\t$this->invoke_callback('after_update',false);\n\t\t\t$this->expire_cache();\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tprotected function expire_cache()\n\t{\n\t\t$table = static::table();\n\t\tif($table->cache_individual_model)\n\t\t{\n\t\t\tCache::delete($this->cache_key());\n\t\t}\n\t}\n\n\tprotected function cache_key()\n\t{\n\t\t$table = static::table();\n\t\treturn $table->cache_key_for_model($this->values_for_pk());\n\t}\n\n\t/**\n\t * Deletes records matching conditions in $options\n\t *\n\t * Does not instantiate models and therefore does not invoke callbacks\n\t *\n\t * Delete all using a hash:\n\t *\n\t * <code>\n\t * YourModel::delete_all(array('conditions' => array('name' => 'Tito')));\n\t * </code>\n\t *\n\t * Delete all using an array:\n\t *\n\t * <code>\n\t * YourModel::delete_all(array('conditions' => array('name = ?', 'Tito')));\n\t * </code>\n\t *\n\t * Delete all using a string:\n\t *\n\t * <code>\n\t * YourModel::delete_all(array('conditions' => 'name = \"Tito\"'));\n\t * </code>\n\t *\n\t * An options array takes the following parameters:\n\t *\n\t * <ul>\n\t * <li><b>conditions:</b> Conditions using a string/hash/array</li>\n\t * <li><b>limit:</b> Limit number of records to delete (MySQL & Sqlite only)</li>\n\t * <li><b>order:</b> A SQL fragment for ordering such as: 'name asc', 'id desc, name asc' (MySQL & Sqlite only)</li>\n\t * </ul>\n\t *\n\t * @params array $options\n\t * return integer Number of rows affected\n\t */\n\tpublic static function delete_all($options=array())\n\t{\n\t\t$table = static::table();\n\t\t$conn = static::connection();\n\t\t$sql = new SQLBuilder($conn, $table->get_fully_qualified_table_name());\n\n\t\t$conditions = is_array($options) ? $options['conditions'] : $options;\n\n\t\tif (is_array($conditions) && !is_hash($conditions))\n\t\t\tcall_user_func_array(array($sql, 'delete'), $conditions);\n\t\telse\n\t\t\t$sql->delete($conditions);\n\n\t\tif (isset($options['limit']))\n\t\t\t$sql->limit($options['limit']);\n\n\t\tif (isset($options['order']))\n\t\t\t$sql->order($options['order']);\n\n\t\t$values = $sql->bind_values();\n\t\t$ret = $conn->query(($table->last_sql = $sql->to_s()), $values);\n\t\treturn $ret->rowCount();\n\t}\n\n\t/**\n\t * Updates records using set in $options\n\t *\n\t * Does not instantiate models and therefore does not invoke callbacks\n\t *\n\t * Update all using a hash:\n\t *\n\t * <code>\n\t * YourModel::update_all(array('set' => array('name' => \"Bob\")));\n\t * </code>\n\t *\n\t * Update all using a string:\n\t *\n\t * <code>\n\t * YourModel::update_all(array('set' => 'name = \"Bob\"'));\n\t * </code>\n\t *\n\t * An options array takes the following parameters:\n\t *\n\t * <ul>\n\t * <li><b>set:</b> String/hash of field names and their values to be updated with\n\t * <li><b>conditions:</b> Conditions using a string/hash/array</li>\n\t * <li><b>limit:</b> Limit number of records to update (MySQL & Sqlite only)</li>\n\t * <li><b>order:</b> A SQL fragment for ordering such as: 'name asc', 'id desc, name asc' (MySQL & Sqlite only)</li>\n\t * </ul>\n\t *\n\t * @params array $options\n\t * return integer Number of rows affected\n\t */\n\tpublic static function update_all($options=array())\n\t{\n\t\t$table = static::table();\n\t\t$conn = static::connection();\n\t\t$sql = new SQLBuilder($conn, $table->get_fully_qualified_table_name());\n\n\t\t$sql->update($options['set']);\n\n\t\tif (isset($options['conditions']) && ($conditions = $options['conditions']))\n\t\t{\n\t\t\tif (is_array($conditions) && !is_hash($conditions))\n\t\t\t\tcall_user_func_array(array($sql, 'where'), $conditions);\n\t\t\telse\n\t\t\t\t$sql->where($conditions);\n\t\t}\n\n\t\tif (isset($options['limit']))\n\t\t\t$sql->limit($options['limit']);\n\n\t\tif (isset($options['order']))\n\t\t\t$sql->order($options['order']);\n\n\t\t$values = $sql->bind_values();\n\t\t$ret = $conn->query(($table->last_sql = $sql->to_s()), $values);\n\t\treturn $ret->rowCount();\n\n\t}\n\n\t/**\n\t * Deletes this model from the database and returns true if successful.\n\t *\n\t * @return boolean\n\t */\n\tpublic function delete()\n\t{\n\t\t$this->verify_not_readonly('delete');\n\n\t\t$pk = $this->values_for_pk();\n\n\t\tif (empty($pk))\n\t\t\tthrow new ActiveRecordException(\"Cannot delete, no primary key defined for: \" . get_called_class());\n\n\t\tif (!$this->invoke_callback('before_destroy',false))\n\t\t\treturn false;\n\n\t\tstatic::table()->delete($pk);\n\t\t$this->invoke_callback('after_destroy',false);\n\t\t$this->expire_cache();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Helper that creates an array of values for the primary key(s).\n\t *\n\t * @return array An array in the form array(key_name => value, ...)\n\t */\n\tpublic function values_for_pk()\n\t{\n\t\treturn $this->values_for(static::table()->pk);\n\t}\n\n\t/**\n\t * Helper to return a hash of values for the specified attributes.\n\t *\n\t * @param array $attribute_names Array of attribute names\n\t * @return array An array in the form array(name => value, ...)\n\t */\n\tpublic function values_for($attribute_names)\n\t{\n\t\t$filter = array();\n\n\t\tforeach ($attribute_names as $name)\n\t\t\t$filter[$name] = $this->$name;\n\n\t\treturn $filter;\n\t}\n\n\t/**\n\t * Validates the model.\n\t *\n\t * @return boolean True if passed validators otherwise false\n\t */\n\tprivate function _validate()\n\t{\n\t\trequire_once 'Validations.php';\n\n\t\t$validator = new Validations($this);\n\t\t$validation_on = 'validation_on_' . ($this->is_new_record() ? 'create' : 'update');\n\n\t\tforeach (array('before_validation', \"before_$validation_on\") as $callback)\n\t\t{\n\t\t\tif (!$this->invoke_callback($callback,false))\n\t\t\t\treturn false;\n\t\t}\n\n\t\t// need to store reference b4 validating so that custom validators have access to add errors\n\t\t$this->errors = $validator->get_record();\n\t\t$validator->validate();\n\n\t\tforeach (array('after_validation', \"after_$validation_on\") as $callback)\n\t\t\t$this->invoke_callback($callback,false);\n\n\t\tif (!$this->errors->is_empty())\n\t\t\treturn false;\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Returns true if the model has been modified.\n\t *\n\t * @return boolean true if modified\n\t */\n\tpublic function is_dirty()\n\t{\n\t\treturn empty($this->__dirty) ? false : true;\n\t}\n\n\t/**\n\t * Run validations on model and returns whether or not model passed validation.\n\t *\n\t * @see is_invalid\n\t * @return boolean\n\t */\n\tpublic function is_valid()\n\t{\n\t\treturn $this->_validate();\n\t}\n\n\t/**\n\t * Runs validations and returns true if invalid.\n\t *\n\t * @see is_valid\n\t * @return boolean\n\t */\n\tpublic function is_invalid()\n\t{\n\t\treturn !$this->_validate();\n\t}\n\n\t/**\n\t * Updates a model's timestamps.\n\t */\n\tpublic function set_timestamps()\n\t{\n\t\t$now = date('Y-m-d H:i:s');\n\n\t\tif (isset($this->updated_at))\n\t\t\t$this->updated_at = $now;\n\n\t\tif (isset($this->created_at) && $this->is_new_record())\n\t\t\t$this->created_at = $now;\n\t}\n\n\t/**\n\t * Mass update the model with an array of attribute data and saves to the database.\n\t *\n\t * @param array $attributes An attribute data array in the form array(name => value, ...)\n\t * @return boolean True if successfully updated and saved otherwise false\n\t */\n\tpublic function update_attributes($attributes)\n\t{\n\t\t$this->set_attributes($attributes);\n\t\treturn $this->save();\n\t}\n\n\t/**\n\t * Updates a single attribute and saves the record without going through the normal validation procedure.\n\t *\n\t * @param string $name Name of attribute\n\t * @param mixed $value Value of the attribute\n\t * @return boolean True if successful otherwise false\n\t */\n\tpublic function update_attribute($name, $value)\n\t{\n\t\t$this->__set($name, $value);\n\t\treturn $this->update(false);\n\t}\n\n\t/**\n\t * Mass update the model with data from an attributes hash.\n\t *\n\t * Unlike update_attributes() this method only updates the model's data\n\t * but DOES NOT save it to the database.\n\t *\n\t * @see update_attributes\n\t * @param array $attributes An array containing data to update in the form array(name => value, ...)\n\t */\n\tpublic function set_attributes(array $attributes)\n\t{\n\t\t$this->set_attributes_via_mass_assignment($attributes, true);\n\t}\n\n\t/**\n\t * Passing $guard_attributes as true will throw an exception if an attribute does not exist.\n\t *\n\t * @throws \\ActiveRecord\\UndefinedPropertyException\n\t * @param array $attributes An array in the form array(name => value, ...)\n\t * @param boolean $guard_attributes Flag of whether or not protected/non-accessible attributes should be guarded\n\t */\n\tprivate function set_attributes_via_mass_assignment(array &$attributes, $guard_attributes)\n\t{\n\t\t//access uninflected columns since that is what we would have in result set\n\t\t$table = static::table();\n\t\t$exceptions = array();\n\t\t$use_attr_accessible = !empty(static::$attr_accessible);\n\t\t$use_attr_protected = !empty(static::$attr_protected);\n\t\t$connection = static::connection();\n\n\t\tforeach ($attributes as $name => $value)\n\t\t{\n\t\t\t// is a normal field on the table\n\t\t\tif (array_key_exists($name,$table->columns))\n\t\t\t{\n\t\t\t\t$value = $table->columns[$name]->cast($value,$connection);\n\t\t\t\t$name = $table->columns[$name]->inflected_name;\n\t\t\t}\n\n\t\t\tif ($guard_attributes)\n\t\t\t{\n\t\t\t\tif ($use_attr_accessible && !in_array($name,static::$attr_accessible))\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif ($use_attr_protected && in_array($name,static::$attr_protected))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// set valid table data\n\t\t\t\ttry {\n\t\t\t\t\t$this->$name = $value;\n\t\t\t\t} catch (UndefinedPropertyException $e) {\n\t\t\t\t\t$exceptions[] = $e->getMessage();\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// ignore OciAdapter's limit() stuff\n\t\t\t\tif ($name == 'ar_rnum__')\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// set arbitrary data\n\t\t\t\t$this->assign_attribute($name,$value);\n\t\t\t}\n\t\t}\n\n\t\tif (!empty($exceptions))\n\t\t\tthrow new UndefinedPropertyException(get_called_class(),$exceptions);\n\t}\n\n\t/**\n\t * Add a model to the given named ($name) relationship.\n\t *\n\t * @internal This should <strong>only</strong> be used by eager load\n\t * @param Model $model\n\t * @param $name of relationship for this table\n\t * @return void\n\t */\n\tpublic function set_relationship_from_eager_load(Model $model=null, $name)\n\t{\n\t\t$table = static::table();\n\n\t\tif (($rel = $table->get_relationship($name)))\n\t\t{\n\t\t\tif ($rel->is_poly())\n\t\t\t{\n\t\t\t\t// if the related model is null and it is a poly then we should have an empty array\n\t\t\t\tif (is_null($model))\n\t\t\t\t\treturn $this->__relationships[$name] = array();\n\t\t\t\telse\n\t\t\t\t\treturn $this->__relationships[$name][] = $model;\n\t\t\t}\n\t\t\telse\n\t\t\t\treturn $this->__relationships[$name] = $model;\n\t\t}\n\n\t\tthrow new RelationshipException(\"Relationship named $name has not been declared for class: {$table->class->getName()}\");\n\t}\n\n\t/**\n\t * Reloads the attributes and relationships of this object from the database.\n\t *\n\t * @return Model\n\t */\n\tpublic function reload()\n\t{\n\t\t$this->__relationships = array();\n\t\t$pk = array_values($this->get_values_for($this->get_primary_key()));\n\n\t\t$this->expire_cache();\n\t\t$this->set_attributes_via_mass_assignment($this->find($pk)->attributes, false);\n\t\t$this->reset_dirty();\n\n\t\treturn $this;\n\t}\n\n\tpublic function __clone()\n\t{\n\t\t$this->__relationships = array();\n\t\t$this->reset_dirty();\n\t\treturn $this;\n\t}\n\n\t/**\n\t * Resets the dirty array.\n\t *\n\t * @see dirty_attributes\n\t */\n\tpublic function reset_dirty()\n\t{\n\t\t$this->__dirty = null;\n\t}\n\n\t/**\n\t * A list of valid finder options.\n\t *\n\t * @var array\n\t */\n\tstatic $VALID_OPTIONS = array('conditions', 'limit', 'offset', 'order', 'select', 'joins', 'include', 'readonly', 'group', 'from', 'having');\n\n\t/**\n\t * Enables the use of dynamic finders.\n\t *\n\t * Dynamic finders are just an easy way to do queries quickly without having to\n\t * specify an options array with conditions in it.\n\t *\n\t * <code>\n\t * SomeModel::find_by_first_name('Tito');\n\t * SomeModel::find_by_first_name_and_last_name('Tito','the Grief');\n\t * SomeModel::find_by_first_name_or_last_name('Tito','the Grief');\n\t * SomeModel::find_all_by_last_name('Smith');\n\t * SomeModel::count_by_name('Bob')\n\t * SomeModel::count_by_name_or_state('Bob','VA')\n\t * SomeModel::count_by_name_and_state('Bob','VA')\n\t * </code>\n\t *\n\t * You can also create the model if the find call returned no results:\n\t *\n\t * <code>\n\t * Person::find_or_create_by_name('Tito');\n\t *\n\t * # would be the equivalent of\n\t * if (!Person::find_by_name('Tito'))\n\t *   Person::create(array('Tito'));\n\t * </code>\n\t *\n\t * Some other examples of find_or_create_by:\n\t *\n\t * <code>\n\t * Person::find_or_create_by_name_and_id('Tito',1);\n\t * Person::find_or_create_by_name_and_id(array('name' => 'Tito', 'id' => 1));\n\t * </code>\n\t *\n\t * @param string $method Name of method\n\t * @param mixed $args Method args\n\t * @return Model\n\t * @throws {@link ActiveRecordException} if invalid query\n\t * @see find\n\t */\n\tpublic static function __callStatic($method, $args)\n\t{\n\t\t$options = static::extract_and_validate_options($args);\n\t\t$create = false;\n\n\t\tif (substr($method,0,17) == 'find_or_create_by')\n\t\t{\n\t\t\t$attributes = substr($method,17);\n\n\t\t\t// can't take any finders with OR in it when doing a find_or_create_by\n\t\t\tif (strpos($attributes,'_or_') !== false)\n\t\t\t\tthrow new ActiveRecordException(\"Cannot use OR'd attributes in find_or_create_by\");\n\n\t\t\t$create = true;\n\t\t\t$method = 'find_by' . substr($method,17);\n\t\t}\n\n\t\tif (substr($method,0,7) === 'find_by')\n\t\t{\n\t\t\t$attributes = substr($method,8);\n\t\t\t$options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),$attributes,$args,static::$alias_attribute);\n\n\t\t\tif (!($ret = static::find('first',$options)) && $create)\n\t\t\t\treturn static::create(SQLBuilder::create_hash_from_underscored_string($attributes,$args,static::$alias_attribute));\n\n\t\t\treturn $ret;\n\t\t}\n\t\telseif (substr($method,0,11) === 'find_all_by')\n\t\t{\n\t\t\t$options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),substr($method,12),$args,static::$alias_attribute);\n\t\t\treturn static::find('all',$options);\n\t\t}\n\t\telseif (substr($method,0,8) === 'count_by')\n\t\t{\n\t\t\t$options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),substr($method,9),$args,static::$alias_attribute);\n\t\t\treturn static::count($options);\n\t\t}\n\n\t\tthrow new ActiveRecordException(\"Call to undefined method: $method\");\n\t}\n\n\t/**\n\t * Enables the use of build|create for associations.\n\t *\n\t * @param string $method Name of method\n\t * @param mixed $args Method args\n\t * @return mixed An instance of a given {@link AbstractRelationship}\n\t */\n\tpublic function __call($method, $args)\n\t{\n\t\t//check for build|create_association methods\n\t\tif (preg_match('/(build|create)_/', $method))\n\t\t{\n\t\t\tif (!empty($args))\n\t\t\t\t$args = $args[0];\n\n\t\t\t$association_name = str_replace(array('build_', 'create_'), '', $method);\n\t\t\t$method = str_replace($association_name, 'association', $method);\n\t\t\t$table = static::table();\n\n\t\t\tif (($association = $table->get_relationship($association_name)) ||\n\t\t\t\t  ($association = $table->get_relationship(($association_name = Utils::pluralize($association_name)))))\n\t\t\t{\n\t\t\t\t// access association to ensure that the relationship has been loaded\n\t\t\t\t// so that we do not double-up on records if we append a newly created\n\t\t\t\t$this->$association_name;\n\t\t\t\treturn $association->$method($this, $args);\n\t\t\t}\n\t\t}\n\n\t\tthrow new ActiveRecordException(\"Call to undefined method: $method\");\n\t}\n\n\t/**\n\t * Alias for self::find('all').\n\t *\n\t * @see find\n\t * @return array array of records found\n\t */\n\tpublic static function all(/* ... */)\n\t{\n\t\treturn call_user_func_array('static::find',array_merge(array('all'),func_get_args()));\n\t}\n\n\t/**\n\t * Get a count of qualifying records.\n\t *\n\t * <code>\n\t * YourModel::count(array('conditions' => 'amount > 3.14159265'));\n\t * </code>\n\t *\n\t * @see find\n\t * @return int Number of records that matched the query\n\t */\n\tpublic static function count(/* ... */)\n\t{\n\t\t$args = func_get_args();\n\t\t$options = static::extract_and_validate_options($args);\n\t\t$options['select'] = 'COUNT(*)';\n\n\t\tif (!empty($args) && !is_null($args[0]) && !empty($args[0]))\n\t\t{\n\t\t\tif (is_hash($args[0]))\n\t\t\t\t$options['conditions'] = $args[0];\n\t\t\telse\n\t\t\t\t$options['conditions'] = call_user_func_array('static::pk_conditions',$args);\n\t\t}\n\n\t\t$table = static::table();\n\t\t$sql = $table->options_to_sql($options);\n\t\t$values = $sql->get_where_values();\n\t\treturn static::connection()->query_and_fetch_one($sql->to_s(),$values);\n\t}\n\n\t/**\n\t * Determine if a record exists.\n\t *\n\t * <code>\n\t * SomeModel::exists(123);\n\t * SomeModel::exists(array('conditions' => array('id=? and name=?', 123, 'Tito')));\n\t * SomeModel::exists(array('id' => 123, 'name' => 'Tito'));\n\t * </code>\n\t *\n\t * @see find\n\t * @return boolean\n\t */\n\tpublic static function exists(/* ... */)\n\t{\n\t\treturn call_user_func_array('static::count',func_get_args()) > 0 ? true : false;\n\t}\n\n\t/**\n\t * Alias for self::find('first').\n\t *\n\t * @see find\n\t * @return Model The first matched record or null if not found\n\t */\n\tpublic static function first(/* ... */)\n\t{\n\t\treturn call_user_func_array('static::find',array_merge(array('first'),func_get_args()));\n\t}\n\n\t/**\n\t * Alias for self::find('last')\n\t *\n\t * @see find\n\t * @return Model The last matched record or null if not found\n\t */\n\tpublic static function last(/* ... */)\n\t{\n\t\treturn call_user_func_array('static::find',array_merge(array('last'),func_get_args()));\n\t}\n\n\t/**\n\t * Find records in the database.\n\t *\n\t * Finding by the primary key:\n\t *\n\t * <code>\n\t * # queries for the model with id=123\n\t * YourModel::find(123);\n\t *\n\t * # queries for model with id in(1,2,3)\n\t * YourModel::find(1,2,3);\n\t *\n\t * # finding by pk accepts an options array\n\t * YourModel::find(123,array('order' => 'name desc'));\n\t * </code>\n\t *\n\t * Finding by using a conditions array:\n\t *\n\t * <code>\n\t * YourModel::find('first', array('conditions' => array('name=?','Tito'),\n\t *   'order' => 'name asc'))\n\t * YourModel::find('all', array('conditions' => 'amount > 3.14159265'));\n\t * YourModel::find('all', array('conditions' => array('id in(?)', array(1,2,3))));\n\t * </code>\n\t *\n\t * Finding by using a hash:\n\t *\n\t * <code>\n\t * YourModel::find(array('name' => 'Tito', 'id' => 1));\n\t * YourModel::find('first',array('name' => 'Tito', 'id' => 1));\n\t * YourModel::find('all',array('name' => 'Tito', 'id' => 1));\n\t * </code>\n\t *\n\t * An options array can take the following parameters:\n\t *\n\t * <ul>\n\t * <li><b>select:</b> A SQL fragment for what fields to return such as: '*', 'people.*', 'first_name, last_name, id'</li>\n\t * <li><b>joins:</b> A SQL join fragment such as: 'JOIN roles ON(roles.user_id=user.id)' or a named association on the model</li>\n\t * <li><b>include:</b> TODO not implemented yet</li>\n\t * <li><b>conditions:</b> A SQL fragment such as: 'id=1', array('id=1'), array('name=? and id=?','Tito',1), array('name IN(?)', array('Tito','Bob')),\n\t * array('name' => 'Tito', 'id' => 1)</li>\n\t * <li><b>limit:</b> Number of records to limit the query to</li>\n\t * <li><b>offset:</b> The row offset to return results from for the query</li>\n\t * <li><b>order:</b> A SQL fragment for order such as: 'name asc', 'name asc, id desc'</li>\n\t * <li><b>readonly:</b> Return all the models in readonly mode</li>\n\t * <li><b>group:</b> A SQL group by fragment</li>\n\t * </ul>\n\t *\n\t * @throws {@link RecordNotFound} if no options are passed or finding by pk and no records matched\n\t * @return mixed An array of records found if doing a find_all otherwise a\n\t *   single Model object or null if it wasn't found. NULL is only return when\n\t *   doing a first/last find. If doing an all find and no records matched this\n\t *   will return an empty array.\n\t */\n\tpublic static function find(/* $type, $options */)\n\t{\n\t\t$class = get_called_class();\n\n\t\tif (func_num_args() <= 0)\n\t\t\tthrow new RecordNotFound(\"Couldn't find $class without an ID\");\n\n\t\t$args = func_get_args();\n\t\t$options = static::extract_and_validate_options($args);\n\t\t$num_args = count($args);\n\t\t$single = true;\n\n\t\tif ($num_args > 0 && ($args[0] === 'all' || $args[0] === 'first' || $args[0] === 'last'))\n\t\t{\n\t\t\tswitch ($args[0])\n\t\t\t{\n\t\t\t\tcase 'all':\n\t\t\t\t\t$single = false;\n\t\t\t\t\tbreak;\n\n\t\t\t \tcase 'last':\n\t\t\t\t\tif (!array_key_exists('order',$options))\n\t\t\t\t\t\t$options['order'] = join(' DESC, ',static::table()->pk) . ' DESC';\n\t\t\t\t\telse\n\t\t\t\t\t\t$options['order'] = SQLBuilder::reverse_order($options['order']);\n\n\t\t\t\t\t// fall thru\n\n\t\t\t \tcase 'first':\n\t\t\t\t\t$options['limit'] = 1;\n\t\t\t\t\t$options['offset'] = 0;\n\t\t\t \t\tbreak;\n\t\t\t}\n\n\t\t\t$args = array_slice($args,1);\n\t\t\t$num_args--;\n\t\t}\n\t\t//find by pk\n\t\telseif (1 === count($args) && 1 == $num_args)\n\t\t\t$args = $args[0];\n\n\t\t// anything left in $args is a find by pk\n\t\tif ($num_args > 0 && !isset($options['conditions']))\n\t\t\treturn static::find_by_pk($args, $options);\n\n\t\t$options['mapped_names'] = static::$alias_attribute;\n\t\t$list = static::table()->find($options);\n\n\t\treturn $single ? (!empty($list) ? $list[0] : null) : $list;\n\t}\n\n\t/**\n\t * Will look up a list of primary keys from cache\n\t *\n\t * @param mixed $pks primary keys\n\t * @return array\n\t */\n\tprotected static function get_models_from_cache($pks, $options)\n\t{\n\t\t$models = array();\n\t\t$table = static::table();\n\n\t\tif(!is_array($pks))\n\t\t{\n\t\t\t$pks = array($pks);\n\t\t}\n\n\t\tforeach($pks as $pk)\n\t\t{\n\t\t\t$options['conditions'] = static::pk_conditions($pk);\n\t\t\t$models[] = Cache::get($table->cache_key_for_model($pk), function() use ($table, $options)\n\t\t\t{\n\t\t\t\t$res = $table->find($options);\n\t\t\t\treturn $res ? $res[0] : null;\n\t\t\t}, $table->cache_model_expire);\n\t\t}\n\t\treturn array_filter($models);\n\t}\n\n\t/**\n\t * Finder method which will find by a single or array of primary keys for this model.\n\t *\n\t * @see find\n\t * @param array $values An array containing values for the pk\n\t * @param array $options An options array\n\t * @return Model\n\t * @throws {@link RecordNotFound} if a record could not be found\n\t */\n\tpublic static function find_by_pk($values, $options)\n\t{\n\t\tif($values===null)\n\t\t{\n\t\t\tthrow new RecordNotFound(\"Couldn't find \".get_called_class().\" without an ID\");\n\t\t}\n\n\t\t$table = static::table();\n\n\t\tif($table->cache_individual_model)\n\t\t{\n\t\t\t$list = static::get_models_from_cache($values, $options);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t$options['conditions'] = static::pk_conditions($values);\n\t\t\t$list = $table->find($options);\n\t\t}\n\t\t$results = count($list);\n\n\t\tif ($results != ($expected = count($values)))\n\t\t{\n\t\t\t$class = get_called_class();\n\t\t\tif (is_array($values))\n\t\t\t\t$values = join(',',$values);\n\n\t\t\tif ($expected == 1)\n\t\t\t{\n\t\t\t\tthrow new RecordNotFound(\"Couldn't find $class with ID=$values\");\n\t\t\t}\n\n\t\t\tthrow new RecordNotFound(\"Couldn't find all $class with IDs ($values) (found $results, but was looking for $expected)\");\n\t\t}\n\t\treturn $expected == 1 ? $list[0] : $list;\n\t}\n\n\t/**\n\t * Find using a raw SELECT query.\n\t *\n\t * <code>\n\t * YourModel::find_by_sql(\"SELECT * FROM people WHERE name=?\",array('Tito'));\n\t * YourModel::find_by_sql(\"SELECT * FROM people WHERE name='Tito'\");\n\t * </code>\n\t *\n\t * @param string $sql The raw SELECT query\n\t * @param array $values An array of values for any parameters that needs to be bound\n\t * @return array An array of models\n\t */\n\tpublic static function find_by_sql($sql, $values=null)\n\t{\n\t\treturn static::table()->find_by_sql($sql, $values, true);\n\t}\n\n\t/**\n\t * Helper method to run arbitrary queries against the model's database connection.\n\t *\n\t * @param string $sql SQL to execute\n\t * @param array $values Bind values, if any, for the query\n\t * @return object A PDOStatement object\n\t */\n\tpublic static function query($sql, $values=null)\n\t{\n\t\treturn static::connection()->query($sql, $values);\n\t}\n\n\t/**\n\t * Determines if the specified array is a valid ActiveRecord options array.\n\t *\n\t * @param array $array An options array\n\t * @param bool $throw True to throw an exception if not valid\n\t * @return boolean True if valid otherwise valse\n\t * @throws {@link ActiveRecordException} if the array contained any invalid options\n\t */\n\tpublic static function is_options_hash($array, $throw=true)\n\t{\n\t\tif (is_hash($array))\n\t\t{\n\t\t\t$keys = array_keys($array);\n\t\t\t$diff = array_diff($keys,self::$VALID_OPTIONS);\n\n\t\t\tif (!empty($diff) && $throw)\n\t\t\t\tthrow new ActiveRecordException(\"Unknown key(s): \" . join(', ',$diff));\n\n\t\t\t$intersect = array_intersect($keys,self::$VALID_OPTIONS);\n\n\t\t\tif (!empty($intersect))\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Returns a hash containing the names => values of the primary key.\n\t *\n\t * @internal This needs to eventually support composite keys.\n\t * @param mixed $args Primary key value(s)\n\t * @return array An array in the form array(name => value, ...)\n\t */\n\tpublic static function pk_conditions($args)\n\t{\n\t\t$table = static::table();\n\t\t$ret = array($table->pk[0] => $args);\n\t\treturn $ret;\n\t}\n\n\t/**\n\t * Pulls out the options hash from $array if any.\n\t *\n\t * @internal DO NOT remove the reference on $array.\n\t * @param array &$array An array\n\t * @return array A valid options array\n\t */\n\tpublic static function extract_and_validate_options(array &$array)\n\t{\n\t\t$options = array();\n\n\t\tif ($array)\n\t\t{\n\t\t\t$last = &$array[count($array)-1];\n\n\t\t\ttry\n\t\t\t{\n\t\t\t\tif (self::is_options_hash($last))\n\t\t\t\t{\n\t\t\t\t\tarray_pop($array);\n\t\t\t\t\t$options = $last;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (ActiveRecordException $e)\n\t\t\t{\n\t\t\t\tif (!is_hash($last))\n\t\t\t\t\tthrow $e;\n\n\t\t\t\t$options = array('conditions' => $last);\n\t\t\t}\n\t\t}\n\t\treturn $options;\n\t}\n\n\t/**\n\t * Returns a JSON representation of this model.\n\t *\n\t * @see Serialization\n\t * @param array $options An array containing options for json serialization (see {@link Serialization} for valid options)\n\t * @return string JSON representation of the model\n\t */\n\tpublic function to_json(array $options=array())\n\t{\n\t\treturn $this->serialize('Json', $options);\n\t}\n\n\t/**\n\t * Returns an XML representation of this model.\n\t *\n\t * @see Serialization\n\t * @param array $options An array containing options for xml serialization (see {@link Serialization} for valid options)\n\t * @return string XML representation of the model\n\t */\n\tpublic function to_xml(array $options=array())\n\t{\n\t\treturn $this->serialize('Xml', $options);\n\t}\n\n   /**\n   * Returns an CSV representation of this model.\n   * Can take optional delimiter and enclosure\n   * (defaults are , and double quotes)\n   *\n   * Ex:\n   * <code>\n   * ActiveRecord\\CsvSerializer::$delimiter=';';\n   * ActiveRecord\\CsvSerializer::$enclosure='';\n   * YourModel::find('first')->to_csv(array('only'=>array('name','level')));\n   * returns: Joe,2\n   *\n   * YourModel::find('first')->to_csv(array('only_header'=>true,'only'=>array('name','level')));\n   * returns: name,level\n   * </code>\n   *\n   * @see Serialization\n   * @param array $options An array containing options for csv serialization (see {@link Serialization} for valid options)\n   * @return string CSV representation of the model\n   */\n  public function to_csv(array $options=array())\n  {\n    return $this->serialize('Csv', $options);\n  }\n\n\t/**\n\t * Returns an Array representation of this model.\n\t *\n\t * @see Serialization\n\t * @param array $options An array containing options for json serialization (see {@link Serialization} for valid options)\n\t * @return array Array representation of the model\n\t */\n\tpublic function to_array(array $options=array())\n\t{\n\t\treturn $this->serialize('Array', $options);\n\t}\n\n\t/**\n\t * Creates a serializer based on pre-defined to_serializer()\n\t *\n\t * An options array can take the following parameters:\n\t *\n\t * <ul>\n\t * <li><b>only:</b> a string or array of attributes to be included.</li>\n\t * <li><b>excluded:</b> a string or array of attributes to be excluded.</li>\n\t * <li><b>methods:</b> a string or array of methods to invoke. The method's name will be used as a key for the final attributes array\n\t * along with the method's returned value</li>\n\t * <li><b>include:</b> a string or array of associated models to include in the final serialized product.</li>\n\t * </ul>\n\t *\n\t * @param string $type Either Xml, Json, Csv or Array\n\t * @param array $options Options array for the serializer\n\t * @return string Serialized representation of the model\n\t */\n\tprivate function serialize($type, $options)\n\t{\n\t\trequire_once 'Serialization.php';\n\t\t$class = \"ActiveRecord\\\\{$type}Serializer\";\n\t\t$serializer = new $class($this, $options);\n\t\treturn $serializer->to_s();\n\t}\n\n\t/**\n\t * Invokes the specified callback on this model.\n\t *\n\t * @param string $method_name Name of the call back to run.\n\t * @param boolean $must_exist Set to true to raise an exception if the callback does not exist.\n\t * @return boolean True if invoked or null if not\n\t */\n\tprivate function invoke_callback($method_name, $must_exist=true)\n\t{\n\t\treturn static::table()->callback->invoke($this,$method_name,$must_exist);\n\t}\n\n\t/**\n\t * Executes a block of code inside a database transaction.\n\t *\n\t * <code>\n\t * YourModel::transaction(function()\n\t * {\n\t *   YourModel::create(array(\"name\" => \"blah\"));\n\t * });\n\t * </code>\n\t *\n\t * If an exception is thrown inside the closure the transaction will\n\t * automatically be rolled back. You can also return false from your\n\t * closure to cause a rollback:\n\t *\n\t * <code>\n\t * YourModel::transaction(function()\n\t * {\n\t *   YourModel::create(array(\"name\" => \"blah\"));\n\t *   throw new Exception(\"rollback!\");\n\t * });\n\t *\n\t * YourModel::transaction(function()\n\t * {\n\t *   YourModel::create(array(\"name\" => \"blah\"));\n\t *   return false; # rollback!\n\t * });\n\t * </code>\n\t *\n\t * @param callable $closure The closure to execute. To cause a rollback have your closure return false or throw an exception.\n\t * @return boolean True if the transaction was committed, False if rolled back.\n\t */\n\tpublic static function transaction($closure)\n\t{\n\t\t$connection = static::connection();\n\n\t\ttry\n\t\t{\n\t\t\t$connection->transaction();\n\n\t\t\tif ($closure() === false)\n\t\t\t{\n\t\t\t\t$connection->rollback();\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\telse\n\t\t\t\t$connection->commit();\n\t\t}\n\t\tcatch (\\Exception $e)\n\t\t{\n\t\t\t$connection->rollback();\n\t\t\tthrow $e;\n\t\t}\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "lib/Reflections.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\nuse ReflectionClass;\n\n/**\n * Simple class that caches reflections of classes.\n *\n * @package ActiveRecord\n */\nclass Reflections extends Singleton\n{\n\t/**\n\t * Current reflections.\n\t *\n\t * @var array\n\t */\n\tprivate $reflections = array();\n\n\t/**\n\t * Instantiates a new ReflectionClass for the given class.\n\t *\n\t * @param string $class Name of a class\n\t * @return Reflections $this so you can chain calls like Reflections::instance()->add('class')->get()\n\t */\n\tpublic function add($class=null)\n\t{\n\t\t$class = $this->get_class($class);\n\n\t\tif (!isset($this->reflections[$class]))\n\t\t\t$this->reflections[$class] = new ReflectionClass($class);\n\t\t\t\n\t\treturn $this;\n\t}\n\n\t/**\n\t * Destroys the cached ReflectionClass.\n\t *\n\t * Put this here mainly for testing purposes.\n\t * \n\t * @param string $class Name of a class.\n\t * @return void\n\t */\n\tpublic function destroy($class)\n\t{\n\t\tif (isset($this->reflections[$class]))\n\t\t\t$this->reflections[$class] = null;\n\t}\n\t\n\t/**\n\t * Get a cached ReflectionClass.\n\t *\n\t * @param string $class Optional name of a class\n\t * @return mixed null or a ReflectionClass instance\n\t * @throws ActiveRecordException if class was not found\n\t */\n\tpublic function get($class=null)\n\t{\n\t\t$class = $this->get_class($class);\n\n\t\tif (isset($this->reflections[$class]))\n\t\t\treturn $this->reflections[$class];\n\n\t\tthrow new ActiveRecordException(\"Class not found: $class\");\n\t}\n\n\t/**\n\t * Retrieve a class name to be reflected.\n\t *\n\t * @param mixed $mixed An object or name of a class\n\t * @return string\n\t */\n\tprivate function get_class($mixed=null)\n\t{\n\t\tif (is_object($mixed))\n\t\t\treturn get_class($mixed);\n\n\t\tif (!is_null($mixed))\n\t\t\treturn $mixed;\n\n\t\treturn $this->get_called_class();\n\t}\n}"
  },
  {
    "path": "lib/Relationship.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Interface for a table relationship.\n *\n * @package ActiveRecord\n */\ninterface InterfaceRelationship\n{\n\tpublic function __construct($options=array());\n\tpublic function build_association(Model $model, $attributes=array(), $guard_attributes=true);\n\tpublic function create_association(Model $model, $attributes=array(), $guard_attributes=true);\n}\n\n/**\n * Abstract class that all relationships must extend from.\n *\n * @package ActiveRecord\n * @see http://www.phpactiverecord.org/guides/associations\n */\nabstract class AbstractRelationship implements InterfaceRelationship\n{\n\t/**\n\t * Name to be used that will trigger call to the relationship.\n\t *\n\t * @var string\n\t */\n\tpublic $attribute_name;\n\n\t/**\n\t * Class name of the associated model.\n\t *\n\t * @var string\n\t */\n\tpublic $class_name;\n\n\t/**\n\t * Name of the foreign key.\n\t *\n\t * @var string\n\t */\n\tpublic $foreign_key = array();\n\n\t/**\n\t * Options of the relationship.\n\t *\n\t * @var array\n\t */\n\tprotected $options = array();\n\n\t/**\n\t * Is the relationship single or multi.\n\t *\n\t * @var boolean\n\t */\n\tprotected $poly_relationship = false;\n\n\t/**\n\t * List of valid options for relationships.\n\t *\n\t * @var array\n\t */\n\tstatic protected $valid_association_options = array('class_name', 'class', 'foreign_key', 'conditions', 'select', 'readonly', 'namespace');\n\n\t/**\n\t * Constructs a relationship.\n\t *\n\t * @param array $options Options for the relationship (see {@link valid_association_options})\n\t * @return mixed\n\t */\n\tpublic function __construct($options=array())\n\t{\n\t\t$this->attribute_name = $options[0];\n\t\t$this->options = $this->merge_association_options($options);\n\n\t\t$relationship = strtolower(denamespace(get_called_class()));\n\n\t\tif ($relationship === 'hasmany' || $relationship === 'hasandbelongstomany')\n\t\t\t$this->poly_relationship = true;\n\n\t\tif (isset($this->options['conditions']) && !is_array($this->options['conditions']))\n\t\t\t$this->options['conditions'] = array($this->options['conditions']);\n\n\t\tif (isset($this->options['class']))\n\t\t\t$this->set_class_name($this->options['class']);\n\t\telseif (isset($this->options['class_name']))\n\t\t\t$this->set_class_name($this->options['class_name']);\n\n\t\t$this->attribute_name = strtolower(Inflector::instance()->variablize($this->attribute_name));\n\n\t\tif (!$this->foreign_key && isset($this->options['foreign_key']))\n\t\t\t$this->foreign_key = is_array($this->options['foreign_key']) ? $this->options['foreign_key'] : array($this->options['foreign_key']);\n\t}\n\n\tprotected function get_table()\n\t{\n\t\treturn Table::load($this->class_name);\n\t}\n\n\t/**\n\t * What is this relationship's cardinality?\n\t *\n\t * @return bool\n\t */\n\tpublic function is_poly()\n\t{\n\t\treturn $this->poly_relationship;\n\t}\n\n\t/**\n\t * Eagerly loads relationships for $models.\n\t *\n\t * This method takes an array of models, collects PK or FK (whichever is needed for relationship), then queries\n\t * the related table by PK/FK and attaches the array of returned relationships to the appropriately named relationship on\n\t * $models.\n\t *\n\t * @param Table $table\n\t * @param $models array of model objects\n\t * @param $attributes array of attributes from $models\n\t * @param $includes array of eager load directives\n\t * @param $query_keys -> key(s) to be queried for on included/related table\n\t * @param $model_values_keys -> key(s)/value(s) to be used in query from model which is including\n\t * @return void\n\t */\n\tprotected function query_and_attach_related_models_eagerly(Table $table, $models, $attributes, $includes=array(), $query_keys=array(), $model_values_keys=array())\n\t{\n\t\t$values = array();\n\t\t$options = $this->options;\n\t\t$inflector = Inflector::instance();\n\t\t$query_key = $query_keys[0];\n\t\t$model_values_key = $model_values_keys[0];\n\n\t\tforeach ($attributes as $column => $value)\n\t\t\t$values[] = $value[$inflector->variablize($model_values_key)];\n\n\t\t$values = array($values);\n\t\t$conditions = SQLBuilder::create_conditions_from_underscored_string($table->conn,$query_key,$values);\n\n\t\tif (isset($options['conditions']) && strlen($options['conditions'][0]) > 1)\n\t\t\tUtils::add_condition($options['conditions'], $conditions);\n\t\telse\n\t\t\t$options['conditions'] = $conditions;\n\n\t\tif (!empty($includes))\n\t\t\t$options['include'] = $includes;\n\n\t\tif (!empty($options['through'])) {\n\t\t\t// save old keys as we will be reseting them below for inner join convenience\n\t\t\t$pk = $this->primary_key;\n\t\t\t$fk = $this->foreign_key;\n\n\t\t\t$this->set_keys($this->get_table()->class->getName(), true);\n\n\t\t\tif (!isset($options['class_name'])) {\n\t\t\t\t$class = classify($options['through'], true);\n\t\t\t\tif (isset($this->options['namespace']) && !class_exists($class))\n\t\t\t\t\t$class = $this->options['namespace'].'\\\\'.$class;\n\n\t\t\t\t$through_table = $class::table();\n\t\t\t} else {\n\t\t\t\t$class = $options['class_name'];\n\t\t\t\t$relation = $class::table()->get_relationship($options['through']);\n\t\t\t\t$through_table = $relation->get_table();\n\t\t\t}\n\t\t\t$options['joins'] = $this->construct_inner_join_sql($through_table, true);\n\n\t\t\t$query_key = $this->primary_key[0];\n\n\t\t\t// reset keys\n\t\t\t$this->primary_key = $pk;\n\t\t\t$this->foreign_key = $fk;\n\t\t}\n\n\t\t$options = $this->unset_non_finder_options($options);\n\n\t\t$class = $this->class_name;\n\n\t\t$related_models = $class::find('all', $options);\n\t\t$used_models_map = array();\n\t\t$related_models_map = array();\n\t\t$model_values_key = $inflector->variablize($model_values_key);\n\t\t$query_key = $inflector->variablize($query_key);\n\n\t\tforeach ($related_models as $related)\n\t\t{\n\t\t\t$related_models_map[$related->$query_key][] = $related;\n\t\t}\n\n\t\tforeach ($models as $model)\n\t\t{\n\t\t\t$key_to_match = $model->$model_values_key;\n\n\t\t\tif (isset($related_models_map[$key_to_match])) {\n\t\t\t\tforeach ($related_models_map[$key_to_match] as $related)\n\t\t\t\t{\n\t\t\t\t\t$hash = spl_object_hash($related);\n\n\t\t\t\t\tif (isset($used_models_map[$hash]))\n\t\t\t\t\t\t$model->set_relationship_from_eager_load(clone($related), $this->attribute_name);\n\t\t\t\t\telse\n\t\t\t\t\t\t$model->set_relationship_from_eager_load($related, $this->attribute_name);\n\n\t\t\t\t\t$used_models_map[$hash] = true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t$model->set_relationship_from_eager_load(null, $this->attribute_name);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Creates a new instance of specified {@link Model} with the attributes pre-loaded.\n\t *\n\t * @param Model $model The model which holds this association\n\t * @param array $attributes Hash containing attributes to initialize the model with\n\t * @return Model\n\t */\n\tpublic function build_association(Model $model, $attributes=array(), $guard_attributes=true)\n\t{\n\t\t$class_name = $this->class_name;\n\t\treturn new $class_name($attributes, $guard_attributes);\n\t}\n\n\t/**\n\t * Creates a new instance of {@link Model} and invokes save.\n\t *\n\t * @param Model $model The model which holds this association\n\t * @param array $attributes Hash containing attributes to initialize the model with\n\t * @return Model\n\t */\n\tpublic function create_association(Model $model, $attributes=array(), $guard_attributes=true)\n\t{\n\t\t$class_name = $this->class_name;\n\t\t$new_record = $class_name::create($attributes, true, $guard_attributes);\n\t\treturn $this->append_record_to_associate($model, $new_record);\n\t}\n\n\tprotected function append_record_to_associate(Model $associate, Model $record)\n\t{\n\t\t$association =& $associate->{$this->attribute_name};\n\n\t\tif ($this->poly_relationship)\n\t\t\t$association[] = $record;\n\t\telse\n\t\t\t$association = $record;\n\n\t\treturn $record;\n\t}\n\n\tprotected function merge_association_options($options)\n\t{\n\t\t$available_options = array_merge(self::$valid_association_options,static::$valid_association_options);\n\t\t$valid_options = array_intersect_key(array_flip($available_options),$options);\n\n\t\tforeach ($valid_options as $option => $v)\n\t\t\t$valid_options[$option] = $options[$option];\n\n\t\treturn $valid_options;\n\t}\n\n\tprotected function unset_non_finder_options($options)\n\t{\n\t\tforeach (array_keys($options) as $option)\n\t\t{\n\t\t\tif (!in_array($option, Model::$VALID_OPTIONS))\n\t\t\t\tunset($options[$option]);\n\t\t}\n\t\treturn $options;\n\t}\n\n\t/**\n\t * Infers the $this->class_name based on $this->attribute_name.\n\t *\n\t * Will try to guess the appropriate class by singularizing and uppercasing $this->attribute_name.\n\t *\n\t * @return void\n\t * @see attribute_name\n\t */\n\tprotected function set_inferred_class_name()\n\t{\n\t\t$singularize = ($this instanceOf HasMany ? true : false);\n\t\t$this->set_class_name(classify($this->attribute_name, $singularize));\n\t}\n\n\tprotected function set_class_name($class_name)\n\t{\n\t\tif (!has_absolute_namespace($class_name) && isset($this->options['namespace'])) {\n\t\t\t$class_name = $this->options['namespace'].'\\\\'.$class_name;\n\t\t}\n\t\t\n\t\t$reflection = Reflections::instance()->add($class_name)->get($class_name);\n\n\t\tif (!$reflection->isSubClassOf('ActiveRecord\\\\Model'))\n\t\t\tthrow new RelationshipException(\"'$class_name' must extend from ActiveRecord\\\\Model\");\n\n\t\t$this->class_name = $class_name;\n\t}\n\n\tprotected function create_conditions_from_keys(Model $model, $condition_keys=array(), $value_keys=array())\n\t{\n\t\t$condition_string = implode('_and_', $condition_keys);\n\t\t$condition_values = array_values($model->get_values_for($value_keys));\n\n\t\t// return null if all the foreign key values are null so that we don't try to do a query like \"id is null\"\n\t\tif (all(null,$condition_values))\n\t\t\treturn null;\n\n\t\t$conditions = SQLBuilder::create_conditions_from_underscored_string(Table::load(get_class($model))->conn,$condition_string,$condition_values);\n\n\t\t# DO NOT CHANGE THE NEXT TWO LINES. add_condition operates on a reference and will screw options array up\n\t\tif (isset($this->options['conditions']))\n\t\t\t$options_conditions = $this->options['conditions'];\n\t\telse\n\t\t\t$options_conditions = array();\n\n\t\treturn Utils::add_condition($options_conditions, $conditions);\n\t}\n\n\t/**\n\t * Creates INNER JOIN SQL for associations.\n\t *\n\t * @param Table $from_table the table used for the FROM SQL statement\n\t * @param bool $using_through is this a THROUGH relationship?\n\t * @param string $alias a table alias for when a table is being joined twice\n\t * @return string SQL INNER JOIN fragment\n\t */\n\tpublic function construct_inner_join_sql(Table $from_table, $using_through=false, $alias=null)\n\t{\n\t\tif ($using_through)\n\t\t{\n\t\t\t$join_table = $from_table;\n\t\t\t$join_table_name = $from_table->get_fully_qualified_table_name();\n\t\t\t$from_table_name = Table::load($this->class_name)->get_fully_qualified_table_name();\n \t\t}\n\t\telse\n\t\t{\n\t\t\t$join_table = Table::load($this->class_name);\n\t\t\t$join_table_name = $join_table->get_fully_qualified_table_name();\n\t\t\t$from_table_name = $from_table->get_fully_qualified_table_name();\n\t\t}\n\n\t\t// need to flip the logic when the key is on the other table\n\t\tif ($this instanceof HasMany || $this instanceof HasOne)\n\t\t{\n\t\t\t$this->set_keys($from_table->class->getName());\n\n\t\t\tif ($using_through)\n\t\t\t{\n\t\t\t\t$foreign_key = $this->primary_key[0];\n\t\t\t\t$join_primary_key = $this->foreign_key[0];\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t$join_primary_key = $this->foreign_key[0];\n\t\t\t\t$foreign_key = $this->primary_key[0];\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t$foreign_key = $this->foreign_key[0];\n\t\t\t$join_primary_key = $this->primary_key[0];\n\t\t}\n\n\t\tif (!is_null($alias))\n\t\t{\n\t\t\t$aliased_join_table_name = $alias = $this->get_table()->conn->quote_name($alias);\n\t\t\t$alias .= ' ';\n\t\t}\n\t\telse\n\t\t\t$aliased_join_table_name = $join_table_name;\n\n\t\treturn \"INNER JOIN $join_table_name {$alias}ON($from_table_name.$foreign_key = $aliased_join_table_name.$join_primary_key)\";\n\t}\n\n\t/**\n\t * This will load the related model data.\n\t *\n\t * @param Model $model The model this relationship belongs to\n\t */\n\tabstract function load(Model $model);\n}\n\n/**\n * One-to-many relationship.\n *\n * <code>\n * # Table: people\n * # Primary key: id\n * # Foreign key: school_id\n * class Person extends ActiveRecord\\Model {}\n *\n * # Table: schools\n * # Primary key: id\n * class School extends ActiveRecord\\Model {\n *   static $has_many = array(\n *     array('people')\n *   );\n * });\n * </code>\n *\n * Example using options:\n *\n * <code>\n * class Payment extends ActiveRecord\\Model {\n *   static $belongs_to = array(\n *     array('person'),\n *     array('order')\n *   );\n * }\n *\n * class Order extends ActiveRecord\\Model {\n *   static $has_many = array(\n *     array('people',\n *           'through'    => 'payments',\n *           'select'     => 'people.*, payments.amount',\n *           'conditions' => 'payments.amount < 200')\n *     );\n * }\n * </code>\n *\n * @package ActiveRecord\n * @see http://www.phpactiverecord.org/guides/associations\n * @see valid_association_options\n */\nclass HasMany extends AbstractRelationship\n{\n\t/**\n\t * Valid options to use for a {@link HasMany} relationship.\n\t *\n\t * <ul>\n\t * <li><b>limit/offset:</b> limit the number of records</li>\n\t * <li><b>primary_key:</b> name of the primary_key of the association (defaults to \"id\")</li>\n\t * <li><b>group:</b> GROUP BY clause</li>\n\t * <li><b>order:</b> ORDER BY clause</li>\n\t * <li><b>through:</b> name of a model</li>\n\t * </ul>\n\t *\n\t * @var array\n\t */\n\tstatic protected $valid_association_options = array('primary_key', 'order', 'group', 'having', 'limit', 'offset', 'through', 'source');\n\n\tprotected $primary_key;\n\n\tprivate $has_one = false;\n\tprivate $through;\n\n\t/**\n\t * Constructs a {@link HasMany} relationship.\n\t *\n\t * @param array $options Options for the association\n\t * @return HasMany\n\t */\n\tpublic function __construct($options=array())\n\t{\n\t\tparent::__construct($options);\n\n\t\tif (isset($this->options['through']))\n\t\t{\n\t\t\t$this->through = $this->options['through'];\n\n\t\t\tif (isset($this->options['source']))\n\t\t\t\t$this->set_class_name($this->options['source']);\n\t\t}\n\n\t\tif (!$this->primary_key && isset($this->options['primary_key']))\n\t\t\t$this->primary_key = is_array($this->options['primary_key']) ? $this->options['primary_key'] : array($this->options['primary_key']);\n\n\t\tif (!$this->class_name)\n\t\t\t$this->set_inferred_class_name();\n\t}\n\n\tprotected function set_keys($model_class_name, $override=false)\n\t{\n\t\t//infer from class_name\n\t\tif (!$this->foreign_key || $override)\n\t\t\t$this->foreign_key = array(Inflector::instance()->keyify($model_class_name));\n\n\t\tif (!$this->primary_key || $override)\n\t\t\t$this->primary_key = Table::load($model_class_name)->pk;\n\t}\n\n\tpublic function load(Model $model)\n\t{\n\t\t$class_name = $this->class_name;\n\t\t$this->set_keys(get_class($model));\n\n\t\t// since through relationships depend on other relationships we can't do\n\t\t// this initiailization in the constructor since the other relationship\n\t\t// may not have been created yet and we only want this to run once\n\t\tif (!isset($this->initialized))\n\t\t{\n\t\t\tif ($this->through)\n\t\t\t{\n\t\t\t\t// verify through is a belongs_to or has_many for access of keys\n\t\t\t\tif (!($through_relationship = $this->get_table()->get_relationship($this->through)))\n\t\t\t\t\tthrow new HasManyThroughAssociationException(\"Could not find the association $this->through in model \" . get_class($model));\n\n\t\t\t\tif (!($through_relationship instanceof HasMany) && !($through_relationship instanceof BelongsTo))\n\t\t\t\t\tthrow new HasManyThroughAssociationException('has_many through can only use a belongs_to or has_many association');\n\n\t\t\t\t// save old keys as we will be reseting them below for inner join convenience\n\t\t\t\t$pk = $this->primary_key;\n\t\t\t\t$fk = $this->foreign_key;\n\n\t\t\t\t$this->set_keys($this->get_table()->class->getName(), true);\n\t\t\t\t\n\t\t\t\t$class = $this->class_name;\n\t\t\t\t$relation = $class::table()->get_relationship($this->through);\n\t\t\t\t$through_table = $relation->get_table();\n\t\t\t\t$this->options['joins'] = $this->construct_inner_join_sql($through_table, true);\n\n\t\t\t\t// reset keys\n\t\t\t\t$this->primary_key = $pk;\n\t\t\t\t$this->foreign_key = $fk;\n\t\t\t}\n\n\t\t\t$this->initialized = true;\n\t\t}\n\n\t\tif (!($conditions = $this->create_conditions_from_keys($model, $this->foreign_key, $this->primary_key)))\n\t\t\treturn null;\n\n\t\t$options = $this->unset_non_finder_options($this->options);\n\t\t$options['conditions'] = $conditions;\n\t\treturn $class_name::find($this->poly_relationship ? 'all' : 'first',$options);\n\t}\n\n\t/**\n\t * Get an array containing the key and value of the foreign key for the association\n\t *\n\t * @param Model $model\n\t * @access private\n\t * @return array\n\t */\n\tprivate function get_foreign_key_for_new_association(Model $model)\n\t{\n\t\t$this->set_keys($model);\n\t\t$primary_key = Inflector::instance()->variablize($this->foreign_key[0]);\n\n\t\treturn array(\n\t\t\t$primary_key => $model->id,\n\t\t);\n\t}\n\n\tprivate function inject_foreign_key_for_new_association(Model $model, &$attributes)\n\t{\n\t\t$primary_key = $this->get_foreign_key_for_new_association($model);\n\n\t\tif (!isset($attributes[key($primary_key)]))\n\t\t\t$attributes[key($primary_key)] = current($primary_key);\n\n\t\treturn $attributes;\n\t}\n\n\tpublic function build_association(Model $model, $attributes=array(), $guard_attributes=true)\n\t{\n\t\t$relationship_attributes = $this->get_foreign_key_for_new_association($model);\n\n\t\tif ($guard_attributes) {\n\t\t\t// First build the record with just our relationship attributes (unguarded)\n\t\t\t$record = parent::build_association($model, $relationship_attributes, false);\n\n\t\t\t// Then, set our normal attributes (using guarding)\n\t\t\t$record->set_attributes($attributes);\n\t\t} else {\n\t\t\t// Merge our attributes\n\t\t\t$attributes = array_merge($relationship_attributes, $attributes);\n\n\t\t\t// First build the record with just our relationship attributes (unguarded)\n\t\t\t$record = parent::build_association($model, $attributes, $guard_attributes);\n\t\t}\n\n\t\treturn $record;\n\t}\n\n\tpublic function create_association(Model $model, $attributes=array(), $guard_attributes=true)\n\t{\n\t\t$relationship_attributes = $this->get_foreign_key_for_new_association($model);\n\n\t\tif ($guard_attributes) {\n\t\t\t// First build the record with just our relationship attributes (unguarded)\n\t\t\t$record = parent::build_association($model, $relationship_attributes, false);\n\n\t\t\t// Then, set our normal attributes (using guarding)\n\t\t\t$record->set_attributes($attributes);\n\n\t\t\t// Save our model, as a \"create\" instantly saves after building\n\t\t\t$record->save();\n\t\t} else {\n\t\t\t// Merge our attributes\n\t\t\t$attributes = array_merge($relationship_attributes, $attributes);\n\n\t\t\t// First build the record with just our relationship attributes (unguarded)\n\t\t\t$record = parent::create_association($model, $attributes, $guard_attributes);\n\t\t}\n\n\t\treturn $record;\n\t}\n\n\tpublic function load_eagerly($models=array(), $attributes=array(), $includes, Table $table)\n\t{\n\t\t$this->set_keys($table->class->name);\n\t\t$this->query_and_attach_related_models_eagerly($table,$models,$attributes,$includes,$this->foreign_key, $table->pk);\n\t}\n}\n\n/**\n * One-to-one relationship.\n *\n * <code>\n * # Table name: states\n * # Primary key: id\n * class State extends ActiveRecord\\Model {}\n *\n * # Table name: people\n * # Foreign key: state_id\n * class Person extends ActiveRecord\\Model {\n *   static $has_one = array(array('state'));\n * }\n * </code>\n *\n * @package ActiveRecord\n * @see http://www.phpactiverecord.org/guides/associations\n */\nclass HasOne extends HasMany\n{\n}\n\n/**\n * @todo implement me\n * @package ActiveRecord\n * @see http://www.phpactiverecord.org/guides/associations\n */\nclass HasAndBelongsToMany extends AbstractRelationship\n{\n\tpublic function __construct($options=array())\n\t{\n\t\t/* options =>\n\t\t *   join_table - name of the join table if not in lexical order\n\t\t *   foreign_key -\n\t\t *   association_foreign_key - default is {assoc_class}_id\n\t\t *   uniq - if true duplicate assoc objects will be ignored\n\t\t *   validate\n\t\t */\n\t}\n\n\tpublic function load(Model $model)\n\t{\n\n\t}\n}\n\n/**\n * Belongs to relationship.\n *\n * <code>\n * class School extends ActiveRecord\\Model {}\n *\n * class Person extends ActiveRecord\\Model {\n *   static $belongs_to = array(\n *     array('school')\n *   );\n * }\n * </code>\n *\n * Example using options:\n *\n * <code>\n * class School extends ActiveRecord\\Model {}\n *\n * class Person extends ActiveRecord\\Model {\n *   static $belongs_to = array(\n *     array('school', 'primary_key' => 'school_id')\n *   );\n * }\n * </code>\n *\n * @package ActiveRecord\n * @see valid_association_options\n * @see http://www.phpactiverecord.org/guides/associations\n */\nclass BelongsTo extends AbstractRelationship\n{\n\tpublic function __construct($options=array())\n\t{\n\t\tparent::__construct($options);\n\n\t\tif (!$this->class_name)\n\t\t\t$this->set_inferred_class_name();\n\n\t\t//infer from class_name\n\t\tif (!$this->foreign_key)\n\t\t\t$this->foreign_key = array(Inflector::instance()->keyify($this->class_name));\n\t}\n\n\tpublic function __get($name)\n\t{\n\t\tif($name === 'primary_key' && !isset($this->primary_key)) {\n\t\t\t$this->primary_key = array(Table::load($this->class_name)->pk[0]);\n\t\t}\n\n\t\treturn $this->$name;\n\t}\n\n\tpublic function load(Model $model)\n\t{\n\t\t$keys = array();\n\t\t$inflector = Inflector::instance();\n\n\t\tforeach ($this->foreign_key as $key)\n\t\t\t$keys[] = $inflector->variablize($key);\n\n\t\tif (!($conditions = $this->create_conditions_from_keys($model, $this->primary_key, $keys)))\n\t\t\treturn null;\n\n\t\t$options = $this->unset_non_finder_options($this->options);\n\t\t$options['conditions'] = $conditions;\n\t\t$class = $this->class_name;\n\t\treturn $class::first($options);\n\t}\n\n\tpublic function load_eagerly($models=array(), $attributes, $includes, Table $table)\n\t{\n\t\t$this->query_and_attach_related_models_eagerly($table,$models,$attributes,$includes, $this->primary_key,$this->foreign_key);\n\t}\n}"
  },
  {
    "path": "lib/SQLBuilder.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Helper class for building sql statements progmatically.\n *\n * @package ActiveRecord\n */\nclass SQLBuilder\n{\n\tprivate $connection;\n\tprivate $operation = 'SELECT';\n\tprivate $table;\n\tprivate $select = '*';\n\tprivate $joins;\n\tprivate $order;\n\tprivate $limit;\n\tprivate $offset;\n\tprivate $group;\n\tprivate $having;\n\tprivate $update;\n\n\t// for where\n\tprivate $where;\n\tprivate $where_values = array();\n\n\t// for insert/update\n\tprivate $data;\n\tprivate $sequence;\n\n\t/**\n\t * Constructor.\n\t *\n\t * @param Connection $connection A database connection object\n\t * @param string $table Name of a table\n\t * @return SQLBuilder\n\t * @throws ActiveRecordException if connection was invalid\n\t */\n\tpublic function __construct($connection, $table)\n\t{\n\t\tif (!$connection)\n\t\t\tthrow new ActiveRecordException('A valid database connection is required.');\n\n\t\t$this->connection\t= $connection;\n\t\t$this->table\t\t= $table;\n\t}\n\n\t/**\n\t * Returns the SQL string.\n\t *\n\t * @return string\n\t */\n\tpublic function __toString()\n\t{\n\t\treturn $this->to_s();\n\t}\n\n\t/**\n\t * Returns the SQL string.\n\t *\n\t * @see __toString\n\t * @return string\n\t */\n\tpublic function to_s()\n\t{\n\t\t$func = 'build_' . strtolower($this->operation);\n\t\treturn $this->$func();\n\t}\n\n\t/**\n\t * Returns the bind values.\n\t *\n\t * @return array\n\t */\n\tpublic function bind_values()\n\t{\n\t\t$ret = array();\n\n\t\tif ($this->data)\n\t\t\t$ret = array_values($this->data);\n\n\t\tif ($this->get_where_values())\n\t\t\t$ret = array_merge($ret,$this->get_where_values());\n\n\t\treturn array_flatten($ret);\n\t}\n\n\tpublic function get_where_values()\n\t{\n\t\treturn $this->where_values;\n\t}\n\n\tpublic function where(/* (conditions, values) || (hash) */)\n\t{\n\t\t$this->apply_where_conditions(func_get_args());\n\t\treturn $this;\n\t}\n\n\tpublic function order($order)\n\t{\n\t\t$this->order = $order;\n\t\treturn $this;\n\t}\n\n\tpublic function group($group)\n\t{\n\t\t$this->group = $group;\n\t\treturn $this;\n\t}\n\n\tpublic function having($having)\n\t{\n\t\t$this->having = $having;\n\t\treturn $this;\n\t}\n\n\tpublic function limit($limit)\n\t{\n\t\t$this->limit = intval($limit);\n\t\treturn $this;\n\t}\n\n\tpublic function offset($offset)\n\t{\n\t\t$this->offset = intval($offset);\n\t\treturn $this;\n\t}\n\n\tpublic function select($select)\n\t{\n\t\t$this->operation = 'SELECT';\n\t\t$this->select = $select;\n\t\treturn $this;\n\t}\n\n\tpublic function joins($joins)\n\t{\n\t\t$this->joins = $joins;\n\t\treturn $this;\n\t}\n\n\tpublic function insert($hash, $pk=null, $sequence_name=null)\n\t{\n\t\tif (!is_hash($hash))\n\t\t\tthrow new ActiveRecordException('Inserting requires a hash.');\n\n\t\t$this->operation = 'INSERT';\n\t\t$this->data = $hash;\n\n\t\tif ($pk && $sequence_name)\n\t\t\t$this->sequence = array($pk,$sequence_name);\n\n\t\treturn $this;\n\t}\n\n\tpublic function update($mixed)\n\t{\n\t\t$this->operation = 'UPDATE';\n\n\t\tif (is_hash($mixed))\n\t\t\t$this->data = $mixed;\n\t\telseif (is_string($mixed))\n\t\t\t$this->update = $mixed;\n\t\telse\n\t\t\tthrow new ActiveRecordException('Updating requires a hash or string.');\n\n\t\treturn $this;\n\t}\n\n\tpublic function delete()\n\t{\n\t\t$this->operation = 'DELETE';\n\t\t$this->apply_where_conditions(func_get_args());\n\t\treturn $this;\n\t}\n\n\t/**\n\t * Reverses an order clause.\n\t */\n\tpublic static function reverse_order($order)\n\t{\n\t\tif (!trim($order))\n\t\t\treturn $order;\n\n\t\t$parts = explode(',',$order);\n\n\t\tfor ($i=0,$n=count($parts); $i<$n; ++$i)\n\t\t{\n\t\t\t$v = strtolower($parts[$i]);\n\n\t\t\tif (strpos($v,' asc') !== false)\n\t\t\t\t$parts[$i] = preg_replace('/asc/i','DESC',$parts[$i]);\n\t\t\telseif (strpos($v,' desc') !== false)\n\t\t\t\t$parts[$i] = preg_replace('/desc/i','ASC',$parts[$i]);\n\t\t\telse\n\t\t\t\t$parts[$i] .= ' DESC';\n\t\t}\n\t\treturn join(',',$parts);\n\t}\n\n\t/**\n\t * Converts a string like \"id_and_name_or_z\" into a conditions value like array(\"id=? AND name=? OR z=?\", values, ...).\n\t *\n\t * @param Connection $connection\n\t * @param $name Underscored string\n\t * @param $values Array of values for the field names. This is used\n\t *   to determine what kind of bind marker to use: =?, IN(?), IS NULL\n\t * @param $map A hash of \"mapped_column_name\" => \"real_column_name\"\n\t * @return A conditions array in the form array(sql_string, value1, value2,...)\n\t */\n\tpublic static function create_conditions_from_underscored_string(Connection $connection, $name, &$values=array(), &$map=null)\n\t{\n\t\tif (!$name)\n\t\t\treturn null;\n\n\t\t$parts = preg_split('/(_and_|_or_)/i',$name,-1,PREG_SPLIT_DELIM_CAPTURE);\n\t\t$num_values = count($values);\n\t\t$conditions = array('');\n\n\t\tfor ($i=0,$j=0,$n=count($parts); $i<$n; $i+=2,++$j)\n\t\t{\n\t\t\tif ($i >= 2)\n\t\t\t\t$conditions[0] .= preg_replace(array('/_and_/i','/_or_/i'),array(' AND ',' OR '),$parts[$i-1]);\n\n\t\t\tif ($j < $num_values)\n\t\t\t{\n\t\t\t\tif (!is_null($values[$j]))\n\t\t\t\t{\n\t\t\t\t\t$bind = is_array($values[$j]) ? ' IN(?)' : '=?';\n\t\t\t\t\t$conditions[] = $values[$j];\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\t$bind = ' IS NULL';\n\t\t\t}\n\t\t\telse\n\t\t\t\t$bind = ' IS NULL';\n\n\t\t\t// map to correct name if $map was supplied\n\t\t\t$name = $map && isset($map[$parts[$i]]) ? $map[$parts[$i]] : $parts[$i];\n\n\t\t\t$conditions[0] .= $connection->quote_name($name) . $bind;\n\t\t}\n\t\treturn $conditions;\n\t}\n\n\t/**\n\t * Like create_conditions_from_underscored_string but returns a hash of name => value array instead.\n\t *\n\t * @param string $name A string containing attribute names connected with _and_ or _or_\n\t * @param $args Array of values for each attribute in $name\n\t * @param $map A hash of \"mapped_column_name\" => \"real_column_name\"\n\t * @return array A hash of array(name => value, ...)\n\t */\n\tpublic static function create_hash_from_underscored_string($name, &$values=array(), &$map=null)\n\t{\n\t\t$parts = preg_split('/(_and_|_or_)/i',$name);\n\t\t$hash = array();\n\n\t\tfor ($i=0,$n=count($parts); $i<$n; ++$i)\n\t\t{\n\t\t\t// map to correct name if $map was supplied\n\t\t\t$name = $map && isset($map[$parts[$i]]) ? $map[$parts[$i]] : $parts[$i];\n\t\t\t$hash[$name] = $values[$i];\n\t\t}\n\t\treturn $hash;\n\t}\n\n\t/**\n\t * prepends table name to hash of field names to get around ambiguous fields when SQL builder\n\t * has joins\n\t *\n\t * @param array $hash\n\t * @return array $new\n\t */\n\tprivate function prepend_table_name_to_fields($hash=array())\n\t{\n\t\t$new = array();\n\t\t$table = $this->connection->quote_name($this->table);\n\n\t\tforeach ($hash as $key => $value)\n\t\t{\n\t\t\t$k = $this->connection->quote_name($key);\n\t\t\t$new[$table.'.'.$k] = $value;\n\t\t}\n\n\t\treturn $new;\n\t}\n\n\tprivate function apply_where_conditions($args)\n\t{\n\t\trequire_once 'Expressions.php';\n\t\t$num_args = count($args);\n\n\t\tif ($num_args == 1 && is_hash($args[0]))\n\t\t{\n\t\t\t$hash = is_null($this->joins) ? $args[0] : $this->prepend_table_name_to_fields($args[0]);\n\t\t\t$e = new Expressions($this->connection,$hash);\n\t\t\t$this->where = $e->to_s();\n\t\t\t$this->where_values = array_flatten($e->values());\n\t\t}\n\t\telseif ($num_args > 0)\n\t\t{\n\t\t\t// if the values has a nested array then we'll need to use Expressions to expand the bind marker for us\n\t\t\t$values = array_slice($args,1);\n\n\t\t\tforeach ($values as $name => &$value)\n\t\t\t{\n\t\t\t\tif (is_array($value))\n\t\t\t\t{\n\t\t\t\t\t$e = new Expressions($this->connection,$args[0]);\n\t\t\t\t\t$e->bind_values($values);\n\t\t\t\t\t$this->where = $e->to_s();\n\t\t\t\t\t$this->where_values = array_flatten($e->values());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// no nested array so nothing special to do\n\t\t\t$this->where = $args[0];\n\t\t\t$this->where_values = &$values;\n\t\t}\n\t}\n\n\tprivate function build_delete()\n\t{\n\t\t$sql = \"DELETE FROM $this->table\";\n\n\t\tif ($this->where)\n\t\t\t$sql .= \" WHERE $this->where\";\n\n\t\tif ($this->connection->accepts_limit_and_order_for_update_and_delete())\n\t\t{\n\t\t\tif ($this->order)\n\t\t\t\t$sql .= \" ORDER BY $this->order\";\n\n\t\t\tif ($this->limit)\n\t\t\t\t$sql = $this->connection->limit($sql,null,$this->limit);\n\t\t}\n\n\t\treturn $sql;\n\t}\n\n\tprivate function build_insert()\n\t{\n\t\trequire_once 'Expressions.php';\n\t\t$keys = join(',',$this->quoted_key_names());\n\n\t\tif ($this->sequence)\n\t\t{\n\t\t\t$sql =\n\t\t\t\t\"INSERT INTO $this->table($keys,\" . $this->connection->quote_name($this->sequence[0]) .\n\t\t\t\t\") VALUES(?,\" . $this->connection->next_sequence_value($this->sequence[1]) . \")\";\n\t\t}\n\t\telse\n\t\t\t$sql = \"INSERT INTO $this->table($keys) VALUES(?)\";\n\n\t\t$e = new Expressions($this->connection,$sql,array_values($this->data));\n\t\treturn $e->to_s();\n\t}\n\n\tprivate function build_select()\n\t{\n\t\t$sql = \"SELECT $this->select FROM $this->table\";\n\n\t\tif ($this->joins)\n\t\t\t$sql .= ' ' . $this->joins;\n\n\t\tif ($this->where)\n\t\t\t$sql .= \" WHERE $this->where\";\n\n\t\tif ($this->group)\n\t\t\t$sql .= \" GROUP BY $this->group\";\n\n\t\tif ($this->having)\n\t\t\t$sql .= \" HAVING $this->having\";\n\n\t\tif ($this->order)\n\t\t\t$sql .= \" ORDER BY $this->order\";\n\n\t\tif ($this->limit || $this->offset)\n\t\t\t$sql = $this->connection->limit($sql,$this->offset,$this->limit);\n\n\t\treturn $sql;\n\t}\n\n\tprivate function build_update()\n\t{\n\t\tif (strlen($this->update) > 0)\n\t\t\t$set = $this->update;\n\t\telse\n\t\t\t$set = join('=?, ', $this->quoted_key_names()) . '=?';\n\n\t\t$sql = \"UPDATE $this->table SET $set\";\n\n\t\tif ($this->where)\n\t\t\t$sql .= \" WHERE $this->where\";\n\n\t\tif ($this->connection->accepts_limit_and_order_for_update_and_delete())\n\t\t{\n\t\t\tif ($this->order)\n\t\t\t\t$sql .= \" ORDER BY $this->order\";\n\n\t\t\tif ($this->limit)\n\t\t\t\t$sql = $this->connection->limit($sql,null,$this->limit);\n\t\t}\n\n\t\treturn $sql;\n\t}\n\n\tprivate function quoted_key_names()\n\t{\n\t\t$keys = array();\n\n\t\tforeach ($this->data as $key => $value)\n\t\t\t$keys[] = $this->connection->quote_name($key);\n\n\t\treturn $keys;\n\t}\n}"
  },
  {
    "path": "lib/Serialization.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\nuse XmlWriter;\n\n/**\n * Base class for Model serializers.\n *\n * All serializers support the following options:\n *\n * <ul>\n * <li><b>only:</b> a string or array of attributes to be included.</li>\n * <li><b>except:</b> a string or array of attributes to be excluded.</li>\n * <li><b>methods:</b> a string or array of methods to invoke. The method's name will be used as a key for the final attributes array\n * along with the method's returned value</li>\n * <li><b>include:</b> a string or array of associated models to include in the final serialized product.</li>\n * <li><b>only_method:</b> a method that's called and only the resulting array is serialized\n * <li><b>skip_instruct:</b> set to true to skip the <?xml ...?> declaration.</li>\n * </ul>\n *\n * Example usage:\n *\n * <code>\n * # include the attributes id and name\n * # run $model->encoded_description() and include its return value\n * # include the comments association\n * # include posts association with its own options (nested)\n * $model->to_json(array(\n *   'only' => array('id','name', 'encoded_description'),\n *   'methods' => array('encoded_description'),\n *   'include' => array('comments', 'posts' => array('only' => 'id'))\n * ));\n *\n * # except the password field from being included\n * $model->to_xml(array('except' => 'password')));\n * </code>\n *\n * @package ActiveRecord\n * @link http://www.phpactiverecord.org/guides/utilities#topic-serialization\n */\nabstract class Serialization\n{\n\tprotected $model;\n\tprotected $options;\n\tprotected $attributes;\n\n\t/**\n\t * The default format to serialize DateTime objects to.\n\t *\n\t * @see DateTime\n\t */\n\tpublic static $DATETIME_FORMAT = 'iso8601';\n\n\t/**\n\t * Set this to true if the serializer needs to create a nested array keyed\n\t * on the name of the included classes such as for xml serialization.\n\t *\n\t * Setting this to true will produce the following attributes array when\n\t * the include option was used:\n\t *\n\t * <code>\n\t * $user = array('id' => 1, 'name' => 'Tito',\n\t *   'permissions' => array(\n\t *     'permission' => array(\n\t *       array('id' => 100, 'name' => 'admin'),\n\t *       array('id' => 101, 'name' => 'normal')\n\t *     )\n\t *   )\n\t * );\n\t * </code>\n\t *\n\t * Setting to false will produce this:\n\t *\n\t * <code>\n\t * $user = array('id' => 1, 'name' => 'Tito',\n\t *   'permissions' => array(\n\t *     array('id' => 100, 'name' => 'admin'),\n\t *     array('id' => 101, 'name' => 'normal')\n\t *   )\n\t * );\n\t * </code>\n\t *\n\t * @var boolean\n\t */\n\tprotected $includes_with_class_name_element = false;\n\n\t/**\n\t * Constructs a {@link Serialization} object.\n\t *\n\t * @param Model $model The model to serialize\n\t * @param array &$options Options for serialization\n\t * @return Serialization\n\t */\n\tpublic function __construct(Model $model, &$options)\n\t{\n\t\t$this->model = $model;\n\t\t$this->options = $options;\n\t\t$this->attributes = $model->attributes();\n\t\t$this->parse_options();\n\t}\n\n\tprivate function parse_options()\n\t{\n\t\t$this->check_only();\n\t\t$this->check_except();\n\t\t$this->check_methods();\n\t\t$this->check_include();\n\t\t$this->check_only_method();        \n\t}\n\n\tprivate function check_only()\n\t{\n\t\tif (isset($this->options['only']))\n\t\t{\n\t\t\t$this->options_to_a('only');\n\n\t\t\t$exclude = array_diff(array_keys($this->attributes),$this->options['only']);\n\t\t\t$this->attributes = array_diff_key($this->attributes,array_flip($exclude));\n\t\t}\n\t}\n\n\tprivate function check_except()\n\t{\n\t\tif (isset($this->options['except']) && !isset($this->options['only']))\n\t\t{\n\t\t\t$this->options_to_a('except');\n\t\t\t$this->attributes = array_diff_key($this->attributes,array_flip($this->options['except']));\n\t\t}\n\t}\n\n\tprivate function check_methods()\n\t{\n\t\tif (isset($this->options['methods']))\n\t\t{\n\t\t\t$this->options_to_a('methods');\n\n\t\t\tforeach ($this->options['methods'] as $method)\n\t\t\t{\n\t\t\t\tif (method_exists($this->model, $method))\n\t\t\t\t\t$this->attributes[$method] = $this->model->$method();\n\t\t\t}\n\t\t}\n\t}\n\t\n\tprivate function check_only_method()\n\t{\n\t\tif (isset($this->options['only_method']))\n\t\t{\n\t\t\t$method = $this->options['only_method'];\n\t\t\tif (method_exists($this->model, $method))\n\t\t\t\t$this->attributes = $this->model->$method();\n\t\t}\n\t}\n\n\tprivate function check_include()\n\t{\n\t\tif (isset($this->options['include']))\n\t\t{\n\t\t\t$this->options_to_a('include');\n\n\t\t\t$serializer_class = get_class($this);\n\n\t\t\tforeach ($this->options['include'] as $association => $options)\n\t\t\t{\n\t\t\t\tif (!is_array($options))\n\t\t\t\t{\n\t\t\t\t\t$association = $options;\n\t\t\t\t\t$options = array();\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\t$assoc = $this->model->$association;\n\n\t\t\t\t\tif ($assoc === null)\n\t\t\t\t\t{\n\t\t\t\t\t\t$this->attributes[$association] = null;\n\t\t\t\t\t}\n\t\t\t\t\telseif (!is_array($assoc))\n\t\t\t\t\t{\n\t\t\t\t\t\t$serialized = new $serializer_class($assoc, $options);\n\t\t\t\t\t\t$this->attributes[$association] = $serialized->to_a();;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t$includes = array();\n\n\t\t\t\t\t\tforeach ($assoc as $a)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t$serialized = new $serializer_class($a, $options);\n\n\t\t\t\t\t\t\tif ($this->includes_with_class_name_element)\n\t\t\t\t\t\t\t\t$includes[strtolower(get_class($a))][] = $serialized->to_a();\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t$includes[] = $serialized->to_a();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t$this->attributes[$association] = $includes;\n\t\t\t\t\t}\n\n\t\t\t\t} catch (UndefinedPropertyException $e) {\n\t\t\t\t\t;//move along\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfinal protected function options_to_a($key)\n\t{\n\t\tif (!is_array($this->options[$key]))\n\t\t\t$this->options[$key] = array($this->options[$key]);\n\t}\n\n\t/**\n\t * Returns the attributes array.\n\t * @return array\n\t */\n\tfinal public function to_a()\n\t{\n\t\t$date_class = Config::instance()->get_date_class();\n\t\tforeach ($this->attributes as &$value)\n\t\t{\n\t\t\tif ($value instanceof $date_class)\n\t\t\t\t$value = $value->format(self::$DATETIME_FORMAT);\n\t\t}\n\t\treturn $this->attributes;\n\t}\n\n\t/**\n\t * Returns the serialized object as a string.\n\t * @see to_s\n\t * @return string\n\t */\n\tfinal public function __toString()\n\t{\n\t\treturn $this->to_s();\n\t}\n\n\t/**\n\t * Performs the serialization.\n\t * @return string\n\t */\n\tabstract public function to_s();\n};\n\n/**\n * Array serializer.\n *\n * @package ActiveRecord\n */\nclass ArraySerializer extends Serialization\n{\n\tpublic static $include_root = false;\n\n\tpublic function to_s()\n\t{\n\t\treturn self::$include_root ? array(strtolower(get_class($this->model)) => $this->to_a()) : $this->to_a();\n\t}\n}\n\n/**\n * JSON serializer.\n *\n * @package ActiveRecord\n */\nclass JsonSerializer extends ArraySerializer\n{\n\tpublic static $include_root = false;\n\n\tpublic function to_s()\n\t{\n\t\tparent::$include_root = self::$include_root;\n\t\treturn json_encode(parent::to_s());\n\t}\n}\n\n/**\n * XML serializer.\n *\n * @package ActiveRecord\n */\nclass XmlSerializer extends Serialization\n{\n\tprivate $writer;\n\n\tpublic function __construct(Model $model, &$options)\n\t{\n\t\t$this->includes_with_class_name_element = true;\n\t\tparent::__construct($model,$options);\n\t}\n\n\tpublic function to_s()\n\t{\n\t\treturn $this->xml_encode();\n\t}\n\n\tprivate function xml_encode()\n\t{\n\t\t$this->writer = new XmlWriter();\n\t\t$this->writer->openMemory();\n\t\t$this->writer->startDocument('1.0', 'UTF-8');\n\t\t$this->writer->startElement(strtolower(denamespace(($this->model))));\n\t\t$this->write($this->to_a());\n\t\t$this->writer->endElement();\n\t\t$this->writer->endDocument();\n\t\t$xml = $this->writer->outputMemory(true);\n\n\t\tif (@$this->options['skip_instruct'] == true)\n\t\t\t$xml = preg_replace('/<\\?xml version.*?\\?>/','',$xml);\n\n\t\treturn $xml;\n\t}\n\n\tprivate function write($data, $tag=null)\n\t{\n\t\tforeach ($data as $attr => $value)\n\t\t{\n\t\t\tif ($tag != null)\n\t\t\t\t$attr = $tag;\n\n\t\t\tif (is_array($value) || is_object($value))\n\t\t\t{\n\t\t\t\tif (!is_int(key($value)))\n\t\t\t\t{\n\t\t\t\t\t$this->writer->startElement($attr);\n\t\t\t\t\t$this->write($value);\n\t\t\t\t\t$this->writer->endElement();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\t$this->write($value, $attr);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$this->writer->writeElement($attr, $value);\n\t\t}\n\t}\n}\n\n/**\n * CSV serializer.\n *\n * @package ActiveRecord\n */\nclass CsvSerializer extends Serialization\n{\n  public static $delimiter = ',';\n  public static $enclosure = '\"';\n\n  public function to_s()\n  {\n    if (@$this->options['only_header'] == true) return $this->header();\n    return $this->row();\n  }\n\n  private function header()\n  {\n    return $this->to_csv(array_keys($this->to_a()));\n  }\n\n  private function row()\n  {\n    return $this->to_csv($this->to_a());\n  }\n\n  private function to_csv($arr)\n  {\n    $outstream = fopen('php://temp', 'w');\n    fputcsv($outstream, $arr, self::$delimiter, self::$enclosure);\n    rewind($outstream);\n    $buffer = trim(stream_get_contents($outstream));\n    fclose($outstream);\n    return $buffer;\n  }\n}"
  },
  {
    "path": "lib/Singleton.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * This implementation of the singleton pattern does not conform to the strong definition\n * given by the \"Gang of Four.\" The __construct() method has not be privatized so that\n * a singleton pattern is capable of being achieved; however, multiple instantiations are also\n * possible. This allows the user more freedom with this pattern.\n *\n * @package ActiveRecord\n */\nabstract class Singleton\n{\n\t/**\n\t * Array of cached singleton objects.\n\t *\n\t * @var array\n\t */\n\tprivate static $instances = array();\n\n\t/**\n\t * Static method for instantiating a singleton object.\n\t *\n\t * @return object\n\t */\n\tfinal public static function instance()\n\t{\n\t\t$class_name = get_called_class();\n\n\t\tif (!isset(self::$instances[$class_name]))\n\t\t\tself::$instances[$class_name] = new $class_name;\n\n\t\treturn self::$instances[$class_name];\n\t}\n\n\t/**\n\t * Singleton objects should not be cloned.\n\t *\n\t * @return void\n\t */\n\tfinal private function __clone() {}\n\n\t/**\n\t * Similar to a get_called_class() for a child class to invoke.\n\t *\n\t * @return string\n\t */\n\tfinal protected function get_called_class()\n\t{\n\t\t$backtrace = debug_backtrace();\n\t\treturn get_class($backtrace[2]['object']);\n\t}\n}"
  },
  {
    "path": "lib/Table.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Manages reading and writing to a database table.\n *\n * This class manages a database table and is used by the Model class for\n * reading and writing to its database table. There is one instance of Table\n * for every table you have a model for.\n *\n * @package ActiveRecord\n */\nclass Table\n{\n\tprivate static $cache = array();\n\n\tpublic $class;\n\tpublic $conn;\n\tpublic $pk;\n\tpublic $last_sql;\n\n\t// Name/value pairs of columns in this table\n\tpublic $columns = array();\n\n\t/**\n\t * Name of the table.\n\t */\n\tpublic $table;\n\n\t/**\n\t * Name of the database (optional)\n\t */\n\tpublic $db_name;\n\n\t/**\n\t * Name of the sequence for this table (optional). Defaults to {$table}_seq\n\t */\n\tpublic $sequence;\n\n\t/**\n\t * Whether to cache individual models or not (not to be confused with caching of table schemas).\n\t */\n\tpublic $cache_individual_model;\n\n\t/**\n\t * Expiration period for model caching.\n\t */\n\tpublic $cache_model_expire;\n\n\t/**\n\t * A instance of CallBack for this model/table\n\t * @static\n\t * @var object ActiveRecord\\CallBack\n\t */\n\tpublic $callback;\n\n\t/**\n\t * List of relationships for this table.\n\t */\n\tprivate $relationships = array();\n\n\tpublic static function load($model_class_name)\n\t{\n\t\tif (!isset(self::$cache[$model_class_name]))\n\t\t{\n\t\t\t/* do not place set_assoc in constructor..it will lead to infinite loop due to\n\t\t\t   relationships requesting the model's table, but the cache hasn't been set yet */\n\t\t\tself::$cache[$model_class_name] = new Table($model_class_name);\n\t\t\tself::$cache[$model_class_name]->set_associations();\n\t\t}\n\n\t\treturn self::$cache[$model_class_name];\n\t}\n\n\tpublic static function clear_cache($model_class_name=null)\n\t{\n\t\tif ($model_class_name && array_key_exists($model_class_name,self::$cache))\n\t\t\tunset(self::$cache[$model_class_name]);\n\t\telse\n\t\t\tself::$cache = array();\n\t}\n\n\tpublic function __construct($class_name)\n\t{\n\t\t$this->class = Reflections::instance()->add($class_name)->get($class_name);\n\n\t\t$this->reestablish_connection(false);\n\t\t$this->set_table_name();\n\t\t$this->get_meta_data();\n\t\t$this->set_primary_key();\n\t\t$this->set_sequence_name();\n\t\t$this->set_delegates();\n\t\t$this->set_cache();\n\t\t$this->set_setters_and_getters();\n\n\t\t$this->callback = new CallBack($class_name);\n\t\t$this->callback->register('before_save', function(Model $model) { $model->set_timestamps(); }, array('prepend' => true));\n\t\t$this->callback->register('after_save', function(Model $model) { $model->reset_dirty(); }, array('prepend' => true));\n\t}\n\n\tpublic function reestablish_connection($close=true)\n\t{\n\t\t// if connection name property is null the connection manager will use the default connection\n\t\t$connection = $this->class->getStaticPropertyValue('connection',null);\n\n\t\tif ($close)\n\t\t{\n\t\t\tConnectionManager::drop_connection($connection);\n\t\t\tstatic::clear_cache();\n\t\t}\n\t\treturn ($this->conn = ConnectionManager::get_connection($connection));\n\t}\n\n\tpublic function create_joins($joins)\n\t{\n\t\tif (!is_array($joins))\n\t\t\treturn $joins;\n\n\t\t$ret = $space = '';\n\n\t\t$existing_tables = array();\n\t\tforeach ($joins as $value)\n\t\t{\n\t\t\t$ret .= $space;\n\n\t\t\tif (stripos($value,'JOIN ') === false)\n\t\t\t{\n\t\t\t\tif (array_key_exists($value, $this->relationships))\n\t\t\t\t{\n\t\t\t\t\t$rel = $this->get_relationship($value);\n\n\t\t\t\t\t// if there is more than 1 join for a given table we need to alias the table names\n\t\t\t\t\tif (array_key_exists($rel->class_name, $existing_tables))\n\t\t\t\t\t{\n\t\t\t\t\t\t$alias = $value;\n\t\t\t\t\t\t$existing_tables[$rel->class_name]++;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t$existing_tables[$rel->class_name] = true;\n\t\t\t\t\t\t$alias = null;\n\t\t\t\t\t}\n\n\t\t\t\t\t$ret .= $rel->construct_inner_join_sql($this, false, $alias);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tthrow new RelationshipException(\"Relationship named $value has not been declared for class: {$this->class->getName()}\");\n\t\t\t}\n\t\t\telse\n\t\t\t\t$ret .= $value;\n\n\t\t\t$space = ' ';\n\t\t}\n\t\treturn $ret;\n\t}\n\n\tpublic function options_to_sql($options)\n\t{\n\t\t$table = array_key_exists('from', $options) ? $options['from'] : $this->get_fully_qualified_table_name();\n\t\t$sql = new SQLBuilder($this->conn, $table);\n\n\t\tif (array_key_exists('joins',$options))\n\t\t{\n\t\t\t$sql->joins($this->create_joins($options['joins']));\n\n\t\t\t// by default, an inner join will not fetch the fields from the joined table\n\t\t\tif (!array_key_exists('select', $options))\n\t\t\t\t$options['select'] = $this->get_fully_qualified_table_name() . '.*';\n\t\t}\n\n\t\tif (array_key_exists('select',$options))\n\t\t\t$sql->select($options['select']);\n\n\t\tif (array_key_exists('conditions',$options))\n\t\t{\n\t\t\tif (!is_hash($options['conditions']))\n\t\t\t{\n\t\t\t\tif (is_string($options['conditions']))\n\t\t\t\t\t$options['conditions'] = array($options['conditions']);\n\n\t\t\t\tcall_user_func_array(array($sql,'where'),$options['conditions']);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (!empty($options['mapped_names']))\n\t\t\t\t\t$options['conditions'] = $this->map_names($options['conditions'],$options['mapped_names']);\n\n\t\t\t\t$sql->where($options['conditions']);\n\t\t\t}\n\t\t}\n\n\t\tif (array_key_exists('order',$options))\n\t\t\t$sql->order($options['order']);\n\n\t\tif (array_key_exists('limit',$options))\n\t\t\t$sql->limit($options['limit']);\n\n\t\tif (array_key_exists('offset',$options))\n\t\t\t$sql->offset($options['offset']);\n\n\t\tif (array_key_exists('group',$options))\n\t\t\t$sql->group($options['group']);\n\n\t\tif (array_key_exists('having',$options))\n\t\t\t$sql->having($options['having']);\n\n\t\treturn $sql;\n\t}\n\n\tpublic function find($options)\n\t{\n\t\t$sql = $this->options_to_sql($options);\n\t\t$readonly = (array_key_exists('readonly',$options) && $options['readonly']) ? true : false;\n\t\t$eager_load = array_key_exists('include',$options) ? $options['include'] : null;\n\n\t\treturn $this->find_by_sql($sql->to_s(),$sql->get_where_values(), $readonly, $eager_load);\n\t}\n\n\tpublic function cache_key_for_model($pk)\n\t{\n\t\tif (is_array($pk))\n\t\t{\n\t\t\t$pk = implode('-', $pk);\n\t\t}\n\t\treturn $this->class->name . '-' . $pk;\n\t}\n\n\tpublic function find_by_sql($sql, $values=null, $readonly=false, $includes=null)\n\t{\n\t\t$this->last_sql = $sql;\n\n\t\t$collect_attrs_for_includes = is_null($includes) ? false : true;\n\t\t$list = $attrs = array();\n\t\t$sth = $this->conn->query($sql,$this->process_data($values));\n\n\t\t$self = $this;\n\t\twhile (($row = $sth->fetch()))\n\t\t{\n\t\t\t$cb = function() use ($row, $self)\n\t\t\t{\n\t\t\t\treturn new $self->class->name($row, false, true, false);\n\t\t\t};\n\t\t\tif ($this->cache_individual_model)\n\t\t\t{\n\t\t\t\t$key = $this->cache_key_for_model(array_intersect_key($row, array_flip($this->pk)));\n\t\t\t\t$model = Cache::get($key, $cb, $this->cache_model_expire);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t$model = $cb();\n\t\t\t}\n\n\t\t\tif ($readonly)\n\t\t\t\t$model->readonly();\n\n\t\t\tif ($collect_attrs_for_includes)\n\t\t\t\t$attrs[] = $model->attributes();\n\n\t\t\t$list[] = $model;\n\t\t}\n\n\t\tif ($collect_attrs_for_includes && !empty($list))\n\t\t\t$this->execute_eager_load($list, $attrs, $includes);\n\n\t\treturn $list;\n\t}\n\n\t/**\n\t * Executes an eager load of a given named relationship for this table.\n\t *\n\t * @param $models array found modesl for this table\n\t * @param $attrs array of attrs from $models\n\t * @param $includes array eager load directives\n\t * @return void\n\t */\n\tprivate function execute_eager_load($models=array(), $attrs=array(), $includes=array())\n\t{\n\t\tif (!is_array($includes))\n\t\t\t$includes = array($includes);\n\n\t\tforeach ($includes as $index => $name)\n\t\t{\n\t\t\t// nested include\n\t\t\tif (is_array($name))\n\t\t\t{\n\t\t\t\t$nested_includes = count($name) > 0 ? $name : array();\n\t\t\t\t$name = $index;\n\t\t\t}\n\t\t\telse\n\t\t\t\t$nested_includes = array();\n\n\t\t\t$rel = $this->get_relationship($name, true);\n\t\t\t$rel->load_eagerly($models, $attrs, $nested_includes, $this);\n\t\t}\n\t}\n\n\tpublic function get_column_by_inflected_name($inflected_name)\n\t{\n\t\tforeach ($this->columns as $raw_name => $column)\n\t\t{\n\t\t\tif ($column->inflected_name == $inflected_name)\n\t\t\t\treturn $column;\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic function get_fully_qualified_table_name($quote_name=true)\n\t{\n\t\t$table = $quote_name ? $this->conn->quote_name($this->table) : $this->table;\n\n\t\tif ($this->db_name)\n\t\t\t$table = $this->conn->quote_name($this->db_name) . \".$table\";\n\n\t\treturn $table;\n\t}\n\n\t/**\n\t * Retrieve a relationship object for this table. Strict as true will throw an error\n\t * if the relationship name does not exist.\n\t *\n\t * @param $name string name of Relationship\n\t * @param $strict bool\n\t * @throws RelationshipException\n\t * @return HasOne|HasMany|BelongsTo Relationship or null\n\t */\n\tpublic function get_relationship($name, $strict=false)\n\t{\n\t\tif ($this->has_relationship($name))\n\t\t\treturn $this->relationships[$name];\n\n\t\tif ($strict)\n\t\t\tthrow new RelationshipException(\"Relationship named $name has not been declared for class: {$this->class->getName()}\");\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Does a given relationship exist?\n\t *\n\t * @param $name string name of Relationship\n\t * @return bool\n\t */\n\tpublic function has_relationship($name)\n\t{\n\t\treturn array_key_exists($name, $this->relationships);\n\t}\n\n\tpublic function insert(&$data, $pk=null, $sequence_name=null)\n\t{\n\t\t$data = $this->process_data($data);\n\n\t\t$sql = new SQLBuilder($this->conn,$this->get_fully_qualified_table_name());\n\t\t$sql->insert($data,$pk,$sequence_name);\n\n\t\t$values = array_values($data);\n\t\treturn $this->conn->query(($this->last_sql = $sql->to_s()),$values);\n\t}\n\n\tpublic function update(&$data, $where)\n\t{\n\t\t$data = $this->process_data($data);\n\n\t\t$sql = new SQLBuilder($this->conn,$this->get_fully_qualified_table_name());\n\t\t$sql->update($data)->where($where);\n\n\t\t$values = $sql->bind_values();\n\t\treturn $this->conn->query(($this->last_sql = $sql->to_s()),$values);\n\t}\n\n\tpublic function delete($data)\n\t{\n\t\t$data = $this->process_data($data);\n\n\t\t$sql = new SQLBuilder($this->conn,$this->get_fully_qualified_table_name());\n\t\t$sql->delete($data);\n\n\t\t$values = $sql->bind_values();\n\t\treturn $this->conn->query(($this->last_sql = $sql->to_s()),$values);\n\t}\n\n\t/**\n\t * Add a relationship.\n\t *\n\t * @param Relationship $relationship a Relationship object\n\t */\n\tprivate function add_relationship($relationship)\n\t{\n\t\t$this->relationships[$relationship->attribute_name] = $relationship;\n\t}\n\n\tprivate function get_meta_data()\n\t{\n\t\t// as more adapters are added probably want to do this a better way\n\t\t// than using instanceof but gud enuff for now\n\t\t$quote_name = !($this->conn instanceof PgsqlAdapter);\n\n\t\t$table_name = $this->get_fully_qualified_table_name($quote_name);\n\t\t$conn = $this->conn;\n\t\t$this->columns = Cache::get(\"get_meta_data-$table_name\", function() use ($conn, $table_name) { return $conn->columns($table_name); });\n\t}\n\n\t/**\n\t * Replaces any aliases used in a hash based condition.\n\t *\n\t * @param $hash array A hash\n\t * @param $map array Hash of used_name => real_name\n\t * @return array Array with any aliases replaced with their read field name\n\t */\n\tprivate function map_names(&$hash, &$map)\n\t{\n\t\t$ret = array();\n\n\t\tforeach ($hash as $name => &$value)\n\t\t{\n\t\t\tif (array_key_exists($name,$map))\n\t\t\t\t$name = $map[$name];\n\n\t\t\t$ret[$name] = $value;\n\t\t}\n\t\treturn $ret;\n\t}\n\n\tprivate function &process_data($hash)\n\t{\n\t\tif (!$hash)\n\t\t\treturn $hash;\n\n\t\t$date_class = Config::instance()->get_date_class();\n\t\tforeach ($hash as $name => &$value)\n\t\t{\n\t\t\tif ($value instanceof $date_class || $value instanceof \\DateTime)\n\t\t\t{\n\t\t\t\tif (isset($this->columns[$name]) && $this->columns[$name]->type == Column::DATE)\n\t\t\t\t\t$hash[$name] = $this->conn->date_to_string($value);\n\t\t\t\telse\n\t\t\t\t\t$hash[$name] = $this->conn->datetime_to_string($value);\n\t\t\t}\n\t\t\telse\n\t\t\t\t$hash[$name] = $value;\n\t\t}\n\t\treturn $hash;\n\t}\n\n\tprivate function set_primary_key()\n\t{\n\t\tif (($pk = $this->class->getStaticPropertyValue('pk',null)) || ($pk = $this->class->getStaticPropertyValue('primary_key',null)))\n\t\t\t$this->pk = is_array($pk) ? $pk : array($pk);\n\t\telse\n\t\t{\n\t\t\t$this->pk = array();\n\n\t\t\tforeach ($this->columns as $c)\n\t\t\t{\n\t\t\t\tif ($c->pk)\n\t\t\t\t\t$this->pk[] = $c->inflected_name;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate function set_table_name()\n\t{\n\t\tif (($table = $this->class->getStaticPropertyValue('table',null)) || ($table = $this->class->getStaticPropertyValue('table_name',null)))\n\t\t\t$this->table = $table;\n\t\telse\n\t\t{\n\t\t\t// infer table name from the class name\n\t\t\t$this->table = Inflector::instance()->tableize($this->class->getName());\n\n\t\t\t// strip namespaces from the table name if any\n\t\t\t$parts = explode('\\\\',$this->table);\n\t\t\t$this->table = $parts[count($parts)-1];\n\t\t}\n\n\t\tif (($db = $this->class->getStaticPropertyValue('db',null)) || ($db = $this->class->getStaticPropertyValue('db_name',null)))\n\t\t\t$this->db_name = $db;\n\t}\n\n\tprivate function set_cache()\n\t{\n\t\tif (!Cache::$adapter)\n\t\t\treturn;\n\n\t\t$model_class_name = $this->class->name;\n\t\t$this->cache_individual_model = $model_class_name::$cache;\n\t\tif (property_exists($model_class_name, 'cache_expire') && isset($model_class_name::$cache_expire))\n\t\t{\n\t\t\t$this->cache_model_expire =  $model_class_name::$cache_expire;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t$this->cache_model_expire = Cache::$options['expire'];\n\t\t}\n\t}\n\n\tprivate function set_sequence_name()\n\t{\n\t\tif (!$this->conn->supports_sequences())\n\t\t\treturn;\n\n\t\tif (!($this->sequence = $this->class->getStaticPropertyValue('sequence')))\n\t\t\t$this->sequence = $this->conn->get_sequence_name($this->table,$this->pk[0]);\n\t}\n\n\tprivate function set_associations()\n\t{\n\t\trequire_once __DIR__ . '/Relationship.php';\n\t\t$namespace = $this->class->getNamespaceName();\n\n\t\tforeach ($this->class->getStaticProperties() as $name => $definitions)\n\t\t{\n\t\t\tif (!$definitions)# || !is_array($definitions))\n\t\t\t\tcontinue;\n\n\t\t\tforeach (wrap_strings_in_arrays($definitions) as $definition)\n\t\t\t{\n\t\t\t\t$relationship = null;\n\t\t\t\t$definition += array('namespace' => $namespace);\n\n\t\t\t\tswitch ($name)\n\t\t\t\t{\n\t\t\t\t\tcase 'has_many':\n\t\t\t\t\t\t$relationship = new HasMany($definition);\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 'has_one':\n\t\t\t\t\t\t$relationship = new HasOne($definition);\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 'belongs_to':\n\t\t\t\t\t\t$relationship = new BelongsTo($definition);\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 'has_and_belongs_to_many':\n\t\t\t\t\t\t$relationship = new HasAndBelongsToMany($definition);\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tif ($relationship)\n\t\t\t\t\t$this->add_relationship($relationship);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Rebuild the delegates array into format that we can more easily work with in Model.\n\t * Will end up consisting of array of:\n\t *\n\t * array('delegate' => array('field1','field2',...),\n\t *       'to'       => 'delegate_to_relationship',\n\t *       'prefix'\t=> 'prefix')\n\t */\n\tprivate function set_delegates()\n\t{\n\t\t$delegates = $this->class->getStaticPropertyValue('delegate',array());\n\t\t$new = array();\n\n\t\tif (!array_key_exists('processed', $delegates))\n\t\t\t$delegates['processed'] = false;\n\n\t\tif (!empty($delegates) && !$delegates['processed'])\n\t\t{\n\t\t\tforeach ($delegates as &$delegate)\n\t\t\t{\n\t\t\t\tif (!is_array($delegate) || !isset($delegate['to']))\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif (!isset($delegate['prefix']))\n\t\t\t\t\t$delegate['prefix'] = null;\n\n\t\t\t\t$new_delegate = array(\n\t\t\t\t\t'to'\t\t=> $delegate['to'],\n\t\t\t\t\t'prefix'\t=> $delegate['prefix'],\n\t\t\t\t\t'delegate'\t=> array());\n\n\t\t\t\tforeach ($delegate as $name => $value)\n\t\t\t\t{\n\t\t\t\t\tif (is_numeric($name))\n\t\t\t\t\t\t$new_delegate['delegate'][] = $value;\n\t\t\t\t}\n\n\t\t\t\t$new[] = $new_delegate;\n\t\t\t}\n\n\t\t\t$new['processed'] = true;\n\t\t\t$this->class->setStaticPropertyValue('delegate',$new);\n\t\t}\n\t}\n\n\t/**\n\t * @deprecated Model.php now checks for get|set_ methods via method_exists so there is no need for declaring static g|setters.\n\t */\n\tprivate function set_setters_and_getters()\n\t{\n\t\t$getters = $this->class->getStaticPropertyValue('getters', array());\n\t\t$setters = $this->class->getStaticPropertyValue('setters', array());\n\n\t\tif (!empty($getters) || !empty($setters))\n\t\t\ttrigger_error('static::$getters and static::$setters are deprecated. Please define your setters and getters by declaring methods in your model prefixed with get_ or set_. See\n\t\t\thttp://www.phpactiverecord.org/projects/main/wiki/Utilities#attribute-setters and http://www.phpactiverecord.org/projects/main/wiki/Utilities#attribute-getters on how to make use of this option.', E_USER_DEPRECATED);\n\t}\n}\n"
  },
  {
    "path": "lib/Utils.php",
    "content": "<?php\n/**\n *\n * @package ActiveRecord\n */\n\n/*\n * Thanks to http://www.eval.ca/articles/php-pluralize (MIT license)\n *           http://dev.rubyonrails.org/browser/trunk/activesupport/lib/active_support/inflections.rb (MIT license)\n *           http://www.fortunecity.com/bally/durrus/153/gramch13.html\n *           http://www2.gsu.edu/~wwwesl/egw/crump.htm\n *\n * Changes (12/17/07)\n *   Major changes\n *   --\n *   Fixed irregular noun algorithm to use regular expressions just like the original Ruby source.\n *       (this allows for things like fireman -> firemen\n *   Fixed the order of the singular array, which was backwards.\n *\n *   Minor changes\n *   --\n *   Removed incorrect pluralization rule for /([^aeiouy]|qu)ies$/ => $1y\n *   Expanded on the list of exceptions for *o -> *oes, and removed rule for buffalo -> buffaloes\n *   Removed dangerous singularization rule for /([^f])ves$/ => $1fe\n *   Added more specific rules for singularizing lives, wives, knives, sheaves, loaves, and leaves and thieves\n *   Added exception to /(us)es$/ => $1 rule for houses => house and blouses => blouse\n *   Added excpetions for feet, geese and teeth\n *   Added rule for deer -> deer\n *\n * Changes:\n *   Removed rule for virus -> viri\n *   Added rule for potato -> potatoes\n *   Added rule for *us -> *uses\n */\nnamespace ActiveRecord;\n\nuse \\Closure;\n\nfunction classify($class_name, $singularize=false)\n{\n\tif ($singularize)\n    $class_name = Utils::singularize($class_name);\n\n\t$class_name = Inflector::instance()->camelize($class_name);\n\treturn ucfirst($class_name);\n}\n\n// http://snippets.dzone.com/posts/show/4660\nfunction array_flatten(array $array)\n{\n\t$i = 0;\n\n\twhile ($i < count($array))\n\t{\n\t\tif (is_array($array[$i]))\n\t\t\tarray_splice($array,$i,1,$array[$i]);\n\t\telse\n\t\t\t++$i;\n\t}\n\treturn $array;\n}\n\n/**\n * Somewhat naive way to determine if an array is a hash.\n */\nfunction is_hash(&$array)\n{\n\tif (!is_array($array))\n\t\treturn false;\n\n\t$keys = array_keys($array);\n\treturn @is_string($keys[0]) ? true : false;\n}\n\n/**\n * Strips a class name of any namespaces and namespace operator.\n *\n * @param string $class\n * @return string stripped class name\n * @access public\n */\nfunction denamespace($class_name)\n{\n\tif (is_object($class_name))\n\t\t$class_name = get_class($class_name);\n\n\tif (has_namespace($class_name))\n\t{\n\t\t$parts = explode('\\\\', $class_name);\n\t\treturn end($parts);\n\t}\n\treturn $class_name;\n}\n\nfunction get_namespaces($class_name)\n{\n\tif (has_namespace($class_name))\n\t\treturn explode('\\\\', $class_name);\n\treturn null;\n}\n\nfunction has_namespace($class_name)\n{\n\tif (strpos($class_name, '\\\\') !== false)\n\t\treturn true;\n\treturn false;\n}\n\nfunction has_absolute_namespace($class_name)\n{\n\tif (strpos($class_name, '\\\\') === 0)\n\t\treturn true;\n\treturn false;\n}\n\n/**\n * Returns true if all values in $haystack === $needle\n * @param $needle\n * @param $haystack\n * @return unknown_type\n */\nfunction all($needle, array $haystack)\n{\n\tforeach ($haystack as $value)\n\t{\n\t\tif ($value !== $needle)\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\nfunction collect(&$enumerable, $name_or_closure)\n{\n\t$ret = array();\n\n\tforeach ($enumerable as $value)\n\t{\n\t\tif (is_string($name_or_closure))\n\t\t\t$ret[] = is_array($value) ? $value[$name_or_closure] : $value->$name_or_closure;\n\t\telseif ($name_or_closure instanceof Closure)\n\t\t\t$ret[] = $name_or_closure($value);\n\t}\n\treturn $ret;\n}\n\n/**\n * Wrap string definitions (if any) into arrays.\n */\nfunction wrap_strings_in_arrays(&$strings)\n{\n\tif (!is_array($strings))\n\t\t$strings = array(array($strings));\n\telse \n\t{\n\t\tforeach ($strings as &$str)\n\t\t{\n\t\t\tif (!is_array($str))\n\t\t\t\t$str = array($str);\n\t\t}\n\t}\n\treturn $strings;\n}\n\n/**\n * Some internal utility functions.\n *\n * @package ActiveRecord\n */\nclass Utils\n{\n\tpublic static function extract_options($options)\n\t{\n\t\treturn is_array(end($options)) ? end($options) : array();\n\t}\n\n\tpublic static function add_condition(&$conditions=array(), $condition, $conjuction='AND')\n\t{\n\t\tif (is_array($condition))\n\t\t{\n\t\t\tif (empty($conditions))\n\t\t\t\t$conditions = array_flatten($condition);\n\t\t\telse\n\t\t\t{\n\t\t\t\t$conditions[0] .= \" $conjuction \" . array_shift($condition);\n\t\t\t\t$conditions[] = array_flatten($condition);\n\t\t\t}\n\t\t}\n\t\telseif (is_string($condition))\n\t\t\t$conditions[0] .= \" $conjuction $condition\";\n\n\t\treturn $conditions;\n\t}\n\n\tpublic static function human_attribute($attr)\n\t{\n\t\t$inflector = Inflector::instance();\n\t\t$inflected = $inflector->variablize($attr);\n\t\t$normal = $inflector->uncamelize($inflected);\n\n\t\treturn ucfirst(str_replace('_', ' ', $normal));\n\t}\n\n\tpublic static function is_odd($number)\n\t{\n\t\treturn $number & 1;\n\t}\n\n\tpublic static function is_a($type, $var)\n\t{\n\t\tswitch($type)\n\t\t{\n\t\t\tcase 'range':\n\t\t\t\tif (is_array($var) && (int)$var[0] < (int)$var[1])\n\t\t\t\t\treturn true;\n\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic static function is_blank($var)\n\t{\n\t\treturn 0 === strlen($var);\n\t}\n\n\tprivate static $plural = array(\n        '/(quiz)$/i'               => \"$1zes\",\n        '/^(ox)$/i'                => \"$1en\",\n        '/([m|l])ouse$/i'          => \"$1ice\",\n        '/(matr|vert|ind)ix|ex$/i' => \"$1ices\",\n        '/(x|ch|ss|sh)$/i'         => \"$1es\",\n        '/([^aeiouy]|qu)y$/i'      => \"$1ies\",\n        '/(hive)$/i'               => \"$1s\",\n        '/(?:([^f])fe|([lr])f)$/i' => \"$1$2ves\",\n        '/(shea|lea|loa|thie)f$/i' => \"$1ves\",\n        '/sis$/i'                  => \"ses\",\n        '/([ti])um$/i'             => \"$1a\",\n        '/(tomat|potat|ech|her|vet)o$/i'=> \"$1oes\",\n        '/(bu)s$/i'                => \"$1ses\",\n        '/(alias)$/i'              => \"$1es\",\n        '/(octop)us$/i'            => \"$1i\",\n        '/(cris|ax|test)is$/i'     => \"$1es\",\n        '/(us)$/i'                 => \"$1es\",\n        '/s$/i'                    => \"s\",\n        '/$/'                      => \"s\"\n    );\n\n    private static $singular = array(\n        '/(quiz)zes$/i'             => \"$1\",\n        '/(matr)ices$/i'            => \"$1ix\",\n        '/(vert|ind)ices$/i'        => \"$1ex\",\n        '/^(ox)en$/i'               => \"$1\",\n        '/(alias)es$/i'             => \"$1\",\n        '/(octop|vir)i$/i'          => \"$1us\",\n        '/(cris|ax|test)es$/i'      => \"$1is\",\n        '/(shoe)s$/i'               => \"$1\",\n        '/(o)es$/i'                 => \"$1\",\n        '/(bus)es$/i'               => \"$1\",\n        '/([m|l])ice$/i'            => \"$1ouse\",\n        '/(x|ch|ss|sh)es$/i'        => \"$1\",\n        '/(m)ovies$/i'              => \"$1ovie\",\n        '/(s)eries$/i'              => \"$1eries\",\n        '/([^aeiouy]|qu)ies$/i'     => \"$1y\",\n        '/([lr])ves$/i'             => \"$1f\",\n        '/(tive)s$/i'               => \"$1\",\n        '/(hive)s$/i'               => \"$1\",\n        '/(li|wi|kni)ves$/i'        => \"$1fe\",\n        '/(shea|loa|lea|thie)ves$/i'=> \"$1f\",\n        '/(^analy)ses$/i'           => \"$1sis\",\n        '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i'  => \"$1$2sis\",\n        '/([ti])a$/i'               => \"$1um\",\n        '/(n)ews$/i'                => \"$1ews\",\n        '/(h|bl)ouses$/i'           => \"$1ouse\",\n        '/(corpse)s$/i'             => \"$1\",\n        '/(us)es$/i'                => \"$1\",\n        '/(us|ss)$/i'               => \"$1\",\n        '/s$/i'                     => \"\"\n    );\n\n    private static $irregular = array(\n        'move'   => 'moves',\n        'foot'   => 'feet',\n        'goose'  => 'geese',\n        'sex'    => 'sexes',\n        'child'  => 'children',\n        'man'    => 'men',\n        'tooth'  => 'teeth',\n        'person' => 'people'\n    );\n\n    private static $uncountable = array(\n        'sheep',\n        'fish',\n        'deer',\n        'series',\n        'species',\n        'money',\n        'rice',\n        'information',\n        'equipment'\n    );\n\n    public static function pluralize( $string )\n    {\n        // save some time in the case that singular and plural are the same\n        if ( in_array( strtolower( $string ), self::$uncountable ) )\n            return $string;\n\n        // check for irregular singular forms\n        foreach ( self::$irregular as $pattern => $result )\n        {\n            $pattern = '/' . $pattern . '$/i';\n\n            if ( preg_match( $pattern, $string ) )\n                return preg_replace( $pattern, $result, $string);\n        }\n\n        // check for matches using regular expressions\n        foreach ( self::$plural as $pattern => $result )\n        {\n            if ( preg_match( $pattern, $string ) )\n                return preg_replace( $pattern, $result, $string );\n        }\n\n        return $string;\n    }\n\n    public static function singularize( $string )\n    {\n        // save some time in the case that singular and plural are the same\n        if ( in_array( strtolower( $string ), self::$uncountable ) )\n            return $string;\n\n        // check for irregular plural forms\n        foreach ( self::$irregular as $result => $pattern )\n        {\n            $pattern = '/' . $pattern . '$/i';\n\n            if ( preg_match( $pattern, $string ) )\n                return preg_replace( $pattern, $result, $string);\n        }\n\n        // check for matches using regular expressions\n        foreach ( self::$singular as $pattern => $result )\n        {\n            if ( preg_match( $pattern, $string ) )\n                return preg_replace( $pattern, $result, $string );\n        }\n\n        return $string;\n    }\n\n    public static function pluralize_if($count, $string)\n    {\n        if ($count == 1)\n            return $string;\n        else\n            return self::pluralize($string);\n    }\n\n\tpublic static function squeeze($char, $string)\n\t{\n\t\treturn preg_replace(\"/$char+/\",$char,$string);\n\t}\n\n    public static function add_irregular($singular, $plural)\n    {\n        self::$irregular[$singular] = $plural;\n    }\n}"
  },
  {
    "path": "lib/Validations.php",
    "content": "<?php\n/**\n * These two classes have been <i>heavily borrowed</i> from Ruby on Rails' ActiveRecord so much that\n * this piece can be considered a straight port. The reason for this is that the vaildation process is\n * tricky due to order of operations/events. The former combined with PHP's odd typecasting means\n * that it was easier to formulate this piece base on the rails code.\n *\n * @package ActiveRecord\n */\n\nnamespace ActiveRecord;\nuse ActiveRecord\\Model;\nuse IteratorAggregate;\nuse ArrayIterator;\n\n/**\n * Manages validations for a {@link Model}.\n *\n * This class isn't meant to be directly used. Instead you define\n * validators thru static variables in your {@link Model}. Example:\n *\n * <code>\n * class Person extends ActiveRecord\\Model {\n *   static $validates_length_of = array(\n *     array('name', 'within' => array(30,100),\n *     array('state', 'is' => 2)\n *   );\n * }\n *\n * $person = new Person();\n * $person->name = 'Tito';\n * $person->state = 'this is not two characters';\n *\n * if (!$person->is_valid())\n *   print_r($person->errors);\n * </code>\n *\n * @package ActiveRecord\n * @see Errors\n * @link http://www.phpactiverecord.org/guides/validations\n */\nclass Validations\n{\n\tprivate $model;\n\tprivate $options = array();\n\tprivate $validators = array();\n\tprivate $record;\n\n\tprivate static $VALIDATION_FUNCTIONS = array(\n\t\t'validates_presence_of',\n\t\t'validates_size_of',\n\t\t'validates_length_of',\n\t\t'validates_inclusion_of',\n\t\t'validates_exclusion_of',\n\t\t'validates_format_of',\n\t\t'validates_numericality_of',\n\t\t'validates_uniqueness_of'\n\t);\n\n\tprivate static $DEFAULT_VALIDATION_OPTIONS = array(\n\t\t'on' => 'save',\n\t\t'allow_null' => false,\n\t\t'allow_blank' => false,\n\t\t'message' => null,\n\t);\n\n\tprivate static  $ALL_RANGE_OPTIONS = array(\n\t\t'is' => null,\n\t\t'within' => null,\n\t\t'in' => null,\n\t\t'minimum' => null,\n\t\t'maximum' => null,\n\t);\n\n\tprivate static $ALL_NUMERICALITY_CHECKS = array(\n\t\t'greater_than' => null,\n\t\t'greater_than_or_equal_to'  => null,\n\t\t'equal_to' => null,\n\t\t'less_than' => null,\n\t\t'less_than_or_equal_to' => null,\n\t\t'odd' => null,\n\t\t'even' => null\n\t);\n\n\t/**\n\t * Constructs a {@link Validations} object.\n\t *\n\t * @param Model $model The model to validate\n\t * @return Validations\n\t */\n\tpublic function __construct(Model $model)\n\t{\n\t\t$this->model = $model;\n\t\t$this->record = new Errors($this->model);\n\t\t$this->klass = Reflections::instance()->get(get_class($this->model));\n\t\t$this->validators = array_intersect(array_keys($this->klass->getStaticProperties()), self::$VALIDATION_FUNCTIONS);\n\t}\n\n\tpublic function get_record()\n\t{\n\t\treturn $this->record;\n\t}\n\n\t/**\n\t * Returns validator data.\n\t *\n\t * @return array\n\t */\n\tpublic function rules()\n\t{\n\t\t$data = array();\n\t\tforeach ($this->validators as $validate)\n\t\t{\n\t\t\t$attrs = $this->klass->getStaticPropertyValue($validate);\n\n\t\t\tforeach (wrap_strings_in_arrays($attrs) as $attr)\n\t\t\t{\n\t\t\t\t$field = $attr[0];\n\n\t\t\t\tif (!isset($data[$field]) || !is_array($data[$field]))\n\t\t\t\t\t$data[$field] = array();\n\n\t\t\t\t$attr['validator'] = $validate;\n\t\t\t\tunset($attr[0]);\n\t\t\t\tarray_push($data[$field],$attr);\n\t\t\t}\n\t\t}\n\t\treturn $data;\n\t}\n\n\t/**\n\t * Runs the validators.\n\t *\n\t * @return Errors the validation errors if any\n\t */\n\tpublic function validate()\n\t{\n\t\tforeach ($this->validators as $validate)\n\t\t{\n\t\t\t$definition = $this->klass->getStaticPropertyValue($validate);\n\t\t\t$this->$validate(wrap_strings_in_arrays($definition));\n\t\t}\n\n\t\t$model_reflection = Reflections::instance()->get($this->model);\n\n\t\tif ($model_reflection->hasMethod('validate') && $model_reflection->getMethod('validate')->isPublic())\n\t\t\t$this->model->validate();\n\n\t\t$this->record->clear_model();\n\t\treturn $this->record;\n\t}\n\n\t/**\n\t * Validates a field is not null and not blank.\n\t *\n\t * <code>\n\t * class Person extends ActiveRecord\\Model {\n\t *   static $validates_presence_of = array(\n\t *     array('first_name'),\n\t *     array('last_name')\n\t *   );\n\t * }\n\t * </code>\n\t *\n\t * Available options:\n\t *\n\t * <ul>\n\t * <li><b>message:</b> custom error message</li>\n\t * <li><b>allow_blank:</b> allow blank strings</li>\n\t * <li><b>allow_null:</b> allow null strings</li>\n\t * </ul>\n\t *\n\t * @param array $attrs Validation definition\n\t */\n\tpublic function validates_presence_of($attrs)\n\t{\n\t\t$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES['blank'], 'on' => 'save'));\n\n\t\tforeach ($attrs as $attr)\n\t\t{\n\t\t\t$options = array_merge($configuration, $attr);\n\t\t\t$this->record->add_on_blank($options[0], $options['message']);\n\t\t}\n\t}\n\n\t/**\n\t * Validates that a value is included the specified array.\n\t *\n\t * <code>\n\t * class Car extends ActiveRecord\\Model {\n\t *   static $validates_inclusion_of = array(\n\t *     array('fuel_type', 'in' => array('hyrdogen', 'petroleum', 'electric')),\n\t *   );\n\t * }\n\t * </code>\n\t *\n\t * Available options:\n\t *\n\t * <ul>\n\t * <li><b>in/within:</b> attribute should/shouldn't be a value within an array</li>\n\t * <li><b>message:</b> custome error message</li>\n\t * <li><b>allow_blank:</b> allow blank strings</li>\n\t * <li><b>allow_null:</b> allow null strings</li>\n\t * </ul>\n\t *\n\t * @param array $attrs Validation definition\n\t */\n\tpublic function validates_inclusion_of($attrs)\n\t{\n\t\t$this->validates_inclusion_or_exclusion_of('inclusion', $attrs);\n\t}\n\n\t/**\n\t * This is the opposite of {@link validates_include_of}.\n\t *\n\t * Available options:\n\t *\n\t * <ul>\n\t * <li><b>in/within:</b> attribute should/shouldn't be a value within an array</li>\n\t * <li><b>message:</b> custome error message</li>\n\t * <li><b>allow_blank:</b> allow blank strings</li>\n\t * <li><b>allow_null:</b> allow null strings</li>\n\t * </ul>\n\t *\n\t * @param array $attrs Validation definition\n\t * @see validates_inclusion_of\n\t */\n\tpublic function validates_exclusion_of($attrs)\n\t{\n\t\t$this->validates_inclusion_or_exclusion_of('exclusion', $attrs);\n\t}\n\n\t/**\n\t * Validates that a value is in or out of a specified list of values.\n\t *\n\t * Available options:\n\t *\n\t * <ul>\n\t * <li><b>in/within:</b> attribute should/shouldn't be a value within an array</li>\n\t * <li><b>message:</b> custome error message</li>\n\t * <li><b>allow_blank:</b> allow blank strings</li>\n\t * <li><b>allow_null:</b> allow null strings</li>\n\t * </ul>\n\t *\n\t * @see validates_inclusion_of\n\t * @see validates_exclusion_of\n\t * @param string $type Either inclusion or exclusion\n\t * @param $attrs Validation definition\n\t */\n\tpublic function validates_inclusion_or_exclusion_of($type, $attrs)\n\t{\n\t\t$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES[$type], 'on' => 'save'));\n\n\t\tforeach ($attrs as $attr)\n\t\t{\n\t\t\t$options = array_merge($configuration, $attr);\n\t\t\t$attribute = $options[0];\n\t\t\t$var = $this->model->$attribute;\n\n\t\t\tif (isset($options['in']))\n\t\t\t\t$enum = $options['in'];\n\t\t\telseif (isset($options['within']))\n\t\t\t\t$enum = $options['within'];\n\n\t\t\tif (!is_array($enum))\n\t\t\t\tarray($enum);\n\n\t\t\t$message = str_replace('%s', $var, $options['message']);\n\n\t\t\tif ($this->is_null_with_option($var, $options) || $this->is_blank_with_option($var, $options))\n\t\t\t\tcontinue;\n\n\t\t\tif (('inclusion' == $type && !in_array($var, $enum)) || ('exclusion' == $type && in_array($var, $enum)))\n\t\t\t\t$this->record->add($attribute, $message);\n\t\t}\n\t}\n\n\t/**\n\t * Validates that a value is numeric.\n\t *\n\t * <code>\n\t * class Person extends ActiveRecord\\Model {\n\t *   static $validates_numericality_of = array(\n\t *     array('salary', 'greater_than' => 19.99, 'less_than' => 99.99)\n\t *   );\n\t * }\n\t * </code>\n\t *\n\t * Available options:\n\t *\n\t * <ul>\n\t * <li><b>only_integer:</b> value must be an integer (e.g. not a float)</li>\n\t * <li><b>even:</b> must be even</li>\n\t * <li><b>odd:</b> must be odd\"</li>\n\t * <li><b>greater_than:</b> must be greater than specified number</li>\n\t * <li><b>greater_than_or_equal_to:</b> must be greater than or equal to specified number</li>\n\t * <li><b>equal_to:</b> ...</li>\n\t * <li><b>less_than:</b> ...</li>\n\t * <li><b>less_than_or_equal_to:</b> ...</li>\n\t * <li><b>allow_blank:</b> allow blank strings</li>\n\t * <li><b>allow_null:</b> allow null strings</li>\n\t * </ul>\n\t *\n\t * @param array $attrs Validation definition\n\t */\n\tpublic function validates_numericality_of($attrs)\n\t{\n\t\t$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('only_integer' => false));\n\n\t\t// Notice that for fixnum and float columns empty strings are converted to nil.\n\t\t// Validates whether the value of the specified attribute is numeric by trying to convert it to a float with Kernel.Float\n\t\t// (if only_integer is false) or applying it to the regular expression /\\A[+\\-]?\\d+\\Z/ (if only_integer is set to true).\n\t\tforeach ($attrs as $attr)\n\t\t{\n\t\t\t$options = array_merge($configuration, $attr);\n\t\t\t$attribute = $options[0];\n\t\t\t$var = $this->model->$attribute;\n\n\t\t\t$numericalityOptions = array_intersect_key(self::$ALL_NUMERICALITY_CHECKS, $options);\n\n\t\t\tif ($this->is_null_with_option($var, $options))\n\t\t\t\tcontinue;\n\n\t\t\t$not_a_number_message = (isset($options['message']) ? $options['message'] : Errors::$DEFAULT_ERROR_MESSAGES['not_a_number']);\n\n\t\t\tif (true === $options['only_integer'] && !is_integer($var))\n\t\t\t{\n\t\t\t\tif (!preg_match('/\\A[+-]?\\d+\\Z/', (string)($var)))\n\t\t\t\t{\n\t\t\t\t\t$this->record->add($attribute, $not_a_number_message);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (!is_numeric($var))\n\t\t\t\t{\n\t\t\t\t\t$this->record->add($attribute, $not_a_number_message);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$var = (float)$var;\n\t\t\t}\n\n\t\t\tforeach ($numericalityOptions as $option => $check)\n\t\t\t{\n\t\t\t\t$option_value = $options[$option];\n\t\t\t\t$message = (isset($options['message']) ? $options['message'] : Errors::$DEFAULT_ERROR_MESSAGES[$option]);\n\n\t\t\t\tif ('odd' != $option && 'even' != $option)\n\t\t\t\t{\n\t\t\t\t\t$option_value = (float)$options[$option];\n\n\t\t\t\t\tif (!is_numeric($option_value))\n\t\t\t\t\t\tthrow new ValidationsArgumentError(\"$option must be a number\");\n\n\t\t\t\t\t$message = str_replace('%d', $option_value, $message);\n\n\t\t\t\t\tif ('greater_than' == $option && !($var > $option_value))\n\t\t\t\t\t\t$this->record->add($attribute, $message);\n\n\t\t\t\t\telseif ('greater_than_or_equal_to' == $option && !($var >= $option_value))\n\t\t\t\t\t\t$this->record->add($attribute, $message);\n\n\t\t\t\t\telseif ('equal_to' == $option && !($var == $option_value))\n\t\t\t\t\t\t$this->record->add($attribute, $message);\n\n\t\t\t\t\telseif ('less_than' == $option && !($var < $option_value))\n\t\t\t\t\t\t$this->record->add($attribute, $message);\n\n\t\t\t\t\telseif ('less_than_or_equal_to' == $option && !($var <= $option_value))\n\t\t\t\t\t\t$this->record->add($attribute, $message);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif (('odd' == $option && !Utils::is_odd($var)) || ('even' == $option && Utils::is_odd($var)))\n\t\t\t\t\t\t$this->record->add($attribute, $message);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Alias of {@link validates_length_of}\n\t *\n\t * @param array $attrs Validation definition\n\t */\n\tpublic function validates_size_of($attrs)\n\t{\n\t\t$this->validates_length_of($attrs);\n\t}\n\n\t/**\n\t * Validates that a value is matches a regex.\n\t *\n\t * <code>\n\t * class Person extends ActiveRecord\\Model {\n\t *   static $validates_format_of = array(\n\t *     array('email', 'with' => '/^.*?@.*$/')\n\t *   );\n\t * }\n\t * </code>\n\t *\n\t * Available options:\n\t *\n\t * <ul>\n\t * <li><b>with:</b> a regular expression</li>\n\t * <li><b>message:</b> custom error message</li>\n\t * <li><b>allow_blank:</b> allow blank strings</li>\n\t * <li><b>allow_null:</b> allow null strings</li>\n\t * </ul>\n\t *\n\t * @param array $attrs Validation definition\n\t */\n\tpublic function validates_format_of($attrs)\n\t{\n\t\t$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES['invalid'], 'on' => 'save', 'with' => null));\n\n\t\tforeach ($attrs as $attr)\n\t\t{\n\t\t\t$options = array_merge($configuration, $attr);\n\t\t\t$attribute = $options[0];\n\t\t\t$var = $this->model->$attribute;\n\n\t\t\tif (is_null($options['with']) || !is_string($options['with']))\n\t\t\t\tthrow new ValidationsArgumentError('A regular expression must be supplied as the [with] option of the configuration array.');\n\t\t\telse\n\t\t\t\t$expression = $options['with'];\n\n\t\t\tif ($this->is_null_with_option($var, $options) || $this->is_blank_with_option($var, $options))\n\t\t\t\tcontinue;\n\n\t\t\tif (!@preg_match($expression, $var))\n\t\t\t$this->record->add($attribute, $options['message']);\n\t\t}\n\t}\n\n\t/**\n\t * Validates the length of a value.\n\t *\n\t * <code>\n\t * class Person extends ActiveRecord\\Model {\n\t *   static $validates_length_of = array(\n\t *     array('name', 'within' => array(1,50))\n\t *   );\n\t * }\n\t * </code>\n\t *\n\t * Available options:\n\t *\n\t * <ul>\n\t * <li><b>is:</b> attribute should be exactly n characters long</li>\n\t * <li><b>in/within:</b> attribute should be within an range array(min,max)</li>\n\t * <li><b>maximum/minimum:</b> attribute should not be above/below respectively</li>\n\t * <li><b>message:</b> custome error message</li>\n\t * <li><b>allow_blank:</b> allow blank strings</li>\n\t * <li><b>allow_null:</b> allow null strings. (Even if this is set to false, a null string is always shorter than a maximum value.)</li>\n\t * </ul>\n\t *\n\t * @param array $attrs Validation definition\n\t */\n\tpublic function validates_length_of($attrs)\n\t{\n\t\t$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array(\n\t\t\t'too_long'     => Errors::$DEFAULT_ERROR_MESSAGES['too_long'],\n\t\t\t'too_short'    => Errors::$DEFAULT_ERROR_MESSAGES['too_short'],\n\t\t\t'wrong_length' => Errors::$DEFAULT_ERROR_MESSAGES['wrong_length']\n\t\t));\n\n\t\tforeach ($attrs as $attr)\n\t\t{\n\t\t\t$options = array_merge($configuration, $attr);\n\t\t\t$range_options = array_intersect(array_keys(self::$ALL_RANGE_OPTIONS), array_keys($attr));\n\t\t\tsort($range_options);\n\n\t\t\tswitch (sizeof($range_options))\n\t\t\t{\n\t\t\t\tcase 0:\n\t\t\t\t\tthrow new  ValidationsArgumentError('Range unspecified.  Specify the [within], [maximum], or [is] option.');\n\n\t\t\t\tcase 1:\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new  ValidationsArgumentError('Too many range options specified.  Choose only one.');\n\t\t\t}\n\n\t\t\t$attribute = $options[0];\n\t\t\t$var = $this->model->$attribute;\n\t\t\tif ($this->is_null_with_option($var, $options) || $this->is_blank_with_option($var, $options))\n\t\t\t\tcontinue;\n\t\t\tif ($range_options[0] == 'within' || $range_options[0] == 'in')\n\t\t\t{\n\t\t\t\t$range = $options[$range_options[0]];\n\n\t\t\t\tif (!(Utils::is_a('range', $range)))\n\t\t\t\t\tthrow new  ValidationsArgumentError(\"$range_options[0] must be an array composing a range of numbers with key [0] being less than key [1]\");\n\t\t\t\t$range_options = array('minimum', 'maximum');\n\t\t\t\t$attr['minimum'] = $range[0];\n\t\t\t\t$attr['maximum'] = $range[1];\n\t\t\t}\n\t\t\tforeach ($range_options as $range_option)\n\t\t\t{\n\t\t\t\t$option = $attr[$range_option];\n\n\t\t\t\tif ((int)$option <= 0)\n\t\t\t\t\tthrow new  ValidationsArgumentError(\"$range_option value cannot use a signed integer.\");\n\n\t\t\t\tif (is_float($option))\n\t\t\t\t\tthrow new  ValidationsArgumentError(\"$range_option value cannot use a float for length.\");\n\n\t\t\t\tif (!($range_option == 'maximum' && is_null($this->model->$attribute)))\n\t\t\t\t{\n\t\t\t\t\t$messageOptions = array('is' => 'wrong_length', 'minimum' => 'too_short', 'maximum' => 'too_long');\n\n\t\t\t\t\tif (isset($options['message']))\n\t\t\t\t\t\t$message = $options['message'];\n\t\t\t\t\telse\n\t\t\t\t\t\t$message = $options[$messageOptions[$range_option]];\n\t\t\t\t\t\n\n\t\t\t\t\t$message = str_replace('%d', $option, $message);\n\t\t\t\t\t$attribute_value = $this->model->$attribute;\n\t\t\t\t\t$len = strlen($attribute_value);\n\t\t\t\t\t$value = (int)$attr[$range_option];\n\n\t\t\t\t\tif ('maximum' == $range_option && $len > $value)\n\t\t\t\t\t\t$this->record->add($attribute, $message);\n\n\t\t\t\t\tif ('minimum' == $range_option && $len < $value)\n\t\t\t\t\t\t$this->record->add($attribute, $message);\n\n\t\t\t\t\tif ('is' == $range_option && $len !== $value)\n\t\t\t\t\t\t$this->record->add($attribute, $message);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Validates the uniqueness of a value.\n\t *\n\t * <code>\n\t * class Person extends ActiveRecord\\Model {\n\t *   static $validates_uniqueness_of = array(\n\t *     array('name'),\n\t *     array(array('blah','bleh'), 'message' => 'blech')\n\t *   );\n\t * }\n\t * </code>\n\t *\n\t * Available options:\n\t *\n\t * <ul>\n\t * <li><b>with:</b> a regular expression</li>\n\t * <li><b>message:</b> custom error message</li>\n\t * <li><b>allow_blank:</b> allow blank strings</li>\n\t * <li><b>allow_null:</b> allow null strings</li>\n\t * </ul>\n\t *\n\t * @param array $attrs Validation definition\n\t */\n\tpublic function validates_uniqueness_of($attrs)\n\t{\n\t\t$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array(\n\t\t\t'message' => Errors::$DEFAULT_ERROR_MESSAGES['unique']\n\t\t));\n\t\t// Retrieve connection from model for quote_name method\n\t\t$connection = $this->klass->getMethod('connection')->invoke(null);\n\n\t\tforeach ($attrs as $attr)\n\t\t{\n\t\t\t$options = array_merge($configuration, $attr);\n\t\t\t$pk = $this->model->get_primary_key();\n\t\t\t$pk_value = $this->model->{$pk[0]};\n\n\t\t\tif (is_array($options[0]))\n\t\t\t{\n\t\t\t\t$add_record = join(\"_and_\", $options[0]);\n\t\t\t\t$fields = $options[0];\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t$add_record = $options[0];\n\t\t\t\t$fields = array($options[0]);\n\t\t\t}\n\n\t\t\t$sql = \"\";\n\t\t\t$conditions = array(\"\");\n\t\t\t$pk_quoted = $connection->quote_name($pk[0]);\n\t\t\tif ($pk_value === null)\n\t\t\t\t$sql = \"{$pk_quoted} IS NOT NULL\";\n\t\t\telse\n\t\t\t{\n\t\t\t\t$sql = \"{$pk_quoted} != ?\";\n\t\t\t\tarray_push($conditions,$pk_value);\n\t\t\t}\n\n\t\t\tforeach ($fields as $field)\n\t\t\t{\n\t\t\t\t$field = $this->model->get_real_attribute_name($field);\n\t\t\t\t$quoted_field = $connection->quote_name($field);\n\t\t\t\t$sql .= \" AND {$quoted_field}=?\";\n\t\t\t\tarray_push($conditions,$this->model->$field);\n\t\t\t}\n\n\t\t\t$conditions[0] = $sql;\n\n\t\t\tif ($this->model->exists(array('conditions' => $conditions)))\n\t\t\t\t$this->record->add($add_record, $options['message']);\n\t\t}\n\t}\n\n\tprivate function is_null_with_option($var, &$options)\n\t{\n\t\treturn (is_null($var) && (isset($options['allow_null']) && $options['allow_null']));\n\t}\n\n\tprivate function is_blank_with_option($var, &$options)\n\t{\n\t\treturn (Utils::is_blank($var) && (isset($options['allow_blank']) && $options['allow_blank']));\n\t}\n}\n\n/**\n * Class that holds {@link Validations} errors.\n *\n * @package ActiveRecord\n */\nclass Errors implements IteratorAggregate\n{\n\tprivate $model;\n\tprivate $errors;\n\n\tpublic static $DEFAULT_ERROR_MESSAGES = array(\n\t\t'inclusion'    => \"is not included in the list\",\n\t\t'exclusion'    => \"is reserved\",\n\t\t'invalid'      => \"is invalid\",\n\t\t'confirmation' => \"doesn't match confirmation\",\n\t\t'accepted'     => \"must be accepted\",\n\t\t'empty'        => \"can't be empty\",\n\t\t'blank'        => \"can't be blank\",\n\t\t'too_long'     => \"is too long (maximum is %d characters)\",\n\t\t'too_short'    => \"is too short (minimum is %d characters)\",\n\t\t'wrong_length' => \"is the wrong length (should be %d characters)\",\n\t\t'taken'        => \"has already been taken\",\n\t\t'not_a_number' => \"is not a number\",\n\t\t'greater_than' => \"must be greater than %d\",\n\t\t'equal_to'     => \"must be equal to %d\",\n\t\t'less_than'    => \"must be less than %d\",\n\t\t'odd'          => \"must be odd\",\n\t\t'even'         => \"must be even\",\n\t\t'unique'       => \"must be unique\",\n\t\t'less_than_or_equal_to' => \"must be less than or equal to %d\",\n\t\t'greater_than_or_equal_to' => \"must be greater than or equal to %d\"\n\t);\n\n\t/**\n\t * Constructs an {@link Errors} object.\n\t *\n\t * @param Model $model The model the error is for\n\t * @return Errors\n\t */\n\tpublic function __construct(Model $model)\n\t{\n\t\t$this->model = $model;\n\t}\n\n\t/**\n\t * Nulls $model so we don't get pesky circular references. $model is only needed during the\n\t * validation process and so can be safely cleared once that is done.\n\t */\n\tpublic function clear_model()\n\t{\n\t\t$this->model = null;\n\t}\n\n\t/**\n\t * Add an error message.\n\t *\n\t * @param string $attribute Name of an attribute on the model\n\t * @param string $msg The error message\n\t */\n\tpublic function add($attribute, $msg)\n\t{\n\t\tif (is_null($msg))\n\t\t\t$msg = self :: $DEFAULT_ERROR_MESSAGES['invalid'];\n\n\t\tif (!isset($this->errors[$attribute]))\n\t\t\t$this->errors[$attribute] = array($msg);\n\t\telse\n\t\t\t$this->errors[$attribute][] = $msg;\n\t}\n\n\t/**\n\t * Adds an error message only if the attribute value is {@link http://www.php.net/empty empty}.\n\t *\n\t * @param string $attribute Name of an attribute on the model\n\t * @param string $msg The error message\n\t */\n\tpublic function add_on_empty($attribute, $msg)\n\t{\n\t\tif (empty($msg))\n\t\t\t$msg = self::$DEFAULT_ERROR_MESSAGES['empty'];\n\n\t\tif (empty($this->model->$attribute))\n\t\t\t$this->add($attribute, $msg);\n\t}\n\n\t/**\n\t * Retrieve error messages for an attribute.\n\t *\n\t * @param string $attribute Name of an attribute on the model\n\t * @return array or null if there is no error.\n\t */\n\tpublic function __get($attribute)\n\t{\n\t\tif (!isset($this->errors[$attribute]))\n\t\t\treturn null;\n\n\t\treturn $this->errors[$attribute];\n\t}\n\n\t/**\n\t * Adds the error message only if the attribute value was null or an empty string.\n\t *\n\t * @param string $attribute Name of an attribute on the model\n\t * @param string $msg The error message\n\t */\n\tpublic function add_on_blank($attribute, $msg)\n\t{\n\t\tif (!$msg)\n\t\t\t$msg = self::$DEFAULT_ERROR_MESSAGES['blank'];\n\n\t\tif (($value = $this->model->$attribute) === '' || $value === null)\n\t\t\t$this->add($attribute, $msg);\n\t}\n\n\t/**\n\t * Returns true if the specified attribute had any error messages.\n\t *\n\t * @param string $attribute Name of an attribute on the model\n\t * @return boolean\n\t */\n\tpublic function is_invalid($attribute)\n\t{\n\t\treturn isset($this->errors[$attribute]);\n\t}\n\n\t/**\n\t * Returns the error message(s) for the specified attribute or null if none.\n\t *\n\t * @param string $attribute Name of an attribute on the model\n\t * @return string/array\tArray of strings if several error occured on this attribute.\n\t */\n\tpublic function on($attribute)\n\t{\n\t\t$errors = $this->$attribute;\n\n\t\treturn $errors && count($errors) == 1 ? $errors[0] : $errors;\n\t}\n\n\t/**\n\t * Returns the internal errors object.\n\t *\n\t * <code>\n\t * $model->errors->get_raw_errors();\n\t *\n\t * # array(\n\t * #  \"name\" => array(\"can't be blank\"),\n\t * #  \"state\" => array(\"is the wrong length (should be 2 chars)\",\n\t * # )\n\t * </code>\n\t */\n\tpublic function get_raw_errors()\n\t{\n\t\treturn $this->errors;\n\t}\n\n\t/**\n\t * Returns all the error messages as an array.\n\t *\n\t * <code>\n\t * $model->errors->full_messages();\n\t *\n\t * # array(\n\t * #  \"Name can't be blank\",\n\t * #  \"State is the wrong length (should be 2 chars)\"\n\t * # )\n\t * </code>\n\t *\n\t * @return array\n\t */\n\tpublic function full_messages()\n\t{\n\t\t$full_messages = array();\n\n\t\t$this->to_array(function($attribute, $message) use (&$full_messages) {\n\t\t\t$full_messages[] = $message;\n\t\t});\n\n\t\treturn $full_messages;\n\t}\n\n\t/**\n\t * Returns all the error messages as an array, including error key.\n\t *\n\t * <code>\n\t * $model->errors->errors();\n\t *\n\t * # array(\n\t * #  \"name\" => array(\"Name can't be blank\"),\n\t * #  \"state\" => array(\"State is the wrong length (should be 2 chars)\")\n\t * # )\n\t * </code>\n\t *\n\t * @param callable $closure Closure to fetch the errors in some other format (optional)\n\t *                       This closure has the signature function($attribute, $message)\n\t *                       and is called for each available error message.\n\t * @return array\n\t */\n\tpublic function to_array($closure=null)\n\t{\n\t\t$errors = array();\n\n\t\tif ($this->errors)\n\t\t{\n\t\t\tforeach ($this->errors as $attribute => $messages)\n\t\t\t{\n\t\t\t\tforeach ($messages as $msg)\n\t\t\t\t{\n\t\t\t\t\tif (is_null($msg))\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t$errors[$attribute][] = ($message = Utils::human_attribute($attribute) . ' ' . $msg);\n\n\t\t\t\t\tif ($closure)\n\t\t\t\t\t\t$closure($attribute,$message);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn $errors;\n\t}\n\n\t/**\n\t * Convert all error messages to a String.\n\t * This function is called implicitely if the object is casted to a string:\n\t *\n\t * <code>\n\t * echo $error;\n\t *\n\t * # \"Name can't be blank\\nState is the wrong length (should be 2 chars)\"\n\t * </code>\n\t * @return string\n\t */\n\tpublic function __toString()\n\t{\n\t\treturn implode(\"\\n\", $this->full_messages());\n\t}\n\n\t/**\n\t * Returns true if there are no error messages.\n\t * @return boolean\n\t */\n\tpublic function is_empty()\n\t{\n\t\treturn empty($this->errors);\n\t}\n\n\t/**\n\t * Clears out all error messages.\n\t */\n\tpublic function clear()\n\t{\n\t\t$this->errors = array();\n\t}\n\n\t/**\n\t * Returns the number of error messages there are.\n\t * @return int\n\t */\n\tpublic function size()\n\t{\n\t\tif ($this->is_empty())\n\t\t\treturn 0;\n\n\t\t$count = 0;\n\n\t\tforeach ($this->errors as $attribute => $error)\n\t\t\t$count += count($error);\n\n\t\treturn $count;\n\t}\n\n\t/**\n\t * Returns an iterator to the error messages.\n\t *\n\t * This will allow you to iterate over the {@link Errors} object using foreach.\n\t *\n\t * <code>\n\t * foreach ($model->errors as $msg)\n\t *   echo \"$msg\\n\";\n\t * </code>\n\t *\n\t * @return ArrayIterator\n\t */\n\tpublic function getIterator()\n\t{\n\t\treturn new ArrayIterator($this->full_messages());\n\t}\n}\n"
  },
  {
    "path": "lib/adapters/MysqlAdapter.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Adapter for MySQL.\n *\n * @package ActiveRecord\n */\nclass MysqlAdapter extends Connection\n{\n\tstatic $DEFAULT_PORT = 3306;\n\n\tpublic function limit($sql, $offset, $limit)\n\t{\n\t\t$offset = is_null($offset) ? '' : intval($offset) . ',';\n\t\t$limit = intval($limit);\n\t\treturn \"$sql LIMIT {$offset}$limit\";\n\t}\n\n\tpublic function query_column_info($table)\n\t{\n\t\treturn $this->query(\"SHOW COLUMNS FROM $table\");\n\t}\n\n\tpublic function query_for_tables()\n\t{\n\t\treturn $this->query('SHOW TABLES');\n\t}\n\n\tpublic function create_column(&$column)\n\t{\n\t\t$c = new Column();\n\t\t$c->inflected_name\t= Inflector::instance()->variablize($column['field']);\n\t\t$c->name\t\t\t= $column['field'];\n\t\t$c->nullable\t\t= ($column['null'] === 'YES' ? true : false);\n\t\t$c->pk\t\t\t\t= ($column['key'] === 'PRI' ? true : false);\n\t\t$c->auto_increment\t= ($column['extra'] === 'auto_increment' ? true : false);\n\n\t\tif ($column['type'] == 'timestamp' || $column['type'] == 'datetime')\n\t\t{\n\t\t\t$c->raw_type = 'datetime';\n\t\t\t$c->length = 19;\n\t\t}\n\t\telseif ($column['type'] == 'date')\n\t\t{\n\t\t\t$c->raw_type = 'date';\n\t\t\t$c->length = 10;\n\t\t}\n\t\telseif ($column['type'] == 'time')\n\t\t{\n\t\t\t$c->raw_type = 'time';\n\t\t\t$c->length = 8;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpreg_match('/^([A-Za-z0-9_]+)(\\(([0-9]+(,[0-9]+)?)\\))?/',$column['type'],$matches);\n\n\t\t\t$c->raw_type = (count($matches) > 0 ? $matches[1] : $column['type']);\n\n\t\t\tif (count($matches) >= 4)\n\t\t\t\t$c->length = intval($matches[3]);\n\t\t}\n\n\t\t$c->map_raw_type();\n\t\t$c->default = $c->cast($column['default'],$this);\n\n\t\treturn $c;\n\t}\n\n\tpublic function set_encoding($charset)\n\t{\n\t\t$params = array($charset);\n\t\t$this->query('SET NAMES ?',$params);\n\t}\n\n\tpublic function accepts_limit_and_order_for_update_and_delete() { return true; }\n\n\tpublic function native_database_types()\n\t{\n\t\treturn array(\n\t\t\t'primary_key' => 'int(11) UNSIGNED DEFAULT NULL auto_increment PRIMARY KEY',\n\t\t\t'string' => array('name' => 'varchar', 'length' => 255),\n\t\t\t'text' => array('name' => 'text'),\n\t\t\t'integer' => array('name' => 'int', 'length' => 11),\n\t\t\t'float' => array('name' => 'float'),\n\t\t\t'datetime' => array('name' => 'datetime'),\n\t\t\t'timestamp' => array('name' => 'datetime'),\n\t\t\t'time' => array('name' => 'time'),\n\t\t\t'date' => array('name' => 'date'),\n\t\t\t'binary' => array('name' => 'blob'),\n\t\t\t'boolean' => array('name' => 'tinyint', 'length' => 1)\n\t\t);\n\t}\n\n}\n?>\n"
  },
  {
    "path": "lib/adapters/OciAdapter.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\nuse PDO;\n\n/**\n * Adapter for OCI (not completed yet).\n * \n * @package ActiveRecord\n */\nclass OciAdapter extends Connection\n{\n\tstatic $QUOTE_CHARACTER = '';\n\tstatic $DEFAULT_PORT = 1521;\n\n\tpublic $dsn_params;\n\n\tprotected function __construct($info)\n\t{\n\t\ttry {\n\t\t\t$this->dsn_params = isset($info->charset) ? \";charset=$info->charset\" : \"\";\n\t\t\t$this->connection = new PDO(\"oci:dbname=//$info->host/$info->db$this->dsn_params\",$info->user,$info->pass,static::$PDO_OPTIONS);\n\t\t} catch (PDOException $e) {\n\t\t\tthrow new DatabaseException($e);\n\t\t}\n\t}\n\n\tpublic function supports_sequences() { return true; }\n\t\n\tpublic function get_next_sequence_value($sequence_name)\n\t{\n\t\treturn $this->query_and_fetch_one('SELECT ' . $this->next_sequence_value($sequence_name) . ' FROM dual');\n\t}\n\n\tpublic function next_sequence_value($sequence_name)\n\t{\n\t\treturn \"$sequence_name.nextval\";\n\t}\n\n\tpublic function date_to_string($datetime)\n\t{\n\t\treturn $datetime->format('d-M-Y');\n\t}\n\n\tpublic function datetime_to_string($datetime)\n\t{\n\t\treturn $datetime->format('d-M-Y h:i:s A');\n\t}\n\n\t// $string = DD-MON-YYYY HH12:MI:SS(\\.[0-9]+) AM\n\tpublic function string_to_datetime($string)\n\t{\n\t\treturn parent::string_to_datetime(str_replace('.000000','',$string));\n\t}\n\n\tpublic function limit($sql, $offset, $limit)\n\t{\n\t\t$offset = intval($offset);\n\t\t$stop = $offset + intval($limit);\n\t\treturn \n\t\t\t\"SELECT * FROM (SELECT a.*, rownum ar_rnum__ FROM ($sql) a \" .\n\t\t\t\"WHERE rownum <= $stop) WHERE ar_rnum__ > $offset\";\n\t}\n\n\tpublic function query_column_info($table)\n\t{\n\t\t$sql = \n\t\t\t\"SELECT c.column_name, c.data_type, c.data_length, c.data_scale, c.data_default, c.nullable, \" .\n\t\t\t\t\"(SELECT a.constraint_type \" .\n\t\t\t\t\"FROM all_constraints a, all_cons_columns b \" .\n\t\t\t\t\"WHERE a.constraint_type='P' \" .\n\t\t\t\t\"AND a.constraint_name=b.constraint_name \" .\n\t\t\t\t\"AND a.table_name = t.table_name AND b.column_name=c.column_name) AS pk \" .\n\t\t\t\"FROM user_tables t \" .\n\t\t\t\"INNER JOIN user_tab_columns c on(t.table_name=c.table_name) \" .\n\t\t\t\"WHERE t.table_name=?\";\n\n\t\t$values = array(strtoupper($table));\n\t\treturn $this->query($sql,$values);\n\t}\n\n\tpublic function query_for_tables()\n\t{\n\t\treturn $this->query(\"SELECT table_name FROM user_tables\");\n\t}\n\n\tpublic function create_column(&$column)\n\t{\n\t\t$column['column_name'] = strtolower($column['column_name']);\n\t\t$column['data_type'] = strtolower(preg_replace('/\\(.*?\\)/','',$column['data_type']));\n\n\t\tif ($column['data_default'] !== null)\n\t\t\t$column['data_default'] = trim($column['data_default'],\"' \");\n\n\t\tif ($column['data_type'] == 'number')\n\t\t{\n\t\t\tif ($column['data_scale'] > 0)\n\t\t\t\t$column['data_type'] = 'decimal';\n\t\t\telseif ($column['data_scale'] == 0)\n\t\t\t\t$column['data_type'] = 'int';\n\t\t}\n\n\t\t$c = new Column();\n\t\t$c->inflected_name\t= Inflector::instance()->variablize($column['column_name']);\n\t\t$c->name\t\t\t= $column['column_name'];\n\t\t$c->nullable\t\t= $column['nullable'] == 'Y' ? true : false;\n\t\t$c->pk\t\t\t\t= $column['pk'] == 'P' ? true : false;\n\t\t$c->length\t\t\t= $column['data_length'];\n\t\n\t\tif ($column['data_type'] == 'timestamp')\n\t\t\t$c->raw_type = 'datetime';\n\t\telse\n\t\t\t$c->raw_type = $column['data_type'];\n\n\t\t$c->map_raw_type();\n\t\t$c->default\t= $c->cast($column['data_default'],$this);\n\n\t\treturn $c;\n\t}\n\n\tpublic function set_encoding($charset)\n\t{\n\t\t// is handled in the constructor\n\t}\n\n\tpublic function native_database_types()\n\t{\n\t\treturn array(\n\t\t\t'primary_key' => \"NUMBER(38) NOT NULL PRIMARY KEY\",\n\t\t\t'string' => array('name' => 'VARCHAR2', 'length' => 255),\n\t\t\t'text' => array('name' => 'CLOB'),\n\t\t\t'integer' => array('name' => 'NUMBER', 'length' => 38),\n\t\t\t'float' => array('name' => 'NUMBER'),\n\t\t\t'datetime' => array('name' => 'DATE'),\n\t\t\t'timestamp' => array('name' => 'DATE'),\n\t\t\t'time' => array('name' => 'DATE'),\n\t\t\t'date' => array('name' => 'DATE'),\n\t\t\t'binary' => array('name' => 'BLOB'),\n\t\t\t'boolean' => array('name' => 'NUMBER', 'length' => 1)\n\t\t);\n\t}\n}\n?>\n"
  },
  {
    "path": "lib/adapters/PgsqlAdapter.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Adapter for Postgres (not completed yet)\n * \n * @package ActiveRecord\n */\nclass PgsqlAdapter extends Connection\n{\n\tstatic $QUOTE_CHARACTER = '\"';\n\tstatic $DEFAULT_PORT = 5432;\n\n\tpublic function supports_sequences()\n\t{\n\t\treturn true;\n\t}\n\n\tpublic function get_sequence_name($table, $column_name)\n\t{\n\t\treturn \"{$table}_{$column_name}_seq\";\n\t}\n\n\tpublic function next_sequence_value($sequence_name)\n\t{\n\t\treturn \"nextval('\" . str_replace(\"'\",\"\\\\'\",$sequence_name) . \"')\";\n\t}\n\n\tpublic function limit($sql, $offset, $limit)\n\t{\n\t\treturn $sql . ' LIMIT ' . intval($limit) . ' OFFSET ' . intval($offset);\n\t}\n\n\tpublic function query_column_info($table)\n\t{\n\t\t$sql = <<<SQL\nSELECT\n      a.attname AS field,\n      a.attlen,\n      REPLACE(pg_catalog.format_type(a.atttypid, a.atttypmod), 'character varying', 'varchar') AS type,\n      a.attnotnull AS not_nullable,\n      (SELECT 't'\n        FROM pg_index\n        WHERE c.oid = pg_index.indrelid\n        AND a.attnum = ANY (pg_index.indkey)\n        AND pg_index.indisprimary = 't'\n      ) IS NOT NULL AS pk,      \n      REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE((SELECT pg_attrdef.adsrc\n        FROM pg_attrdef\n        WHERE c.oid = pg_attrdef.adrelid\n        AND pg_attrdef.adnum=a.attnum\n      ),'::[a-z_ ]+',''),'''$',''),'^''','') AS default\nFROM pg_attribute a, pg_class c, pg_type t\nWHERE c.relname = ?\n      AND a.attnum > 0\n      AND a.attrelid = c.oid\n      AND a.atttypid = t.oid\nORDER BY a.attnum\nSQL;\n\t\t$values = array($table);\n\t\treturn $this->query($sql,$values);\n\t}\n\n\tpublic function query_for_tables()\n\t{\n\t\treturn $this->query(\"SELECT tablename FROM pg_tables WHERE schemaname NOT IN('information_schema','pg_catalog')\");\n\t}\n\n\tpublic function create_column(&$column)\n\t{\n\t\t$c = new Column();\n\t\t$c->inflected_name\t= Inflector::instance()->variablize($column['field']);\n\t\t$c->name\t\t\t= $column['field'];\n\t\t$c->nullable\t\t= ($column['not_nullable'] ? false : true);\n\t\t$c->pk\t\t\t\t= ($column['pk'] ? true : false);\n\t\t$c->auto_increment\t= false;\n\n\t\tif (substr($column['type'],0,9) == 'timestamp')\n\t\t{\n\t\t\t$c->raw_type = 'datetime';\n\t\t\t$c->length = 19;\n\t\t}\n\t\telseif ($column['type'] == 'date')\n\t\t{\n\t\t\t$c->raw_type = 'date';\n\t\t\t$c->length = 10;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpreg_match('/^([A-Za-z0-9_]+)(\\(([0-9]+(,[0-9]+)?)\\))?/',$column['type'],$matches);\n\n\t\t\t$c->raw_type = (count($matches) > 0 ? $matches[1] : $column['type']);\n\t\t\t$c->length = count($matches) >= 4 ? intval($matches[3]) : intval($column['attlen']);\n\n\t\t\tif ($c->length < 0)\n\t\t\t\t$c->length = null;\n\t\t}\n\n\t\t$c->map_raw_type();\n\n\t\tif ($column['default'])\n\t\t{\n\t\t\tpreg_match(\"/^nextval\\('(.*)'\\)$/\",$column['default'],$matches);\n\n\t\t\tif (count($matches) == 2)\n\t\t\t\t$c->sequence = $matches[1];\n\t\t\telse\n\t\t\t\t$c->default = $c->cast($column['default'],$this);\n\t\t}\n\t\treturn $c;\n\t}\n\n\tpublic function set_encoding($charset)\n\t{\n\t\t$this->query(\"SET NAMES '$charset'\");\n\t}\n\n\tpublic function native_database_types()\n\t{\n\t\treturn array(\n\t\t\t'primary_key' => 'serial primary key',\n\t\t\t'string' => array('name' => 'character varying', 'length' => 255),\n\t\t\t'text' => array('name' => 'text'),\n\t\t\t'integer' => array('name' => 'integer'),\n\t\t\t'float' => array('name' => 'float'),\n\t\t\t'datetime' => array('name' => 'datetime'),\n\t\t\t'timestamp' => array('name' => 'timestamp'),\n\t\t\t'time' => array('name' => 'time'),\n\t\t\t'date' => array('name' => 'date'),\n\t\t\t'binary' => array('name' => 'binary'),\n\t\t\t'boolean' => array('name' => 'boolean')\n\t\t);\n\t}\n\n}\n?>\n"
  },
  {
    "path": "lib/adapters/SqliteAdapter.php",
    "content": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\nuse PDO;\n\n/**\n * Adapter for SQLite.\n *\n * @package ActiveRecord\n */\nclass SqliteAdapter extends Connection\n{\n\n\tstatic $datetime_format = 'Y-m-d H:i:s';\n\n\tprotected function __construct($info)\n\t{\n\t\tif (!file_exists($info->host))\n\t\t\tthrow new DatabaseException(\"Could not find sqlite db: $info->host\");\n\n\t\t$this->connection = new PDO(\"sqlite:$info->host\",null,null,static::$PDO_OPTIONS);\n\t}\n\n\tpublic function limit($sql, $offset, $limit)\n\t{\n\t\t$offset = is_null($offset) ? '' : intval($offset) . ',';\n\t\t$limit = intval($limit);\n\t\treturn \"$sql LIMIT {$offset}$limit\";\n\t}\n\n\tpublic function query_column_info($table)\n\t{\n\t\treturn $this->query(\"pragma table_info($table)\");\n\t}\n\n\tpublic function query_for_tables()\n\t{\n\t\treturn $this->query(\"SELECT name FROM sqlite_master\");\n\t}\n\n\tpublic function create_column($column)\n\t{\n\t\t$c = new Column();\n\t\t$c->inflected_name  = Inflector::instance()->variablize($column['name']);\n\t\t$c->name            = $column['name'];\n\t\t$c->nullable        = $column['notnull'] ? false : true;\n\t\t$c->pk              = $column['pk'] ? true : false;\n\t\t$c->auto_increment  = in_array(\n\t\t\t\tstrtoupper($column['type']),\n\t\t\t\tarray('INT', 'INTEGER')\n\t\t\t) && $c->pk;\n\n\t\t$column['type'] = preg_replace('/ +/',' ',$column['type']);\n\t\t$column['type'] = str_replace(array('(',')'),' ',$column['type']);\n\t\t$column['type'] = Utils::squeeze(' ',$column['type']);\n\t\t$matches = explode(' ',$column['type']);\n\n\t\tif (!empty($matches))\n\t\t{\n\t\t\t$c->raw_type = strtolower($matches[0]);\n\n\t\t\tif (count($matches) > 1)\n\t\t\t\t$c->length = intval($matches[1]);\n\t\t}\n\n\t\t$c->map_raw_type();\n\n\t\tif ($c->type == Column::DATETIME)\n\t\t\t$c->length = 19;\n\t\telseif ($c->type == Column::DATE)\n\t\t\t$c->length = 10;\n\n\t\t// From SQLite3 docs: The value is a signed integer, stored in 1, 2, 3, 4, 6,\n\t\t// or 8 bytes depending on the magnitude of the value.\n\t\t// so is it ok to assume it's possible an int can always go up to 8 bytes?\n\t\tif ($c->type == Column::INTEGER && !$c->length)\n\t\t\t$c->length = 8;\n\n\t\t$c->default = $c->cast($column['dflt_value'],$this);\n\n\t\treturn $c;\n\t}\n\n\tpublic function set_encoding($charset)\n\t{\n\t\tthrow new ActiveRecordException(\"SqliteAdapter::set_charset not supported.\");\n\t}\n\n\tpublic function accepts_limit_and_order_for_update_and_delete() { return true; }\n\n\tpublic function native_database_types()\n\t{\n\t\treturn array(\n\t\t\t'primary_key' => 'integer not null primary key',\n\t\t\t'string' => array('name' => 'varchar', 'length' => 255),\n\t\t\t'text' => array('name' => 'text'),\n\t\t\t'integer' => array('name' => 'integer'),\n\t\t\t'float' => array('name' => 'float'),\n\t\t\t'decimal' => array('name' => 'decimal'),\n\t\t\t'datetime' => array('name' => 'datetime'),\n\t\t\t'timestamp' => array('name' => 'datetime'),\n\t\t\t'time' => array('name' => 'time'),\n\t\t\t'date' => array('name' => 'date'),\n\t\t\t'binary' => array('name' => 'blob'),\n\t\t\t'boolean' => array('name' => 'boolean')\n\t\t);\n\t}\n\n}\n?>"
  },
  {
    "path": "lib/cache/Memcache.php",
    "content": "<?php\nnamespace ActiveRecord;\n\nclass Memcache\n{\n\tconst DEFAULT_PORT = 11211;\n\n\tprivate $memcache;\n\n\t/**\n\t * Creates a Memcache instance.\n\t *\n\t * Takes an $options array w/ the following parameters:\n\t *\n\t * <ul>\n\t * <li><b>host:</b> host for the memcache server </li>\n\t * <li><b>port:</b> port for the memcache server </li>\n\t * </ul>\n\t * @param array $options\n\t */\n\tpublic function __construct($options)\n\t{\n\t\t$this->memcache = new \\Memcache();\n\t\t$options['port'] = isset($options['port']) ? $options['port'] : self::DEFAULT_PORT;\n\n\t\tif (!@$this->memcache->connect($options['host'], $options['port'])) {\n\t\t\tif ($error = error_get_last()) {\n\t\t\t\t$message = $error['message'];\n\t\t\t} else {\n\t\t\t\t$message = sprintf('Could not connect to %s:%s', $options['host'], $options['port']);\n\t\t\t}\n\t\t\tthrow new CacheException($message);\n\t\t}\n\t}\n\n\tpublic function flush()\n\t{\n\t\t$this->memcache->flush();\n\t}\n\n\tpublic function read($key)\n\t{\n\t\treturn $this->memcache->get($key);\n\t}\n\n\tpublic function write($key, $value, $expire)\n\t{\n\t\t$this->memcache->set($key,$value,null,$expire);\n\t}\n\n\tpublic function delete($key)\n\t{\n\t\t$this->memcache->delete($key);\n\t}\n}\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit backupGlobals=\"false\"\n         backupStaticAttributes=\"false\"\n         colors=\"true\"\n         convertErrorsToExceptions=\"true\"\n         convertNoticesToExceptions=\"true\"\n         convertWarningsToExceptions=\"true\"\n         processIsolation=\"false\"\n         stopOnFailure=\"false\"\n         bootstrap=\"test/helpers/config.php\"\n>\n    <testsuites>\n        <testsuite name=\"PHP ActiveRecord Test Suite\">\n            <directory>./test/</directory>\n        </testsuite>\n    </testsuites>\n</phpunit>\n"
  },
  {
    "path": "test/ActiveRecordCacheTest.php",
    "content": "<?php\nuse ActiveRecord\\Cache;\n\nclass ActiveRecordCacheTest extends DatabaseTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tif (!extension_loaded('memcache'))\n\t\t{\n\t\t\t$this->markTestSkipped('The memcache extension is not available');\n\t\t\treturn;\n\t\t}\n\t\t\n\t\tparent::set_up($connection_name);\n\t\tActiveRecord\\Config::instance()->set_cache('memcache://localhost');\n\t}\n\n\tpublic function tear_down()\n\t{\n\t\tCache::flush();\n\t\tCache::initialize(null);\n\t}\n\n\tpublic function test_default_expire()\n\t{\n\t\t$this->assert_equals(30,Cache::$options['expire']);\n\t}\n\n\tpublic function test_explicit_default_expire()\n\t{\n\t\tActiveRecord\\Config::instance()->set_cache('memcache://localhost',array('expire' => 1));\n\t\t$this->assert_equals(1,Cache::$options['expire']);\n\t}\n\n\tpublic function test_caches_column_meta_data()\n\t{\n\t\tAuthor::first();\n\n\t\t$table_name = Author::table()->get_fully_qualified_table_name(!($this->conn instanceof ActiveRecord\\PgsqlAdapter));\n\t\t$value = Cache::$adapter->read(\"get_meta_data-$table_name\");\n\t\t$this->assert_true(is_array($value));\n\t}\n}\n?>\n"
  },
  {
    "path": "test/ActiveRecordFindTest.php",
    "content": "<?php\n\nclass ActiveRecordFindTest extends DatabaseTest\n{\n\t/**\n\t * @expectedException ActiveRecord\\RecordNotFound\n\t */\n\tpublic function test_find_with_no_params()\n\t{\n\t\tAuthor::find();\n\t}\n\n\tpublic function test_find_by_pk()\n\t{\n\t\t$author = Author::find(3);\n\t\t$this->assert_equals(3,$author->id);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\RecordNotFound\n\t */\n\tpublic function test_find_by_pkno_results()\n\t{\n\t\tAuthor::find(99999999);\n\t}\n\n\tpublic function test_find_by_multiple_pk_with_partial_match()\n\t{\n\t\ttry\n\t\t{\n\t\t\tAuthor::find(1,999999999);\n\t\t\t$this->fail();\n\t\t}\n\t\tcatch (ActiveRecord\\RecordNotFound $e)\n\t\t{\n\t\t\t$this->assert_true(strpos($e->getMessage(),'found 1, but was looking for 2') !== false);\n\t\t}\n\t}\n\n\tpublic function test_find_by_pk_with_options()\n\t{\n\t\t$author = Author::find(3,array('order' => 'name'));\n\t\t$this->assert_equals(3,$author->id);\n\t\t$this->assert_true(strpos(Author::table()->last_sql,'ORDER BY name') !== false);\n\t}\n\n\tpublic function test_find_by_pk_array()\n\t{\n\t\t$authors = Author::find(1,'2');\n\t\t$this->assert_equals(2, count($authors));\n\t\t$this->assert_equals(1, $authors[0]->id);\n\t\t$this->assert_equals(2, $authors[1]->id);\n\t}\n\n\tpublic function test_find_by_pk_array_with_options()\n\t{\n\t\t$authors = Author::find(1,'2',array('order' => 'name'));\n\t\t$this->assert_equals(2, count($authors));\n\t\t$this->assert_true(strpos(Author::table()->last_sql,'ORDER BY name') !== false);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\RecordNotFound\n\t */\n\tpublic function test_find_nothing_with_sql_in_string()\n\t{\n\t\tAuthor::first('name = 123123123');\n\t}\n\n\tpublic function test_find_all()\n\t{\n\t\t$authors = Author::find('all',array('conditions' => array('author_id IN(?)',array(1,2,3))));\n\t\t$this->assert_true(count($authors) >= 3);\n\t}\n\n\tpublic function test_find_all_with_no_bind_values()\n\t{\n\t\t$authors = Author::find('all',array('conditions' => array('author_id IN(1,2,3)')));\n\t\t$this->assert_equals(1,$authors[0]->author_id);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_find_all_with_empty_array_bind_value_throws_exception()\n\t{\n\t\t$authors = Author::find('all',array('conditions' => array('author_id IN(?)', array())));\n\t\t$this->assertCount(0,$authors);\n\t}\n\n\tpublic function test_find_hash_using_alias()\n\t{\n\t\t$venues = Venue::all(array('conditions' => array('marquee' => 'Warner Theatre', 'city' => array('Washington','New York'))));\n\t\t$this->assert_true(count($venues) >= 1);\n\t}\n\n\tpublic function test_find_hash_using_alias_with_null()\n\t{\n\t\t$venues = Venue::all(array('conditions' => array('marquee' => null)));\n\t\t$this->assert_equals(0,count($venues));\n\t}\n\n\tpublic function test_dynamic_finder_using_alias()\n\t{\n\t\t$this->assert_not_null(Venue::find_by_marquee('Warner Theatre'));\n\t}\n\n\tpublic function test_find_all_hash()\n\t{\n\t\t$books = Book::find('all',array('conditions' => array('author_id' => 1)));\n\t\t$this->assert_true(count($books) > 0);\n\t}\n\n\tpublic function test_find_all_hash_with_order()\n\t{\n\t\t$books = Book::find('all',array('conditions' => array('author_id' => 1), 'order' => 'name DESC'));\n\t\t$this->assert_true(count($books) > 0);\n\t}\n\n\tpublic function test_find_all_no_args()\n\t{\n\t\t$author = Author::all();\n\t\t$this->assert_true(count($author) > 1);\n\t}\n\n\tpublic function test_find_all_no_results()\n\t{\n\t\t$authors = Author::find('all',array('conditions' => array('author_id IN(11111111111,22222222222,333333333333)')));\n\t\t$this->assert_equals(array(),$authors);\n\t}\n\n\tpublic function test_find_first()\n\t{\n\t\t$author = Author::find('first',array('conditions' => array('author_id IN(?)', array(1,2,3))));\n\t\t$this->assert_equals(1,$author->author_id);\n\t\t$this->assert_equals('Tito',$author->name);\n\t}\n\n\tpublic function test_find_first_no_results()\n\t{\n\t\t$this->assert_null(Author::find('first',array('conditions' => 'author_id=1111111')));\n\t}\n\n\tpublic function test_find_first_using_pk()\n\t{\n\t\t$author = Author::find('first',3);\n\t\t$this->assert_equals(3,$author->author_id);\n\t}\n\n\tpublic function test_find_first_with_conditions_as_string()\n\t{\n\t\t$author = Author::find('first',array('conditions' => 'author_id=3'));\n\t\t$this->assert_equals(3,$author->author_id);\n\t}\n\n\tpublic function test_find_all_with_conditions_as_string()\n\t{\n\t\t$author = Author::find('all',array('conditions' => 'author_id in(2,3)'));\n\t\t$this->assert_equals(2,count($author));\n\t}\n\n\tpublic function test_find_by_sql()\n\t{\n\t\t$author = Author::find_by_sql(\"SELECT * FROM authors WHERE author_id in(1,2)\");\n\t\t$this->assert_equals(1,$author[0]->author_id);\n\t\t$this->assert_equals(2,count($author));\n\t}\n\n\tpublic function test_find_by_sqltakes_values_array()\n\t{\n\t\t$author = Author::find_by_sql(\"SELECT * FROM authors WHERE author_id=?\",array(1));\n\t\t$this->assert_not_null($author);\n\t}\n\n\tpublic function test_find_with_conditions()\n\t{\n\t\t$author = Author::find(array('conditions' => array('author_id=? and name=?', 1, 'Tito')));\n\t\t$this->assert_equals(1,$author->author_id);\n\t}\n\n\tpublic function test_find_last()\n\t{\n\t\t$author = Author::last();\n\t\t$this->assert_equals(4, $author->author_id);\n\t\t$this->assert_equals('Uncle Bob',$author->name);\n\t}\n\n\tpublic function test_find_last_using_string_condition()\n\t{\n\t\t$author = Author::find('last', array('conditions' => 'author_id IN(1,2,3,4)'));\n\t\t$this->assert_equals(4, $author->author_id);\n\t\t$this->assert_equals('Uncle Bob',$author->name);\n\t}\n\n\tpublic function test_limit_before_order()\n\t{\n\t\t$authors = Author::all(array('limit' => 2, 'order' => 'author_id desc', 'conditions' => 'author_id in(1,2)'));\n\t\t$this->assert_equals(2,$authors[0]->author_id);\n\t\t$this->assert_equals(1,$authors[1]->author_id);\n\t}\n\n\tpublic function test_for_each()\n\t{\n\t\t$i = 0;\n\t\t$res = Author::all();\n\n\t\tforeach ($res as $author)\n\t\t{\n\t\t\t$this->assert_true($author instanceof ActiveRecord\\Model);\n\t\t\t$i++;\n\t\t}\n\t\t$this->assert_true($i > 0);\n\t}\n\n\tpublic function test_fetch_all()\n\t{\n\t\t$i = 0;\n\n\t\tforeach (Author::all() as $author)\n\t\t{\n\t\t\t$this->assert_true($author instanceof ActiveRecord\\Model);\n\t\t\t$i++;\n\t\t}\n\t\t$this->assert_true($i > 0);\n\t}\n\n\tpublic function test_count()\n\t{\n\t\t$this->assert_equals(1,Author::count(1));\n\t\t$this->assert_equals(2,Author::count(array(1,2)));\n\t\t$this->assert_true(Author::count() > 1);\n\t\t$this->assert_equals(0,Author::count(array('conditions' => 'author_id=99999999999999')));\n\t\t$this->assert_equals(2,Author::count(array('conditions' => 'author_id=1 or author_id=2')));\n\t\t$this->assert_equals(1,Author::count(array('name' => 'Tito', 'author_id' => 1)));\n\t}\n\n\tpublic function test_gh149_empty_count()\n\t{\n\t\t$total = Author::count();\n\t\t$this->assert_equals($total, Author::count(null));\n\t\t$this->assert_equals($total, Author::count(array()));\n\t}\n\n\tpublic function test_exists()\n\t{\n\t\t$this->assert_true(Author::exists(1));\n\t\t$this->assert_true(Author::exists(array('conditions' => 'author_id=1')));\n\t\t$this->assert_true(Author::exists(array('conditions' => array('author_id=? and name=?', 1, 'Tito'))));\n\t\t$this->assert_false(Author::exists(9999999));\n\t\t$this->assert_false(Author::exists(array('conditions' => 'author_id=999999')));\n\t}\n\n\tpublic function test_find_by_call_static()\n\t{\n\t\t$this->assert_equals('Tito',Author::find_by_name('Tito')->name);\n\t\t$this->assert_equals('Tito',Author::find_by_author_id_and_name(1,'Tito')->name);\n\t\t$this->assert_equals('George W. Bush',Author::find_by_author_id_or_name(2,'Tito',array('order' => 'author_id desc'))->name);\n\t\t$this->assert_equals('Tito',Author::find_by_name(array('Tito','George W. Bush'),array('order' => 'name desc'))->name);\n\t}\n\n\tpublic function test_find_by_call_static_no_results()\n\t{\n\t\t$this->assert_null(Author::find_by_name('SHARKS WIT LASERZ'));\n\t\t$this->assert_null(Author::find_by_name_or_author_id());\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_find_by_call_static_invalid_column_name()\n\t{\n\t\tAuthor::find_by_sharks();\n\t}\n\n\tpublic function test_find_all_by_call_static()\n\t{\n\t\t$x = Author::find_all_by_name('Tito');\n\t\t$this->assert_equals('Tito',$x[0]->name);\n\t\t$this->assert_equals(1,count($x));\n\n\t\t$x = Author::find_all_by_author_id_or_name(2,'Tito',array('order' => 'name asc'));\n\t\t$this->assert_equals(2,count($x));\n\t\t$this->assert_equals('George W. Bush',$x[0]->name);\n\t}\n\n\tpublic function test_find_all_by_call_static_no_results()\n\t{\n\t\t$x = Author::find_all_by_name('SHARKSSSSSSS');\n\t\t$this->assert_equals(0,count($x));\n\t}\n\n\tpublic function test_find_all_by_call_static_with_array_values_and_options()\n\t{\n\t\t$author = Author::find_all_by_name(array('Tito','Bill Clinton'),array('order' => 'name desc'));\n\t\t$this->assert_equals('Tito',$author[0]->name);\n\t\t$this->assert_equals('Bill Clinton',$author[1]->name);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_find_all_by_call_static_undefined_method()\n\t{\n\t\tAuthor::find_sharks('Tito');\n\t}\n\n\tpublic function test_find_all_takes_limit_options()\n\t{\n\t\t$authors = Author::all(array('limit' => 1, 'offset' => 2, 'order' => 'name desc'));\n\t\t$this->assert_equals('George W. Bush',$authors[0]->name);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_find_by_call_static_with_invalid_field_name()\n\t{\n\t\tAuthor::find_by_some_invalid_field_name('Tito');\n\t}\n\n\tpublic function test_find_with_select()\n\t{\n\t\t$author = Author::first(array('select' => 'name, 123 as bubba', 'order' => 'name desc'));\n\t\t$this->assert_equals('Uncle Bob',$author->name);\n\t\t$this->assert_equals(123,$author->bubba);\n\t}\n\n\tpublic function test_find_with_select_non_selected_fields_should_not_have_attributes()\n\t{\n\t\t$author = Author::first(array('select' => 'name, 123 as bubba'));\n\t\ttry {\n\t\t\t$author->id;\n\t\t\t$this->fail('expected ActiveRecord\\UndefinedPropertyExecption');\n\t\t} catch (ActiveRecord\\UndefinedPropertyException $e) {\n\t\t\t;\n\t\t}\n\t}\n\n\tpublic function test_joins_on_model_with_association_and_explicit_joins()\n\t{\n\t\tJoinBook::$belongs_to = array(array('author'));\n\t\tJoinBook::first(array('joins' => array('author','LEFT JOIN authors a ON(books.secondary_author_id=a.author_id)')));\n\t\t$this->assert_sql_has('INNER JOIN authors ON(books.author_id = authors.author_id)',JoinBook::table()->last_sql);\n\t\t$this->assert_sql_has('LEFT JOIN authors a ON(books.secondary_author_id=a.author_id)',JoinBook::table()->last_sql);\n\t}\n\n\tpublic function test_joins_on_model_with_explicit_joins()\n\t{\n\t\tJoinBook::first(array('joins' => array('LEFT JOIN authors a ON(books.secondary_author_id=a.author_id)')));\n\t\t$this->assert_sql_has('LEFT JOIN authors a ON(books.secondary_author_id=a.author_id)',JoinBook::table()->last_sql);\n\t}\n\n\tpublic function test_group()\n\t{\n\t\t$venues = Venue::all(array('select' => 'state', 'group' => 'state'));\n\t\t$this->assert_true(count($venues) > 0);\n\t\t$this->assert_sql_has('GROUP BY state',ActiveRecord\\Table::load('Venue')->last_sql);\n\t}\n\n\tpublic function test_group_with_order_and_limit_and_having()\n\t{\n\t\t$venues = Venue::all(array('select' => 'state', 'group' => 'state', 'having' => 'length(state) = 2', 'order' => 'state', 'limit' => 2));\n\t\t$this->assert_true(count($venues) > 0);\n\t\t$this->assert_sql_has($this->conn->limit('SELECT state FROM venues GROUP BY state HAVING length(state) = 2 ORDER BY state',null,2),Venue::table()->last_sql);\n\t}\n\n\tpublic function test_escape_quotes()\n\t{\n\t\t$author = Author::find_by_name(\"Tito's\");\n\t\t$this->assert_not_equals(\"Tito's\",Author::table()->last_sql);\n\t}\n\n\tpublic function test_from()\n\t{\n\t\t$author = Author::find('first', array('from' => 'books', 'order' => 'author_id asc'));\n\t\t$this->assert_true($author instanceof Author);\n\t\t$this->assert_not_null($author->book_id);\n\n\t\t$author = Author::find('first', array('from' => 'authors', 'order' => 'author_id asc'));\n\t\t$this->assert_true($author instanceof Author);\n\t\t$this->assert_equals(1, $author->id);\n\t}\n\n\tpublic function test_having()\n\t{\n\t\tif ($this->conn instanceof ActiveRecord\\OciAdapter)\n\t\t{\n\t\t\t$author = Author::first(array(\n\t\t\t\t'select' => 'to_char(created_at,\\'YYYY-MM-DD\\') as created_at',\n\t\t\t\t'group'  => 'to_char(created_at,\\'YYYY-MM-DD\\')',\n\t\t\t\t'having' => \"to_char(created_at,'YYYY-MM-DD') > '2009-01-01'\"));\n\t\t\t$this->assert_sql_has(\"GROUP BY to_char(created_at,'YYYY-MM-DD') HAVING to_char(created_at,'YYYY-MM-DD') > '2009-01-01'\",Author::table()->last_sql);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t$author = Author::first(array(\n\t\t\t\t'select' => 'date(created_at) as created_at',\n\t\t\t\t'group'  => 'date(created_at)',\n\t\t\t\t'having' => \"date(created_at) > '2009-01-01'\"));\n\t\t\t$this->assert_sql_has(\"GROUP BY date(created_at) HAVING date(created_at) > '2009-01-01'\",Author::table()->last_sql);\n\t\t}\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_from_with_invalid_table()\n\t{\n\t\t$author = Author::find('first', array('from' => 'wrong_authors_table'));\n\t}\n\n\tpublic function test_find_with_hash()\n\t{\n\t\t$this->assert_not_null(Author::find(array('name' => 'Tito')));\n\t\t$this->assert_not_null(Author::find('first',array('name' => 'Tito')));\n\t\t$this->assert_equals(1,count(Author::find('all',array('name' => 'Tito'))));\n\t\t$this->assert_equals(1,count(Author::all(array('name' => 'Tito'))));\n\t}\n\n\tpublic function test_find_or_create_by_on_existing_record()\n\t{\n\t\t$this->assert_not_null(Author::find_or_create_by_name('Tito'));\n\t}\n\n\tpublic function test_find_or_create_by_creates_new_record()\n\t{\n\t\t$author = Author::find_or_create_by_name_and_encrypted_password('New Guy','pencil');\n\t\t$this->assert_true($author->author_id > 0);\n\t\t$this->assert_equals('pencil',$author->encrypted_password);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_find_or_create_by_throws_exception_when_using_or()\n\t{\n\t\tAuthor::find_or_create_by_name_or_encrypted_password('New Guy','pencil');\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\RecordNotFound\n\t */\n\tpublic function test_find_by_zero()\n\t{\n\t\tAuthor::find(0);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\RecordNotFound\n\t */\n\tpublic function test_find_by_null()\n\t{\n\t\tAuthor::find(null);\n\t}\n\n\tpublic function test_count_by()\n\t{\n\t\t$this->assert_equals(2,Venue::count_by_state('VA'));\n\t\t$this->assert_equals(3,Venue::count_by_state_or_name('VA','Warner Theatre'));\n\t\t$this->assert_equals(0,Venue::count_by_state_and_name('VA','zzzzzzzzzzzzz'));\n\t}\n\n\tpublic function test_find_by_pk_should_not_use_limit()\n\t{\n\t\tAuthor::find(1);\n\t\t$this->assert_sql_has('SELECT * FROM authors WHERE author_id=?',Author::table()->last_sql);\n\t}\n\n\tpublic function test_find_by_datetime()\n\t{\n\t\t$now = new DateTime();\n\t\t$arnow = new ActiveRecord\\DateTime();\n\t\t$arnow->setTimestamp($now->getTimestamp());\n\n\t\tAuthor::find(1)->update_attribute('created_at',$now);\n\t\t$this->assert_not_null(Author::find_by_created_at($now));\n\t\t$this->assert_not_null(Author::find_by_created_at($arnow));\n\t}\n};\n?>\n"
  },
  {
    "path": "test/ActiveRecordTest.php",
    "content": "<?php\n\nclass ActiveRecordTest extends DatabaseTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up($connection_name);\n\t\t$this->options = array('conditions' => 'blah', 'order' => 'blah');\n\t}\n\n\tpublic function test_options_is_not()\n\t{\n\t\t$this->assert_false(Author::is_options_hash(null));\n\t\t$this->assert_false(Author::is_options_hash(''));\n\t\t$this->assert_false(Author::is_options_hash('tito'));\n\t\t$this->assert_false(Author::is_options_hash(array()));\n\t\t$this->assert_false(Author::is_options_hash(array(1,2,3)));\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_options_hash_with_unknown_keys() {\n\t\t$this->assert_false(Author::is_options_hash(array('conditions' => 'blah', 'sharks' => 'laserz', 'dubya' => 'bush')));\n\t}\n\n\tpublic function test_options_is_hash()\n\t{\n\t\t$this->assert_true(Author::is_options_hash($this->options));\n\t}\n\n\tpublic function test_extract_and_validate_options() {\n\t\t$args = array('first',$this->options);\n\t\t$this->assert_equals($this->options,Author::extract_and_validate_options($args));\n\t\t$this->assert_equals(array('first'),$args);\n\t}\n\n\tpublic function test_extract_and_validate_options_with_array_in_args() {\n\t\t$args = array('first',array(1,2),$this->options);\n\t\t$this->assert_equals($this->options,Author::extract_and_validate_options($args));\n\t}\n\n\tpublic function test_extract_and_validate_options_removes_options_hash() {\n\t\t$args = array('first',$this->options);\n\t\tAuthor::extract_and_validate_options($args);\n\t\t$this->assert_equals(array('first'),$args);\n\t}\n\n\tpublic function test_extract_and_validate_options_nope() {\n\t\t$args = array('first');\n\t\t$this->assert_equals(array(),Author::extract_and_validate_options($args));\n\t\t$this->assert_equals(array('first'),$args);\n\t}\n\n\tpublic function test_extract_and_validate_options_nope_because_wasnt_at_end() {\n\t\t$args = array('first',$this->options,array(1,2));\n\t\t$this->assert_equals(array(),Author::extract_and_validate_options($args));\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\UndefinedPropertyException\n\t */\n\tpublic function test_invalid_attribute()\n\t{\n\t\t$author = Author::find('first',array('conditions' => 'author_id=1'));\n\t\t$author->some_invalid_field_name;\n\t}\n\n\tpublic function test_invalid_attributes()\n\t{\n\t\t$book = Book::find(1);\n\t\ttry {\n\t\t\t$book->update_attributes(array('name' => 'new name', 'invalid_attribute' => true , 'another_invalid_attribute' => 'something'));\n\t\t} catch (ActiveRecord\\UndefinedPropertyException $e) {\n\t\t\t$exceptions = explode(\"\\r\\n\", $e->getMessage());\n\t\t}\n\n\t\t$this->assert_equals(1, substr_count($exceptions[0], 'invalid_attribute'));\n\t\t$this->assert_equals(1, substr_count($exceptions[1], 'another_invalid_attribute'));\n\t}\n\n\tpublic function test_getter_undefined_property_exception_includes_model_name()\n\t{\n\t\t$this->assert_exception_message_contains(\"Author->this_better_not_exist\",function()\n\t\t{\n\t\t\t$author = new Author();\n\t\t\t$author->this_better_not_exist;\n\t\t});\n\t}\n\n\tpublic function test_mass_assignment_undefined_property_exception_includes_model_name()\n\t{\n\t\t$this->assert_exception_message_contains(\"Author->this_better_not_exist\",function()\n\t\t{\n\t\t\tnew Author(array(\"this_better_not_exist\" => \"hi\"));\n\t\t});\n\t}\n\n\tpublic function test_setter_undefined_property_exception_includes_model_name()\n\t{\n\t\t$this->assert_exception_message_contains(\"Author->this_better_not_exist\",function()\n\t\t{\n\t\t\t$author = new Author();\n\t\t\t$author->this_better_not_exist = \"hi\";\n\t\t});\n\t}\n\n\tpublic function test_get_values_for()\n\t{\n\t\t$book = Book::find_by_name('Ancient Art of Main Tanking');\n\t\t$ret = $book->get_values_for(array('book_id','author_id'));\n\t\t$this->assert_equals(array('book_id','author_id'),array_keys($ret));\n\t\t$this->assert_equals(array(1,1),array_values($ret));\n\t}\n\n\tpublic function test_hyphenated_column_names_to_underscore()\n\t{\n\t\tif ($this->conn instanceof ActiveRecord\\OciAdapter)\n\t\t\treturn;\n\n\t\t$keys = array_keys(RmBldg::first()->attributes());\n\t\t$this->assert_true(in_array('rm_name',$keys));\n\t}\n\n\tpublic function test_column_names_with_spaces()\n\t{\n\t\tif ($this->conn instanceof ActiveRecord\\OciAdapter)\n\t\t\treturn;\n\n\t\t$keys = array_keys(RmBldg::first()->attributes());\n\t\t$this->assert_true(in_array('space_out',$keys));\n\t}\n\n\tpublic function test_mixed_case_column_name()\n\t{\n\t\t$keys = array_keys(Author::first()->attributes());\n\t\t$this->assert_true(in_array('mixedcasefield',$keys));\n\t}\n\n\tpublic function test_mixed_case_primary_key_save()\n\t{\n\t\t$venue = Venue::find(1);\n\t\t$venue->name = 'should not throw exception';\n\t\t$venue->save();\n\t\t$this->assert_equals($venue->name,Venue::find(1)->name);\n\t}\n\n\tpublic function test_reload()\n\t{\n\t\t$venue = Venue::find(1);\n\t\t$this->assert_equals('NY', $venue->state);\n\t\t$venue->state = 'VA';\n\t\t$this->assert_equals('VA', $venue->state);\n\t\t$venue->reload();\n\t\t$this->assert_equals('NY', $venue->state);\n\t}\n\t\n\tpublic function test_reload_protected_attribute()\n\t{\n\t\t$book = BookAttrAccessible::find(1);\n\t\n\t\t$book->name = \"Should not stay\";\n\t\t$book->reload();\n\t\t$this->assert_not_equals(\"Should not stay\", $book->name);\n\t}\n\n\tpublic function test_active_record_model_home_not_set()\n\t{\n\t\t$home = ActiveRecord\\Config::instance()->get_model_directory();\n\t\tActiveRecord\\Config::instance()->set_model_directory(__FILE__);\n\t\t$this->assert_equals(false,class_exists('TestAutoload'));\n\n\t\tActiveRecord\\Config::instance()->set_model_directory($home);\n\t}\n\n\tpublic function test_auto_load_with_namespaced_model()\n\t{\n\t\t$this->assert_true(class_exists('NamespaceTest\\Book'));\n\t}\n\n\tpublic function test_namespace_gets_stripped_from_table_name()\n\t{\n\t\t$model = new NamespaceTest\\Book();\n\t\t$this->assert_equals('books',$model->table()->table);\n\t}\n\n\tpublic function test_namespace_gets_stripped_from_inferred_foreign_key()\n\t{\n\t\t$model = new NamespaceTest\\Book();\n\t\t$table = ActiveRecord\\Table::load(get_class($model));\n\n\t\t$this->assert_equals($table->get_relationship('parent_book')->foreign_key[0], 'book_id');\n\t\t$this->assert_equals($table->get_relationship('parent_book_2')->foreign_key[0], 'book_id');\n\t\t$this->assert_equals($table->get_relationship('parent_book_3')->foreign_key[0], 'book_id');\n\t}\n\n\tpublic function test_namespaced_relationship_associates_correctly()\n\t{\n\t\t$model = new NamespaceTest\\Book();\n\t\t$table = ActiveRecord\\Table::load(get_class($model));\n\n\t\t$this->assert_not_null($table->get_relationship('parent_book'));\n\t\t$this->assert_not_null($table->get_relationship('parent_book_2'));\n\t\t$this->assert_not_null($table->get_relationship('parent_book_3'));\n\n\t\t$this->assert_not_null($table->get_relationship('pages'));\n\t\t$this->assert_not_null($table->get_relationship('pages_2'));\n\n\t\t$this->assert_null($table->get_relationship('parent_book_4'));\n\t\t$this->assert_null($table->get_relationship('pages_3'));\n\n\t\t// Should refer to the same class\n\t\t$this->assert_same(\n\t\t\tltrim($table->get_relationship('parent_book')->class_name, '\\\\'),\n\t\t\tltrim($table->get_relationship('parent_book_2')->class_name, '\\\\')\n\t\t);\n\n\t\t// Should refer to different classes\n\t\t$this->assert_not_same(\n\t\t\tltrim($table->get_relationship('parent_book_2')->class_name, '\\\\'),\n\t\t\tltrim($table->get_relationship('parent_book_3')->class_name, '\\\\')\n\t\t);\n\n\t\t// Should refer to the same class\n\t\t$this->assert_same(\n\t\t\tltrim($table->get_relationship('pages')->class_name, '\\\\'),\n\t\t\tltrim($table->get_relationship('pages_2')->class_name, '\\\\')\n\t\t);\n\t}\n\n\tpublic function test_should_have_all_column_attributes_when_initializing_with_array()\n\t{\n\t\t$author = new Author(array('name' => 'Tito'));\n\t\t$this->assert_true(count(array_keys($author->attributes())) >= 9);\n\t}\n\n\tpublic function test_defaults()\n\t{\n\t\t$author = new Author();\n\t\t$this->assert_equals('default_name',$author->name);\n\t}\n\n\tpublic function test_alias_attribute_getter()\n\t{\n\t\t$venue = Venue::find(1);\n\t\t$this->assert_equals($venue->marquee, $venue->name);\n\t\t$this->assert_equals($venue->mycity, $venue->city);\n\t}\n\n\tpublic function test_alias_attribute_setter()\n\t{\n\t\t$venue = Venue::find(1);\n\t\t$venue->marquee = 'new name';\n\t\t$this->assert_equals($venue->marquee, 'new name');\n\t\t$this->assert_equals($venue->marquee, $venue->name);\n\n\t\t$venue->name = 'another name';\n\t\t$this->assert_equals($venue->name, 'another name');\n\t\t$this->assert_equals($venue->marquee, $venue->name);\n\t}\n\n\tpublic function test_alias_from_mass_attributes()\n\t{\n\t\t$venue = new Venue(array('marquee' => 'meme', 'id' => 123));\n\t\t$this->assert_equals('meme',$venue->name);\n\t\t$this->assert_equals($venue->marquee,$venue->name);\n\t}\n\n\tpublic function test_gh18_isset_on_aliased_attribute()\n\t{\n\t\t$this->assert_true(isset(Venue::first()->marquee));\n\t}\n\n\tpublic function test_attr_accessible()\n\t{\n\t\t$book = new BookAttrAccessible(array('name' => 'should not be set', 'author_id' => 1));\n\t\t$this->assert_null($book->name);\n\t\t$this->assert_equals(1,$book->author_id);\n\t\t$book->name = 'test';\n\t\t$this->assert_equals('test', $book->name);\n\t}\n\n\tpublic function test_attr_protected()\n\t{\n\t\t$book = new BookAttrAccessible(array('book_id' => 999));\n\t\t$this->assert_null($book->book_id);\n\t\t$book->book_id = 999;\n\t\t$this->assert_equals(999, $book->book_id);\n\t}\n\n\tpublic function test_isset()\n\t{\n\t\t$book = new Book();\n\t\t$this->assert_true(isset($book->name));\n\t\t$this->assert_false(isset($book->sharks));\n\t}\n\n\tpublic function test_readonly_only_halt_on_write_method()\n\t{\n\t\t$book = Book::first(array('readonly' => true));\n\t\t$this->assert_true($book->is_readonly());\n\n\t\ttry {\n\t\t\t$book->save();\n\t\t\t$this->fail('expected exception ActiveRecord\\ReadonlyException');\n\t\t} catch (ActiveRecord\\ReadonlyException $e) {\n\t\t}\n\n\t\t$book->name = 'some new name';\n\t\t$this->assert_equals($book->name, 'some new name');\n\t}\n\n\tpublic function test_cast_when_using_setter()\n\t{\n\t\t$book = new Book();\n\t\t$book->book_id = '1';\n\t\t$this->assert_same(1,$book->book_id);\n\t}\n\n\tpublic function test_cast_when_loading()\n\t{\n\t\t$book = Book::find(1);\n\t\t$this->assert_same(1,$book->book_id);\n\t\t$this->assert_same('Ancient Art of Main Tanking',$book->name);\n\t}\n\n\tpublic function test_cast_defaults()\n\t{\n\t\t$book = new Book();\n\t\t$this->assert_same(0.0,$book->special);\n\t}\n\n\tpublic function test_transaction_committed()\n\t{\n\t\t$original = Author::count();\n\t\t$ret = Author::transaction(function() { Author::create(array(\"name\" => \"blah\")); });\n\t\t$this->assert_equals($original+1,Author::count());\n\t\t$this->assert_true($ret);\n\t}\n\t\n\tpublic function test_transaction_committed_when_returning_true()\n\t{\n\t\t$original = Author::count();\n\t\t$ret = Author::transaction(function() { Author::create(array(\"name\" => \"blah\")); return true; });\n\t\t$this->assert_equals($original+1,Author::count());\n\t\t$this->assert_true($ret);\n\t}\n\t\n\tpublic function test_transaction_rolledback_by_returning_false()\n\t{\n\t\t$original = Author::count();\n\t\t\n\t\t$ret = Author::transaction(function()\n\t\t{\n\t\t\tAuthor::create(array(\"name\" => \"blah\"));\n\t\t\treturn false;\n\t\t});\n\t\t\n\t\t$this->assert_equals($original,Author::count());\n\t\t$this->assert_false($ret);\n\t}\n\t\n\tpublic function test_transaction_rolledback_by_throwing_exception()\n\t{\n\t\t$original = Author::count();\n\t\t$exception = null;\n\n\t\ttry\n\t\t{\n\t\t\tAuthor::transaction(function()\n\t\t\t{\n\t\t\t\tAuthor::create(array(\"name\" => \"blah\"));\n\t\t\t\tthrow new Exception(\"blah\");\n\t\t\t});\n\t\t}\n\t\tcatch (Exception $e)\n\t\t{\n\t\t\t$exception = $e;\n\t\t}\n\n\t\t$this->assert_not_null($exception);\n\t\t$this->assert_equals($original,Author::count());\n\t}\n\n\tpublic function test_delegate()\n\t{\n\t\t$event = Event::first();\n\t\t$this->assert_equals($event->venue->state,$event->state);\n\t\t$this->assert_equals($event->venue->address,$event->address);\n\t}\n\n\tpublic function test_delegate_prefix()\n\t{\n\t\t$event = Event::first();\n\t\t$this->assert_equals($event->host->name,$event->woot_name);\n\t}\n\n\tpublic function test_delegate_returns_null_if_relationship_does_not_exist()\n\t{\n\t\t$event = new Event();\n\t\t$this->assert_null($event->state);\n\t}\n\n\tpublic function test_delegate_set_attribute()\n\t{\n\t\t$event = Event::first();\n\t\t$event->state = 'MEXICO';\n\t\t$this->assert_equals('MEXICO',$event->venue->state);\n\t}\n\n\tpublic function test_delegate_getter_gh_98()\n\t{\n\t\tVenue::$use_custom_get_state_getter = true;\n\n\t\t$event = Event::first();\n\t\t$this->assert_equals('ny', $event->venue->state);\n\t\t$this->assert_equals('ny', $event->state);\n\n\t\tVenue::$use_custom_get_state_getter = false;\n\t}\n\n\tpublic function test_delegate_setter_gh_98()\n\t{\n\t\tVenue::$use_custom_set_state_setter = true;\n\n\t\t$event = Event::first();\n\t\t$event->state = 'MEXICO';\n\t\t$this->assert_equals('MEXICO#',$event->venue->state);\n\n\t\tVenue::$use_custom_set_state_setter = false;\n\t}\n\n\tpublic function test_table_name_with_underscores()\n\t{\n\t\t$this->assert_not_null(AwesomePerson::first());\n\t}\n\n\tpublic function test_model_should_default_as_new_record()\n\t{\n\t\t$author = new Author();\n\t\t$this->assert_true($author->is_new_record());\n\t}\n\n\tpublic function test_setter()\n\t{\n\t\t$author = new Author();\n\t\t$author->password = 'plaintext';\n\t\t$this->assert_equals(md5('plaintext'),$author->encrypted_password);\n\t}\n\n\tpublic function test_setter_with_same_name_as_an_attribute()\n\t{\n\t\t$author = new Author();\n\t\t$author->name = 'bob';\n\t\t$this->assert_equals('BOB',$author->name);\n\t}\n\n\tpublic function test_getter()\n\t{\n\t\t$book = Book::first();\n\t\t$this->assert_equals(strtoupper($book->name), $book->upper_name);\n\t}\n\n\tpublic function test_getter_with_same_name_as_an_attribute()\n\t{\n\t\tBook::$use_custom_get_name_getter = true;\n\t\t$book = new Book;\n\t\t$book->name = 'bob';\n\t\t$this->assert_equals('BOB', $book->name);\n\t\tBook::$use_custom_get_name_getter = false;\n\t}\n\n\tpublic function test_setting_invalid_date_should_set_date_to_null()\n\t{\n\t\t$author = new Author();\n\t\t$author->created_at = 'CURRENT_TIMESTAMP';\n\t\t$this->assertNull($author->created_at);\n\t}\n\n\tpublic function test_table_name()\n\t{\n\t\t$this->assert_equals('authors',Author::table_name());\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_undefined_instance_method()\n\t{\n\t\tAuthor::first()->find_by_name('sdf');\n\t}\n\n\tpublic function test_clear_cache_for_specific_class()\n\t{\n\t\t$book_table1 = ActiveRecord\\Table::load('Book');\n\t\t$book_table2 = ActiveRecord\\Table::load('Book');\n\t\tActiveRecord\\Table::clear_cache('Book');\n\t\t$book_table3 = ActiveRecord\\Table::load('Book');\n\n\t\t$this->assert_true($book_table1 === $book_table2);\n\t\t$this->assert_true($book_table1 !== $book_table3);\n\t}\n\n\tpublic function test_flag_dirty()\n\t{\n\t\t$author = new Author();\n\t\t$author->flag_dirty('some_date');\n\t\t$this->assert_has_keys('some_date', $author->dirty_attributes());\n\t\t$this->assert_true($author->attribute_is_dirty('some_date'));\n\t\t$author->save();\n\t\t$this->assert_false($author->attribute_is_dirty('some_date'));\n\t}\n\n\tpublic function test_flag_dirty_attribute_which_does_not_exit()\n\t{\n\t\t$author = new Author();\n\t\t$author->flag_dirty('some_inexistant_property');\n\t\t$this->assert_null($author->dirty_attributes());\n\t\t$this->assert_false($author->attribute_is_dirty('some_inexistant_property'));\n\t}\n\n\tpublic function test_gh245_dirty_attribute_should_not_raise_php_notice_if_not_dirty()\n\t{\n\t\t$event = new Event(array('title' => \"Fun\"));\n\t\t$this->assert_false($event->attribute_is_dirty('description'));\n\t\t$this->assert_true($event->attribute_is_dirty('title'));\n\t}\n\n\tpublic function test_assigning_php_datetime_gets_converted_to_date_class_with_defaults()\n\t{\n\t\t$author = new Author();\n\t\t$author->created_at = $now = new \\DateTime();\n\t\t$this->assert_is_a(\"ActiveRecord\\\\DateTime\", $author->created_at);\n\t\t$this->assert_datetime_equals($now,$author->created_at);\n\t}\n\n\tpublic function test_assigning_php_datetime_gets_converted_to_date_class_with_custom_date_class()\n\t{\n\t\tActiveRecord\\Config::instance()->set_date_class('\\\\DateTime'); // use PHP built-in DateTime\n\t\t$author = new Author();\n\t\t$author->created_at = $now = new \\DateTime();\n\t\t$this->assert_is_a(\"DateTime\", $author->created_at);\n\t\t$this->assert_datetime_equals($now,$author->created_at);\n\t}\n\n\tpublic function test_assigning_from_mass_assignment_php_datetime_gets_converted_to_ar_datetime()\n\t{\n\t\t$author = new Author(array('created_at' => new \\DateTime()));\n\t\t$this->assert_is_a(\"ActiveRecord\\\\DateTime\",$author->created_at);\n\t}\n\n\tpublic function test_get_real_attribute_name()\n\t{\n\t\t$venue = new Venue();\n\t\t$this->assert_equals('name', $venue->get_real_attribute_name('name'));\n\t\t$this->assert_equals('name', $venue->get_real_attribute_name('marquee'));\n\t\t$this->assert_equals(null, $venue->get_real_attribute_name('invalid_field'));\n\t}\n\n\tpublic function test_id_setter_works_with_table_without_pk_named_attribute()\n\t{\n\t\t$author = new Author(array('id' => 123));\n\t\t$this->assert_equals(123,$author->author_id);\n\t}\n\n\tpublic function test_query()\n\t{\n\t\t$row = Author::query('SELECT COUNT(*) AS n FROM authors',null)->fetch();\n\t\t$this->assert_true($row['n'] > 1);\n\n\t\t$row = Author::query('SELECT COUNT(*) AS n FROM authors WHERE name=?',array('Tito'))->fetch();\n\t\t$this->assert_equals(array('n' => 1), $row);\n\t}\n};\n?>\n"
  },
  {
    "path": "test/ActiveRecordWriteTest.php",
    "content": "<?php\nuse ActiveRecord\\DateTime;\n\nclass DirtyAuthor extends ActiveRecord\\Model\n{\n\tstatic $table = 'authors';\n\tstatic $before_save = 'before_save';\n\n\tpublic function before_save()\n\t{\n\t\t$this->name = 'i saved';\n\t}\n};\n\nclass AuthorWithoutSequence extends ActiveRecord\\Model\n{\n\tstatic $table = 'authors';\n\tstatic $sequence = 'invalid_seq';\n}\n\nclass AuthorExplicitSequence extends ActiveRecord\\Model\n{\n\tstatic $sequence = 'blah_seq';\n}\n\nclass ActiveRecordWriteTest extends DatabaseTest\n{\n\tprivate function make_new_book_and($save=true)\n\t{\n\t\t$book = new Book();\n\t\t$book->name = 'rivers cuomo';\n\t\t$book->special = 1;\n\n\t\tif ($save)\n\t\t\t$book->save();\n\n\t\treturn $book;\n\t}\n\n\tpublic function test_save()\n\t{\n\t\t$venue = new Venue(array('name' => 'Tito'));\n\t\t$venue->save();\n\t}\n\n\tpublic function test_insert()\n\t{\n\t\t$author = new Author(array('name' => 'Blah Blah'));\n\t\t$author->save();\n\t\t$this->assert_not_null(Author::find($author->id));\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_insert_with_no_sequence_defined()\n\t{\n\t\tif (!$this->conn->supports_sequences())\n\t\t\tthrow new ActiveRecord\\DatabaseException('');\n\n\t\tAuthorWithoutSequence::create(array('name' => 'Bob!'));\n\t}\n\n\tpublic function test_insert_should_quote_keys()\n\t{\n\t\t$author = new Author(array('name' => 'Blah Blah'));\n\t\t$author->save();\n\t\t$this->assert_true(strpos($author->connection()->last_query,$author->connection()->quote_name('updated_at')) !== false);\n\t}\n\n\tpublic function test_save_auto_increment_id()\n\t{\n\t\t$venue = new Venue(array('name' => 'Bob'));\n\t\t$venue->save();\n\t\t$this->assert_true($venue->id > 0);\n\t}\n\n\tpublic function test_sequence_was_set()\n\t{\n\t\tif ($this->conn->supports_sequences())\n\t\t\t$this->assert_equals($this->conn->get_sequence_name('authors','author_id'),Author::table()->sequence);\n\t\telse\n\t\t\t$this->assert_null(Author::table()->sequence);\n\t}\n\n\tpublic function test_sequence_was_explicitly_set()\n\t{\n\t\tif ($this->conn->supports_sequences())\n\t\t\t$this->assert_equals(AuthorExplicitSequence::$sequence,AuthorExplicitSequence::table()->sequence);\n\t\telse\n\t\t\t$this->assert_null(Author::table()->sequence);\n\t}\n\n\tpublic function test_delete()\n\t{\n\t\t$author = Author::find(1);\n\t\t$author->delete();\n\n\t\t$this->assert_false(Author::exists(1));\n\t}\n\n\tpublic function test_delete_by_find_all()\n\t{\n\t\t$books = Book::all();\n\n\t\tforeach ($books as $model)\n\t\t\t$model->delete();\n\n\t\t$res = Book::all();\n\t\t$this->assert_equals(0,count($res));\n\t}\n\n\tpublic function test_update()\n\t{\n\t\t$book = Book::find(1);\n\t\t$new_name = 'new name';\n\t\t$book->name = $new_name;\n\t\t$book->save();\n\n\t\t$this->assert_same($new_name, $book->name);\n\t\t$this->assert_same($new_name, $book->name, Book::find(1)->name);\n\t}\n\n\tpublic function test_update_should_quote_keys()\n\t{\n\t\t$book = Book::find(1);\n\t\t$book->name = 'new name';\n\t\t$book->save();\n\t\t$this->assert_true(strpos($book->connection()->last_query,$book->connection()->quote_name('name')) !== false);\n\t}\n\n\tpublic function test_update_attributes()\n\t{\n\t\t$book = Book::find(1);\n\t\t$new_name = 'How to lose friends and alienate people'; // jax i'm worried about you\n\t\t$attrs = array('name' => $new_name);\n\t\t$book->update_attributes($attrs);\n\n\t\t$this->assert_same($new_name, $book->name);\n\t\t$this->assert_same($new_name, $book->name, Book::find(1)->name);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\UndefinedPropertyException\n\t */\n\tpublic function test_update_attributes_undefined_property()\n\t{\n\t\t$book = Book::find(1);\n\t\t$book->update_attributes(array('name' => 'new name', 'invalid_attribute' => true , 'another_invalid_attribute' => 'blah'));\n\t}\n\n\tpublic function test_update_attribute()\n\t{\n\t\t$book = Book::find(1);\n\t\t$new_name = 'some stupid self-help book';\n\t\t$book->update_attribute('name', $new_name);\n\n\t\t$this->assert_same($new_name, $book->name);\n\t\t$this->assert_same($new_name, $book->name, Book::find(1)->name);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\UndefinedPropertyException\n\t */\n\tpublic function test_update_attribute_undefined_property()\n\t{\n\t\t$book = Book::find(1);\n\t\t$book->update_attribute('invalid_attribute', true);\n\t}\n\n\tpublic function test_save_null_value()\n\t{\n\t\t$book = Book::first();\n\t\t$book->name = null;\n\t\t$book->save();\n\t\t$this->assert_same(null,Book::find($book->id)->name);\n\t}\n\n\tpublic function test_save_blank_value()\n\t{\n\t\t// oracle doesn't do blanks. probably an option to enable?\n\t\tif ($this->conn instanceof ActiveRecord\\OciAdapter)\n\t\t\treturn;\n\n\t\t$book = Book::find(1);\n\t\t$book->name = '';\n\t\t$book->save();\n\t\t$this->assert_same('',Book::find(1)->name);\n\t}\n\n\tpublic function test_dirty_attributes()\n\t{\n\t\t$book = $this->make_new_book_and(false);\n\t\t$this->assert_equals(array('name','special'),array_keys($book->dirty_attributes()));\n\t}\n\n\tpublic function test_dirty_attributes_cleared_after_saving()\n\t{\n\t\t$book = $this->make_new_book_and();\n\t\t$this->assert_true(strpos($book->table()->last_sql,'name') !== false);\n\t\t$this->assert_true(strpos($book->table()->last_sql,'special') !== false);\n\t\t$this->assert_equals(null,$book->dirty_attributes());\n\t}\n\n\tpublic function test_dirty_attributes_cleared_after_inserting()\n\t{\n\t\t$book = $this->make_new_book_and();\n\t\t$this->assert_equals(null,$book->dirty_attributes());\n\t}\n\n\tpublic function test_no_dirty_attributes_but_still_insert_record()\n\t{\n\t\t$book = new Book;\n\t\t$this->assert_equals(null,$book->dirty_attributes());\n\t\t$book->save();\n\t\t$this->assert_equals(null,$book->dirty_attributes());\n\t\t$this->assert_not_null($book->id);\n\t}\n\n\tpublic function test_dirty_attributes_cleared_after_updating()\n\t{\n\t\t$book = Book::first();\n\t\t$book->name = 'rivers cuomo';\n\t\t$book->save();\n\t\t$this->assert_equals(null,$book->dirty_attributes());\n\t}\n\n\tpublic function test_dirty_attributes_after_reloading()\n\t{\n\t\t$book = Book::first();\n\t\t$book->name = 'rivers cuomo';\n\t\t$book->reload();\n\t\t$this->assert_equals(null,$book->dirty_attributes());\n\t}\n\n\tpublic function test_dirty_attributes_with_mass_assignment()\n\t{\n\t\t$book = Book::first();\n\t\t$book->set_attributes(array('name' => 'rivers cuomo'));\n\t\t$this->assert_equals(array('name'), array_keys($book->dirty_attributes()));\n\t}\n\n\tpublic function test_timestamps_set_before_save()\n\t{\n\t\t$author = new Author;\n\t\t$author->save();\n\t\t$this->assert_not_null($author->created_at, $author->updated_at);\n\n\t\t$author->reload();\n\t\t$this->assert_not_null($author->created_at, $author->updated_at);\n\t}\n\n\tpublic function test_timestamps_updated_at_only_set_before_update()\n\t{\n\t\t$author = new Author();\n\t\t$author->save();\n\t\t$created_at = $author->created_at;\n\t\t$updated_at = $author->updated_at;\n\t\tsleep(1);\n\n\t\t$author->name = 'test';\n\t\t$author->save();\n\n\t\t$this->assert_not_null($author->updated_at);\n\t\t$this->assert_same($created_at, $author->created_at);\n\t\t$this->assert_not_equals($updated_at, $author->updated_at);\n\t}\n\n\tpublic function test_create()\n\t{\n\t\t$author = Author::create(array('name' => 'Blah Blah'));\n\t\t$this->assert_not_null(Author::find($author->id));\n\t}\n\n\tpublic function test_create_should_set_created_at()\n\t{\n\t\t$author = Author::create(array('name' => 'Blah Blah'));\n\t\t$this->assert_not_null($author->created_at);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_update_with_no_primary_key_defined()\n\t{\n\t\tAuthor::table()->pk = array();\n\t\t$author = Author::first();\n\t\t$author->name = 'blahhhhhhhhhh';\n\t\t$author->save();\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_delete_with_no_primary_key_defined()\n\t{\n\t\tAuthor::table()->pk = array();\n\t\t$author = author::first();\n\t\t$author->delete();\n\t}\n\n\tpublic function test_inserting_with_explicit_pk()\n\t{\n\t\t$author = Author::create(array('author_id' => 9999, 'name' => 'blah'));\n\t\t$this->assert_equals(9999,$author->author_id);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ReadOnlyException\n\t */\n\tpublic function test_readonly()\n\t{\n\t\t$author = Author::first(array('readonly' => true));\n\t\t$author->save();\n\t}\n\n\tpublic function test_modified_attributes_in_before_handlers_get_saved()\n\t{\n\t\t$author = DirtyAuthor::first();\n\t\t$author->encrypted_password = 'coco';\n\t\t$author->save();\n\t\t$this->assert_equals('i saved',DirtyAuthor::find($author->id)->name);\n\t}\n\n\tpublic function test_is_dirty()\n\t{\n\t\t$author = Author::first();\n\t\t$this->assert_equals(false,$author->is_dirty());\n\n\t\t$author->name = 'coco';\n\t\t$this->assert_equals(true,$author->is_dirty());\n\t}\n\n\tpublic function test_set_date_flags_dirty()\n\t{\n\t\t$author = Author::create(array('some_date' => new DateTime()));\n\t\t$author = Author::find($author->id);\n\t\t$author->some_date->setDate(2010,1,1);\n\t\t$this->assert_has_keys('some_date', $author->dirty_attributes());\n\t}\n\n\tpublic function test_set_date_flags_dirty_with_php_datetime()\n\t{\n\t\t$author = Author::create(array('some_date' => new \\DateTime()));\n\t\t$author = Author::find($author->id);\n\t\t$author->some_date->setDate(2010,1,1);\n\t\t$this->assert_has_keys('some_date', $author->dirty_attributes());\n\t}\n\n\tpublic function test_delete_all_with_conditions_as_string()\n\t{\n\t\t$num_affected = Author::delete_all(array('conditions' => 'parent_author_id = 2'));\n\t\t$this->assert_equals(2, $num_affected);\n\t}\n\n\tpublic function test_delete_all_with_conditions_as_hash()\n\t{\n\t\t$num_affected = Author::delete_all(array('conditions' => array('parent_author_id' => 2)));\n\t\t$this->assert_equals(2, $num_affected);\n\t}\n\n\tpublic function test_delete_all_with_conditions_as_array()\n\t{\n\t\t$num_affected = Author::delete_all(array('conditions' => array('parent_author_id = ?', 2)));\n\t\t$this->assert_equals(2, $num_affected);\n\t}\n\n\tpublic function test_delete_all_with_limit_and_order()\n\t{\n\t\tif (!$this->conn->accepts_limit_and_order_for_update_and_delete())\n\t\t\t$this->mark_test_skipped('Only MySQL & Sqlite accept limit/order with UPDATE clause');\n\n\t\t$num_affected = Author::delete_all(array('conditions' => array('parent_author_id = ?', 2), 'limit' => 1, 'order' => 'name asc'));\n\t\t$this->assert_equals(1, $num_affected);\n\t\t$this->assert_true(strpos(Author::table()->last_sql, 'ORDER BY name asc LIMIT 1') !== false);\n\t}\n\n\tpublic function test_update_all_with_set_as_string()\n\t{\n\t\t$num_affected = Author::update_all(array('set' => 'parent_author_id = 2'));\n\t\t$this->assert_equals(2, $num_affected);\n\t\t$this->assert_equals(4, Author::count_by_parent_author_id(2));\n\t}\n\n\tpublic function test_update_all_with_set_as_hash()\n\t{\n\t\t$num_affected = Author::update_all(array('set' => array('parent_author_id' => 2)));\n\t\t$this->assert_equals(2, $num_affected);\n\t}\n\n\t/**\n\t * TODO: not implemented\n\tpublic function test_update_all_with_set_as_array()\n\t{\n\t\t$num_affected = Author::update_all(array('set' => array('parent_author_id = ?', 2)));\n\t\t$this->assert_equals(2, $num_affected);\n\t}\n\t */\n\n\tpublic function test_update_all_with_conditions_as_string()\n\t{\n\t\t$num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'conditions' => 'name = \"Tito\"'));\n\t\t$this->assert_equals(1, $num_affected);\n\t}\n\n\tpublic function test_update_all_with_conditions_as_hash()\n\t{\n\t\t$num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'conditions' => array('name' => \"Tito\")));\n\t\t$this->assert_equals(1, $num_affected);\n\t}\n\n\tpublic function test_update_all_with_conditions_as_array()\n\t{\n\t\t$num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'conditions' => array('name = ?', \"Tito\")));\n\t\t$this->assert_equals(1, $num_affected);\n\t}\n\n\tpublic function test_update_all_with_limit_and_order()\n\t{\n\t\tif (!$this->conn->accepts_limit_and_order_for_update_and_delete())\n\t\t\t$this->mark_test_skipped('Only MySQL & Sqlite accept limit/order with UPDATE clause');\n\n\t\t$num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'limit' => 1, 'order' => 'name asc'));\n\t\t$this->assert_equals(1, $num_affected);\n\t\t$this->assert_true(strpos(Author::table()->last_sql, 'ORDER BY name asc LIMIT 1') !== false);\n\t}\n\n\tpublic function test_update_native_datetime()\n\t{\n\t\t$author = Author::create(array('name' => 'Blah Blah'));\n\t\t$native_datetime = new \\DateTime('1983-12-05');\n\t\t$author->some_date = $native_datetime;\n\t\t$this->assert_false($native_datetime === $author->some_date);\n\t}\n\n\tpublic function test_update_our_datetime()\n\t{\n\t\t$author = Author::create(array('name' => 'Blah Blah'));\n\t\t$our_datetime = new DateTime('1983-12-05');\n\t\t$author->some_date = $our_datetime;\n\t\t$this->assert_true($our_datetime === $author->some_date);\n\t}\n\n};\n"
  },
  {
    "path": "test/CacheModelTest.php",
    "content": "<?php\nuse ActiveRecord\\Cache;\n\nclass CacheModelTest extends DatabaseTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tif (!extension_loaded('memcache'))\n\t\t{\n\t\t\t$this->markTestSkipped('The memcache extension is not available');\n\t\t\treturn;\n\t\t}\n\t\tparent::set_up($connection_name);\n\t\tActiveRecord\\Config::instance()->set_cache('memcache://localhost');\n\t}\n\n\tprotected static function set_method_public($className, $methodName)\n\t{\n\t\t$class = new ReflectionClass($className);\n\t\t$method = $class->getMethod($methodName);\n\t\t$method->setAccessible(true);\n\t\treturn $method;\n\t}\n\n\tpublic function tear_down()\n\t{\n\t\tCache::flush();\n\t\tCache::initialize(null);\n\t}\n\n\tpublic function test_default_expire()\n\t{\n\t\t$this->assert_equals(30,Author::table()->cache_model_expire);\n\t}\n\n\tpublic function test_explicit_expire()\n\t{\n\t\t$this->assert_equals(2592000,Publisher::table()->cache_model_expire);\n\t}\n\n\tpublic function test_cache_key()\n\t{\n\t\t$method = $this->set_method_public('Author', 'cache_key');\n\t\t$author = Author::first();\n\n\t\t$this->assert_equals(\"Author-1\", $method->invokeArgs($author, array()));\n\t}\n\n\tpublic function test_model_cache_find_by_pk()\n\t{\n\t\t$publisher = Publisher::find(1);\n\t\t$method = $this->set_method_public('Publisher', 'cache_key');\n\t\t$cache_key = $method->invokeArgs($publisher, array());\n\t\t$from_cache = Cache::$adapter->read($cache_key);\n\n\t\t$this->assertEquals($publisher->name, $from_cache->name);\n\t}\n\n\tpublic function test_model_cache_new()\n\t{\n\t\t$publisher = new Publisher(array(\n\t\t\t'name' => 'HarperCollins'\n\t\t));\n\t\t$publisher->save();\n\n\t\t$method = $this->set_method_public('Publisher', 'cache_key');\n\t\t$cache_key = $method->invokeArgs($publisher, array());\n\n\t\t// Model is cached on first find\n\t\t$actual = Publisher::find($publisher->id);\n\t\t$from_cache = Cache::$adapter->read($cache_key);\n\n\t\t$this->assertEquals($actual, $from_cache);\n\t}\n\n\tpublic function test_model_cache_find()\n\t{\n\t\t$method = $this->set_method_public('Publisher', 'cache_key');\n\t\t$publishers = Publisher::all();\n\n\t\tforeach($publishers as $publisher)\n\t\t{\n\t\t\t$cache_key = $method->invokeArgs($publisher, array());\n\t\t\t$from_cache = Cache::$adapter->read($cache_key);\n\n\t\t\t$this->assertEquals($publisher->name, $from_cache->name);\n\t\t}\n\t}\n\n\tpublic function test_regular_models_not_cached()\n\t{\n\t\t$method = $this->set_method_public('Author', 'cache_key');\n\t\t$author = Author::first();\n\t\t$cache_key = $method->invokeArgs($author, array());\n\t\t$this->assertFalse(Cache::$adapter->read($cache_key));\n\t}\n\n\tpublic function test_model_delete_from_cache()\n\t{\n\t\t$method = $this->set_method_public('Publisher', 'cache_key');\n\t\t$publisher = Publisher::find(1);\n\t\t$cache_key = $method->invokeArgs($publisher, array());\n\n\t\t$publisher->delete();\n\n\t\t// at this point, the cached record should be gone\n\t\t$this->assertFalse(Cache::$adapter->read($cache_key));\n\n\t}\n\n\tpublic function test_model_update_cache(){\n\t\t$method = $this->set_method_public('Publisher', 'cache_key');\n\n\t\t$publisher = Publisher::find(1);\n\t\t$cache_key = $method->invokeArgs($publisher, array());\n\t\t$this->assertEquals('Random House', $publisher->name);\n\n\t\t$from_cache = Cache::$adapter->read($cache_key);\n\t\t$this->assertEquals('Random House', $from_cache->name);\n\n\t\t// make sure that updates make it to cache\n\t\t$publisher->name = 'Puppy Publishing';\n\t\t$publisher->save();\n\n\t\t$actual = Publisher::find($publisher->id);\n\t\t$from_cache = Cache::$adapter->read($cache_key);\n\n\t\t$this->assertEquals('Puppy Publishing', $from_cache->name);\n\t}\n\n\tpublic function test_model_reload_expires_cache(){\n\t\t$method = $this->set_method_public('Publisher', 'cache_key');\n\n\t\t$publisher = Publisher::find(1);\n\t\t$cache_key = $method->invokeArgs($publisher, array());\n\t\t$this->assertEquals('Random House', $publisher->name);\n\n\t\t// Raw query to not update model properties\n\t\tPublisher::query('UPDATE publishers SET name = ? WHERE publisher_id = ?', array('Specific House', 1));\n\n\t\t$publisher->reload();\n\n\t\t$this->assertEquals('Specific House', $publisher->name);\n\n\t\t$from_cache = Cache::$adapter->read($cache_key);\n\n\t\t$this->assertEquals('Specific House', $from_cache->name);\n\t}\n\n}\n"
  },
  {
    "path": "test/CacheTest.php",
    "content": "<?php\nuse ActiveRecord\\Cache;\n\nclass CacheTest extends SnakeCase_PHPUnit_Framework_TestCase\n{\n\tpublic function set_up()\n\t{\n\t\tif (!extension_loaded('memcache'))\n\t\t{\n\t\t\t$this->markTestSkipped('The memcache extension is not available');\n\t\t\treturn;\n\t\t}\n\t\t\n\t\tCache::initialize('memcache://localhost');\n\t}\n\n\tpublic function tear_down()\n\t{\n\t\tCache::flush();\n\t}\n\n\tprivate function cache_get()\n\t{\n\t\treturn Cache::get(\"1337\", function() { return \"abcd\"; });\n\t}\n\n\tpublic function test_initialize()\n\t{\n\t\t$this->assert_not_null(Cache::$adapter);\n\t}\n\n\tpublic function test_initialize_with_null()\n\t{\n\t\tCache::initialize(null);\n\t\t$this->assert_null(Cache::$adapter);\n\t}\n\n\tpublic function test_get_returns_the_value()\n\t{\n\t\t$this->assert_equals(\"abcd\", $this->cache_get());\n\t}\n\n\tpublic function test_get_writes_to_the_cache()\n\t{\n\t\t$this->cache_get();\n\t\t$this->assert_equals(\"abcd\", Cache::$adapter->read(\"1337\"));\n\t}\n\n\tpublic function test_get_does_not_execute_closure_on_cache_hit()\n\t{\n\t\t$this->cache_get();\n\t\tCache::get(\"1337\", function() { throw new Exception(\"I better not execute!\"); });\n\t}\n\n\tpublic function test_cache_adapter_returns_false_on_cache_miss()\n\t{\n\t\t$this->assert_same(false, Cache::$adapter->read(\"some-key\"));\n\t}\n\n\tpublic function test_get_works_without_caching_enabled()\n\t{\n\t\tCache::$adapter = null;\n\t\t$this->assert_equals(\"abcd\", $this->cache_get());\n\t}\n\n\tpublic function test_cache_expire()\n\t{\n\t\tCache::$options['expire'] = 1;\n\t\t$this->cache_get();\n\t\tsleep(2);\n\n\t\t$this->assert_same(false, Cache::$adapter->read(\"1337\"));\n\t}\n\t\n\tpublic function test_namespace_is_set_properly()\n\t{\n\t\tCache::$options['namespace'] = 'myapp';\n\t\t$this->cache_get();\n\t\t$this->assert_same(\"abcd\", Cache::$adapter->read(\"myapp::1337\"));\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\CacheException\n\t * @expectedExceptionMessage Connection refused\n\t */\n\tpublic function test_exception_when_connect_fails()\n\t{\n\t\tCache::initialize('memcache://127.0.0.1:1234');\n\t}\n}\n?>\n"
  },
  {
    "path": "test/CallbackTest.php",
    "content": "<?php\n\nclass CallBackTest extends DatabaseTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up($connection_name);\n\n\t\t// ensure VenueCB model has been loaded\n\t\tVenueCB::find(1);\n\n\t\t$this->callback = new ActiveRecord\\CallBack('VenueCB');\n\t}\n\n\tpublic function assert_has_callback($callback_name, $method_name=null)\n\t{\n\t\tif (!$method_name)\n\t\t\t$method_name = $callback_name;\n\n\t\t$this->assert_true(in_array($method_name,$this->callback->get_callbacks($callback_name)));\n\t}\n\n\tpublic function assert_implicit_save($first_method, $second_method)\n\t{\n\t\t$i_ran = array();\n\t\t$this->callback->register($first_method,function($model) use (&$i_ran, $first_method) { $i_ran[] = $first_method; });\n\t\t$this->callback->register($second_method,function($model) use (&$i_ran, $second_method) { $i_ran[] = $second_method; });\n\t\t$this->callback->invoke(null,$second_method);\n\t\t$this->assert_equals(array($first_method,$second_method),$i_ran);\n\t}\n\n\tpublic function test_gh_266_calling_save_in_after_save_callback_uses_update_instead_of_insert()\n\t{\n\t\t$venue = new VenueAfterCreate();\n\t\t$venue->name = 'change me';\n\t\t$venue->city = 'Awesome City';\n\t\t$venue->save();\n\t\t\n\t\t$this->assert_true(VenueAfterCreate::exists(array('conditions'=>\n\t\t     array('name'=>'changed!'))));\n\t\t$this->assert_false(VenueAfterCreate::exists(array('conditions'=>\n\t\t     array('name'=>'change me'))));\n\t}\n\t\n\tpublic function test_generic_callback_was_auto_registered()\n\t{\n\t\t$this->assert_has_callback('after_construct');\n\t}\n\n\tpublic function test_register()\n\t{\n\t\t$this->callback->register('after_construct');\n\t\t$this->assert_has_callback('after_construct');\n\t}\n\n\tpublic function test_register_non_generic()\n\t{\n\t\t$this->callback->register('after_construct','non_generic_after_construct');\n\t\t$this->assert_has_callback('after_construct','non_generic_after_construct');\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_register_invalid_callback()\n\t{\n\t\t$this->callback->register('invalid_callback');\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_register_callback_with_undefined_method()\n\t{\n\t\t$this->callback->register('after_construct','do_not_define_me');\n\t}\n\n\tpublic function test_register_with_string_definition()\n\t{\n\t\t$this->callback->register('after_construct','after_construct');\n\t\t$this->assert_has_callback('after_construct');\n\t}\n\n\tpublic function test_register_with_closure()\n\t{\n\t\t$this->callback->register('after_construct',function($mode) { });\n\t}\n\n\tpublic function test_register_with_null_definition()\n\t{\n\t\t$this->callback->register('after_construct',null);\n\t\t$this->assert_has_callback('after_construct');\n\t}\n\n\tpublic function test_register_with_no_definition()\n\t{\n\t\t$this->callback->register('after_construct');\n\t\t$this->assert_has_callback('after_construct');\n\t}\n\n\tpublic function test_register_appends_to_registry()\n\t{\n\t\t$this->callback->register('after_construct');\n\t\t$this->callback->register('after_construct','non_generic_after_construct');\n\t\t$this->assert_equals(array('after_construct','after_construct','non_generic_after_construct'),$this->callback->get_callbacks('after_construct'));\n\t}\n\n\tpublic function test_register_prepends_to_registry()\n\t{\n\t\t$this->callback->register('after_construct');\n\t\t$this->callback->register('after_construct','non_generic_after_construct',array('prepend' => true));\n\t\t$this->assert_equals(array('non_generic_after_construct','after_construct','after_construct'),$this->callback->get_callbacks('after_construct'));\n\t}\n\n\tpublic function test_registers_via_static_array_definition()\n\t{\n\t\t$this->assert_has_callback('after_destroy','after_destroy_one');\n\t\t$this->assert_has_callback('after_destroy','after_destroy_two');\n\t}\n\n\tpublic function test_registers_via_static_string_definition()\n\t{\n\t\t$this->assert_has_callback('before_destroy','before_destroy_using_string');\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_register_via_static_with_invalid_definition()\n\t{\n\t\t$class_name = \"Venues_\" . md5(uniqid());\n\t\teval(\"class $class_name extends ActiveRecord\\\\Model { static \\$table_name = 'venues'; static \\$after_save = 'method_that_does_not_exist'; };\");\n\t\tnew $class_name();\n\t\tnew ActiveRecord\\CallBack($class_name);\n\t}\n\n\tpublic function test_can_register_same_multiple_times()\n\t{\n\t\t$this->callback->register('after_construct');\n\t\t$this->callback->register('after_construct');\n\t\t$this->assert_equals(array('after_construct','after_construct','after_construct'),$this->callback->get_callbacks('after_construct'));\n\t}\n\n\tpublic function test_register_closure_callback()\n\t{\n\t\t$closure = function($model) {};\n\t\t$this->callback->register('after_save',$closure);\n\t\t$this->assert_equals(array($closure),$this->callback->get_callbacks('after_save'));\n\t}\n\n\tpublic function test_get_callbacks_returns_array()\n\t{\n\t\t$this->callback->register('after_construct');\n\t\t$this->assert_true(is_array($this->callback->get_callbacks('after_construct')));\n\t}\n\n\tpublic function test_get_callbacks_returns_null()\n\t{\n\t\t$this->assert_null($this->callback->get_callbacks('this_callback_name_should_never_exist'));\n\t}\n\n\tpublic function test_invoke_runs_all_callbacks()\n\t{\n\t\t$mock = $this->get_mock('VenueCB',array('after_destroy_one','after_destroy_two'));\n\t\t$mock->expects($this->once())->method('after_destroy_one');\n\t\t$mock->expects($this->once())->method('after_destroy_two');\n\t\t$this->callback->invoke($mock,'after_destroy');\n\t}\n\n\tpublic function test_invoke_closure()\n\t{\n\t\t$i_ran = false;\n\t\t$this->callback->register('after_validation',function($model) use (&$i_ran) { $i_ran = true; });\n\t\t$this->callback->invoke(null,'after_validation');\n\t\t$this->assert_true($i_ran);\n\t}\n\n\tpublic function test_invoke_implicitly_calls_save_first()\n\t{\n\t\t$this->assert_implicit_save('before_save','before_create');\n\t\t$this->assert_implicit_save('before_save','before_update');\n\t\t$this->assert_implicit_save('after_save','after_create');\n\t\t$this->assert_implicit_save('after_save','after_update');\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_invoke_unregistered_callback()\n\t{\n\t\t$mock = $this->get_mock('VenueCB', array('columns'));\n\t\t$this->callback->invoke($mock,'before_validation_on_create');\n\t}\n\n\tpublic function test_before_callbacks_pass_on_false_return_callback_returned_false()\n\t{\n\t\t$this->callback->register('before_validation',function($model) { return false; });\n\t\t$this->assert_false($this->callback->invoke(null,'before_validation'));\n\t}\n\n\tpublic function test_before_callbacks_does_not_pass_on_false_for_after_callbacks()\n\t{\n\t\t$this->callback->register('after_validation',function($model) { return false; });\n\t\t$this->assert_true($this->callback->invoke(null,'after_validation'));\n\t}\n\n\tpublic function test_gh_28_after_create_should_be_invoked_after_auto_incrementing_pk_is_set()\n\t{\n\t\t$that = $this;\n\t\tVenueCB::$after_create = function($model) use ($that) { $that->assert_not_null($model->id); };\n\t\tActiveRecord\\Table::clear_cache('VenueCB');\n\t\t$venue = VenueCB::find(1);\n\t\t$venue = new VenueCB($venue->attributes());\n\t\t$venue->id = null;\n\t\t$venue->name = 'alksdjfs';\n\t\t$venue->save();\n\t}\n\n\tpublic function test_before_create_returned_false_halts_execution()\n\t{\n\t\tVenueCB::$before_create = array('before_create_halt_execution');\n\t\tActiveRecord\\Table::clear_cache('VenueCB');\n\t\t$table = ActiveRecord\\Table::load('VenueCB');\n\n\t\t$i_ran = false;\n\t\t$i_should_have_ran = false;\n\t\t$table->callback->register('before_save', function($model) use (&$i_should_have_ran) { $i_should_have_ran = true; });\n\t\t$table->callback->register('before_create',function($model) use (&$i_ran) { $i_ran = true; });\n\t\t$table->callback->register('after_create',function($model) use (&$i_ran) { $i_ran = true; });\n\n\t\t$v = VenueCB::find(1);\n\t\t$v->id = null;\n\t\tVenueCB::create($v->attributes());\n\n\t\t$this->assert_true($i_should_have_ran);\n\t\t$this->assert_false($i_ran);\n\t\t$this->assert_true(strpos(ActiveRecord\\Table::load('VenueCB')->last_sql, 'INSERT') === false);\n\t}\n\n\tpublic function test_before_save_returned_false_halts_execution()\n\t{\n\t\tVenueCB::$before_update = array('before_update_halt_execution');\n\t\tActiveRecord\\Table::clear_cache('VenueCB');\n\t\t$table = ActiveRecord\\Table::load('VenueCB');\n\n\t\t$i_ran = false;\n\t\t$i_should_have_ran = false;\n\t\t$table->callback->register('before_save', function($model) use (&$i_should_have_ran) { $i_should_have_ran = true; });\n\t\t$table->callback->register('before_update',function($model) use (&$i_ran) { $i_ran = true; });\n\t\t$table->callback->register('after_save',function($model) use (&$i_ran) { $i_ran = true; });\n\n\t\t$v = VenueCB::find(1);\n\t\t$v->name .= 'test';\n\t\t$ret = $v->save();\n\n\t\t$this->assert_true($i_should_have_ran);\n\t\t$this->assert_false($i_ran);\n\t\t$this->assert_false($ret);\n\t\t$this->assert_true(strpos(ActiveRecord\\Table::load('VenueCB')->last_sql, 'UPDATE') === false);\n\t}\n\n\tpublic function test_before_destroy_returned_false_halts_execution()\n\t{\n\t\tVenueCB::$before_destroy = array('before_destroy_halt_execution');\n\t\tActiveRecord\\Table::clear_cache('VenueCB');\n\t\t$table = ActiveRecord\\Table::load('VenueCB');\n\n\t\t$i_ran = false;\n\t\t$table->callback->register('before_destroy',function($model) use (&$i_ran) { $i_ran = true; });\n\t\t$table->callback->register('after_destroy',function($model) use (&$i_ran) { $i_ran = true; });\n\n\t\t$v = VenueCB::find(1);\n\t\t$ret = $v->delete();\n\n\t\t$this->assert_false($i_ran);\n\t\t$this->assert_false($ret);\n\t\t$this->assert_true(strpos(ActiveRecord\\Table::load('VenueCB')->last_sql, 'DELETE') === false);\n\t}\n\n\tpublic function test_before_validation_returned_false_halts_execution()\n\t{\n\t\tVenueCB::$before_validation = array('before_validation_halt_execution');\n\t\tActiveRecord\\Table::clear_cache('VenueCB');\n\t\t$table = ActiveRecord\\Table::load('VenueCB');\n\n\t\t$v = VenueCB::find(1);\n\t\t$v->name .= 'test';\n\t\t$ret = $v->save();\n\n\t\t$this->assert_false($ret);\n\t\t$this->assert_true(strpos(ActiveRecord\\Table::load('VenueCB')->last_sql, 'UPDATE') === false);\n\t}\n};\n?>"
  },
  {
    "path": "test/ColumnTest.php",
    "content": "<?php\n\nuse ActiveRecord\\Column;\nuse ActiveRecord\\DateTime;\nuse ActiveRecord\\DatabaseException;\n\nclass ColumnTest extends SnakeCase_PHPUnit_Framework_TestCase\n{\n\tpublic function set_up()\n\t{\n\t\t$this->column = new Column();\n\t\ttry {\n\t\t\t$this->conn = ActiveRecord\\ConnectionManager::get_connection(ActiveRecord\\Config::instance()->get_default_connection());\n\t\t} catch (DatabaseException $e) {\n\t\t\t$this->mark_test_skipped('failed to connect using default connection. '.$e->getMessage());\n\t\t}\n\t}\n\n\tpublic function assert_mapped_type($type, $raw_type)\n\t{\n\t\t$this->column->raw_type = $raw_type;\n\t\t$this->assert_equals($type,$this->column->map_raw_type());\n\t}\n\n\tpublic function assert_cast($type, $casted_value, $original_value)\n\t{\n\t\t$this->column->type = $type;\n\t\t$value = $this->column->cast($original_value,$this->conn);\n\n\t\tif ($original_value != null && ($type == Column::DATETIME || $type == Column::DATE))\n\t\t\t$this->assert_true($value instanceof DateTime);\n\t\telse\n\t\t\t$this->assert_same($casted_value,$value);\n\t}\n\n\tpublic function test_map_raw_type_dates()\n\t{\n\t\t$this->assert_mapped_type(Column::DATETIME,'datetime');\n\t\t$this->assert_mapped_type(Column::DATE,'date');\n\t}\n\n\tpublic function test_map_raw_type_integers()\n\t{\n\t\t$this->assert_mapped_type(Column::INTEGER,'integer');\n\t\t$this->assert_mapped_type(Column::INTEGER,'int');\n\t\t$this->assert_mapped_type(Column::INTEGER,'tinyint');\n\t\t$this->assert_mapped_type(Column::INTEGER,'smallint');\n\t\t$this->assert_mapped_type(Column::INTEGER,'mediumint');\n\t\t$this->assert_mapped_type(Column::INTEGER,'bigint');\n\t}\n\n\tpublic function test_map_raw_type_decimals()\n\t{\n\t\t$this->assert_mapped_type(Column::DECIMAL,'float');\n\t\t$this->assert_mapped_type(Column::DECIMAL,'double');\n\t\t$this->assert_mapped_type(Column::DECIMAL,'numeric');\n\t\t$this->assert_mapped_type(Column::DECIMAL,'dec');\n\t}\n\n\tpublic function test_map_raw_type_strings()\n\t{\n\t\t$this->assert_mapped_type(Column::STRING,'string');\n\t\t$this->assert_mapped_type(Column::STRING,'varchar');\n\t\t$this->assert_mapped_type(Column::STRING,'text');\n\t}\n\n\tpublic function test_map_raw_type_default_to_string()\n\t{\n\t\t$this->assert_mapped_type(Column::STRING,'bajdslfjasklfjlksfd');\n\t}\n\n\tpublic function test_map_raw_type_changes_integer_to_int()\n\t{\n\t\t$this->column->raw_type = 'integer';\n\t\t$this->column->map_raw_type();\n\t\t$this->assert_equals('int',$this->column->raw_type);\n\t}\n\n\tpublic function test_cast()\n\t{\n\t\t$datetime = new DateTime('2001-01-01');\n\t\t$this->assert_cast(Column::INTEGER,1,'1');\n\t\t$this->assert_cast(Column::INTEGER,1,'1.5');\n\t\t$this->assert_cast(Column::DECIMAL,1.5,'1.5');\n\t\t$this->assert_cast(Column::DATETIME,$datetime,'2001-01-01');\n\t\t$this->assert_cast(Column::DATE,$datetime,'2001-01-01');\n\t\t$this->assert_cast(Column::DATE,$datetime,$datetime);\n\t\t$this->assert_cast(Column::STRING,'bubble tea','bubble tea');\n\t\t$this->assert_cast(Column::INTEGER,4294967295,'4294967295');\n\t\t$this->assert_cast(Column::INTEGER,'18446744073709551615','18446744073709551615');\n\n\t\t// 32 bit\n\t\tif (PHP_INT_SIZE === 4)\n\t\t\t$this->assert_cast(Column::INTEGER,'2147483648',(((float) PHP_INT_MAX) + 1));\n\t\t// 64 bit\n\t\telseif (PHP_INT_SIZE === 8)\n\t\t\t$this->assert_cast(Column::INTEGER,'9223372036854775808',(((float) PHP_INT_MAX) + 1));\n\t}\n\n\tpublic function test_cast_leave_null_alone()\n\t{\n\t\t$types = array(\n\t\t\tColumn::STRING,\n\t\t\tColumn::INTEGER,\n\t\t\tColumn::DECIMAL,\n\t\t\tColumn::DATETIME,\n\t\t\tColumn::DATE);\n\n\t\tforeach ($types as $type) {\n\t\t\t$this->assert_cast($type,null,null);\n\t\t}\n\t}\n\n\tpublic function test_empty_and_null_date_strings_should_return_null()\n\t{\n\t\t$column = new Column();\n\t\t$column->type = Column::DATE;\n\t\t$this->assert_equals(null,$column->cast(null,$this->conn));\n\t\t$this->assert_equals(null,$column->cast('',$this->conn));\n\t}\n\n\tpublic function test_empty_and_null_datetime_strings_should_return_null()\n\t{\n\t\t$column = new Column();\n\t\t$column->type = Column::DATETIME;\n\t\t$this->assert_equals(null,$column->cast(null,$this->conn));\n\t\t$this->assert_equals(null,$column->cast('',$this->conn));\n\t}\n\n\tpublic function test_native_date_time_attribute_copies_exact_tz()\n\t{\n\t\t$dt = new \\DateTime(null, new \\DateTimeZone('America/New_York'));\n\n\t\t$column = new Column();\n\t\t$column->type = Column::DATETIME;\n\n\t\t$dt2 = $column->cast($dt, $this->conn);\n\n\t\t$this->assert_equals($dt->getTimestamp(), $dt2->getTimestamp());\n\t\t$this->assert_equals($dt->getTimeZone(), $dt2->getTimeZone());\n\t\t$this->assert_equals($dt->getTimeZone()->getName(), $dt2->getTimeZone()->getName());\n\t}\n\n\tpublic function test_ar_date_time_attribute_copies_exact_tz()\n\t{\n\t\t$dt = new DateTime(null, new \\DateTimeZone('America/New_York'));\n\n\t\t$column = new Column();\n\t\t$column->type = Column::DATETIME;\n\n\t\t$dt2 = $column->cast($dt, $this->conn);\n\n\t\t$this->assert_equals($dt->getTimestamp(), $dt2->getTimestamp());\n\t\t$this->assert_equals($dt->getTimeZone(), $dt2->getTimeZone());\n\t\t$this->assert_equals($dt->getTimeZone()->getName(), $dt2->getTimeZone()->getName());\n\t}\n}\n?>\n"
  },
  {
    "path": "test/ConfigTest.php",
    "content": "<?php\n\nuse ActiveRecord\\Config;\nuse ActiveRecord\\ConfigException;\n\nclass TestLogger\n{\n\tprivate function log() {}\n}\n\nclass TestDateTimeWithoutCreateFromFormat\n{\n   public function format($format=null) {}\n}\n\nclass TestDateTime\n{\n   public function format($format=null) {}\n   public static function createFromFormat($format, $time) {}\n}\n\nclass ConfigTest extends SnakeCase_PHPUnit_Framework_TestCase\n{\n\tpublic function set_up()\n\t{\n\t\t$this->config = new Config();\n\t\t$this->connections = array('development' => 'mysql://blah/development', 'test' => 'mysql://blah/test');\n\t\t$this->config->set_connections($this->connections);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ConfigException\n\t */\n\tpublic function test_set_connections_must_be_array()\n\t{\n\t\t$this->config->set_connections(null);\n\t}\n\n\tpublic function test_get_connections()\n\t{\n\t\t$this->assert_equals($this->connections,$this->config->get_connections());\n\t}\n\n\tpublic function test_get_connection()\n\t{\n\t\t$this->assert_equals($this->connections['development'],$this->config->get_connection('development'));\n\t}\n\n\tpublic function test_get_invalid_connection()\n\t{\n\t\t$this->assert_null($this->config->get_connection('whiskey tango foxtrot'));\n\t}\n\n\tpublic function test_get_default_connection_and_connection()\n\t{\n\t\t$this->config->set_default_connection('development');\n\t\t$this->assert_equals('development',$this->config->get_default_connection());\n\t\t$this->assert_equals($this->connections['development'],$this->config->get_default_connection_string());\n\t}\n\n\tpublic function test_get_default_connection_and_connection_string_defaults_to_development()\n\t{\n\t\t$this->assert_equals('development',$this->config->get_default_connection());\n\t\t$this->assert_equals($this->connections['development'],$this->config->get_default_connection_string());\n\t}\n\n\tpublic function test_get_default_connection_string_when_connection_name_is_not_valid()\n\t{\n\t\t$this->config->set_default_connection('little mac');\n\t\t$this->assert_null($this->config->get_default_connection_string());\n\t}\n\n\tpublic function test_default_connection_is_set_when_only_one_connection_is_present()\n\t{\n\t\t$this->config->set_connections(array('development' => $this->connections['development']));\n\t\t$this->assert_equals('development',$this->config->get_default_connection());\n\t}\n\n\tpublic function test_set_connections_with_default()\n\t{\n\t\t$this->config->set_connections($this->connections,'test');\n\t\t$this->assert_equals('test',$this->config->get_default_connection());\n\t}\n\n\tpublic function test_get_date_class_with_default()\n\t{\n\t\t$this->assert_equals('ActiveRecord\\\\DateTime', $this->config->get_date_class());\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ConfigException\n\t */\n\tpublic function test_set_date_class_when_class_doesnt_exist()\n\t{\n\t\t$this->config->set_date_class('doesntexist');\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ConfigException\n\t */\n\tpublic function test_set_date_class_when_class_doesnt_have_format_or_createfromformat()\n\t{\n\t\t$this->config->set_date_class('TestLogger');\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ConfigException\n\t */\n\tpublic function test_set_date_class_when_class_doesnt_have_createfromformat()\n\t{\n\t\t$this->config->set_date_class('TestDateTimeWithoutCreateFromFormat');\n\t}\n\n\tpublic function test_set_date_class_with_valid_class()\n\t{\n\t\t$this->config->set_date_class('TestDateTime');\n\t\t$this->assert_equals('TestDateTime', $this->config->get_date_class());\n\t}\n\n\tpublic function test_initialize_closure()\n\t{\n\t\t$test = $this;\n\n\t\tConfig::initialize(function($cfg) use ($test)\n\t\t{\n\t\t\t$test->assert_not_null($cfg);\n\t\t\t$test->assert_equals('ActiveRecord\\Config',get_class($cfg));\n\t\t});\n\t}\n\n\tpublic function test_logger_object_must_implement_log_method()\n\t{\n\t\ttry {\n\t\t\t$this->config->set_logger(new TestLogger);\n\t\t\t$this->fail();\n\t\t} catch (ConfigException $e) {\n\t\t\t$this->assert_equals($e->getMessage(), \"Logger object must implement a public log method\");\n\t\t}\n\t}\n}\n?>\n"
  },
  {
    "path": "test/ConnectionManagerTest.php",
    "content": "<?php\n\nuse ActiveRecord\\Config;\nuse ActiveRecord\\ConnectionManager;\n\nclass ConnectionManagerTest extends DatabaseTest\n{\n\tpublic function test_get_connection_with_null_connection()\n\t{\n\t\t$this->assert_not_null(ConnectionManager::get_connection(null));\n\t\t$this->assert_not_null(ConnectionManager::get_connection());\n\t}\n\n\tpublic function test_get_connection()\n\t{\n\t\t$this->assert_not_null(ConnectionManager::get_connection('mysql'));\n\t}\n\n\tpublic function test_get_connection_uses_existing_object()\n\t{\n\t\t$connection = ConnectionManager::get_connection('mysql');\n\t\t$this->assert_same($connection, ConnectionManager::get_connection('mysql'));\n\t}\n\n\tpublic function test_get_connection_with_default()\n\t{\n\t\t$default = ActiveRecord\\Config::instance()->get_default_connection('mysql');\n\t\t$connection = ConnectionManager::get_connection();\n\t\t$this->assert_same(ConnectionManager::get_connection($default), $connection);\n\t}\n\n\tpublic function test_gh_91_get_connection_with_null_connection_is_always_default()\n\t{\n\t\t$conn_one = ConnectionManager::get_connection('mysql');\n\t\t$conn_two = ConnectionManager::get_connection();\n\t\t$conn_three = ConnectionManager::get_connection('mysql');\n\t\t$conn_four = ConnectionManager::get_connection();\n\n\t\t$this->assert_same($conn_one, $conn_three);\n\t\t$this->assert_same($conn_two, $conn_three);\n\t\t$this->assert_same($conn_four, $conn_three);\n\t}\n\n\tpublic function test_drop_connection()\n\t{\n\t\t$connection = ConnectionManager::get_connection('mysql');\n\t\tConnectionManager::drop_connection('mysql');\n\t\t$this->assert_not_same($connection, ConnectionManager::get_connection('mysql'));\n\t}\n\n\tpublic function test_drop_connection_with_default()\n\t{\n\t\t$connection = ConnectionManager::get_connection();\n\t\tConnectionManager::drop_connection();\n\t\t$this->assert_not_same($connection, ConnectionManager::get_connection());\n\t}\n}\n"
  },
  {
    "path": "test/ConnectionTest.php",
    "content": "<?php\nuse ActiveRecord\\Connection;\n\n\n// Only use this to test static methods in Connection that are not specific\n// to any database adapter.\n\nclass ConnectionTest extends SnakeCase_PHPUnit_Framework_TestCase\n{\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_connection_info_from_should_throw_exception_when_no_host()\n\t{\n\t\tActiveRecord\\Connection::parse_connection_url('mysql://user:pass@');\n\t}\n\n\tpublic function test_connection_info()\n\t{\n\t\t$info = ActiveRecord\\Connection::parse_connection_url('mysql://user:pass@127.0.0.1:3306/dbname');\n\t\t$this->assert_equals('mysql',$info->protocol);\n\t\t$this->assert_equals('user',$info->user);\n\t\t$this->assert_equals('pass',$info->pass);\n\t\t$this->assert_equals('127.0.0.1',$info->host);\n\t\t$this->assert_equals(3306,$info->port);\n\t\t$this->assert_equals('dbname',$info->db);\n\t}\n\t\n\tpublic function test_gh_103_sqlite_connection_string_relative()\n\t{\n\t\t$info = ActiveRecord\\Connection::parse_connection_url('sqlite://../some/path/to/file.db');\n\t\t$this->assert_equals('../some/path/to/file.db', $info->host);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_gh_103_sqlite_connection_string_absolute()\n\t{\n\t\t$info = ActiveRecord\\Connection::parse_connection_url('sqlite:///some/path/to/file.db');\n\t}\n\n\tpublic function test_gh_103_sqlite_connection_string_unix()\n\t{\n\t\t$info = ActiveRecord\\Connection::parse_connection_url('sqlite://unix(/some/path/to/file.db)');\n\t\t$this->assert_equals('/some/path/to/file.db', $info->host);\n       \t\n\t\t$info = ActiveRecord\\Connection::parse_connection_url('sqlite://unix(/some/path/to/file.db)/');\n\t\t$this->assert_equals('/some/path/to/file.db', $info->host);\n    \t\n\t\t$info = ActiveRecord\\Connection::parse_connection_url('sqlite://unix(/some/path/to/file.db)/dummy');\n\t\t$this->assert_equals('/some/path/to/file.db', $info->host);\n\t}\n\n\tpublic function test_gh_103_sqlite_connection_string_windows()\n\t{\n\t\t$info = ActiveRecord\\Connection::parse_connection_url('sqlite://windows(c%3A/some/path/to/file.db)');\n\t\t$this->assert_equals('c:/some/path/to/file.db', $info->host);\n\t}\n\n\tpublic function test_parse_connection_url_with_unix_sockets()\n\t{\n\t\t$info = ActiveRecord\\Connection::parse_connection_url('mysql://user:password@unix(/tmp/mysql.sock)/database');\n\t\t$this->assert_equals('/tmp/mysql.sock',$info->host);\n\t}\n\n\tpublic function test_parse_connection_url_with_decode_option()\n\t{\n\t\t$info = ActiveRecord\\Connection::parse_connection_url('mysql://h%20az:h%40i@127.0.0.1/test?decode=true');\n\t\t$this->assert_equals('h az',$info->user);\n\t\t$this->assert_equals('h@i',$info->pass);\n\t}\n\n\tpublic function test_encoding()\n\t{\n\t\t$info = ActiveRecord\\Connection::parse_connection_url('mysql://test:test@127.0.0.1/test?charset=utf8');\n\t\t$this->assert_equals('utf8', $info->charset);\n\t}\n}\n?>\n"
  },
  {
    "path": "test/DateFormatTest.php",
    "content": "<?php\n\nclass DateFormatTest extends DatabaseTest\n{\n\n\tpublic function test_datefield_gets_converted_to_ar_datetime()\n\t{\n\t\t//make sure first author has a date\n\t\t$author = Author::first();\n\t\t$author->some_date = new DateTime();\n\t\t$author->save();\n\t\t\n\t\t$author = Author::first();\n\t\t$this->assert_is_a(\"ActiveRecord\\\\DateTime\",$author->some_date);\n\t}\n\n};\n?>\n"
  },
  {
    "path": "test/DateTimeTest.php",
    "content": "<?php\nuse ActiveRecord\\DatabaseException;\nuse ActiveRecord\\DateTime as DateTime;\n\nclass DateTimeTest extends SnakeCase_PHPUnit_Framework_TestCase\n{\n\tpublic function set_up()\n\t{\n\t\t$this->date = new DateTime();\n\t\t$this->original_format = DateTime::$DEFAULT_FORMAT;\n\t}\n\n\tpublic function tear_down()\n\t{\n\t\tDateTime::$DEFAULT_FORMAT = $this->original_format;\n\t}\n\n\tprivate function get_model()\n\t{\n\t\ttry {\n\t\t\t$model = new Author();\n\t\t} catch (DatabaseException $e) {\n\t\t\t$this->mark_test_skipped('failed to connect. '.$e->getMessage());\n\t\t}\n\n\t\treturn $model;\n\t}\n\n\tprivate function assert_dirtifies($method /*, method params, ...*/)\n\t{\n\t\t$model = $this->get_model();\n\t\t$datetime = new DateTime();\n\t\t$datetime->attribute_of($model,'some_date');\n\n\t\t$args = func_get_args();\n\t\tarray_shift($args);\n\n\t\tcall_user_func_array(array($datetime,$method),$args);\n\t\t$this->assert_has_keys('some_date', $model->dirty_attributes());\n\t}\n\n\tpublic function test_should_flag_the_attribute_dirty()\n\t{\n\t\t$interval = new DateInterval('PT1S');\n\t\t$timezone = new DateTimeZone('America/New_York');\n\t\t$this->assert_dirtifies('setDate',2001,1,1);\n\t\t$this->assert_dirtifies('setISODate',2001,1);\n\t\t$this->assert_dirtifies('setTime',1,1);\n\t\t$this->assert_dirtifies('setTimestamp',1);\n\t\t$this->assert_dirtifies('setTimezone',$timezone);\n\t\t$this->assert_dirtifies('modify','+1 day');\n\t\t$this->assert_dirtifies('add',$interval);\n\t\t$this->assert_dirtifies('sub',$interval);\n\t}\n\n\tpublic function test_set_iso_date()\n\t{\n\t\t$a = new \\DateTime();\n\t\t$a->setISODate(2001,1);\n\n\t\t$b = new DateTime();\n\t\t$b->setISODate(2001,1);\n\n\t\t$this->assert_datetime_equals($a,$b);\n\t}\n\n\tpublic function test_set_time()\n\t{\n\t\t$a = new \\DateTime();\n\t\t$a->setTime(1,1);\n\n\t\t$b = new DateTime();\n\t\t$b->setTime(1,1);\n\n\t\t$this->assert_datetime_equals($a,$b);\n\t}\n\n    public function test_set_time_microseconds()\n    {\n        $a = new \\DateTime();\n        $a->setTime(1, 1, 1);\n\n        $b = new DateTime();\n        $b->setTime(1, 1, 1, 0);\n\n        $this->assert_datetime_equals($a,$b);\n\t}\n\n\tpublic function test_get_format_with_friendly()\n\t{\n\t\t$this->assert_equals('Y-m-d H:i:s', DateTime::get_format('db'));\n\t}\n\n\tpublic function test_get_format_with_format()\n\t{\n\t\t$this->assert_equals('Y-m-d', DateTime::get_format('Y-m-d'));\n\t}\n\n\tpublic function test_get_format_with_null()\n\t{\n\t\t$this->assert_equals(\\DateTime::RFC2822, DateTime::get_format());\n\t}\n\n\tpublic function test_format()\n\t{\n\t\t$this->assert_true(is_string($this->date->format()));\n\t\t$this->assert_true(is_string($this->date->format('Y-m-d')));\n\t}\n\n\tpublic function test_format_by_friendly_name()\n\t{\n\t\t$d = date(DateTime::get_format('db'));\n\t\t$this->assert_equals($d, $this->date->format('db'));\n\t}\n\n\tpublic function test_format_by_custom_format()\n\t{\n\t\t$format = 'Y/m/d';\n\t\t$this->assert_equals(date($format), $this->date->format($format));\n\t}\n\n\tpublic function test_format_uses_default()\n\t{\n\t\t$d = date(DateTime::$FORMATS[DateTime::$DEFAULT_FORMAT]);\n\t\t$this->assert_equals($d, $this->date->format());\n\t}\n\n\tpublic function test_all_formats()\n\t{\n\t\tforeach (DateTime::$FORMATS as $name => $format)\n\t\t\t$this->assert_equals(date($format), $this->date->format($name));\n\t}\n\n\tpublic function test_change_default_format_to_format_string()\n\t{\n\t\tDateTime::$DEFAULT_FORMAT = 'H:i:s';\n\t\t$this->assert_equals(date(DateTime::$DEFAULT_FORMAT), $this->date->format());\n\t}\n\n\tpublic function test_change_default_format_to_friently()\n\t{\n\t\tDateTime::$DEFAULT_FORMAT = 'short';\n\t\t$this->assert_equals(date(DateTime::$FORMATS['short']), $this->date->format());\n\t}\n\n\tpublic function test_to_string()\n\t{\n\t\t$this->assert_equals(date(DateTime::get_format()), \"\" . $this->date);\n\t}\n\n\tpublic function test_create_from_format_error_handling()\n\t{\n\t\t$d = DateTime::createFromFormat('H:i:s Y-d-m', '!!!');\n\t\t$this->assert_false($d);\n\t}\n\n\tpublic function test_create_from_format_without_tz()\n\t{\n\t\t$d = DateTime::createFromFormat('H:i:s Y-d-m', '03:04:05 2000-02-01');\n\t\t$this->assert_equals(new DateTime('2000-01-02 03:04:05'), $d);\n\t}\n\n\tpublic function test_create_from_format_with_tz()\n\t{\n\t\t$d = DateTime::createFromFormat('Y-m-d H:i:s', '2000-02-01 03:04:05', new \\DateTimeZone('Etc/GMT-10'));\n\t\t$d2 = new DateTime('2000-01-31 17:04:05');\n\n\t\t$this->assert_equals($d2->getTimestamp(), $d->getTimestamp());\n\t}\n\n\tpublic function test_native_date_time_attribute_copies_exact_tz()\n\t{\n\t\t$dt = new \\DateTime(null, new \\DateTimeZone('America/New_York'));\n\t\t$model = $this->get_model();\n\n\t\t// Test that the data transforms without modification\n\t\t$model->assign_attribute('updated_at', $dt);\n\t\t$dt2 = $model->read_attribute('updated_at');\n\n\t\t$this->assert_equals($dt->getTimestamp(), $dt2->getTimestamp());\n\t\t$this->assert_equals($dt->getTimeZone(), $dt2->getTimeZone());\n\t\t$this->assert_equals($dt->getTimeZone()->getName(), $dt2->getTimeZone()->getName());\n\t}\n\n\tpublic function test_ar_date_time_attribute_copies_exact_tz()\n\t{\n\t\t$dt = new DateTime(null, new \\DateTimeZone('America/New_York'));\n\t\t$model = $this->get_model();\n\n\t\t// Test that the data transforms without modification\n\t\t$model->assign_attribute('updated_at', $dt);\n\t\t$dt2 = $model->read_attribute('updated_at');\n\n\t\t$this->assert_equals($dt->getTimestamp(), $dt2->getTimestamp());\n\t\t$this->assert_equals($dt->getTimeZone(), $dt2->getTimeZone());\n\t\t$this->assert_equals($dt->getTimeZone()->getName(), $dt2->getTimeZone()->getName());\n\t}\n\n\tpublic function test_clone()\n\t{\n\t\t$model = $this->get_model();\n\t\t$model_attribute = 'some_date';\n\n\t\t$datetime = new DateTime();\n\t\t$datetime->attribute_of($model, $model_attribute);\n\n\t\t$cloned_datetime = clone $datetime;\n\n\t\t// Assert initial state\n\t\t$this->assert_false($model->attribute_is_dirty($model_attribute));\n\n\t\t$cloned_datetime->add(new DateInterval('PT1S'));\n\n\t\t// Assert that modifying the cloned object didn't flag the model\n\t\t$this->assert_false($model->attribute_is_dirty($model_attribute));\n\n\t\t$datetime->add(new DateInterval('PT1S'));\n\n\t\t// Assert that modifying the model-attached object did flag the model\n\t\t$this->assert_true($model->attribute_is_dirty($model_attribute));\n\n\t\t// Assert that the dates are equal but not the same instance\n\t\t$this->assert_equals($datetime, $cloned_datetime);\n\t\t$this->assert_not_same($datetime, $cloned_datetime);\n\t}\n}\n?>\n"
  },
  {
    "path": "test/ExpressionsTest.php",
    "content": "<?php\nrequire_once __DIR__ . '/../lib/Expressions.php';\n\nuse ActiveRecord\\Expressions;\nuse ActiveRecord\\ConnectionManager;\nuse ActiveRecord\\DatabaseException;\n\nclass ExpressionsTest extends SnakeCase_PHPUnit_Framework_TestCase\n{\n\tpublic function test_values()\n\t{\n\t\t$c = new Expressions(null,'a=? and b=?',1,2);\n\t\t$this->assert_equals(array(1,2), $c->values());\n\t}\n\n\tpublic function test_one_variable()\n\t{\n\t\t$c = new Expressions(null,'name=?','Tito');\n\t\t$this->assert_equals('name=?',$c->to_s());\n\t\t$this->assert_equals(array('Tito'),$c->values());\n\t}\n\n\tpublic function test_array_variable()\n\t{\n\t\t$c = new Expressions(null,'name IN(?) and id=?',array('Tito','George'),1);\n\t\t$this->assert_equals(array(array('Tito','George'),1),$c->values());\n\t}\n\n\tpublic function test_multiple_variables()\n\t{\n\t\t$c = new Expressions(null,'name=? and book=?','Tito','Sharks');\n\t\t$this->assert_equals('name=? and book=?',$c->to_s());\n\t\t$this->assert_equals(array('Tito','Sharks'),$c->values());\n\t}\n\n\tpublic function test_to_string()\n\t{\n\t\t$c = new Expressions(null,'name=? and book=?','Tito','Sharks');\n\t\t$this->assert_equals('name=? and book=?',$c->to_s());\n\t}\n\n\tpublic function test_to_string_with_array_variable()\n\t{\n\t\t$c = new Expressions(null,'name IN(?) and id=?',array('Tito','George'),1);\n\t\t$this->assert_equals('name IN(?,?) and id=?',$c->to_s());\n\t}\n\n\tpublic function test_to_string_with_null_options()\n\t{\n\t\t$c = new Expressions(null,'name=? and book=?','Tito','Sharks');\n\t\t$x = null;\n\t\t$this->assert_equals('name=? and book=?',$c->to_s(false,$x));\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ExpressionsException\n\t */\n\tpublic function test_insufficient_variables()\n\t{\n\t\t$c = new Expressions(null,'name=? and id=?','Tito');\n\t\t$c->to_s();\n\t}\n\n\tpublic function test_no_values()\n\t{\n\t\t$c = new Expressions(null,\"name='Tito'\");\n\t\t$this->assert_equals(\"name='Tito'\",$c->to_s());\n\t\t$this->assert_equals(0,count($c->values()));\n\t}\n\n\tpublic function test_null_variable()\n\t{\n\t\t$a = new Expressions(null,'name=?',null);\n\t\t$this->assert_equals('name=?',$a->to_s());\n\t\t$this->assert_equals(array(null),$a->values());\n\t}\n\n\tpublic function test_zero_variable()\n\t{\n\t\t$a = new Expressions(null,'name=?',0);\n\t\t$this->assert_equals('name=?',$a->to_s());\n\t\t$this->assert_equals(array(0),$a->values());\n\t}\n\n\tpublic function test_empty_array_variable()\n\t{\n\t\t$a = new Expressions(null,'id IN(?)',array());\n\t\t$this->assert_equals('id IN(?)',$a->to_s());\n\t\t$this->assert_equals(array(array()),$a->values());\n\t}\n\n\tpublic function test_ignore_invalid_parameter_marker()\n\t{\n\t\t$a = new Expressions(null,\"question='Do you love backslashes?' and id in(?)\",array(1,2));\n\t\t$this->assert_equals(\"question='Do you love backslashes?' and id in(?,?)\",$a->to_s());\n\t}\n\n\tpublic function test_ignore_parameter_marker_with_escaped_quote()\n\t{\n\t\t$a = new Expressions(null,\"question='Do you love''s backslashes?' and id in(?)\",array(1,2));\n\t\t$this->assert_equals(\"question='Do you love''s backslashes?' and id in(?,?)\",$a->to_s());\n\t}\n\n\tpublic function test_ignore_parameter_marker_with_backspace_escaped_quote()\n\t{\n\t\t$a = new Expressions(null,\"question='Do you love\\\\'s backslashes?' and id in(?)\",array(1,2));\n\t\t$this->assert_equals(\"question='Do you love\\\\'s backslashes?' and id in(?,?)\",$a->to_s());\n\t}\n\n\tpublic function test_substitute()\n\t{\n\t\t$a = new Expressions(null,'name=? and id=?','Tito',1);\n\t\t$this->assert_equals(\"name='Tito' and id=1\",$a->to_s(true));\n\t}\n\n\tpublic function test_substitute_quotes_scalars_but_not_others()\n\t{\n\t\t$a = new Expressions(null,'id in(?)',array(1,'2',3.5));\n\t\t$this->assert_equals(\"id in(1,'2',3.5)\",$a->to_s(true));\n\t}\n\n\tpublic function test_substitute_where_value_has_question_mark()\n\t{\n\t\t$a = new Expressions(null,'name=? and id=?','??????',1);\n\t\t$this->assert_equals(\"name='??????' and id=1\",$a->to_s(true));\n\t}\n\n\tpublic function test_substitute_array_value()\n\t{\n\t\t$a = new Expressions(null,'id in(?)',array(1,2));\n\t\t$this->assert_equals(\"id in(1,2)\",$a->to_s(true));\n\t}\n\n\tpublic function test_substitute_escapes_quotes()\n\t{\n\t\t$a = new Expressions(null,'name=? or name in(?)',\"Tito's Guild\",array(1,\"Tito's Guild\"));\n\t\t$this->assert_equals(\"name='Tito''s Guild' or name in(1,'Tito''s Guild')\",$a->to_s(true));\n\t}\n\n\tpublic function test_substitute_escape_quotes_with_connections_escape_method()\n\t{\n\t\ttry {\n\t\t\t$conn = ConnectionManager::get_connection();\n\t\t} catch (DatabaseException $e) {\n\t\t\t$this->mark_test_skipped('failed to connect. '.$e->getMessage());\n\t\t}\n\t\t$a = new Expressions(null,'name=?',\"Tito's Guild\");\n\t\t$a->set_connection($conn);\n\t\t$escaped = $conn->escape(\"Tito's Guild\");\n\t\t$this->assert_equals(\"name=$escaped\",$a->to_s(true));\n\t}\n\n\tpublic function test_bind()\n\t{\n\t\t$a = new Expressions(null,'name=? and id=?','Tito');\n\t\t$a->bind(2,1);\n\t\t$this->assert_equals(array('Tito',1),$a->values());\n\t}\n\n\tpublic function test_bind_overwrite_existing()\n\t{\n\t\t$a = new Expressions(null,'name=? and id=?','Tito',1);\n\t\t$a->bind(2,99);\n\t\t$this->assert_equals(array('Tito',99),$a->values());\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ExpressionsException\n\t */\n\tpublic function test_bind_invalid_parameter_number()\n\t{\n\t\t$a = new Expressions(null,'name=?');\n\t\t$a->bind(0,99);\n\t}\n\n\tpublic function test_subsitute_using_alternate_values()\n\t{\n\t\t$a = new Expressions(null,'name=?','Tito');\n\t\t$this->assert_equals(\"name='Tito'\",$a->to_s(true));\n\t\t$x = array('values' => array('Hocus'));\n\t\t$this->assert_equals(\"name='Hocus'\",$a->to_s(true,$x));\n\t}\n\n\tpublic function test_null_value()\n\t{\n\t\t$a = new Expressions(null,'name=?',null);\n\t\t$this->assert_equals('name=NULL',$a->to_s(true));\n\t}\n\n\tpublic function test_hash_with_default_glue()\n\t{\n\t\t$a = new Expressions(null,array('id' => 1, 'name' => 'Tito'));\n\t\t$this->assert_equals('id=? AND name=?',$a->to_s());\n\t}\n\n\tpublic function test_hash_with_glue()\n\t{\n\t\t$a = new Expressions(null,array('id' => 1, 'name' => 'Tito'),', ');\n\t\t$this->assert_equals('id=?, name=?',$a->to_s());\n\t}\n\n\tpublic function test_hash_with_array()\n\t{\n\t\t$a = new Expressions(null,array('id' => 1, 'name' => array('Tito','Mexican')));\n\t\t$this->assert_equals('id=? AND name IN(?,?)',$a->to_s());\n\t}\n}\n?>\n"
  },
  {
    "path": "test/HasManyThroughTest.php",
    "content": "<?php\ninclude 'helpers/foo.php';\n\nuse foo\\bar\\biz\\User;\nuse foo\\bar\\biz\\Newsletter;\n\nclass HasManyThroughTest extends DatabaseTest\n{\n\tpublic function test_gh101_has_many_through()\n\t{\n\t\t$user = User::find(1);\n\t\t$newsletter = Newsletter::find(1);\n\n\t\t$this->assert_equals($newsletter->id, $user->newsletters[0]->id);\n\t\t$this->assert_equals(\n\t\t\t'foo\\bar\\biz\\Newsletter',\n\t\t\tget_class($user->newsletters[0])\n\t\t);\n\t\t$this->assert_equals($user->id, $newsletter->users[0]->id);\n\t\t$this->assert_equals(\n\t\t\t'foo\\bar\\biz\\User',\n\t\t\tget_class($newsletter->users[0])\n\t\t);\n\t}\n\n\tpublic function test_gh101_has_many_through_include()\n\t{\n\t\t$user = User::find(1, array(\n\t\t\t'include' => array(\n\t\t\t\t'user_newsletters'\n\t\t\t)\n\t\t));\n\n\t\t$this->assert_equals(1, $user->id);\n\t\t$this->assert_equals(1, $user->user_newsletters[0]->id);\n\t}\n\n\tpublic function test_gh107_has_many_through_include_eager()\n\t{\n\t\t$venue = Venue::find(1, array('include' => array('events')));\n\t\t$this->assert_equals(1, $venue->events[0]->id);\n\n\t\t$venue = Venue::find(1, array('include' => array('hosts')));\n\t\t$this->assert_equals(1, $venue->hosts[0]->id);\n\t}\n\n\tpublic function test_gh107_has_many_though_include_eager_with_namespace()\n\t{\n\t\t$user = User::find(1, array(\n\t\t\t'include' => array(\n\t\t\t\t'newsletters'\n\t\t\t)\n\t\t));\n\n\t\t$this->assert_equals(1, $user->id);\n\t\t$this->assert_equals(1, $user->newsletters[0]->id);\n\t}\n}\n# vim: noet ts=4 nobinary\n?>\n"
  },
  {
    "path": "test/InflectorTest.php",
    "content": "<?php\nrequire_once __DIR__ . '/../lib/Inflector.php';\n\nclass InflectorTest extends SnakeCase_PHPUnit_Framework_TestCase\n{\n\tpublic function set_up()\n\t{\n\t\t$this->inflector = ActiveRecord\\Inflector::instance();\n\t}\n\n\tpublic function test_underscorify()\n\t{\n\t\t$this->assert_equals('rm__name__bob',$this->inflector->variablize('rm--name  bob'));\n\t\t$this->assert_equals('One_Two_Three',$this->inflector->underscorify('OneTwoThree'));\n\t}\n\n\tpublic function test_tableize()\n\t{\n\t\t$this->assert_equals('angry_people',$this->inflector->tableize('AngryPerson'));\n\t\t$this->assert_equals('my_sqls',$this->inflector->tableize('MySQL'));\n\t}\n\n\tpublic function test_keyify()\n\t{\n\t\t$this->assert_equals('building_type_id', $this->inflector->keyify('BuildingType'));\n\t}\n};\n?>"
  },
  {
    "path": "test/ModelCallbackTest.php",
    "content": "<?php\n\nclass ModelCallbackTest extends DatabaseTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up($connection_name);\n\n\t\t$this->venue = new Venue();\n\t\t$this->callback = Venue::table()->callback;\n\t}\n\n\tpublic function register_and_invoke_callbacks($callbacks, $return, $closure)\n\t{\n\t\tif (!is_array($callbacks))\n\t\t\t$callbacks = array($callbacks);\n\n\t\t$fired = array();\n\n\t\tforeach ($callbacks as $name)\n\t\t\t$this->callback->register($name,function($model) use (&$fired, $name, $return) { $fired[] = $name; return $return; });\n\n\t\t$closure($this->venue);\n\t\treturn array_intersect($callbacks,$fired);\n\t}\n\n\tpublic function assert_fires($callbacks, $closure)\n\t{\n\t\t$executed = $this->register_and_invoke_callbacks($callbacks,true,$closure);\n\t\t$this->assert_equals(count($callbacks),count($executed));\n\t}\n\n\tpublic function assert_does_not_fire($callbacks, $closure)\n\t{\n\t\t$executed = $this->register_and_invoke_callbacks($callbacks,true,$closure);\n\t\t$this->assert_equals(0,count($executed));\n\t}\n\n\tpublic function assert_fires_returns_false($callbacks, $only_fire, $closure)\n\t{\n\t\tif (!is_array($only_fire))\n\t\t\t$only_fire = array($only_fire);\n\n\t\t$executed = $this->register_and_invoke_callbacks($callbacks,false,$closure);\n\t\tsort($only_fire);\n\t\t$intersect = array_intersect($only_fire,$executed);\n\t\tsort($intersect);\n\t\t$this->assert_equals($only_fire,$intersect);\n\t}\n\n\tpublic function test_after_construct_fires_by_default()\n\t{\n\t\t$this->assert_fires('after_construct',function($model) { new Venue(); });\n\t}\n\n\tpublic function test_fire_validation_callbacks_on_insert()\n\t{\n\t\t$this->assert_fires(array('before_validation','after_validation','before_validation_on_create','after_validation_on_create'),\n\t\t\tfunction($model) { $model = new Venue(); $model->save(); });\n\t}\n\n\tpublic function test_fire_validation_callbacks_on_update()\n\t{\n\t\t$this->assert_fires(array('before_validation','after_validation','before_validation_on_update','after_validation_on_update'),\n\t\t\tfunction($model) { $model = Venue::first(); $model->save(); });\n\t}\n\n\tpublic function test_validation_call_backs_not_fired_due_to_bypassing_validations()\n\t{\n\t\t$this->assert_does_not_fire('before_validation',function($model) { $model->save(false); });\n\t}\n\n\tpublic function test_before_validation_returning_false_cancels_callbacks()\n\t{\n\t\t$this->assert_fires_returns_false(array('before_validation','after_validation'),'before_validation',\n\t\t\tfunction($model) { $model->save(); });\n\t}\n\n\tpublic function test_fires_before_save_and_before_update_when_updating()\n\t{\n\t\t$this->assert_fires(array('before_save','before_update'),\n\t\t\tfunction($model) { $model = Venue::first(); $model->name = \"something new\"; $model->save(); });\n\t}\n\n\tpublic function test_before_save_returning_false_cancels_callbacks()\n\t{\n\t\t$this->assert_fires_returns_false(array('before_save','before_create'),'before_save',\n\t\t\tfunction($model) { $model = new Venue(); $model->save(); });\n\t}\n\n\tpublic function test_destroy()\n\t{\n\t\t$this->assert_fires(array('before_destroy','after_destroy'),\n\t\t\tfunction($model) { $model->delete(); });\n\t}\n}\n?>\n"
  },
  {
    "path": "test/MysqlAdapterTest.php",
    "content": "<?php\nuse ActiveRecord\\Column;\n\nrequire_once __DIR__ . '/../lib/adapters/MysqlAdapter.php';\n\nclass MysqlAdapterTest extends AdapterTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up('mysql');\n\t}\n\n\tpublic function test_enum()\n\t{\n\t\t$author_columns = $this->conn->columns('authors');\n\t\t$this->assert_equals('enum',$author_columns['some_enum']->raw_type);\n\t\t$this->assert_equals(Column::STRING,$author_columns['some_enum']->type);\n\t\t$this->assert_same(null,$author_columns['some_enum']->length);\n\t}\n\n\tpublic function test_set_charset()\n\t{\n\t\t$connection_string = ActiveRecord\\Config::instance()->get_connection($this->connection_name);\n\t\t$conn = ActiveRecord\\Connection::instance($connection_string . '?charset=utf8');\n\t\t$this->assert_equals('SET NAMES ?',$conn->last_query);\n\t}\n\n\tpublic function test_limit_with_null_offset_does_not_contain_offset()\n\t{\n\t\t$ret = array();\n\t\t$sql = 'SELECT * FROM authors ORDER BY name ASC';\n\t\t$this->conn->query_and_fetch($this->conn->limit($sql,null,1),function($row) use (&$ret) { $ret[] = $row; });\n\n\t\t$this->assert_true(strpos($this->conn->last_query, 'LIMIT 1') !== false);\n\t}\n}\n?>\n"
  },
  {
    "path": "test/OciAdapterTest.php",
    "content": "<?php\nrequire_once __DIR__ . '/../lib/adapters/OciAdapter.php';\n\nclass OciAdapterTest extends AdapterTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up('oci');\n\t}\n\n\tpublic function test_get_sequence_name()\n\t{\n\t\t$this->assert_equals('authors_seq',$this->conn->get_sequence_name('authors','author_id'));\n\t}\n\n\tpublic function test_columns_text()\n\t{\n\t\t$author_columns = $this->conn->columns('authors');\n\t\t$this->assert_equals('varchar2',$author_columns['some_text']->raw_type);\n\t\t$this->assert_equals(100,$author_columns['some_text']->length);\n\t}\n\n\tpublic function test_datetime_to_string()\n\t{\n\t\t$this->assert_equals('01-Jan-2009 01:01:01 AM',$this->conn->datetime_to_string(date_create('2009-01-01 01:01:01 EST')));\n\t}\n\n\tpublic function test_date_to_string()\n\t{\n\t\t$this->assert_equals('01-Jan-2009',$this->conn->date_to_string(date_create('2009-01-01 01:01:01 EST')));\n\t}\n\n\tpublic function test_insert_id() {}\n\tpublic function test_insert_id_with_params() {}\n\tpublic function test_insert_id_should_return_explicitly_inserted_id() {}\n\tpublic function test_columns_time() {}\n\tpublic function test_columns_sequence() {}\n\n\tpublic function test_set_charset()\n\t{\n\t\t$connection_string = ActiveRecord\\Config::instance()->get_connection($this->connection_name);\n\t\t$conn = ActiveRecord\\Connection::instance($connection_string . '?charset=utf8');\n\t\t$this->assert_equals(';charset=utf8', $conn->dsn_params);\n\t}\n}\n?>\n"
  },
  {
    "path": "test/PgsqlAdapterTest.php",
    "content": "<?php\nuse ActiveRecord\\Column;\n\nrequire_once __DIR__ . '/../lib/adapters/PgsqlAdapter.php';\n\nclass PgsqlAdapterTest extends AdapterTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up('pgsql');\n\t}\n\n\tpublic function test_insert_id()\n\t{\n\t\t$this->conn->query(\"INSERT INTO authors(author_id,name) VALUES(nextval('authors_author_id_seq'),'name')\");\n\t\t$this->assert_true($this->conn->insert_id('authors_author_id_seq') > 0);\n\t}\n\n\tpublic function test_insert_id_with_params()\n\t{\n\t\t$x = array('name');\n\t\t$this->conn->query(\"INSERT INTO authors(author_id,name) VALUES(nextval('authors_author_id_seq'),?)\",$x);\n\t\t$this->assert_true($this->conn->insert_id('authors_author_id_seq') > 0);\n\t}\n\n\tpublic function test_insert_id_should_return_explicitly_inserted_id()\n\t{\n\t\t$this->conn->query('INSERT INTO authors(author_id,name) VALUES(99,\\'name\\')');\n\t\t$this->assert_true($this->conn->insert_id('authors_author_id_seq') > 0);\n\t}\n\n\tpublic function test_set_charset()\n\t{\n\t\t$connection_string = ActiveRecord\\Config::instance()->get_connection($this->connection_name);\n\t\t$conn = ActiveRecord\\Connection::instance($connection_string . '?charset=utf8');\n\t\t$this->assert_equals(\"SET NAMES 'utf8'\",$conn->last_query);\n\t}\n\n\tpublic function test_gh96_columns_not_duplicated_by_index()\n\t{\n\t\t$this->assert_equals(3,$this->conn->query_column_info(\"user_newsletters\")->rowCount());\n\t}\n}\n?>\n"
  },
  {
    "path": "test/RelationshipTest.php",
    "content": "<?php\n\nclass NotModel {};\n\nclass AuthorWithNonModelRelationship extends ActiveRecord\\Model\n{\n\tstatic $pk = 'id';\n\tstatic $table_name = 'authors';\n\tstatic $has_many = array(array('books', 'class_name' => 'NotModel'));\n}\n\nclass RelationshipTest extends DatabaseTest\n{\n\tprotected $relationship_name;\n\tprotected $relationship_names = array('has_many', 'belongs_to', 'has_one');\n\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up($connection_name);\n\n\t\tEvent::$belongs_to = array(array('venue'), array('host'));\n\t\tVenue::$has_many = array(array('events', 'order' => 'id asc'),array('hosts', 'through' => 'events', 'order' => 'hosts.id asc'));\n\t\tVenue::$has_one = array();\n\t\tEmployee::$has_one = array(array('position'));\n\t\tHost::$has_many = array(array('events', 'order' => 'id asc'));\n\t\t\n\t\tforeach ($this->relationship_names as $name)\n\t\t{\n\t\t\tif (preg_match(\"/$name/\", $this->getName(), $match))\n\t\t\t\t$this->relationship_name = $match[0];\n\t\t}\n\t}\n\n\tprotected function get_relationship($type=null)\n\t{\n\t\tif (!$type)\n\t\t\t$type = $this->relationship_name;\n\n\t\tswitch ($type)\n\t\t{\n\t\t\tcase 'belongs_to';\n\t\t\t\t$ret = Event::find(5);\n\t\t\t\tbreak;\n\n\t\t\tcase 'has_one';\n\t\t\t\t$ret = Employee::find(1);\n\t\t\t\tbreak;\n\n\t\t\tcase 'has_many';\n\t\t\t\t$ret = Venue::find(2);\n\t\t\t\tbreak;\n\t\t}\n\n\t\treturn $ret;\n\t}\n\n\tprotected function assert_default_belongs_to($event, $association_name='venue')\n\t{\n\t\t$this->assert_true($event->$association_name instanceof Venue);\n\t\t$this->assert_equals(5,$event->id);\n\t\t$this->assert_equals('West Chester',$event->$association_name->city);\n\t\t$this->assert_equals(6,$event->$association_name->id);\n\t}\n\n\tprotected function assert_default_has_many($venue, $association_name='events')\n\t{\n\t\t$this->assert_equals(2,$venue->id);\n\t\t$this->assert_true(count($venue->$association_name) > 1);\n\t\t$this->assert_equals('Yeah Yeah Yeahs',$venue->{$association_name}[0]->title);\n\t}\n\n\tprotected function assert_default_has_one($employee, $association_name='position')\n\t{\n\t\t$this->assert_true($employee->$association_name instanceof Position);\n\t\t$this->assert_equals('physicist',$employee->$association_name->title);\n\t\t$this->assert_not_null($employee->id, $employee->$association_name->title);\n\t}\n\n\tpublic function test_has_many_basic()\n\t{\n\t\t$this->assert_default_has_many($this->get_relationship());\n\t}\n\t\n\tpublic function test_eager_load_with_empty_nested_includes()\n\t{\n\t\t$conditions['include'] = array('events'=>array());\n\t\tVenue::find(2, $conditions);\n\n\t\t$this->assert_sql_has(\"WHERE venue_id IN(?)\", ActiveRecord\\Table::load('Event')->last_sql);\n\t}\n\n    public function test_gh_256_eager_loading_three_levels_deep()\n    {\n        /* Before fix Undefined offset: 0 */\n        $conditions['include'] = array('events'=>array('host'=>array('events')));\n        $venue = Venue::find(2,$conditions);\n\n        $events = $venue->events;\n        $this->assertEquals(2,count($events));\n        $event_yeah_yeahs = $events[0];\n        $this->assertEquals('Yeah Yeah Yeahs',$event_yeah_yeahs->title);\n\n        $event_host = $event_yeah_yeahs->host;\n        $this->assertEquals('Billy Crystal',$event_host->name);\n\n        $bill_events = $event_host->events;\n\n        $this->assertEquals('Yeah Yeah Yeahs',$bill_events[0]->title);\n    }\n\t\n\t/**\n\t * @expectedException ActiveRecord\\RelationshipException\n\t */\n\tpublic function test_joins_on_model_via_undeclared_association()\n\t{\n\t\t$x = JoinBook::first(array('joins' => array('undeclared')));\n\t}\n\n\tpublic function test_joins_only_loads_given_model_attributes()\n\t{\n\t\t$x = Event::first(array('joins' => array('venue')));\n\t\t$this->assert_sql_has('SELECT events.*',Event::table()->last_sql);\n\t\t$this->assert_false(array_key_exists('city', $x->attributes()));\n\t}\n\n\tpublic function test_joins_combined_with_select_loads_all_attributes()\n\t{\n\t\t$x = Event::first(array('select' => 'events.*, venues.city as venue_city', 'joins' => array('venue')));\n\t\t$this->assert_sql_has('SELECT events.*, venues.city as venue_city',Event::table()->last_sql);\n\t\t$this->assert_true(array_key_exists('venue_city', $x->attributes()));\n\t}\n\n\tpublic function test_belongs_to_basic()\n\t{\n\t\t$this->assert_default_belongs_to($this->get_relationship());\n\t}\n\n\tpublic function test_belongs_to_returns_null_when_no_record()\n\t{\n\t\t$event = Event::find(6);\n\t\t$this->assert_null($event->venue);\n\t}\n\n\tpublic function test_belongs_to_returns_null_when_foreign_key_is_null()\n\t{\n\t\t$event = Event::create(array('title' => 'venueless event'));\n\t\t$this->assert_null($event->venue);\n\t}\n\n\tpublic function test_belongs_to_with_explicit_class_name()\n\t{\n\t\tEvent::$belongs_to = array(array('explicit_class_name', 'class_name' => 'Venue'));\n\t\t$this->assert_default_belongs_to($this->get_relationship(), 'explicit_class_name');\n\t}\n\n\tpublic function test_belongs_to_with_explicit_foreign_key()\n\t{\n\t\t$old = Book::$belongs_to;\n\t\tBook::$belongs_to = array(array('explicit_author', 'class_name' => 'Author', 'foreign_key' => 'secondary_author_id'));\n\n\t\t$book = Book::find(1);\n\t\t$this->assert_equals(2, $book->secondary_author_id);\n\t\t$this->assert_equals($book->secondary_author_id, $book->explicit_author->author_id);\n\n\t\tBook::$belongs_to = $old;\n\t}\n\n\tpublic function test_belongs_to_with_select()\n\t{\n\t\tEvent::$belongs_to[0]['select'] = 'id, city';\n\t\t$event = $this->get_relationship();\n\t\t$this->assert_default_belongs_to($event);\n\n\t\ttry {\n\t\t\t$event->venue->name;\n\t\t\t$this->fail('expected Exception ActiveRecord\\UndefinedPropertyException');\n\t\t} catch (ActiveRecord\\UndefinedPropertyException $e) {\n\t\t\t$this->assert_true(strpos($e->getMessage(), 'name') !== false);\n\t\t}\n\t}\n\n\tpublic function test_belongs_to_with_readonly()\n\t{\n\t\tEvent::$belongs_to[0]['readonly'] = true;\n\t\t$event = $this->get_relationship();\n\t\t$this->assert_default_belongs_to($event);\n\n\t\ttry {\n\t\t\t$event->venue->save();\n\t\t\t$this->fail('expected exception ActiveRecord\\ReadonlyException');\n\t\t} catch (ActiveRecord\\ReadonlyException $e) {\n\t\t}\n\n\t\t$event->venue->name = 'new name';\n\t\t$this->assert_equals($event->venue->name, 'new name');\n\t}\n\n\tpublic function test_belongs_to_with_plural_attribute_name()\n\t{\n\t\tEvent::$belongs_to = array(array('venues', 'class_name' => 'Venue'));\n\t\t$this->assert_default_belongs_to($this->get_relationship(), 'venues');\n\t}\n\n\tpublic function test_belongs_to_with_conditions_and_non_qualifying_record()\n\t{\n\t\tEvent::$belongs_to[0]['conditions'] = \"state = 'NY'\";\n\t\t$event = $this->get_relationship();\n\t\t$this->assert_equals(5,$event->id);\n\t\t$this->assert_null($event->venue);\n\t}\n\n\tpublic function test_belongs_to_with_conditions_and_qualifying_record()\n\t{\n\t\tEvent::$belongs_to[0]['conditions'] = \"state = 'PA'\";\n\t\t$this->assert_default_belongs_to($this->get_relationship());\n\t}\n\n\tpublic function test_belongs_to_build_association()\n\t{\n\t\t$event = $this->get_relationship();\n\t\t$values = array('city' => 'Richmond', 'state' => 'VA');\n\t\t$venue = $event->build_venue($values);\n\t\t$this->assert_equals($values, array_intersect_key($values, $venue->attributes()));\n\t}\n\n\tpublic function test_has_many_build_association()\n\t{\n\t\t$author = Author::first();\n\t\t$this->assert_equals($author->id, $author->build_books()->author_id);\n\t\t$this->assert_equals($author->id, $author->build_book()->author_id);\n\t}\n\n\tpublic function test_belongs_to_create_association()\n\t{\n\t\t$event = $this->get_relationship();\n\t\t$values = array('city' => 'Richmond', 'state' => 'VA', 'name' => 'Club 54', 'address' => '123 street');\n\t\t$venue = $event->create_venue($values);\n\t\t$this->assert_not_null($venue->id);\n\t}\n\n\tpublic function test_build_association_overwrites_guarded_foreign_keys()\n\t{\n\t\t$author = new AuthorAttrAccessible();\n\t\t$author->save();\n\n\t\t$book = $author->build_book();\n\n\t\t$this->assert_not_null($book->author_id);\n\t}\n\n\tpublic function test_belongs_to_can_be_self_referential()\n\t{\n\t\tAuthor::$belongs_to = array(array('parent_author', 'class_name' => 'Author', 'foreign_key' => 'parent_author_id'));\n\t\t$author = Author::find(1);\n\t\t$this->assert_equals(1, $author->id);\n\t\t$this->assert_equals(3, $author->parent_author->id);\n\t}\n\n\tpublic function test_belongs_to_with_an_invalid_option()\n\t{\n\t\tEvent::$belongs_to[0]['joins'] = 'venue';\n\t\t$event = Event::first()->venue;\n\t\t$this->assert_sql_doesnt_has('INNER JOIN venues ON(events.venue_id = venues.id)',Event::table()->last_sql);\n\t}\n\n\tpublic function test_has_many_with_explicit_class_name()\n\t{\n\t\tVenue::$has_many = array(array('explicit_class_name', 'class_name' => 'Event', 'order' => 'id asc'));;\n\t\t$this->assert_default_has_many($this->get_relationship(), 'explicit_class_name');\n\t}\n\n\tpublic function test_has_many_with_select()\n\t{\n\t\tVenue::$has_many[0]['select'] = 'title, type';\n\t\t$venue = $this->get_relationship();\n\t\t$this->assert_default_has_many($venue);\n\n\t\ttry {\n\t\t\t$venue->events[0]->description;\n\t\t\t$this->fail('expected Exception ActiveRecord\\UndefinedPropertyException');\n\t\t} catch (ActiveRecord\\UndefinedPropertyException $e) {\n\t\t\t$this->assert_true(strpos($e->getMessage(), 'description') !== false);\n\t\t}\n\t}\n\n\tpublic function test_has_many_with_readonly()\n\t{\n\t\tVenue::$has_many[0]['readonly'] = true;\n\t\t$venue = $this->get_relationship();\n\t\t$this->assert_default_has_many($venue);\n\n\t\ttry {\n\t\t\t$venue->events[0]->save();\n\t\t\t$this->fail('expected exception ActiveRecord\\ReadonlyException');\n\t\t} catch (ActiveRecord\\ReadonlyException $e) {\n\t\t}\n\n\t\t$venue->events[0]->description = 'new desc';\n\t\t$this->assert_equals($venue->events[0]->description, 'new desc');\n\t}\n\n\tpublic function test_has_many_with_singular_attribute_name()\n\t{\n\t\tVenue::$has_many = array(array('event', 'class_name' => 'Event', 'order' => 'id asc'));\n\t\t$this->assert_default_has_many($this->get_relationship(), 'event');\n\t}\n\n\tpublic function test_has_many_with_conditions_and_non_qualifying_record()\n\t{\n\t\tVenue::$has_many[0]['conditions'] = \"title = 'pr0n @ railsconf'\";\n\t\t$venue = $this->get_relationship();\n\t\t$this->assert_equals(2,$venue->id);\n\t\t$this->assert_true(empty($venue->events), is_array($venue->events));\n\t}\n\n\tpublic function test_has_many_with_conditions_and_qualifying_record()\n\t{\n\t\tVenue::$has_many[0]['conditions'] = \"title = 'Yeah Yeah Yeahs'\";\n\t\t$venue = $this->get_relationship();\n\t\t$this->assert_equals(2,$venue->id);\n\t\t$this->assert_equals($venue->events[0]->title,'Yeah Yeah Yeahs');\n\t}\n\n\tpublic function test_has_many_with_sql_clause_options()\n\t{\n\t\tVenue::$has_many[0] = array('events',\n\t\t\t'select' => 'type',\n\t\t\t'group'  => 'type',\n\t\t\t'limit'  => 2,\n\t\t\t'offset' => 1);\n\t\tVenue::first()->events;\n\t\t$this->assert_sql_has($this->conn->limit(\"SELECT type FROM events WHERE venue_id=? GROUP BY type\",1,2),Event::table()->last_sql);\n\t}\n\n\tpublic function test_has_many_through()\n\t{\n\t\t$hosts = Venue::find(2)->hosts;\n\t\t$this->assert_equals(2,$hosts[0]->id);\n\t\t$this->assert_equals(3,$hosts[1]->id);\n\t}\n\n\tpublic function test_gh27_has_many_through_with_explicit_keys()\n\t{\n\t\t$property = Property::first();\n\n\t\t$this->assert_equals(1, $property->amenities[0]->amenity_id);\n\t\t$this->assert_equals(2, $property->amenities[1]->amenity_id);\n\t}\n\n\tpublic function test_gh16_has_many_through_inside_a_loop_should_not_cause_an_exception()\n\t{\n\t\t$count = 0;\n\n\t\tforeach (Venue::all() as $venue)\n\t\t\t$count += count($venue->hosts);\n\n\t\t$this->assert_true($count >= 5);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\HasManyThroughAssociationException\n\t */\n\tpublic function test_has_many_through_no_association()\n\t{\n\t\tEvent::$belongs_to = array(array('host'));\n\t\tVenue::$has_many[1] = array('hosts', 'through' => 'blahhhhhhh');\n\n\t\t$venue = $this->get_relationship();\n\t\t$n = $venue->hosts;\n\t\t$this->assert_true(count($n) > 0);\n\t}\n\n\tpublic function test_has_many_through_with_select()\n\t{\n\t\tEvent::$belongs_to = array(array('host'));\n\t\tVenue::$has_many[1] = array('hosts', 'through' => 'events', 'select' => 'hosts.*, events.*');\n\n\t\t$venue = $this->get_relationship();\n\t\t$this->assert_true(count($venue->hosts) > 0);\n\t\t$this->assert_not_null($venue->hosts[0]->title);\n\t}\n\n\tpublic function test_has_many_through_with_conditions()\n\t{\n\t\tEvent::$belongs_to = array(array('host'));\n\t\tVenue::$has_many[1] = array('hosts', 'through' => 'events', 'conditions' => array('events.title != ?', 'Love Overboard'));\n\n\t\t$venue = $this->get_relationship();\n\t\t$this->assert_true(count($venue->hosts) === 1);\n\t\t$this->assert_sql_has(\"events.title !=\",ActiveRecord\\Table::load('Host')->last_sql);\n\t}\n\n\tpublic function test_has_many_through_using_source()\n\t{\n\t\tEvent::$belongs_to = array(array('host'));\n\t\tVenue::$has_many[1] = array('hostess', 'through' => 'events', 'source' => 'host');\n\n\t\t$venue = $this->get_relationship();\n\t\t$this->assert_true(count($venue->hostess) > 0);\n\t}\n\n\t/**\n\t * @expectedException ReflectionException\n\t */\n\tpublic function test_has_many_through_with_invalid_class_name()\n\t{\n\t\tEvent::$belongs_to = array(array('host'));\n\t\tVenue::$has_one = array(array('invalid_assoc'));\n\t\tVenue::$has_many[1] = array('hosts', 'through' => 'invalid_assoc');\n\n\t\t$this->get_relationship()->hosts;\n\t}\n\n\tpublic function test_has_many_with_joins()\n\t{\n\t\t$x = Venue::first(array('joins' => array('events')));\n\t\t$this->assert_sql_has('INNER JOIN events ON(venues.id = events.venue_id)',Venue::table()->last_sql);\n\t}\n\n\tpublic function test_has_many_with_explicit_keys()\n\t{\n\t\t$old = Author::$has_many;\n\t\tAuthor::$has_many = array(array('explicit_books', 'class_name' => 'Book', 'primary_key' => 'parent_author_id', 'foreign_key' => 'secondary_author_id'));\n\t\t$author = Author::find(4);\n\n\t\tforeach ($author->explicit_books as $book)\n\t\t\t$this->assert_equals($book->secondary_author_id, $author->parent_author_id);\n\n\t\t$this->assert_true(strpos(ActiveRecord\\Table::load('Book')->last_sql, \"secondary_author_id\") !== false);\n\t\tAuthor::$has_many = $old;\n\t}\n\n\tpublic function test_has_one_basic()\n\t{\n\t\t$this->assert_default_has_one($this->get_relationship());\n\t}\n\n\tpublic function test_has_one_with_explicit_class_name()\n\t{\n\t\tEmployee::$has_one = array(array('explicit_class_name', 'class_name' => 'Position'));\n\t\t$this->assert_default_has_one($this->get_relationship(), 'explicit_class_name');\n\t}\n\n\tpublic function test_has_one_with_select()\n\t{\n\t\tEmployee::$has_one[0]['select'] = 'title';\n\t\t$employee = $this->get_relationship();\n\t\t$this->assert_default_has_one($employee);\n\n\t\ttry {\n\t\t\t$employee->position->active;\n\t\t\t$this->fail('expected Exception ActiveRecord\\UndefinedPropertyException');\n\t\t} catch (ActiveRecord\\UndefinedPropertyException $e) {\n\t\t\t$this->assert_true(strpos($e->getMessage(), 'active') !== false);\n\t\t}\n\t}\n\n\tpublic function test_has_one_with_order()\n\t{\n\t\tEmployee::$has_one[0]['order'] = 'title';\n\t\t$employee = $this->get_relationship();\n\t\t$this->assert_default_has_one($employee);\n\t\t$this->assert_sql_has('ORDER BY title',Position::table()->last_sql);\n\t}\n\n\tpublic function test_has_one_with_conditions_and_non_qualifying_record()\n\t{\n\t\tEmployee::$has_one[0]['conditions'] = \"title = 'programmer'\";\n\t\t$employee = $this->get_relationship();\n\t\t$this->assert_equals(1,$employee->id);\n\t\t$this->assert_null($employee->position);\n\t}\n\n\tpublic function test_has_one_with_conditions_and_qualifying_record()\n\t{\n\t\tEmployee::$has_one[0]['conditions'] = \"title = 'physicist'\";\n\t\t$this->assert_default_has_one($this->get_relationship());\n\t}\n\n\tpublic function test_has_one_with_readonly()\n\t{\n\t\tEmployee::$has_one[0]['readonly'] = true;\n\t\t$employee = $this->get_relationship();\n\t\t$this->assert_default_has_one($employee);\n\n\t\ttry {\n\t\t\t$employee->position->save();\n\t\t\t$this->fail('expected exception ActiveRecord\\ReadonlyException');\n\t\t} catch (ActiveRecord\\ReadonlyException $e) {\n\t\t}\n\n\t\t$employee->position->title = 'new title';\n\t\t$this->assert_equals($employee->position->title, 'new title');\n\t}\n\n\tpublic function test_has_one_can_be_self_referential()\n\t{\n\t\tAuthor::$has_one[1] = array('parent_author', 'class_name' => 'Author', 'foreign_key' => 'parent_author_id');\n\t\t$author = Author::find(1);\n\t\t$this->assert_equals(1, $author->id);\n\t\t$this->assert_equals(3, $author->parent_author->id);\n\t}\n\n\tpublic function test_has_one_with_joins()\n\t{\n\t\t$x = Employee::first(array('joins' => array('position')));\n\t\t$this->assert_sql_has('INNER JOIN positions ON(employees.id = positions.employee_id)',Employee::table()->last_sql);\n\t}\n\n\tpublic function test_has_one_with_explicit_keys()\n\t{\n\t\tBook::$has_one = array(array('explicit_author', 'class_name' => 'Author', 'foreign_key' => 'parent_author_id', 'primary_key' => 'secondary_author_id'));\n\n\t\t$book = Book::find(1);\n\t\t$this->assert_equals($book->secondary_author_id, $book->explicit_author->parent_author_id);\n\t\t$this->assert_true(strpos(ActiveRecord\\Table::load('Author')->last_sql, \"parent_author_id\") !== false);\n\t}\n\n\tpublic function test_dont_attempt_to_load_if_all_foreign_keys_are_null()\n\t{\n\t\t$event = new Event();\n\t\t$event->venue;\n\t\t$this->assert_sql_doesnt_has($this->conn->last_query,'is IS NULL');\n\t}\n\n\tpublic function test_relationship_on_table_with_underscores()\n\t{\n\t\t$this->assert_equals(1,Author::find(1)->awesome_person->is_awesome);\n\t}\n\n\tpublic function test_has_one_through()\n\t{\n\t\tVenue::$has_many = array(array('events'),array('hosts', 'through' => 'events'));\n\t\t$venue = Venue::first();\n\t\t$this->assert_true(count($venue->hosts) > 0);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\RelationshipException\n\t */\n\tpublic function test_throw_error_if_relationship_is_not_a_model()\n\t{\n\t\tAuthorWithNonModelRelationship::first()->books;\n\t}\n\n\tpublic function test_gh93_and_gh100_eager_loading_respects_association_options()\n\t{\n\t\tVenue::$has_many = array(array('events', 'class_name' => 'Event', 'order' => 'id asc', 'conditions' => array('length(title) = ?', 14)));\n\t\t$venues = Venue::find(array(2, 6), array('include' => 'events'));\n\n\t\t$this->assert_sql_has(\"WHERE length(title) = ? AND venue_id IN(?,?) ORDER BY id asc\",ActiveRecord\\Table::load('Event')->last_sql);\n\t\t$this->assert_equals(1, count($venues[0]->events));\n    }\n\n\tpublic function test_eager_loading_has_many_x()\n\t{\n\t\t$venues = Venue::find(array(2, 6), array('include' => 'events'));\n\t\t$this->assert_sql_has(\"WHERE venue_id IN(?,?)\",ActiveRecord\\Table::load('Event')->last_sql);\n\n\t\tforeach ($venues[0]->events as $event)\n\t\t\t$this->assert_equals($event->venue_id, $venues[0]->id);\n\n\t\t$this->assert_equals(2, count($venues[0]->events));\n\t}\n\n\tpublic function test_eager_loading_has_many_x_with_caching()\n\t{\n\t\t$this->markTestSkipped('fails on with php 7+');\n\t\tPublisher::find(array(1, 2, 3), array('include' => 'authors'));\n\t\t$this->assert_sql_has(\"WHERE publisher_id IN(?)\",ActiveRecord\\Table::load('Author')->last_sql);\n\t}\n\n\tpublic function test_eager_loading_has_many_with_no_related_rows()\n\t{\n\t\t$venues = Venue::find(array(7, 8), array('include' => 'events'));\n\n\t\tforeach ($venues as $v)\n\t\t\t$this->assert_true(empty($v->events));\n\n\t\t$this->assert_sql_has(\"WHERE id IN(?,?)\",ActiveRecord\\Table::load('Venue')->last_sql);\n\t\t$this->assert_sql_has(\"WHERE venue_id IN(?,?)\",ActiveRecord\\Table::load('Event')->last_sql);\n\t}\n\n\tpublic function test_eager_loading_has_many_array_of_includes()\n\t{\n\t\tAuthor::$has_many = array(array('books'), array('awesome_people'));\n\t\t$authors = Author::find(array(1,2), array('include' => array('books', 'awesome_people')));\n\n\t\t$assocs = array('books', 'awesome_people');\n\n\t\tforeach ($assocs as $assoc)\n\t\t{\n\t\t\t$this->assert_internal_type('array', $authors[0]->$assoc);\n\n\t\t\tforeach ($authors[0]->$assoc as $a)\n\t\t\t\t$this->assert_equals($authors[0]->author_id,$a->author_id);\n\t\t}\n\n\t\tforeach ($assocs as $assoc)\n\t\t{\n\t\t\t$this->assert_internal_type('array', $authors[1]->$assoc);\n\t\t\t$this->assert_true(empty($authors[1]->$assoc));\n\t\t}\n\n\t\t$this->assert_sql_has(\"WHERE author_id IN(?,?)\",ActiveRecord\\Table::load('Author')->last_sql);\n\t\t$this->assert_sql_has(\"WHERE author_id IN(?,?)\",ActiveRecord\\Table::load('Book')->last_sql);\n\t\t$this->assert_sql_has(\"WHERE author_id IN(?,?)\",ActiveRecord\\Table::load('AwesomePerson')->last_sql);\n\t}\n\n\tpublic function test_eager_loading_has_many_nested()\n\t{\n\t\t$venues = Venue::find(array(1,2), array('include' => array('events' => array('host'))));\n\n\t\t$this->assert_equals(2, count($venues));\n\n\t\tforeach ($venues as $v)\n\t\t{\n\t\t\t$this->assert_true(count($v->events) > 0);\n\n\t\t\tforeach ($v->events as $e)\n\t\t\t{\n\t\t\t\t$this->assert_equals($e->host_id, $e->host->id);\n\t\t\t\t$this->assert_equals($v->id, $e->venue_id);\n\t\t\t}\n\t\t}\n\n\t\t$this->assert_sql_has(\"WHERE id IN(?,?)\",ActiveRecord\\Table::load('Venue')->last_sql);\n\t\t$this->assert_sql_has(\"WHERE venue_id IN(?,?)\",ActiveRecord\\Table::load('Event')->last_sql);\n\t\t$this->assert_sql_has(\"WHERE id IN(?,?,?)\",ActiveRecord\\Table::load('Host')->last_sql);\n\t}\n\n\tpublic function test_eager_loading_belongs_to()\n\t{\n\t\t$events = Event::find(array(1,2,3,5,7), array('include' => 'venue'));\n\n\t\tforeach ($events as $event)\n\t\t\t$this->assert_equals($event->venue_id, $event->venue->id);\n\n\t\t$this->assert_sql_has(\"WHERE id IN(?,?,?,?,?)\",ActiveRecord\\Table::load('Venue')->last_sql);\n\t}\n\n\tpublic function test_eager_loading_belongs_to_array_of_includes()\n\t{\n\t\t$events = Event::find(array(1,2,3,5,7), array('include' => array('venue', 'host')));\n\n\t\tforeach ($events as $event)\n\t\t{\n\t\t\t$this->assert_equals($event->venue_id, $event->venue->id);\n\t\t\t$this->assert_equals($event->host_id, $event->host->id);\n\t\t}\n\n\t\t$this->assert_sql_has(\"WHERE id IN(?,?,?,?,?)\",ActiveRecord\\Table::load('Event')->last_sql);\n\t\t$this->assert_sql_has(\"WHERE id IN(?,?,?,?,?)\",ActiveRecord\\Table::load('Host')->last_sql);\n\t\t$this->assert_sql_has(\"WHERE id IN(?,?,?,?,?)\",ActiveRecord\\Table::load('Venue')->last_sql);\n\t}\n\n\tpublic function test_eager_loading_belongs_to_nested()\n\t{\n\t\tAuthor::$has_many = array(array('awesome_people'));\n\n\t\t$books = Book::find(array(1,2), array('include' => array('author' => array('awesome_people'))));\n\n\t\t$assocs = array('author', 'awesome_people');\n\n\t\tforeach ($books as $book)\n\t\t{\n\t\t\t$this->assert_equals($book->author_id,$book->author->author_id);\n\t\t\t$this->assert_equals($book->author->author_id,$book->author->awesome_people[0]->author_id);\n\t\t}\n\n\t\t$this->assert_sql_has(\"WHERE book_id IN(?,?)\",ActiveRecord\\Table::load('Book')->last_sql);\n\t\t$this->assert_sql_has(\"WHERE author_id IN(?,?)\",ActiveRecord\\Table::load('Author')->last_sql);\n\t\t$this->assert_sql_has(\"WHERE author_id IN(?,?)\",ActiveRecord\\Table::load('AwesomePerson')->last_sql);\n\t}\n\n\tpublic function test_eager_loading_belongs_to_with_no_related_rows()\n\t{\n\t\t$e1 = Event::create(array('venue_id' => 200, 'host_id' => 200, 'title' => 'blah','type' => 'Music'));\n\t\t$e2 = Event::create(array('venue_id' => 200, 'host_id' => 200, 'title' => 'blah2','type' => 'Music'));\n\n\t\t$events = Event::find(array($e1->id, $e2->id), array('include' => 'venue'));\n\n\t\tforeach ($events as $e)\n\t\t\t$this->assert_null($e->venue);\n\n\t\t$this->assert_sql_has(\"WHERE id IN(?,?)\",ActiveRecord\\Table::load('Event')->last_sql);\n\t\t$this->assert_sql_has(\"WHERE id IN(?,?)\",ActiveRecord\\Table::load('Venue')->last_sql);\n\t}\n\n\tpublic function test_eager_loading_clones_related_objects()\n\t{\n\t\t$events = Event::find(array(2,3), array('include' => array('venue')));\n\n\t\t$venue = $events[0]->venue;\n\t\t$venue->name = \"new name\";\n\n\t\t$this->assert_equals($venue->id, $events[1]->venue->id);\n\t\t$this->assert_not_equals($venue->name, $events[1]->venue->name);\n\t\t$this->assert_not_equals(spl_object_hash($venue), spl_object_hash($events[1]->venue));\n\t}\n\n\tpublic function test_eager_loading_clones_nested_related_objects()\n\t{\n\t\t$venues = Venue::find(array(1,2,6,9), array('include' => array('events' => array('host'))));\n\n\t\t$unchanged_host = $venues[2]->events[0]->host;\n\t\t$changed_host = $venues[3]->events[0]->host;\n\t\t$changed_host->name = \"changed\";\n\n\t\t$this->assert_equals($changed_host->id, $unchanged_host->id);\n\t\t$this->assert_not_equals($changed_host->name, $unchanged_host->name);\n\t\t$this->assert_not_equals(spl_object_hash($changed_host), spl_object_hash($unchanged_host));\n\t}\n\n\tpublic function test_gh_23_relationships_with_joins_to_same_table_should_alias_table_name()\n\t{\n\t\t$old = Book::$belongs_to;\n\t\tBook::$belongs_to = array(\n\t\t\tarray('from_', 'class_name' => 'Author', 'foreign_key' => 'author_id'),\n\t\t\tarray('to', 'class_name' => 'Author', 'foreign_key' => 'secondary_author_id'),\n\t\t\tarray('another', 'class_name' => 'Author', 'foreign_key' => 'secondary_author_id')\n\t\t);\n\n\t\t$c = ActiveRecord\\Table::load('Book')->conn;\n\n\t\t$select = \"books.*, authors.name as to_author_name, {$c->quote_name('from_')}.name as from_author_name, {$c->quote_name('another')}.name as another_author_name\";\n\t\t$book = Book::find(2, array('joins' => array('to', 'from_', 'another'),\n\t\t\t'select' => $select));\n\n\t\t$this->assert_not_null($book->from_author_name);\n\t\t$this->assert_not_null($book->to_author_name);\n\t\t$this->assert_not_null($book->another_author_name);\n\t\tBook::$belongs_to = $old;\n\t}\n\n\tpublic function test_gh_40_relationships_with_joins_aliases_table_name_in_conditions()\n\t{\n\t\t$event = Event::find(1, array('joins' => array('venue')));\n\n\t\t$this->assert_equals($event->id, $event->venue->id);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\RecordNotFound\n\t */\n\tpublic function test_dont_attempt_eager_load_when_record_does_not_exist()\n\t{\n\t\tAuthor::find(999999, array('include' => array('books')));\n\t}\n};\n?>\n"
  },
  {
    "path": "test/SQLBuilderTest.php",
    "content": "<?php\n\nuse ActiveRecord\\SQLBuilder;\nuse ActiveRecord\\Table;\n\nclass SQLBuilderTest extends DatabaseTest\n{\n\tprotected $table_name = 'authors';\n\tprotected $class_name = 'Author';\n\tprotected $table;\n\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up($connection_name);\n\t\t$this->sql = new SQLBuilder($this->conn,$this->table_name);\n\t\t$this->table = Table::load($this->class_name);\n\t}\n\n\tprotected function cond_from_s($name, $values=null, $map=null)\n\t{\n\t\treturn SQLBuilder::create_conditions_from_underscored_string($this->table->conn, $name, $values, $map);\n\t}\n\n\tpublic function assert_conditions($expected_sql, $values, $underscored_string, $map=null)\n\t{\n\t\t$cond = SQLBuilder::create_conditions_from_underscored_string($this->table->conn,$underscored_string,$values,$map);\n\t\t$this->assert_sql_has($expected_sql,array_shift($cond));\n\n\t\tif ($values)\n\t\t\t$this->assert_equals(array_values(array_filter($values,function($s) { return $s !== null; })),array_values($cond));\n\t\telse\n\t\t\t$this->assert_equals(array(),$cond);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_no_connection()\n\t{\n\t\tnew SQLBuilder(null,'authors');\n\t}\n\n\tpublic function test_nothing()\n\t{\n\t\t$this->assert_equals('SELECT * FROM authors',(string)$this->sql);\n\t}\n\n\tpublic function test_where_with_array()\n\t{\n\t\t$this->sql->where(\"id=? AND name IN(?)\",1,array('Tito','Mexican'));\n\t\t$this->assert_sql_has(\"SELECT * FROM authors WHERE id=? AND name IN(?,?)\",(string)$this->sql);\n\t\t$this->assert_equals(array(1,'Tito','Mexican'),$this->sql->get_where_values());\n\t}\n\n\tpublic function test_where_with_hash()\n\t{\n\t\t$this->sql->where(array('id' => 1, 'name' => 'Tito'));\n\t\t$this->assert_sql_has(\"SELECT * FROM authors WHERE id=? AND name=?\",(string)$this->sql);\n\t\t$this->assert_equals(array(1,'Tito'),$this->sql->get_where_values());\n\t}\n\n\tpublic function test_where_with_hash_and_array()\n\t{\n\t\t$this->sql->where(array('id' => 1, 'name' => array('Tito','Mexican')));\n\t\t$this->assert_sql_has(\"SELECT * FROM authors WHERE id=? AND name IN(?,?)\",(string)$this->sql);\n\t\t$this->assert_equals(array(1,'Tito','Mexican'),$this->sql->get_where_values());\n\t}\n\n\tpublic function test_gh134_where_with_hash_and_null()\n\t{\n\t\t$this->sql->where(array('id' => 1, 'name' => null));\n\t\t$this->assert_sql_has(\"SELECT * FROM authors WHERE id=? AND name IS ?\",(string)$this->sql);\n\t\t$this->assert_equals(array(1, null),$this->sql->get_where_values());\n\t}\n\n\tpublic function test_where_with_null()\n\t{\n\t\t$this->sql->where(null);\n\t\t$this->assert_equals('SELECT * FROM authors',(string)$this->sql);\n\t}\n\n\tpublic function test_where_with_no_args()\n\t{\n\t\t$this->sql->where();\n\t\t$this->assert_equals('SELECT * FROM authors',(string)$this->sql);\n\t}\n\n\tpublic function test_order()\n\t{\n\t\t$this->sql->order('name');\n\t\t$this->assert_equals('SELECT * FROM authors ORDER BY name',(string)$this->sql);\n\t}\n\n\tpublic function test_limit()\n\t{\n\t\t$this->sql->limit(10)->offset(1);\n\t\t$this->assert_equals($this->conn->limit('SELECT * FROM authors',1,10),(string)$this->sql);\n\t}\n\n\tpublic function test_select()\n\t{\n\t\t$this->sql->select('id,name');\n\t\t$this->assert_equals('SELECT id,name FROM authors',(string)$this->sql);\n\t}\n\n\tpublic function test_joins()\n\t{\n\t\t$join = 'inner join books on(authors.id=books.author_id)';\n\t\t$this->sql->joins($join);\n\t\t$this->assert_equals(\"SELECT * FROM authors $join\",(string)$this->sql);\n\t}\n\n\tpublic function test_group()\n\t{\n\t\t$this->sql->group('name');\n\t\t$this->assert_equals('SELECT * FROM authors GROUP BY name',(string)$this->sql);\n\t}\n\n\tpublic function test_having()\n\t{\n\t\t$this->sql->having(\"created_at > '2009-01-01'\");\n\t\t$this->assert_equals(\"SELECT * FROM authors HAVING created_at > '2009-01-01'\", (string)$this->sql);\n\t}\n\n\tpublic function test_all_clauses_after_where_should_be_correctly_ordered()\n\t{\n\t\t$this->sql->limit(10)->offset(1);\n\t\t$this->sql->having(\"created_at > '2009-01-01'\");\n\t\t$this->sql->order('name');\n\t\t$this->sql->group('name');\n\t\t$this->sql->where(array('id' => 1));\n\t\t$this->assert_sql_has($this->conn->limit(\"SELECT * FROM authors WHERE id=? GROUP BY name HAVING created_at > '2009-01-01' ORDER BY name\",1,10), (string)$this->sql);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\ActiveRecordException\n\t */\n\tpublic function test_insert_requires_hash()\n\t{\n\t\t$this->sql->insert(array(1));\n\t}\n\n\tpublic function test_insert()\n\t{\n\t\t$this->sql->insert(array('id' => 1, 'name' => 'Tito'));\n\t\t$this->assert_sql_has(\"INSERT INTO authors(id,name) VALUES(?,?)\",(string)$this->sql);\n\t}\n\n\tpublic function test_insert_with_null()\n\t{\n\t\t$this->sql->insert(array('id' => 1, 'name' => null));\n\t\t$this->assert_sql_has(\"INSERT INTO authors(id,name) VALUES(?,?)\",$this->sql->to_s());\n\t}\n\n\tpublic function test_update_with_hash()\n\t{\n\t\t$this->sql->update(array('id' => 1, 'name' => 'Tito'))->where('id=1 AND name IN(?)',array('Tito','Mexican'));\n \t\t$this->assert_sql_has(\"UPDATE authors SET id=?, name=? WHERE id=1 AND name IN(?,?)\",(string)$this->sql);\n\t\t$this->assert_equals(array(1,'Tito','Tito','Mexican'),$this->sql->bind_values());\n\t}\n\n\tpublic function test_update_with_limit_and_order()\n\t{\n\t\tif (!$this->conn->accepts_limit_and_order_for_update_and_delete())\n\t\t\t$this->mark_test_skipped('Only MySQL & Sqlite accept limit/order with UPDATE operation');\n\n\t\t$this->sql->update(array('id' => 1))->order('name asc')->limit(1);\n\t\t$this->assert_sql_has(\"UPDATE authors SET id=? ORDER BY name asc LIMIT 1\", $this->sql->to_s());\n\t}\n\n\tpublic function test_update_with_string()\n\t{\n\t\t$this->sql->update(\"name='Bob'\");\n\t\t$this->assert_sql_has(\"UPDATE authors SET name='Bob'\", $this->sql->to_s());\n\t}\n\n\tpublic function test_update_with_null()\n\t{\n\t\t$this->sql->update(array('id' => 1, 'name' => null))->where('id=1');\n\t\t$this->assert_sql_has(\"UPDATE authors SET id=?, name=? WHERE id=1\",$this->sql->to_s());\n\t}\n\n\tpublic function test_delete()\n\t{\n\t\t$this->sql->delete();\n\t\t$this->assert_equals('DELETE FROM authors',$this->sql->to_s());\n\t}\n\n\tpublic function test_delete_with_where()\n\t{\n\t\t$this->sql->delete('id=? or name in(?)',1,array('Tito','Mexican'));\n\t\t$this->assert_equals('DELETE FROM authors WHERE id=? or name in(?,?)',$this->sql->to_s());\n\t\t$this->assert_equals(array(1,'Tito','Mexican'),$this->sql->bind_values());\n\t}\n\n\tpublic function test_delete_with_hash()\n\t{\n\t\t$this->sql->delete(array('id' => 1, 'name' => array('Tito','Mexican')));\n\t\t$this->assert_sql_has(\"DELETE FROM authors WHERE id=? AND name IN(?,?)\",$this->sql->to_s());\n\t\t$this->assert_equals(array(1,'Tito','Mexican'),$this->sql->get_where_values());\n\t}\n\n\tpublic function test_delete_with_limit_and_order()\n\t{\n\t\tif (!$this->conn->accepts_limit_and_order_for_update_and_delete())\n\t\t\t$this->mark_test_skipped('Only MySQL & Sqlite accept limit/order with DELETE operation');\n\n\t\t$this->sql->delete(array('id' => 1))->order('name asc')->limit(1);\n\t\t$this->assert_sql_has(\"DELETE FROM authors WHERE id=? ORDER BY name asc LIMIT 1\",$this->sql->to_s());\n\t}\n\n\tpublic function test_reverse_order()\n\t{\n\t\t$this->assert_equals('id ASC, name DESC', SQLBuilder::reverse_order('id DESC, name ASC'));\n\t\t$this->assert_equals('id ASC, name DESC , zzz ASC', SQLBuilder::reverse_order('id DESC, name ASC , zzz DESC'));\n\t\t$this->assert_equals('id DESC, name DESC', SQLBuilder::reverse_order('id, name'));\n\t\t$this->assert_equals('id DESC', SQLBuilder::reverse_order('id'));\n\t\t$this->assert_equals('', SQLBuilder::reverse_order(''));\n\t\t$this->assert_equals(' ', SQLBuilder::reverse_order(' '));\n\t\t$this->assert_equals(null, SQLBuilder::reverse_order(null));\n\t}\n\n\tpublic function test_create_conditions_from_underscored_string()\n\t{\n\t\t$this->assert_conditions('id=? AND name=? OR z=?',array(1,'Tito','X'),'id_and_name_or_z');\n\t\t$this->assert_conditions('id=?',array(1),'id');\n\t\t$this->assert_conditions('id IN(?)',array(array(1,2)),'id');\n\t}\n\n\tpublic function test_create_conditions_from_underscored_string_with_nulls()\n\t{\n\t\t$this->assert_conditions('id=? AND name IS NULL',array(1,null),'id_and_name');\n\t}\n\n\tpublic function test_create_conditions_from_underscored_string_with_missing_args()\n\t{\n\t\t$this->assert_conditions('id=? AND name IS NULL OR z IS NULL',array(1,null),'id_and_name_or_z');\n\t\t$this->assert_conditions('id IS NULL',null,'id');\n\t}\n\n\tpublic function test_create_conditions_from_underscored_string_with_blank()\n\t{\n\t\t$this->assert_conditions('id=? AND name IS NULL OR z=?',array(1,null,''),'id_and_name_or_z');\n\t}\n\n\tpublic function test_create_conditions_from_underscored_string_invalid()\n\t{\n\t\t$this->assert_equals(null,$this->cond_from_s(''));\n\t\t$this->assert_equals(null,$this->cond_from_s(null));\n\t}\n\n\tpublic function test_create_conditions_from_underscored_string_with_mapped_columns()\n\t{\n\t\t$this->assert_conditions('id=? AND name=?',array(1,'Tito'),'id_and_my_name',array('my_name' => 'name'));\n\t}\n\n\tpublic function test_create_hash_from_underscored_string()\n\t{\n\t\t$values = array(1,'Tito');\n\t\t$hash = SQLBuilder::create_hash_from_underscored_string('id_and_my_name',$values);\n\t\t$this->assert_equals(array('id' => 1, 'my_name' => 'Tito'),$hash);\n\t}\n\n\tpublic function test_create_hash_from_underscored_string_with_mapped_columns()\n\t{\n\t\t$values = array(1,'Tito');\n\t\t$map = array('my_name' => 'name');\n\t\t$hash = SQLBuilder::create_hash_from_underscored_string('id_and_my_name',$values,$map);\n\t\t$this->assert_equals(array('id' => 1, 'name' => 'Tito'),$hash);\n\t}\n\n\tpublic function test_where_with_joins_prepends_table_name_to_fields()\n\t{\n\t\t$joins = 'INNER JOIN books ON (books.id = authors.id)';\n\t\t// joins needs to be called prior to where\n\t\t$this->sql->joins($joins);\n\t\t$this->sql->where(array('id' => 1, 'name' => 'Tito'));\n\n\t\t$this->assert_sql_has(\"SELECT * FROM authors $joins WHERE authors.id=? AND authors.name=?\",(string)$this->sql);\n\t}\n};\n?>"
  },
  {
    "path": "test/SerializationTest.php",
    "content": "<?php\nrequire_once __DIR__ . '/../lib/Serialization.php';\n\nuse ActiveRecord\\DateTime;\n\nclass SerializationTest extends DatabaseTest\n{\n\tpublic function tear_down()\n\t{\n\t\tparent::tear_down();\n\t\tActiveRecord\\ArraySerializer::$include_root = false;\n\t\tActiveRecord\\JsonSerializer::$include_root = false;\n\t}\n\n\tpublic function _a($options=array(), $model=null)\n\t{\n\t\tif (!$model)\n\t\t\t$model = Book::find(1);\n\n\t\t$s = new ActiveRecord\\JsonSerializer($model,$options);\n\t\treturn $s->to_a();\n\t}\n\n\tpublic function test_only()\n\t{\n\t\t$this->assert_has_keys('name', 'special', $this->_a(array('only' => array('name', 'special'))));\n\t}\n\n\tpublic function test_only_not_array()\n\t{\n\t\t$this->assert_has_keys('name', $this->_a(array('only' => 'name')));\n\t}\n\n\tpublic function test_only_should_only_apply_to_attributes()\n\t{\n\t\t$this->assert_has_keys('name','author', $this->_a(array('only' => 'name', 'include' => 'author')));\n\t\t$this->assert_has_keys('book_id','upper_name', $this->_a(array('only' => 'book_id', 'methods' => 'upper_name')));\n\t}\n\n\tpublic function test_only_overrides_except()\n\t{\n\t\t$this->assert_has_keys('name', $this->_a(array('only' => 'name', 'except' => 'name')));\n\t}\n\n\tpublic function test_except()\n\t{\n\t\t$this->assert_doesnt_has_keys('name', 'special', $this->_a(array('except' => array('name','special'))));\n\t}\n\n\tpublic function test_except_takes_a_string()\n\t{\n\t\t$this->assert_doesnt_has_keys('name', $this->_a(array('except' => 'name')));\n\t}\n\n\tpublic function test_methods()\n\t{\n\t\t$a = $this->_a(array('methods' => array('upper_name')));\n\t\t$this->assert_equals('ANCIENT ART OF MAIN TANKING', $a['upper_name']);\n\t}\n\n\tpublic function test_methods_takes_a_string()\n\t{\n\t\t$a = $this->_a(array('methods' => 'upper_name'));\n\t\t$this->assert_equals('ANCIENT ART OF MAIN TANKING', $a['upper_name']);\n\t}\n\n\t// methods added last should we shuld have value of the method in our json\n\t// rather than the regular attribute value\n\tpublic function test_methods_method_same_as_attribute()\n\t{\n\t\t$a = $this->_a(array('methods' => 'name'));\n\t\t$this->assert_equals('ancient art of main tanking', $a['name']);\n\t}\n\n\tpublic function test_include()\n\t{\n\t\t$a = $this->_a(array('include' => array('author')));\n\t\t$this->assert_has_keys('parent_author_id', $a['author']);\n\t}\n\n\tpublic function test_include_nested_with_nested_options()\n\t{\n\t\t$a = $this->_a(\n\t\t\tarray('include' => array('events' => array('except' => 'title', 'include' => array('host' => array('only' => 'id'))))),\n\t\t\tHost::find(4));\n\n\t\t$this->assert_equals(3, count($a['events']));\n\t\t$this->assert_doesnt_has_keys('title', $a['events'][0]);\n\t\t$this->assert_equals(array('id' => 4), $a['events'][0]['host']);\n\t}\n\n\tpublic function test_datetime_values_get_converted_to_strings()\n\t{\n\t\t$now = new DateTime();\n\t\t$a = $this->_a(array('only' => 'created_at'),new Author(array('created_at' => $now)));\n\t\t$this->assert_equals($now->format(ActiveRecord\\Serialization::$DATETIME_FORMAT),$a['created_at']);\n\t}\n\n\tpublic function test_to_json()\n\t{\n\t\t$book = Book::find(1);\n\t\t$json = $book->to_json();\n\t\t$this->assert_equals($book->attributes(),(array)json_decode($json));\n\t}\n\n\tpublic function test_to_json_include_root()\n\t{\n\t\tActiveRecord\\JsonSerializer::$include_root = true;\n\t\t$this->assert_not_null(json_decode(Book::find(1)->to_json())->book);\n\t}\n\n\tpublic function test_to_xml_include()\n\t{\n\t\t$xml = Host::find(4)->to_xml(array('include' => 'events'));\n\t\t$decoded = get_object_vars(new SimpleXMLElement($xml));\n\n\t\t$this->assert_equals(3, count($decoded['events']->event));\n\t}\n\n\tpublic function test_to_xml()\n\t{\n\t\t$book = Book::find(1);\n\t\t$this->assert_equals($book->attributes(),get_object_vars(new SimpleXMLElement($book->to_xml())));\n\t}\n\n  public function test_to_array()\n  {\n \t\t$book = Book::find(1);\n\t\t$array = $book->to_array();\n\t\t$this->assert_equals($book->attributes(), $array);\n  }\n\n  public function test_to_array_include_root()\n  {\n\t\tActiveRecord\\ArraySerializer::$include_root = true;\n \t\t$book = Book::find(1);\n\t\t$array = $book->to_array();\n    $book_attributes = array('book' => $book->attributes());\n\t\t$this->assert_equals($book_attributes, $array);\n  }\n\n  public function test_to_array_except()\n  {\n \t\t$book = Book::find(1);\n\t\t$array = $book->to_array(array('except' => array('special')));\n\t\t$book_attributes = $book->attributes();\n\t\tunset($book_attributes['special']);\n\t\t$this->assert_equals($book_attributes, $array);\n  }\n\n\tpublic function test_works_with_datetime()\n\t{\n\t\tAuthor::find(1)->update_attribute('created_at',new DateTime());\n\t\t$this->assert_reg_exp('/<updated_at>[0-9]{4}-[0-9]{2}-[0-9]{2}/',Author::find(1)->to_xml());\n\t\t$this->assert_reg_exp('/\"updated_at\":\"[0-9]{4}-[0-9]{2}-[0-9]{2}/',Author::find(1)->to_json());\n\t}\n\n\tpublic function test_to_xml_skip_instruct()\n\t{\n\t\t$this->assert_same(false,strpos(Book::find(1)->to_xml(array('skip_instruct' => true)),'<?xml version'));\n\t\t$this->assert_same(0,    strpos(Book::find(1)->to_xml(array('skip_instruct' => false)),'<?xml version'));\n\t}\n\n\tpublic function test_only_method()\n\t{\n\t\t$this->assert_contains('<sharks>lasers</sharks>', Author::first()->to_xml(array('only_method' => 'return_something')));\n\t}\n\n  public function test_to_csv()\n  {\n    $book = Book::find(1);\n    $this->assert_equals('1,1,2,\"Ancient Art of Main Tanking\",0,0',$book->to_csv());\n  }\n\n  public function test_to_csv_only_header()\n  {\n    $book = Book::find(1);\n    $this->assert_equals('book_id,author_id,secondary_author_id,name,numeric_test,special',\n                         $book->to_csv(array('only_header'=>true))\n                         );\n  }\n\n  public function test_to_csv_only_method()\n  {\n    $book = Book::find(1);\n    $this->assert_equals('2,\"Ancient Art of Main Tanking\"',\n                         $book->to_csv(array('only'=>array('name','secondary_author_id')))\n                         );\n  }\n\n  public function test_to_csv_only_method_on_header()\n  {\n    $book = Book::find(1);\n    $this->assert_equals('secondary_author_id,name',\n                         $book->to_csv(array('only'=>array('secondary_author_id','name'),\n                                             'only_header'=>true))\n                         );\n  }\n\n  public function test_to_csv_with_custom_delimiter()\n  {\n    $book = Book::find(1);\n    ActiveRecord\\CsvSerializer::$delimiter=';';\n    $this->assert_equals('1;1;2;\"Ancient Art of Main Tanking\";0;0',$book->to_csv());\n  }\n\n  public function test_to_csv_with_custom_enclosure()\n  {\n    $book = Book::find(1);\n    ActiveRecord\\CsvSerializer::$delimiter=',';\n    ActiveRecord\\CsvSerializer::$enclosure=\"'\";\n    $this->assert_equals(\"1,1,2,'Ancient Art of Main Tanking',0,0\",$book->to_csv());\n  }\n};\n?>\n"
  },
  {
    "path": "test/SqliteAdapterTest.php",
    "content": "<?php\nrequire_once __DIR__ . '/../lib/adapters/SqliteAdapter.php';\n\nclass SqliteAdapterTest extends AdapterTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up('sqlite');\n\t}\n\n\tpublic function tearDown()\n\t{\n\t\tparent::tearDown();\n\n\t\t@unlink(self::InvalidDb);\n\t}\n\n\n\tpublic static function tearDownAfterClass()\n\t{\n\t\tparent::tearDownAfterClass();\n\n\t\t@unlink(static::$db);\n\t}\n\n\tpublic function testConnectToInvalidDatabaseShouldNotCreateDbFile()\n\t{\n\t\ttry\n\t\t{\n\t\t\tActiveRecord\\Connection::instance(\"sqlite://\" . self::InvalidDb);\n\t\t\t$this->assertFalse(true);\n\t\t}\n\t\tcatch (ActiveRecord\\DatabaseException $e)\n\t\t{\n\t\t\t$this->assertFalse(file_exists(__DIR__ . \"/\" . self::InvalidDb));\n\t\t}\n\t}\n\n\tpublic function test_limit_with_null_offset_does_not_contain_offset()\n\t{\n\t\t$ret = array();\n\t\t$sql = 'SELECT * FROM authors ORDER BY name ASC';\n\t\t$this->conn->query_and_fetch($this->conn->limit($sql,null,1),function($row) use (&$ret) { $ret[] = $row; });\n\n\t\t$this->assert_true(strpos($this->conn->last_query, 'LIMIT 1') !== false);\n\t}\n\n\tpublic function test_gh183_sqliteadapter_autoincrement()\n\t{\n\t\t// defined in lowercase: id integer not null primary key\n\t\t$columns = $this->conn->columns('awesome_people');\n\t\t$this->assert_true($columns['id']->auto_increment);\n\n\t\t// defined in uppercase: `amenity_id` INTEGER NOT NULL PRIMARY KEY\n\t\t$columns = $this->conn->columns('amenities');\n\t\t$this->assert_true($columns['amenity_id']->auto_increment);\n\n\t\t// defined using int: `rm-id` INT NOT NULL\n\t\t$columns = $this->conn->columns('`rm-bldg`');\n\t\t$this->assert_false($columns['rm-id']->auto_increment);\n\n\t\t// defined using int: id INT NOT NULL PRIMARY KEY\n\t\t$columns = $this->conn->columns('hosts');\n\t\t$this->assert_true($columns['id']->auto_increment);\n\t}\n\n\tpublic function test_datetime_to_string()\n\t{\n\t\t$datetime = '2009-01-01 01:01:01';\n\t\t$this->assert_equals($datetime,$this->conn->datetime_to_string(date_create($datetime)));\n\t}\n\n\tpublic function test_date_to_string()\n\t{\n\t\t$datetime = '2009-01-01';\n\t\t$this->assert_equals($datetime,$this->conn->date_to_string(date_create($datetime)));\n\t}\n\n\t// not supported\n\tpublic function test_connect_with_port() {}\n}\n?>"
  },
  {
    "path": "test/UtilsTest.php",
    "content": "<?php\n\nuse ActiveRecord as AR;\n\nclass UtilsTest extends SnakeCase_PHPUnit_Framework_TestCase\n{\n\tpublic function set_up()\n\t{\n\t\t$this->object_array = array(null,null);\n\t\t$this->object_array[0] = new stdClass();\n\t\t$this->object_array[0]->a = \"0a\";\n\t\t$this->object_array[0]->b = \"0b\";\n\t\t$this->object_array[1] = new stdClass();\n\t\t$this->object_array[1]->a = \"1a\";\n\t\t$this->object_array[1]->b = \"1b\";\n\n\t\t$this->array_hash = array(\n\t\t\tarray(\"a\" => \"0a\", \"b\" => \"0b\"),\n\t\t\tarray(\"a\" => \"1a\", \"b\" => \"1b\"));\n\t}\n\n\tpublic function test_collect_with_array_of_objects_using_closure()\n\t{\n\t\t$this->assert_equals(array(\"0a\",\"1a\"),AR\\collect($this->object_array,function($obj) { return $obj->a; }));\n\t}\n\n\tpublic function test_collect_with_array_of_objects_using_string()\n\t{\n\t\t$this->assert_equals(array(\"0a\",\"1a\"),AR\\collect($this->object_array,\"a\"));\n\t}\n\n\tpublic function test_collect_with_array_hash_using_closure()\n\t{\n\t\t$this->assert_equals(array(\"0a\",\"1a\"),AR\\collect($this->array_hash,function($item) { return $item[\"a\"]; }));\n\t}\n\n\tpublic function test_collect_with_array_hash_using_string()\n\t{\n\t\t$this->assert_equals(array(\"0a\",\"1a\"),AR\\collect($this->array_hash,\"a\"));\n\t}\n\n    public function test_array_flatten()\n    {\n\t\t$this->assert_equals(array(), AR\\array_flatten(array()));\n\t\t$this->assert_equals(array(1), AR\\array_flatten(array(1)));\n\t\t$this->assert_equals(array(1), AR\\array_flatten(array(array(1))));\n\t\t$this->assert_equals(array(1, 2), AR\\array_flatten(array(array(1, 2))));\n\t\t$this->assert_equals(array(1, 2), AR\\array_flatten(array(array(1), 2)));\n\t\t$this->assert_equals(array(1, 2), AR\\array_flatten(array(1, array(2))));\n\t\t$this->assert_equals(array(1, 2, 3), AR\\array_flatten(array(1, array(2), 3)));\n\t\t$this->assert_equals(array(1, 2, 3, 4), AR\\array_flatten(array(1, array(2, 3), 4)));\n\t\t$this->assert_equals(array(1, 2, 3, 4, 5, 6), AR\\array_flatten(array(1, array(2, 3), 4, array(5, 6))));\n\t}\n\n\tpublic function test_all()\n\t{\n\t\t$this->assert_true(AR\\all(null,array(null,null)));\n\t\t$this->assert_true(AR\\all(1,array(1,1)));\n\t\t$this->assert_false(AR\\all(1,array(1,'1')));\n\t\t$this->assert_false(AR\\all(null,array('',null)));\n\t}\n\n\tpublic function test_classify()\n\t{\n\t\t$bad_class_names = array('ubuntu_rox', 'stop_the_Snake_Case', 'CamelCased', 'camelCased');\n\t\t$good_class_names = array('UbuntuRox', 'StopTheSnakeCase', 'CamelCased', 'CamelCased');\n\n\t\t$class_names = array();\n\t\tforeach ($bad_class_names as $s)\n\t\t\t$class_names[] = AR\\classify($s);\n\n\t\t$this->assert_equals($class_names, $good_class_names);\n\t}\n\n\tpublic function test_classify_singularize()\n\t{\n\t\t$bad_class_names = array('events', 'stop_the_Snake_Cases', 'angry_boxes', 'Mad_Sheep_herders', 'happy_People');\n\t\t$good_class_names = array('Event', 'StopTheSnakeCase', 'AngryBox', 'MadSheepHerder', 'HappyPerson');\n\n\t\t$class_names = array();\n\t\tforeach ($bad_class_names as $s)\n\t\t\t$class_names[] = AR\\classify($s, true);\n\n\t\t$this->assert_equals($class_names, $good_class_names);\n\t}\n\n\tpublic function test_singularize()\n\t{\n\t\t$this->assert_equals('order_status',AR\\Utils::singularize('order_status'));\n\t\t$this->assert_equals('order_status',AR\\Utils::singularize('order_statuses'));\n\t\t$this->assert_equals('os_type', AR\\Utils::singularize('os_type'));\n\t\t$this->assert_equals('os_type', AR\\Utils::singularize('os_types'));\n\t\t$this->assert_equals('photo', AR\\Utils::singularize('photos'));\n\t\t$this->assert_equals('pass', AR\\Utils::singularize('pass'));\n\t\t$this->assert_equals('pass', AR\\Utils::singularize('passes'));\n\t}\n\n\tpublic function test_wrap_strings_in_arrays()\n\t{\n\t\t$x = array('1',array('2'));\n\t\t$this->assert_equals(array(array('1'),array('2')),ActiveRecord\\wrap_strings_in_arrays($x));\n\n\t\t$x = '1';\n\t\t$this->assert_equals(array(array('1')),ActiveRecord\\wrap_strings_in_arrays($x));\n\t}\n};\n?>\n"
  },
  {
    "path": "test/ValidatesFormatOfTest.php",
    "content": "<?php\r\n\r\nclass BookFormat extends ActiveRecord\\Model\r\n{\r\n\tstatic $table = 'books';\r\n\tstatic $validates_format_of = array(\r\n\t\tarray('name')\r\n\t);\r\n};\r\n\r\nclass ValidatesFormatOfTest extends DatabaseTest\r\n{\r\n\tpublic function set_up($connection_name=null)\r\n\t{\r\n\t\tparent::set_up($connection_name);\r\n\t\tBookFormat::$validates_format_of[0] = array('name');\r\n\t}\r\n\r\n\tpublic function test_format()\r\n\t{\r\n\t\tBookFormat::$validates_format_of[0]['with'] = '/^[a-z\\W]*$/';\r\n\t\t$book = new BookFormat(array('author_id' => 1, 'name' => 'testing reg'));\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\r\n\t\tBookFormat::$validates_format_of[0]['with'] = '/[0-9]/';\r\n\t\t$book = new BookFormat(array('author_id' => 1, 'name' => 12));\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_invalid_null()\r\n\t{\r\n\t\tBookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';\r\n\t\t$book = new BookFormat;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_invalid_blank()\r\n\t{\r\n\t\tBookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';\r\n\t\t$book = new BookFormat;\r\n\t\t$book->name = '';\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_valid_blank_andallow_blank()\r\n\t{\r\n\t\tBookFormat::$validates_format_of[0]['allow_blank'] = true;\r\n\t\tBookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';\r\n\t\t$book = new BookFormat(array('author_id' => 1, 'name' => ''));\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_valid_null_and_allow_null()\r\n\t{\r\n\t\tBookFormat::$validates_format_of[0]['allow_null'] = true;\r\n\t\tBookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';\r\n\t\t$book = new BookFormat();\r\n\t\t$book->author_id = 1;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\t/**\r\n\t * @expectedException ActiveRecord\\ValidationsArgumentError\r\n\t */\r\n\tpublic function test_invalid_lack_of_with_key()\r\n\t{\r\n\t\t$book = new BookFormat;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t}\r\n\r\n\t/**\r\n\t * @expectedException ActiveRecord\\ValidationsArgumentError\r\n\t */\r\n\tpublic function test_invalid_with_expression_as_non_string()\r\n\t{\r\n\t\tBookFormat::$validates_format_of[0]['with'] = array('test');\r\n\t\t$book = new BookFormat;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t}\r\n\r\n\tpublic function test_invalid_with_expression_as_non_regexp()\r\n\t{\r\n\t\tBookFormat::$validates_format_of[0]['with'] = 'blah';\r\n\t\t$book = new BookFormat;\r\n\t\t$book->name = 'blah';\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_custom_message()\r\n\t{\r\n\t\tBookFormat::$validates_format_of[0]['message'] = 'is using a custom message.';\r\n\t\tBookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';\r\n\r\n\t\t$book = new BookFormat;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t\t$this->assert_equals('is using a custom message.', $book->errors->on('name'));\r\n\t}\r\n};\r\n?>"
  },
  {
    "path": "test/ValidatesInclusionAndExclusionOfTest.php",
    "content": "<?php\r\n\r\nclass BookExclusion extends ActiveRecord\\Model\r\n{\r\n\tstatic $table = 'books';\r\n\tpublic static $validates_exclusion_of = array(\r\n\t\tarray('name', 'in' => array('blah', 'alpha', 'bravo'))\r\n\t);\r\n};\r\n\r\nclass BookInclusion extends ActiveRecord\\Model\r\n{\r\n\tstatic $table = 'books';\r\n\tpublic static $validates_inclusion_of = array(\r\n\t\tarray('name', 'in' => array('blah', 'tanker', 'shark'))\r\n\t);\r\n};\r\n\r\nclass ValidatesInclusionAndExclusionOfTest extends DatabaseTest\r\n{\r\n\tpublic function set_up($connection_name=null)\r\n\t{\r\n\t\tparent::set_up($connection_name);\r\n\t\tBookInclusion::$validates_inclusion_of[0] = array('name', 'in' => array('blah', 'tanker', 'shark'));\r\n\t\tBookExclusion::$validates_exclusion_of[0] = array('name', 'in' => array('blah', 'alpha', 'bravo'));\r\n\t}\r\n\r\n\tpublic function test_inclusion()\r\n\t{\r\n\t\t$book = new BookInclusion;\r\n\t\t$book->name = 'blah';\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_exclusion()\r\n\t{\r\n\t\t$book = new BookExclusion;\r\n\t\t$book->name = 'blahh';\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_invalid_inclusion()\r\n\t{\r\n\t\t$book = new BookInclusion;\r\n\t\t$book->name = 'thanker';\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\t\t$book->name = 'alpha ';\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_invalid_exclusion()\r\n\t{\r\n\t\t$book = new BookExclusion;\r\n\t\t$book->name = 'alpha';\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\r\n\t\t$book = new BookExclusion;\r\n\t\t$book->name = 'bravo';\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_inclusion_with_numeric()\r\n\t{\r\n\t\tBookInclusion::$validates_inclusion_of[0]['in']= array(0, 1, 2);\r\n\t\t$book = new BookInclusion;\r\n\t\t$book->name = 2;\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_inclusion_with_boolean()\r\n\t{\r\n\t\tBookInclusion::$validates_inclusion_of[0]['in']= array(true);\r\n\t\t$book = new BookInclusion;\r\n\t\t$book->name = true;\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_inclusion_with_null()\r\n\t{\r\n\t\tBookInclusion::$validates_inclusion_of[0]['in']= array(null);\r\n\t\t$book = new BookInclusion;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_invalid_inclusion_with_numeric()\r\n\t{\r\n\t\tBookInclusion::$validates_inclusion_of[0]['in']= array(0, 1, 2);\r\n\t\t$book = new BookInclusion;\r\n\t\t$book->name = 5;\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function tes_inclusion_within_option()\r\n\t{\r\n\t\tBookInclusion::$validates_inclusion_of[0] = array('name', 'within' => array('okay'));\r\n\t\t$book = new BookInclusion;\r\n\t\t$book->name = 'okay';\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function tes_inclusion_scalar_value()\r\n\t{\r\n\t\tBookInclusion::$validates_inclusion_of[0] = array('name', 'within' => 'okay');\r\n\t\t$book = new BookInclusion;\r\n\t\t$book->name = 'okay';\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_valid_null()\r\n\t{\r\n\t\tBookInclusion::$validates_inclusion_of[0]['allow_null'] = true;\r\n\t\t$book = new BookInclusion;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_valid_blank()\r\n\t{\r\n\t\tBookInclusion::$validates_inclusion_of[0]['allow_blank'] = true;\r\n\t\t$book = new BookInclusion;\r\n\t\t$book->name = '';\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_custom_message()\r\n\t{\r\n\t\t$msg = 'is using a custom message.';\r\n\t\tBookInclusion::$validates_inclusion_of[0]['message'] = $msg;\r\n\t\tBookExclusion::$validates_exclusion_of[0]['message'] = $msg;\r\n\r\n\t\t$book = new BookInclusion;\r\n\t\t$book->name = 'not included';\r\n\t\t$book->save();\r\n\t\t$this->assert_equals('is using a custom message.', $book->errors->on('name'));\r\n\t\t$book = new BookExclusion;\r\n\t\t$book->name = 'bravo';\r\n\t\t$book->save();\r\n\t\t$this->assert_equals('is using a custom message.', $book->errors->on('name'));\r\n\t}\r\n\r\n};\r\n?>"
  },
  {
    "path": "test/ValidatesLengthOfTest.php",
    "content": "<?php\r\n\r\nclass BookLength extends ActiveRecord\\Model\r\n{\r\n\tstatic $table = 'books';\r\n\tstatic $validates_length_of = array();\r\n}\r\n\r\nclass BookSize extends ActiveRecord\\Model\r\n{\r\n\tstatic $table = 'books';\r\n\tstatic $validates_size_of = array();\r\n}\r\n\r\nclass ValidatesLengthOfTest extends DatabaseTest\r\n{\r\n\tpublic function set_up($connection_name=null)\r\n\t{\r\n\t\tparent::set_up($connection_name);\r\n\t\tBookLength::$validates_length_of[0] = array('name', 'allow_blank' => false, 'allow_null' => false);\r\n\t}\r\n\t\r\n\tpublic function test_within()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['within'] = array(1, 5);\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = '12345';\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_within_error_message()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['within'] = array(2,5);\r\n\t\t$book = new BookLength();\r\n\t\t$book->name = '1';\r\n\t\t$book->is_valid();\r\n\t\t$this->assert_equals(array('Name is too short (minimum is 2 characters)'),$book->errors->full_messages());\r\n\r\n\t\t$book->name = '123456';\r\n\t\t$book->is_valid();\r\n\t\t$this->assert_equals(array('Name is too long (maximum is 5 characters)'),$book->errors->full_messages());\r\n\t}\r\n\r\n\tpublic function test_within_custom_error_message()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['within'] = array(2,5);\r\n\t\tBookLength::$validates_length_of[0]['too_short'] = 'is too short';\r\n\t\tBookLength::$validates_length_of[0]['message'] = 'is not between 2 and 5 characters';\r\n\t\t$book = new BookLength();\r\n\t\t$book->name = '1';\r\n\t\t$book->is_valid();\r\n\t\t$this->assert_equals(array('Name is not between 2 and 5 characters'),$book->errors->full_messages());\r\n\r\n\t\t$book->name = '123456';\r\n\t\t$book->is_valid();\r\n\t\t$this->assert_equals(array('Name is not between 2 and 5 characters'),$book->errors->full_messages());\r\n\t}\r\n\t\r\n\tpublic function test_valid_in()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['in'] = array(1, 5);\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = '12345';\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_aliased_size_of()\r\n\t{\r\n\t\tBookSize::$validates_size_of = BookLength::$validates_length_of;\r\n\t\tBookSize::$validates_size_of[0]['within'] = array(1, 5);\r\n\t\t$book = new BookSize;\r\n\t\t$book->name = '12345';\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_invalid_within_and_in()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['within'] = array(1, 3);\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = 'four';\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\r\n\t\t$this->set_up();\r\n\t\tBookLength::$validates_length_of[0]['in'] = array(1, 3);\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = 'four';\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_valid_null()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['within'] = array(1, 3);\r\n\t\tBookLength::$validates_length_of[0]['allow_null'] = true;\r\n\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_valid_blank()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['within'] = array(1, 3);\r\n\t\tBookLength::$validates_length_of[0]['allow_blank'] = true;\r\n\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = '';\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_invalid_blank()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['within'] = array(1, 3);\r\n\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = '';\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\t\t$this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name'));\r\n\t}\r\n\r\n\tpublic function test_invalid_null_within()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['within'] = array(1, 3);\r\n\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\t\t$this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name'));\r\n\t}\r\n\t\r\n\tpublic function test_invalid_null_minimum()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['minimum'] = 1;\r\n\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t\t$this->assert_true($book->errors->is_invalid('name'));\r\n\t\t$this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name'));\r\n\t\t\r\n\t}\r\n\t\r\n\tpublic function test_valid_null_maximum()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['maximum'] = 1;\r\n\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t\t$this->assert_false($book->errors->is_invalid('name'));\r\n\t}\r\n\r\n\tpublic function test_float_as_impossible_range_option()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['within'] = array(1, 3.6);\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = '123';\r\n\t\ttry {\r\n\t\t\t$book->save();\r\n\t\t} catch (ActiveRecord\\ValidationsArgumentError $e) {\r\n\t\t\t$this->assert_equals('maximum value cannot use a float for length.', $e->getMessage());\r\n\t\t}\r\n\r\n\t\t$this->set_up();\r\n\t\tBookLength::$validates_length_of[0]['is'] = 1.8;\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = '123';\r\n\t\ttry {\r\n\t\t\t$book->save();\r\n\t\t} catch (ActiveRecord\\ValidationsArgumentError $e) {\r\n\t\t\t$this->assert_equals('is value cannot use a float for length.', $e->getMessage());\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t$this->fail('An expected exception has not be raised.');\r\n\t}\r\n\r\n\tpublic function test_signed_integer_as_impossible_within_option()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['within'] = array(-1, 3);\r\n\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = '123';\r\n\t\ttry {\r\n\t\t\t$book->save();\r\n\t\t} catch (ActiveRecord\\ValidationsArgumentError $e) {\r\n\t\t\t$this->assert_equals('minimum value cannot use a signed integer.', $e->getMessage());\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t$this->fail('An expected exception has not be raised.');\r\n\t}\r\n\r\n    public function test_not_array_as_impossible_range_option()\r\n    {\r\n        BookLength::$validates_length_of[0]['within'] = 'string';\r\n        $book = new BookLength;\r\n        $book->name = '123';\r\n        try {\r\n            $book->save();\r\n        } catch (ActiveRecord\\ValidationsArgumentError $e) {\r\n            $this->assert_equals('within must be an array composing a range of numbers with key [0] being less than key [1]', $e->getMessage());\r\n        }\r\n\r\n        $this->set_up();\r\n        BookLength::$validates_length_of[0]['in'] = 'string';\r\n        $book = new BookLength;\r\n        $book->name = '123';\r\n        try {\r\n            $book->save();\r\n        } catch (ActiveRecord\\ValidationsArgumentError $e) {\r\n            $this->assert_equals('in must be an array composing a range of numbers with key [0] being less than key [1]', $e->getMessage());\r\n            return;\r\n        }\r\n\r\n        $this->fail('An expected exception has not be raised.');\r\n    }\r\n\r\n\tpublic function test_signed_integer_as_impossible_is_option()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['is'] = -8;\r\n\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = '123';\r\n\t\ttry {\r\n\t\t\t$book->save();\r\n\t\t} catch (ActiveRecord\\ValidationsArgumentError $e) {\r\n\t\t\t$this->assert_equals('is value cannot use a signed integer.', $e->getMessage());\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t$this->fail('An expected exception has not be raised.');\r\n\t}\r\n\r\n\tpublic function test_lack_of_option()\r\n\t{\r\n\t\ttry {\r\n\t\t\t$book = new BookLength;\r\n\t\t\t$book->name = null;\r\n\t\t\t$book->save();\r\n\t\t} catch (ActiveRecord\\ValidationsArgumentError $e) {\r\n\t\t\t$this->assert_equals('Range unspecified.  Specify the [within], [maximum], or [is] option.', $e->getMessage());\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t$this->fail('An expected exception has not be raised.');\r\n\t}\r\n\r\n\tpublic function test_too_many_options()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['within'] = array(1, 3);\r\n\t\tBookLength::$validates_length_of[0]['in'] = array(1, 3);\r\n\r\n\t\ttry {\r\n\t\t\t$book = new BookLength;\r\n\t\t\t$book->name = null;\r\n\t\t\t$book->save();\r\n\t\t} catch (ActiveRecord\\ValidationsArgumentError $e) {\r\n\t\t\t$this->assert_equals('Too many range options specified.  Choose only one.', $e->getMessage());\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t$this->fail('An expected exception has not be raised.');\r\n\t}\r\n\r\n\tpublic function test_too_many_options_with_different_option_types()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['within'] = array(1, 3);\r\n\t\tBookLength::$validates_length_of[0]['is'] = 3;\r\n\r\n\t\ttry {\r\n\t\t\t$book = new BookLength;\r\n\t\t\t$book->name = null;\r\n\t\t\t$book->save();\r\n\t\t} catch (ActiveRecord\\ValidationsArgumentError $e) {\r\n\t\t\t$this->assert_equals('Too many range options specified.  Choose only one.', $e->getMessage());\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t$this->fail('An expected exception has not be raised.');\r\n\t}\r\n\r\n\t/**\r\n\t * @expectedException ActiveRecord\\ValidationsArgumentError\r\n\t */\r\n\tpublic function test_with_option_as_non_numeric()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['with'] = array('test');\r\n\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t}\r\n\r\n\t/**\r\n\t * @expectedException ActiveRecord\\ValidationsArgumentError\r\n\t */\r\n\tpublic function test_with_option_as_non_numeric_non_array()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0]['with'] = 'test';\r\n\r\n\t\t$book = new BookLength;\r\n\t\t$book->name = null;\r\n\t\t$book->save();\r\n\t}\r\n\r\n\tpublic function test_validates_length_of_maximum()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0] = array('name', 'maximum' => 10);\r\n\t\t$book = new BookLength(array('name' => '12345678901'));\r\n\t\t$book->is_valid();\r\n\t\t$this->assert_equals(array(\"Name is too long (maximum is 10 characters)\"),$book->errors->full_messages());\r\n\t}\r\n\r\n\tpublic function test_validates_length_of_minimum()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0] = array('name', 'minimum' => 2);\r\n\t\t$book = new BookLength(array('name' => '1'));\r\n\t\t$book->is_valid();\r\n\t\t$this->assert_equals(array(\"Name is too short (minimum is 2 characters)\"),$book->errors->full_messages());\r\n\t}\r\n\t\r\n\tpublic function test_validates_length_of_min_max_custom_message()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0] = array('name', 'maximum' => 10, 'message' => 'is far too long');\r\n\t\t$book = new BookLength(array('name' => '12345678901'));\r\n\t\t$book->is_valid();\r\n\t\t$this->assert_equals(array(\"Name is far too long\"),$book->errors->full_messages());\r\n\r\n\t\tBookLength::$validates_length_of[0] = array('name', 'minimum' => 10, 'message' => 'is far too short');\r\n\t\t$book = new BookLength(array('name' => '123456789'));\r\n\t\t$book->is_valid();\r\n\t\t$this->assert_equals(array(\"Name is far too short\"),$book->errors->full_messages());\r\n\t}\r\n\t\r\n\tpublic function test_validates_length_of_min_max_custom_message_overridden()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0] = array('name', 'minimum' => 10, 'too_short' => 'is too short', 'message' => 'is custom message');\r\n\t\t$book = new BookLength(array('name' => '123456789'));\r\n\t\t$book->is_valid();\r\n\t\t$this->assert_equals(array(\"Name is custom message\"),$book->errors->full_messages());\r\n\t}\r\n\r\n\tpublic function test_validates_length_of_is()\r\n\t{\r\n\t\tBookLength::$validates_length_of[0] = array('name', 'is' => 2);\r\n\t\t$book = new BookLength(array('name' => '123'));\r\n\t\t$book->is_valid();\r\n\t\t$this->assert_equals(array(\"Name is the wrong length (should be 2 characters)\"),$book->errors->full_messages());\r\n\t}\r\n};\r\n?>"
  },
  {
    "path": "test/ValidatesNumericalityOfTest.php",
    "content": "<?php\n\nclass BookNumericality extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'books';\n\n\tstatic $validates_numericality_of = array(\n\t\tarray('name')\n\t);\n}\n\nclass ValidatesNumericalityOfTest extends DatabaseTest\n{\n\tstatic $NULL = array(null);\n\tstatic $BLANK = array(\"\", \" \", \" \\t \\r \\n\");\n\tstatic $FLOAT_STRINGS = array('0.0','+0.0','-0.0','10.0','10.5','-10.5','-0.0001','-090.1');\n\tstatic $INTEGER_STRINGS = array('0', '+0', '-0', '10', '+10', '-10', '0090', '-090');\n\tstatic $FLOATS = array(0.0, 10.0, 10.5, -10.5, -0.0001);\n\tstatic $INTEGERS = array(0, 10, -10);\n\tstatic $JUNK = array(\"not a number\", \"42 not a number\", \"00-1\", \"--3\", \"+-3\", \"+3-1\", \"-+019.0\", \"12.12.13.12\", \"123\\nnot a number\");\n\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up($connection_name);\n\t\tBookNumericality::$validates_numericality_of = array(\n\t\t\tarray('numeric_test')\n\t\t);\n\t}\n\n\tprivate function assert_validity($value, $boolean, $msg=null)\n\t{\n\t\t$book = new BookNumericality;\n\t\t$book->numeric_test = $value;\n\n\t\tif ($boolean == 'valid')\n\t\t{\n\t\t\t$this->assert_true($book->save());\n\t\t\t$this->assert_false($book->errors->is_invalid('numeric_test'));\n\t\t}\n\t\telse\n\t\t{\n\t\t\t$this->assert_false($book->save());\n\t\t\t$this->assert_true($book->errors->is_invalid('numeric_test'));\n\n\t\t\tif (!is_null($msg))\n\t\t\t\t$this->assert_same($msg, $book->errors->on('numeric_test'));\n\t\t}\n\t}\n\n\tprivate function assert_invalid($values, $msg=null)\n\t{\n\t\tforeach ($values as $value)\n\t\t\t$this->assert_validity($value, 'invalid', $msg);\n\t}\n\n\tprivate function assert_valid($values, $msg=null)\n\t{\n\t\tforeach ($values as $value)\n\t\t\t$this->assert_validity($value, 'valid', $msg);\n\t}\n\n\tpublic function test_numericality()\n\t{\n\t\t//$this->assert_invalid(array(\"0xdeadbeef\"));\n\n\t\t$this->assert_valid(array_merge(self::$FLOATS, self::$INTEGERS));\n\t\t$this->assert_invalid(array_merge(self::$NULL, self::$BLANK, self::$JUNK));\n\t}\n\n\tpublic function test_not_anumber()\n\t{\n\t\t$this->assert_invalid(array('blah'), 'is not a number');\n\t}\n\n\tpublic function test_invalid_null()\n\t{\n\t\t$this->assert_invalid(array(null));\n\t}\n\n\tpublic function test_invalid_blank()\n\t{\n\t\t$this->assert_invalid(array(' ', '  '), 'is not a number');\n\t}\n\n\tpublic function test_invalid_whitespace()\n\t{\n\t\t$this->assert_invalid(array(''));\n\t}\n\n\tpublic function test_valid_null()\n\t{\n\t\tBookNumericality::$validates_numericality_of[0]['allow_null'] = true;\n\t\t$this->assert_valid(array(null));\n\t}\n\n\tpublic function test_only_integer()\n\t{\n\t\tBookNumericality::$validates_numericality_of[0]['only_integer'] = true;\n\n\t\t$this->assert_valid(array(1, '1'));\n\t\t$this->assert_invalid(array(1.5, '1.5'));\n\t}\n\n\tpublic function test_only_integer_matching_does_not_ignore_other_options()\n\t{\n\t\tBookNumericality::$validates_numericality_of[0]['only_integer'] = true;\n\t\tBookNumericality::$validates_numericality_of[0]['greater_than'] = 0;\n\n\t\t$this->assert_invalid(array(-1,'-1'));\n\t}\n\n\tpublic function test_greater_than()\n\t{\n\t\tBookNumericality::$validates_numericality_of[0]['greater_than'] = 5;\n\n\t\t$this->assert_valid(array(6, '7'));\n\t\t$this->assert_invalid(array(5, '5'), 'must be greater than 5');\n\t}\n\n\tpublic function test_greater_than_or_equal_to()\n\t{\n\t\tBookNumericality::$validates_numericality_of[0]['greater_than_or_equal_to'] = 5;\n\n\t\t$this->assert_valid(array(5, 5.1, '5.1'));\n\t\t$this->assert_invalid(array(-50, 4.9, '4.9','-5.1'));\n\t}\n\n\tpublic function test_less_than()\n\t{\n\t\tBookNumericality::$validates_numericality_of[0]['less_than'] = 5;\n\n\t\t$this->assert_valid(array(4.9, -1, 0, '-5'));\n\t\t$this->assert_invalid(array(5, '5'), 'must be less than 5');\n\t}\n\n\tpublic function test_less_than_or_equal_to()\n\t{\n\t\tBookNumericality::$validates_numericality_of[0]['less_than_or_equal_to'] = 5;\n\n\t\t$this->assert_valid(array(5, -1, 0, 4.9, '-5'));\n\t\t$this->assert_invalid(array('8', 5.1), 'must be less than or equal to 5');\n\t}\n\n\tpublic function test_greater_than_less_than_and_even()\n\t{\n\t\tBookNumericality::$validates_numericality_of[0] = array('numeric_test', 'greater_than' => 1, 'less_than' => 4, 'even' => true);\n\n\t\t$this->assert_valid(array(2));\n\t\t$this->assert_invalid(array(1,3,4));\n\t}\n\n\tpublic function test_custom_message()\n\t{\n\t\tBookNumericality::$validates_numericality_of = array(\n\t\t\tarray('numeric_test', 'message' => 'Hello')\n\t\t);\n\t\t$book = new BookNumericality(array('numeric_test' => 'NaN'));\n\t\t$book->is_valid();\n\t\t$this->assert_equals(array('Numeric test Hello'),$book->errors->full_messages());\n\t}\n};\n\narray_merge(ValidatesNumericalityOfTest::$INTEGERS, ValidatesNumericalityOfTest::$INTEGER_STRINGS);\narray_merge(ValidatesNumericalityOfTest::$FLOATS, ValidatesNumericalityOfTest::$FLOAT_STRINGS);\n?>\n"
  },
  {
    "path": "test/ValidatesPresenceOfTest.php",
    "content": "<?php\r\n\r\nclass BookPresence extends ActiveRecord\\Model\r\n{\r\n\tstatic $table_name = 'books';\r\n\r\n\tstatic $validates_presence_of = array(\r\n\t\tarray('name')\r\n\t);\r\n}\r\n\r\nclass AuthorPresence extends ActiveRecord\\Model\r\n{\r\n\tstatic $table_name = 'authors';\r\n\r\n\tstatic $validates_presence_of = array(\r\n\t\tarray('some_date')\r\n\t);\r\n};\r\n\r\nclass ValidatesPresenceOfTest extends DatabaseTest\r\n{\r\n\tpublic function test_presence()\r\n\t{\r\n\t\t$book = new BookPresence(array('name' => 'blah'));\r\n\t\t$this->assert_false($book->is_invalid());\r\n\t}\r\n\r\n\tpublic function test_presence_on_date_field_is_valid()\r\n\t{\r\n\t\t$author = new AuthorPresence(array('some_date' => '2010-01-01'));\r\n\t\t$this->assert_true($author->is_valid());\r\n\t}\r\n\r\n\tpublic function test_presence_on_date_field_is_not_valid()\r\n\t{\r\n\t\t$author = new AuthorPresence();\r\n\t\t$this->assert_false($author->is_valid());\r\n\t}\r\n\t\r\n\tpublic function test_invalid_null()\r\n\t{\r\n\t\t$book = new BookPresence(array('name' => null));\r\n\t\t$this->assert_true($book->is_invalid());\r\n\t}\r\n\r\n\tpublic function test_invalid_blank()\r\n\t{\r\n\t\t$book = new BookPresence(array('name' => ''));\r\n\t\t$this->assert_true($book->is_invalid());\r\n\t}\r\n\r\n\tpublic function test_valid_white_space()\r\n\t{\r\n\t\t$book = new BookPresence(array('name' => ' '));\r\n\t\t$this->assert_false($book->is_invalid());\r\n\t}\r\n\r\n\tpublic function test_custom_message()\r\n\t{\r\n\t\tBookPresence::$validates_presence_of[0]['message'] = 'is using a custom message.';\r\n\r\n\t\t$book = new BookPresence(array('name' => null));\r\n\t\t$book->is_valid();\r\n\t\t$this->assert_equals('is using a custom message.', $book->errors->on('name'));\r\n\t}\r\n\r\n\tpublic function test_valid_zero()\r\n\t{\r\n\t\t$book = new BookPresence(array('name' => 0));\r\n\t\t$this->assert_true($book->is_valid());\r\n\t}\r\n};\r\n?>"
  },
  {
    "path": "test/ValidationsTest.php",
    "content": "<?php\n\nuse ActiveRecord as AR;\n\nclass BookValidations extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'books';\n\tstatic $alias_attribute = array('name_alias' => 'name', 'x' => 'secondary_author_id');\n\tstatic $validates_presence_of = array();\n\tstatic $validates_uniqueness_of = array();\n\tstatic $custom_validator_error_msg = 'failed custom validation';\n\n\t// fired for every validation - but only used for custom validation test\n\tpublic function validate()\n\t{\n\t\tif ($this->name == 'test_custom_validation')\n\t\t\t$this->errors->add('name', self::$custom_validator_error_msg);\n\t}\n}\n\nclass ValuestoreValidations extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'valuestore';\n\tstatic $validates_uniqueness_of = array();\n}\n\nclass ValidationsTest extends DatabaseTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up($connection_name);\n\n\t\tBookValidations::$validates_presence_of[0] = 'name';\n\t\tBookValidations::$validates_uniqueness_of[0] = 'name';\n\t\t\n\t\tValuestoreValidations::$validates_uniqueness_of[0] = 'key';\n\t}\n\n\tpublic function test_is_valid_invokes_validations()\n\t{\n\t\t$book = new Book;\n\t\t$this->assert_true(empty($book->errors));\n\t\t$book->is_valid();\n\t\t$this->assert_false(empty($book->errors));\n\t}\n\n\tpublic function test_is_valid_returns_true_if_no_validations_exist()\n\t{\n\t\t$book = new Book;\n\t\t$this->assert_true($book->is_valid());\n\t}\n\n\tpublic function test_is_valid_returns_false_if_failed_validations()\n\t{\n\t\t$book = new BookValidations;\n\t\t$this->assert_false($book->is_valid());\n\t}\n\n\tpublic function test_is_invalid()\n\t{\n\t\t$book = new Book();\n\t\t$this->assert_false($book->is_invalid());\n\t}\n\n\tpublic function test_is_invalid_is_true()\n\t{\n\t\t$book = new BookValidations();\n\t\t$this->assert_true($book->is_invalid());\n\t}\n\n\tpublic function test_is_iterable()\n\t{\n\t\t$book = new BookValidations();\n\t\t$book->is_valid();\n\n\t\tforeach ($book->errors as $name => $message)\n\t\t\t$this->assert_equals(\"Name can't be blank\",$message);\n\t}\n\n\tpublic function test_full_messages()\n\t{\n\t\t$book = new BookValidations();\n\t\t$book->is_valid();\n\n\t\t$this->assert_equals(array(\"Name can't be blank\"),array_values($book->errors->full_messages(array('hash' => true))));\n\t}\n\n\tpublic function test_to_array()\n\t{\n\t\t$book = new BookValidations();\n\t\t$book->is_valid();\n\n\t\t$this->assert_equals(array(\"name\" => array(\"Name can't be blank\")), $book->errors->to_array());\n\t}\n\t\n\tpublic function test_toString()\n\t{\n\t\t$book = new BookValidations();\n\t\t$book->is_valid();\n\t\t$book->errors->add('secondary_author_id', \"is invalid\");\n\t\t\n\t\t$this->assert_equals(\"Name can't be blank\\nSecondary author id is invalid\", (string) $book->errors);\n\t}\n\n\tpublic function test_validates_uniqueness_of()\n\t{\n\t\tBookValidations::create(array('name' => 'bob'));\n\t\t$book = BookValidations::create(array('name' => 'bob'));\n\n\t\t$this->assert_equals(array(\"Name must be unique\"),$book->errors->full_messages());\n\t\t$this->assert_equals(1,BookValidations::count(array('conditions' => \"name='bob'\")));\n\t}\n\n\tpublic function test_validates_uniqueness_of_excludes_self()\n\t{\n\t\t$book = BookValidations::first();\n\t\t$this->assert_equals(true,$book->is_valid());\n\t}\n\n\tpublic function test_validates_uniqueness_of_with_multiple_fields()\n\t{\n\t\tBookValidations::$validates_uniqueness_of[0] = array(array('name','special'));\n\t\t$book1 = BookValidations::first();\n\t\t$book2 = new BookValidations(array('name' => $book1->name, 'special' => $book1->special+1));\n\t\t$this->assert_true($book2->is_valid());\n\t}\n\n\tpublic function test_validates_uniqueness_of_with_multiple_fields_is_not_unique()\n\t{\n\t\tBookValidations::$validates_uniqueness_of[0] = array(array('name','special'));\n\t\t$book1 = BookValidations::first();\n\t\t$book2 = new BookValidations(array('name' => $book1->name, 'special' => $book1->special));\n\t\t$this->assert_false($book2->is_valid());\n\t\t$this->assert_equals(array('Name and special must be unique'),$book2->errors->full_messages());\n\t}\n\n\tpublic function test_validates_uniqueness_of_works_with_alias_attribute()\n\t{\n\t\tBookValidations::$validates_uniqueness_of[0] = array(array('name_alias','x'));\n\t\t$book = BookValidations::create(array('name_alias' => 'Another Book', 'x' => 2));\n\t\t$this->assert_false($book->is_valid());\n\t\t$this->assert_equals(array('Name alias and x must be unique'), $book->errors->full_messages());\n\t}\n\n\tpublic function test_validates_uniqueness_of_works_with_mysql_reserved_word_as_column_name()\n\t{\n\t\tValuestoreValidations::create(array('key' => 'GA_KEY', 'value' => 'UA-1234567-1'));\n\t\t$valuestore = ValuestoreValidations::create(array('key' => 'GA_KEY', 'value' => 'UA-1234567-2'));\n\n\t\t$this->assert_equals(array(\"Key must be unique\"),$valuestore->errors->full_messages());\n\t\t$this->assert_equals(1,ValuestoreValidations::count(array('conditions' => \"`key`='GA_KEY'\")));\n\t}\n\n\tpublic function test_get_validation_rules()\n\t{\n\t\t$validators = BookValidations::first()->get_validation_rules();\n\t\t$this->assert_true(in_array(array('validator' => 'validates_presence_of'),$validators['name']));\n\t}\n\n\tpublic function test_model_is_nulled_out_to_prevent_memory_leak()\n\t{\n\t\t$book = new BookValidations();\n\t\t$book->is_valid();\n\t\t$this->assert_true(strpos(serialize($book->errors),'model\";N;') !== false);\n\t}\n\n\tpublic function test_validations_takes_strings()\n\t{\n\t\tBookValidations::$validates_presence_of = array('numeric_test', array('special'), 'name');\n\t\t$book = new BookValidations(array('numeric_test' => 1, 'special' => 1));\n\t\t$this->assert_false($book->is_valid());\n\t}\n\n\tpublic function test_gh131_custom_validation()\n\t{\n\t\t$book = new BookValidations(array('name' => 'test_custom_validation'));\n\t\t$book->save();\n\t\t$this->assert_true($book->errors->is_invalid('name'));\n\t\t$this->assert_equals(BookValidations::$custom_validator_error_msg, $book->errors->on('name'));\n\t}\n};\n?>\n"
  },
  {
    "path": "test/fixtures/amenities.csv",
    "content": "amenity_id, type\n1, \"Test #1\"\n2, \"Test #2\"\n3, \"Test #3\""
  },
  {
    "path": "test/fixtures/authors.csv",
    "content": "author_id,parent_author_id,name,publisher_id\n1,3,\"Tito\",1\n2,2,\"George W. Bush\",1\n3,1,\"Bill Clinton\",2\n4,2,\"Uncle Bob\",3"
  },
  {
    "path": "test/fixtures/awesome_people.csv",
    "content": "id,author_id\n1,1\n2,2\n3,3"
  },
  {
    "path": "test/fixtures/books.csv",
    "content": "book_id,author_id,secondary_author_id,name,special\n1,1,2,\"Ancient Art of Main Tanking\",0\n2,2,2,\"Another Book\",0"
  },
  {
    "path": "test/fixtures/employees.csv",
    "content": "id,first_name,last_name,nick_name\n1,\"michio\",\"kaku\",\"kakz\"\n2,\"jacques\",\"fuentes\",\"jax\"\n3,\"kien\",\"la\",\"kla\""
  },
  {
    "path": "test/fixtures/events.csv",
    "content": "id,venue_id,host_id,title,description,type\n1,1,1,\"Monday Night Music Club feat. The Shivers\",\"\",\"Music\"\n2,2,2,\"Yeah Yeah Yeahs\",\"\",\"Music\"\n3,2,3,\"Love Overboard\",\"\",\"Music\"\n5,6,4,\"1320 Records Presents A \\\"Live PA Set\\\" By STS9 with\",,\"Music\"\n6,500,4,\"Kla likes to dance to YMCA\",\"\",\"Music\"\n7,9,4,\"Blah\",,\"Blah\""
  },
  {
    "path": "test/fixtures/hosts.csv",
    "content": "id,name\n1,\"David Letterman\"\n2,\"Billy Crystal\"\n3,\"Jon Stewart\"\n4,\"Funny Guy\""
  },
  {
    "path": "test/fixtures/newsletters.csv",
    "content": "id\n1\n"
  },
  {
    "path": "test/fixtures/positions.csv",
    "content": "id,employee_id,title,active\n3,1,\"physicist\",0\n2,2,\"programmer\",1\n1,3,\"programmer\",1"
  },
  {
    "path": "test/fixtures/property.csv",
    "content": "property_id\n28840\n28841"
  },
  {
    "path": "test/fixtures/property_amenities.csv",
    "content": "id, amenity_id, property_id\n257117, 1, 28840\n257118, 2, 28840\n257119, 2, 28841\n257120, 3, 28841\n"
  },
  {
    "path": "test/fixtures/publishers.csv",
    "content": "publisher_id,name\n1,\"Random House\"\n2,\"Houghton Mifflin\"\n3,\"Scholastic\"\n"
  },
  {
    "path": "test/fixtures/rm-bldg.csv",
    "content": "rm-id,rm-name,\"space out\"\n1,\"name\",\"x\""
  },
  {
    "path": "test/fixtures/user_newsletters.csv",
    "content": "id,user_id,newsletter_id\n1,1,1\n"
  },
  {
    "path": "test/fixtures/users.csv",
    "content": "id\n1\n"
  },
  {
    "path": "test/fixtures/valuestore.csv",
    "content": ""
  },
  {
    "path": "test/fixtures/venues.csv",
    "content": "id,name,city,state,address,phone\n1,\"Blender Theater at Gramercy\",\"New York\",\"NY\",\"127 East 23rd Street\",\"2127776800\"\n2,\"Warner Theatre\",\"Washington\",\"DC\",\"1299 Pennsylvania Ave NW\",\"2027834000\"\n6,\"The Note - West Chester\",\"West Chester\",\"PA\",\"142 E. Market St.\",\"0000000000\"\n7,\"The National\",\"Richmond\",\"VA\",\"708 East Broad Street\",\"1112223333\"\n8,\"Hampton Coliseum\",\"Hampton\",\"VA\",\"1000 Coliseum Dr\",\"2223334444\"\n9,\"YMCA\",\"Washington\",\"DC\",\"1234 YMCA Way\",\"2222222222\""
  },
  {
    "path": "test/helpers/AdapterTest.php",
    "content": "<?php\nuse ActiveRecord\\Column;\n\nclass AdapterTest extends DatabaseTest\n{\n\tconst InvalidDb = '__1337__invalid_db__';\n\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tif (($connection_name && !in_array($connection_name, PDO::getAvailableDrivers())) ||\n\t\t\tActiveRecord\\Config::instance()->get_connection($connection_name) == 'skip')\n\t\t\t$this->mark_test_skipped($connection_name . ' drivers are not present');\n\n\t\tparent::set_up($connection_name);\n\t}\n\n\tpublic function test_i_has_a_default_port_unless_im_sqlite()\n\t{\n\t\tif ($this->conn instanceof ActiveRecord\\SqliteAdapter)\n\t\t\treturn;\n\n\t\t$c = $this->conn;\n\t\t$this->assert_true($c::$DEFAULT_PORT > 0);\n\t}\n\n\tpublic function test_should_set_adapter_variables()\n\t{\n\t\t$this->assert_not_null($this->conn->protocol);\n\t}\n\n\tpublic function test_null_connection_string_uses_default_connection()\n\t{\n\t\t$this->assert_not_null(ActiveRecord\\Connection::instance(null));\n\t\t$this->assert_not_null(ActiveRecord\\Connection::instance(''));\n\t\t$this->assert_not_null(ActiveRecord\\Connection::instance());\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_invalid_connection_protocol()\n\t{\n\t\tActiveRecord\\Connection::instance('terribledb://user:pass@host/db');\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_no_host_connection()\n\t{\n\t\tif (!$GLOBALS['slow_tests'])\n\t\t\tthrow new ActiveRecord\\DatabaseException(\"\");\n\n\t\tActiveRecord\\Connection::instance(\"{$this->conn->protocol}://user:pass\");\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_connection_failed_invalid_host()\n\t{\n\t\tif (!$GLOBALS['slow_tests'])\n\t\t\tthrow new ActiveRecord\\DatabaseException(\"\");\n\n\t\tActiveRecord\\Connection::instance(\"{$this->conn->protocol}://user:pass/1.1.1.1/db\");\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_connection_failed()\n\t{\n\t\tActiveRecord\\Connection::instance(\"{$this->conn->protocol}://baduser:badpass@127.0.0.1/db\");\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_connect_failed()\n\t{\n\t\tActiveRecord\\Connection::instance(\"{$this->conn->protocol}://zzz:zzz@127.0.0.1/test\");\n\t}\n\n\tpublic function test_connect_with_port()\n\t{\n\t\t$config = ActiveRecord\\Config::instance();\n\t\t$name = $config->get_default_connection();\n\t\t$url = parse_url($config->get_connection($name));\n\t\t$conn = $this->conn;\n\t\t$port = $conn::$DEFAULT_PORT;\n\n\t\t$connection_string = \"{$url['scheme']}://{$url['user']}\";\n\t\tif(isset($url['pass'])){\n\t\t\t$connection_string =  \"{$connection_string}:{$url['pass']}\";\n\t\t}\n\t\t$connection_string = \"{$connection_string}@{$url['host']}:$port{$url['path']}\";\n\n\t\tif ($this->conn->protocol != 'sqlite')\n\t\t\tActiveRecord\\Connection::instance($connection_string);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_connect_to_invalid_database()\n\t{\n\t\tActiveRecord\\Connection::instance(\"{$this->conn->protocol}://test:test@127.0.0.1/\" . self::InvalidDb);\n\t}\n\n\tpublic function test_date_time_type()\n\t{\n\t\t$columns = $this->conn->columns('authors');\n\t\t$this->assert_equals('datetime',$columns['created_at']->raw_type);\n\t\t$this->assert_equals(Column::DATETIME,$columns['created_at']->type);\n\t\t$this->assert_true($columns['created_at']->length > 0);\n\t}\n\n\tpublic function test_date()\n\t{\n\t\t$columns = $this->conn->columns('authors');\n\t\t$this->assert_equals('date', $columns['some_Date']->raw_type);\n\t\t$this->assert_equals(Column::DATE, $columns['some_Date']->type);\n\t\t$this->assert_true($columns['some_Date']->length >= 7);\n\t}\n\n\tpublic function test_columns_no_inflection_on_hash_key()\n\t{\n\t\t$author_columns = $this->conn->columns('authors');\n\t\t$this->assert_true(array_key_exists('author_id',$author_columns));\n\t}\n\n\tpublic function test_columns_nullable()\n\t{\n\t\t$author_columns = $this->conn->columns('authors');\n\t\t$this->assert_false($author_columns['author_id']->nullable);\n\t\t$this->assert_true($author_columns['parent_author_id']->nullable);\n\t}\n\n\tpublic function test_columns_pk()\n\t{\n\t\t$author_columns = $this->conn->columns('authors');\n\t\t$this->assert_true($author_columns['author_id']->pk);\n\t\t$this->assert_false($author_columns['parent_author_id']->pk);\n\t}\n\n\tpublic function test_columns_sequence()\n\t{\n\t\tif ($this->conn->supports_sequences())\n\t\t{\n\t\t\t$author_columns = $this->conn->columns('authors');\n\t\t\t$this->assert_equals('authors_author_id_seq',$author_columns['author_id']->sequence);\n\t\t}\n\t}\n\n\tpublic function test_columns_default()\n\t{\n\t\t$author_columns = $this->conn->columns('authors');\n\t\t$this->assert_equals('default_name',$author_columns['name']->default);\n\t}\n\n\tpublic function test_columns_type()\n\t{\n\t\t$author_columns = $this->conn->columns('authors');\n\t\t$this->assert_equals('varchar',substr($author_columns['name']->raw_type,0,7));\n\t\t$this->assert_equals(Column::STRING,$author_columns['name']->type);\n\t\t$this->assert_equals(25,$author_columns['name']->length);\n\t}\n\n\tpublic function test_columns_text()\n\t{\n\t\t$author_columns = $this->conn->columns('authors');\n\t\t$this->assert_equals('text',$author_columns['some_text']->raw_type);\n\t\t$this->assert_equals(null,$author_columns['some_text']->length);\n\t}\n\n\tpublic function test_columns_time()\n\t{\n\t\t$author_columns = $this->conn->columns('authors');\n\t\t$this->assert_equals('time',$author_columns['some_time']->raw_type);\n\t\t$this->assert_equals(Column::TIME,$author_columns['some_time']->type);\n\t}\n\n\tpublic function test_query()\n\t{\n\t\t$sth = $this->conn->query('SELECT * FROM authors');\n\n\t\twhile (($row = $sth->fetch()))\n\t\t\t$this->assert_not_null($row);\n\n\t\t$sth = $this->conn->query('SELECT * FROM authors WHERE author_id=1');\n\t\t$row = $sth->fetch();\n\t\t$this->assert_equals('Tito',$row['name']);\n\t}\n\n\t/**\n\t * @expectedException ActiveRecord\\DatabaseException\n\t */\n\tpublic function test_invalid_query()\n\t{\n\t\t$this->conn->query('alsdkjfsdf');\n\t}\n\n\tpublic function test_fetch()\n\t{\n\t\t$sth = $this->conn->query('SELECT * FROM authors WHERE author_id IN(1,2,3)');\n\t\t$i = 0;\n\t\t$ids = array();\n\n\t\twhile (($row = $sth->fetch()))\n\t\t{\n\t\t\t++$i;\n\t\t\t$ids[] = $row['author_id'];\n\t\t}\n\n\t\t$this->assert_equals(3,$i);\n\t\t$this->assert_equals(array(1,2,3),$ids);\n\t}\n\n\tpublic function test_query_with_params()\n\t{\n\t\t$x=array('Bill Clinton','Tito');\n\t\t$sth = $this->conn->query('SELECT * FROM authors WHERE name IN(?,?) ORDER BY name DESC',$x);\n\t\t$row = $sth->fetch();\n\t\t$this->assert_equals('Tito',$row['name']);\n\n\t\t$row = $sth->fetch();\n\t\t$this->assert_equals('Bill Clinton',$row['name']);\n\n\t\t$row = $sth->fetch();\n\t\t$this->assert_equals(null,$row);\n\t}\n\n\tpublic function test_insert_id_should_return_explicitly_inserted_id()\n\t{\n\t\t$this->conn->query('INSERT INTO authors(author_id,name) VALUES(99,\\'name\\')');\n\t\t$this->assert_true($this->conn->insert_id() > 0);\n\t}\n\n\tpublic function test_insert_id()\n\t{\n\t\t$this->conn->query(\"INSERT INTO authors(name) VALUES('name')\");\n\t\t$this->assert_true($this->conn->insert_id() > 0);\n\t}\n\n\tpublic function test_insert_id_with_params()\n\t{\n\t\t$x = array('name');\n\t\t$this->conn->query('INSERT INTO authors(name) VALUES(?)',$x);\n\t\t$this->assert_true($this->conn->insert_id() > 0);\n\t}\n\n\tpublic function test_inflection()\n\t{\n\t\t$columns = $this->conn->columns('authors');\n\t\t$this->assert_equals('parent_author_id',$columns['parent_author_id']->inflected_name);\n\t}\n\n\tpublic function test_escape()\n\t{\n\t\t$s = \"Bob's\";\n\t\t$this->assert_not_equals($s,$this->conn->escape($s));\n\t}\n\n\tpublic function test_columnsx()\n\t{\n\t\t$columns = $this->conn->columns('authors');\n\t\t$names = array('author_id','parent_author_id','name','updated_at','created_at','some_Date','some_time','some_text','encrypted_password','mixedCaseField');\n\n\t\tif ($this->conn instanceof ActiveRecord\\OciAdapter)\n\t\t\t$names = array_filter(array_map('strtolower',$names),function($s) { return $s !== 'some_time'; });\n\n\t\tforeach ($names as $field)\n\t\t\t$this->assert_true(array_key_exists($field,$columns));\n\n\t\t$this->assert_equals(true,$columns['author_id']->pk);\n\t\t$this->assert_equals('int',$columns['author_id']->raw_type);\n\t\t$this->assert_equals(Column::INTEGER,$columns['author_id']->type);\n\t\t$this->assert_true($columns['author_id']->length > 1);\n\t\t$this->assert_false($columns['author_id']->nullable);\n\n\t\t$this->assert_equals(false,$columns['parent_author_id']->pk);\n\t\t$this->assert_true($columns['parent_author_id']->nullable);\n\n\t\t$this->assert_equals('varchar',substr($columns['name']->raw_type,0,7));\n\t\t$this->assert_equals(Column::STRING,$columns['name']->type);\n\t\t$this->assert_equals(25,$columns['name']->length);\n\t}\n\n\tpublic function test_columns_decimal()\n\t{\n\t\t$columns = $this->conn->columns('books');\n\t\t$this->assert_equals(Column::DECIMAL,$columns['special']->type);\n\t\t$this->assert_true($columns['special']->length >= 10);\n\t}\n\n\tprivate function limit($offset, $limit)\n\t{\n\t\t$ret = array();\n\t\t$sql = 'SELECT * FROM authors ORDER BY name ASC';\n\t\t$this->conn->query_and_fetch($this->conn->limit($sql,$offset,$limit),function($row) use (&$ret) { $ret[] = $row; });\n\t\treturn ActiveRecord\\collect($ret,'author_id');\n\t}\n\n\tpublic function test_limit()\n\t{\n\t\t$this->assert_equals(array(2,1),$this->limit(1,2));\n\t}\n\n\tpublic function test_limit_to_first_record()\n\t{\n\t\t$this->assert_equals(array(3),$this->limit(0,1));\n\t}\n\n\tpublic function test_limit_to_last_record()\n\t{\n\t\t$this->assert_equals(array(1),$this->limit(2,1));\n\t}\n\n\tpublic function test_limit_with_null_offset()\n\t{\n\t\t$this->assert_equals(array(3),$this->limit(null,1));\n\t}\n\n\tpublic function test_limit_with_nulls()\n\t{\n\t\t$this->assert_equals(array(),$this->limit(null,null));\n\t}\n\n\tpublic function test_fetch_no_results()\n\t{\n\t\t$sth = $this->conn->query('SELECT * FROM authors WHERE author_id=65534');\n\t\t$this->assert_equals(null,$sth->fetch());\n\t}\n\n\tpublic function test_tables()\n\t{\n\t\t$this->assert_true(count($this->conn->tables()) > 0);\n\t}\n\n\tpublic function test_query_column_info()\n\t{\n\t\t$this->assert_greater_than(0,count($this->conn->query_column_info(\"authors\")));\n\t}\n\n\tpublic function test_query_table_info()\n\t{\n\t\t$this->assert_greater_than(0,count($this->conn->query_for_tables()));\n\t}\n\n\tpublic function test_query_table_info_must_return_one_field()\n\t{\n\t\t$sth = $this->conn->query_for_tables();\n\t\t$this->assert_equals(1,count($sth->fetch()));\n\t}\n\n\tpublic function test_transaction_commit()\n\t{\n\t\t$original = $this->conn->query_and_fetch_one(\"select count(*) from authors\");\n\n\t\t$this->conn->transaction();\n\t\t$this->conn->query(\"insert into authors(author_id,name) values(9999,'blahhhhhhhh')\");\n\t\t$this->conn->commit();\n\n\t\t$this->assert_equals($original+1,$this->conn->query_and_fetch_one(\"select count(*) from authors\"));\n\t}\n\n\tpublic function test_transaction_rollback()\n\t{\n\t\t$original = $this->conn->query_and_fetch_one(\"select count(*) from authors\");\n\n\t\t$this->conn->transaction();\n\t\t$this->conn->query(\"insert into authors(author_id,name) values(9999,'blahhhhhhhh')\");\n\t\t$this->conn->rollback();\n\n\t\t$this->assert_equals($original,$this->conn->query_and_fetch_one(\"select count(*) from authors\"));\n\t}\n\n\tpublic function test_show_me_a_useful_pdo_exception_message()\n\t{\n\t\ttry {\n\t\t\t$this->conn->query('select * from an_invalid_column');\n\t\t\t$this->fail();\n\t\t} catch (Exception $e) {\n\t\t\t$this->assert_equals(1,preg_match('/(an_invalid_column)|(exist)/',$e->getMessage()));\n\t\t}\n\t}\n\n\tpublic function test_quote_name_does_not_over_quote()\n\t{\n\t\t$c = $this->conn;\n\t\t$q = $c::$QUOTE_CHARACTER;\n\t\t$qn = function($s) use ($c) { return $c->quote_name($s); };\n\n\t\t$this->assert_equals(\"{$q}string\", $qn(\"{$q}string\"));\n\t\t$this->assert_equals(\"string{$q}\", $qn(\"string{$q}\"));\n\t\t$this->assert_equals(\"{$q}string{$q}\", $qn(\"{$q}string{$q}\"));\n\t}\n\n\tpublic function test_datetime_to_string()\n\t{\n\t\t$datetime = '2009-01-01 01:01:01 EST';\n\t\t$this->assert_equals($datetime,$this->conn->datetime_to_string(date_create($datetime)));\n\t}\n\n\tpublic function test_date_to_string()\n\t{\n\t\t$datetime = '2009-01-01';\n\t\t$this->assert_equals($datetime,$this->conn->date_to_string(date_create($datetime)));\n\t}\n}\n?>\n"
  },
  {
    "path": "test/helpers/DatabaseLoader.php",
    "content": "<?php\nclass DatabaseLoader\n{\n\tprivate $db;\n\tstatic $instances = array();\n\n\tpublic function __construct($db)\n\t{\n\t\t$this->db = $db;\n\n\t\tif (!isset(static::$instances[$db->protocol]))\n\t\t\tstatic::$instances[$db->protocol] = 0;\n\n\t\tif (static::$instances[$db->protocol]++ == 0)\n\t\t{\n\t\t\t// drop and re-create the tables one time only\n\t\t\t$this->drop_tables();\n\t\t\t$this->exec_sql_script($db->protocol);\n\t\t}\n\t}\n\n\tpublic function reset_table_data()\n\t{\n\t\tforeach ($this->get_fixture_tables() as $table)\n\t\t{\n\t\t\tif ($this->db->protocol == 'oci' && $table == 'rm-bldg')\n\t\t\t\tcontinue;\n\n\t\t\t$this->db->query('DELETE FROM ' . $this->quote_name($table));\n\t\t\t$this->load_fixture_data($table);\n\t\t}\n\n\t\t$after_fixtures = $this->db->protocol.'-after-fixtures';\n\t\ttry {\n\t\t\t$this->exec_sql_script($after_fixtures);\n\t\t} catch (Exception $e) {\n\t\t\t// pass\n\t\t}\n\t}\n\n\tpublic function drop_tables()\n\t{\n\t\t$tables = $this->db->tables();\n\n\t\tforeach ($this->get_fixture_tables() as $table)\n\t\t{\n\t\t\tif ($this->db->protocol == 'oci')\n\t\t\t{\n\t\t\t\t$table = strtoupper($table);\n\n\t\t\t\tif ($table == 'RM-BLDG')\n\t\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (in_array($table,$tables))\n\t\t\t\t$this->db->query('DROP TABLE ' . $this->quote_name($table));\n\n\t\t\tif ($this->db->protocol == 'oci')\n\t\t\t{\n\t\t\t\ttry {\n\t\t\t\t\t$this->db->query(\"DROP SEQUENCE {$table}_seq\");\n\t\t\t\t} catch (ActiveRecord\\DatabaseException $e) {\n\t\t\t\t\t// ignore\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic function exec_sql_script($file)\n\t{\n\t\tforeach (explode(';',$this->get_sql($file)) as $sql)\n\t\t{\n\t\t\tif (trim($sql) != '')\n\t\t\t\t$this->db->query($sql);\n\t\t}\n\t}\n\n\tpublic function get_fixture_tables()\n\t{\n\t\t$tables = array();\n\n\t\tforeach (glob(__DIR__ . '/../fixtures/*.csv') as $file)\n\t\t{\n\t\t\t$info = pathinfo($file);\n\t\t\t$tables[] = $info['filename'];\n\t\t}\n\n\t\treturn $tables;\n\t}\n\n\tpublic function get_sql($file)\n\t{\n\t\t$file = __DIR__ . \"/../sql/$file.sql\";\n\n\t\tif (!file_exists($file))\n\t\t\tthrow new Exception(\"File not found: $file\");\n\n\t\treturn file_get_contents($file);\n\t}\n\n\tpublic function load_fixture_data($table)\n\t{\n\t\t$fp = fopen(__DIR__ . \"/../fixtures/$table.csv\",'r');\n\t\t$fields = fgetcsv($fp);\n\n\t\tif (!empty($fields))\n\t\t{\n\t\t\t$markers = join(',',array_fill(0,count($fields),'?'));\n\t\t\t$table = $this->quote_name($table);\n\n\t\t\tforeach ($fields as &$name)\n\t\t\t\t$name = $this->quote_name(trim($name));\n\n\t\t\t$fields = join(',',$fields);\n\n\t\t\twhile (($values = fgetcsv($fp)))\n\t\t\t\t$this->db->query(\"INSERT INTO $table($fields) VALUES($markers)\",$values);\n\t\t}\n\t\tfclose($fp);\n\t}\n\n\tpublic function quote_name($name)\n\t{\n\t\tif ($this->db->protocol == 'oci')\n\t\t\t$name = strtoupper($name);\n\n\t\treturn $this->db->quote_name($name);\n\t}\n}\n?>\n"
  },
  {
    "path": "test/helpers/DatabaseTest.php",
    "content": "<?php\nrequire_once __DIR__ . '/DatabaseLoader.php';\n\nclass DatabaseTest extends SnakeCase_PHPUnit_Framework_TestCase\n{\n\tprotected $conn;\n\tpublic static $log = false;\n\tpublic static $db;\n\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tActiveRecord\\Table::clear_cache();\n\n\t\t$config = ActiveRecord\\Config::instance();\n\t\t$this->original_default_connection = $config->get_default_connection();\n\n\t\t$this->original_date_class = $config->get_date_class();\n\n\t\tif ($connection_name)\n\t\t\t$config->set_default_connection($connection_name);\n\n\t\tif ($connection_name == 'sqlite' || $config->get_default_connection() == 'sqlite')\n\t\t{\n\t\t\t// need to create the db. the adapter specifically does not create it for us.\n\t\t\tstatic::$db = substr(ActiveRecord\\Config::instance()->get_connection('sqlite'),9);\n\t\t\tnew SQLite3(static::$db);\n\t\t}\n\n\t\t$this->connection_name = $connection_name;\n\t\ttry {\n\t\t\t$this->conn = ActiveRecord\\ConnectionManager::get_connection($connection_name);\n\t\t} catch (ActiveRecord\\DatabaseException $e) {\n\t\t\t$this->mark_test_skipped($connection_name . ' failed to connect. '.$e->getMessage());\n\t\t}\n\n\t\t$GLOBALS['ACTIVERECORD_LOG'] = false;\n\n\t\t$loader = new DatabaseLoader($this->conn);\n\t\t$loader->reset_table_data();\n\n\t\tif (self::$log)\n\t\t\t$GLOBALS['ACTIVERECORD_LOG'] = true;\n\t}\n\n\tpublic function tear_down()\n\t{\n\t\tActiveRecord\\Config::instance()->set_date_class($this->original_date_class);\n\t\tif ($this->original_default_connection)\n\t\t\tActiveRecord\\Config::instance()->set_default_connection($this->original_default_connection);\n\t}\n\n\tpublic function assert_exception_message_contains($contains, $closure)\n\t{\n\t\t$message = \"\";\n\n\t\ttry {\n\t\t\t$closure();\n\t\t} catch (ActiveRecord\\UndefinedPropertyException $e) {\n\t\t\t$message = $e->getMessage();\n\t\t}\n\n\t\t$this->assertContains($contains, $message);\n\t}\n\n\t/**\n\t * Returns true if $regex matches $actual.\n\t *\n\t * Takes database specific quotes into account by removing them. So, this won't\n\t * work if you have actual quotes in your strings.\n\t */\n\tpublic function assert_sql_has($needle, $haystack)\n\t{\n\t\t$needle = str_replace(array('\"','`'),'',$needle);\n\t\t$haystack = str_replace(array('\"','`'),'',$haystack);\n\t\treturn $this->assertContains($needle, $haystack);\n\t}\n\n\tpublic function assert_sql_doesnt_has($needle, $haystack)\n\t{\n\t\t$needle = str_replace(array('\"','`'),'',$needle);\n\t\t$haystack = str_replace(array('\"','`'),'',$haystack);\n\t\treturn $this->assertNotContains($needle, $haystack);\n\t}\n}\n?>\n"
  },
  {
    "path": "test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php",
    "content": "<?php\nclass SnakeCase_PHPUnit_Framework_TestCase extends PHPUnit_Framework_TestCase\n{\n\tpublic function __call($meth, $args)\n\t{\n\t\t$camel_cased_method = ActiveRecord\\Inflector::instance()->camelize($meth);\n\n\t\tif (method_exists($this, $camel_cased_method))\n\t\t\treturn call_user_func_array(array($this, $camel_cased_method), $args);\n\n\t\t$class_name = get_called_class();\n\t\t$trace = debug_backtrace();\n\t\tdie(\"PHP Fatal Error:  Call to undefined method $class_name::$meth() in {$trace[1]['file']} on line {$trace[1]['line']}\" . PHP_EOL);\n\t}\n\n\tpublic function setUp()\n\t{\n\t\tif (method_exists($this,'set_up'))\n\t\t\tcall_user_func_array(array($this,'set_up'),func_get_args());\n\t}\n\n\tpublic function tearDown()\n\t{\n\t\tif (method_exists($this,'tear_down'))\n\t\t\tcall_user_func_array(array($this,'tear_down'),func_get_args());\n\t}\n\n\tprivate function setup_assert_keys($args)\n\t{\n\t\t$last = count($args)-1;\n\t\t$keys = array_slice($args,0,$last);\n\t\t$array = $args[$last];\n\t\treturn array($keys,$array);\n\t}\n\n\tpublic function assert_has_keys(/* $keys..., $array */)\n\t{\n\t\tlist($keys,$array) = $this->setup_assert_keys(func_get_args());\n\n\t\t$this->assert_not_null($array,'Array was null');\n\n\t\tforeach ($keys as $name)\n\t\t\t$this->assert_array_has_key($name,$array);\n\t}\n\n\tpublic function assert_doesnt_has_keys(/* $keys..., $array */)\n\t{\n\t\tlist($keys,$array) = $this->setup_assert_keys(func_get_args());\n\n\t\tforeach ($keys as $name)\n\t\t\t$this->assert_array_not_has_key($name,$array);\n\t}\n\n\tpublic function assert_is_a($expected_class, $object)\n\t{\n\t\t$this->assert_equals($expected_class,get_class($object));\n\t}\n\n\tpublic function assert_datetime_equals($expected, $actual)\n\t{\n\t\t$this->assert_equals($expected->format(DateTime::ISO8601),$actual->format(DateTime::ISO8601));\n\t}\n}\n?>"
  },
  {
    "path": "test/helpers/config.php",
    "content": "<?php\n\n/**\n * In order to run these unit tests, you need to install the required packages using Composer:\n *\n *    $ composer install\n *\n * After that you can run the tests by invoking the local PHPUnit\n *\n * To run all test simply use:\n *\n *    $ vendor/bin/phpunit\n *\n * Or run a single test file by specifying its path:\n *\n *    $ vendor/bin/phpunit test/InflectorTest.php\n *\n **/\n\nrequire_once 'vendor/autoload.php';\n\nrequire_once 'SnakeCase_PHPUnit_Framework_TestCase.php';\n\nrequire_once 'DatabaseTest.php';\nrequire_once 'AdapterTest.php';\n\nrequire_once __DIR__ . '/../../ActiveRecord.php';\n\n// whether or not to run the slow non-crucial tests\n$GLOBALS['slow_tests'] = false;\n\n// whether or not to show warnings when Log or Memcache is missing\n$GLOBALS['show_warnings'] = true;\n\n\nif (getenv('LOG') !== 'false')\n\tDatabaseTest::$log = true;\n\nActiveRecord\\Config::initialize(function($cfg)\n{\n\t$cfg->set_model_directory(realpath(__DIR__ . '/../models'));\n\t$cfg->set_connections(array(\n\t\t'mysql'  => getenv('PHPAR_MYSQL')  ?: 'mysql://test:test@127.0.0.1/test',\n\t\t'pgsql'  => getenv('PHPAR_PGSQL')  ?: 'pgsql://test:test@127.0.0.1/test',\n\t\t'oci'    => getenv('PHPAR_OCI')    ?: 'oci://test:test@127.0.0.1/dev',\n\t\t'sqlite' => getenv('PHPAR_SQLITE') ?: 'sqlite://test.db'));\n\n\t$cfg->set_default_connection('mysql');\n\n\tfor ($i=0; $i<count($GLOBALS['argv']); ++$i)\n\t{\n\t\tif ($GLOBALS['argv'][$i] == '--adapter')\n\t\t\t$cfg->set_default_connection($GLOBALS['argv'][$i+1]);\n\t\telseif ($GLOBALS['argv'][$i] == '--slow-tests')\n\t\t\t$GLOBALS['slow_tests'] = true;\n\t}\n\n\tif (class_exists('Log_file')) // PEAR Log installed\n\t{\n\t\t$logger = new Log_file(dirname(__FILE__) . '/../log/query.log','ident',array('mode' => 0664, 'timeFormat' =>  '%Y-%m-%d %H:%M:%S'));\n\t\n\t\t$cfg->set_logging(true);\n\t\t$cfg->set_logger($logger);\n\t}\n\telse\n\t{\n\t\tif ($GLOBALS['show_warnings'] && !isset($GLOBALS['show_warnings_done']))\n\t\t\techo \"(Logging SQL queries disabled, PEAR::Log not found.)\\n\";\n\n\t\tDatabaseTest::$log = false;\n\t}\n\t\n\tif ($GLOBALS['show_warnings']  && !isset($GLOBALS['show_warnings_done']))\n\t{\n\t\tif (!extension_loaded('memcache'))\n\t\t\techo \"(Cache Tests will be skipped, Memcache not found.)\\n\";\n\t}\n\n\tdate_default_timezone_set('UTC');\n\n\t$GLOBALS['show_warnings_done'] = true;\n});\n\nerror_reporting(E_ALL | E_STRICT);\n?>"
  },
  {
    "path": "test/helpers/foo.php",
    "content": "<?php\n\nnamespace foo\\bar\\biz;\n\nclass User extends \\ActiveRecord\\Model {\n\tstatic $has_many = array(\n\t\tarray('user_newsletters'),\n\t\tarray('newsletters', 'through' => 'user_newsletters')\n\t);\n\n}\n\nclass Newsletter extends \\ActiveRecord\\Model {\n\tstatic $has_many = array(\n\t\tarray('user_newsletters'),\n\t\tarray('users', 'through' => 'user_newsletters'),\n\t);\n}\n\nclass UserNewsletter extends \\ActiveRecord\\Model {\n\tstatic $belong_to = array(\n\t\tarray('user'),\n\t\tarray('newsletter'),\n\t);\n}\n\n# vim: ts=4 noet nobinary\n?>\n"
  },
  {
    "path": "test/models/Amenity.php",
    "content": "<?php\nclass Amenity extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'amenities';\n\tstatic $primary_key = 'amenity_id';\n\n\tstatic $has_many = array(\n\t\t'property_amenities'\n\t);\n};\n?>\n"
  },
  {
    "path": "test/models/Author.php",
    "content": "<?php\nclass Author extends ActiveRecord\\Model\n{\n\tstatic $pk = 'author_id';\n//\tstatic $has_one = array(array('awesome_person', 'foreign_key' => 'author_id', 'primary_key' => 'author_id'),\n//\tarray('parent_author', 'class_name' => 'Author', 'foreign_key' => 'parent_author_id'));\n\tstatic $has_many = array('books');\n\tstatic $has_one = array(\n\t\tarray('awesome_person', 'foreign_key' => 'author_id', 'primary_key' => 'author_id'),\n\t\tarray('parent_author', 'class_name' => 'Author', 'foreign_key' => 'parent_author_id'));\n\tstatic $belongs_to = array();\n\n\tpublic function set_password($plaintext)\n\t{\n\t\t$this->encrypted_password = md5($plaintext);\n\t}\n\n\tpublic function set_name($value)\n\t{\n\t\t$value = strtoupper($value);\n\t\t$this->assign_attribute('name',$value);\n\t}\n\n\tpublic function return_something()\n\t{\n\t\treturn array(\"sharks\" => \"lasers\");\n\t}\n};\n?>\n"
  },
  {
    "path": "test/models/AuthorAttrAccessible.php",
    "content": "<?php\nclass AuthorAttrAccessible extends ActiveRecord\\Model\n{\n\tstatic $pk = 'author_id';\n\tstatic $table_name = 'authors';\n\tstatic $has_many = array(\n\t\tarray('books', 'class_name' => 'BookAttrProtected', 'foreign_key' => 'author_id', 'primary_key' => 'book_id')\n\t);\n\tstatic $has_one = array(\n\t\tarray('parent_author', 'class_name' => 'AuthorAttrAccessible', 'foreign_key' => 'parent_author_id', 'primary_key' => 'author_id')\n\t);\n\tstatic $belongs_to = array();\n\n\t// No attributes should be accessible\n\tstatic $attr_accessible = array(null);\n};\n?>\n"
  },
  {
    "path": "test/models/AwesomePerson.php",
    "content": "<?php\nclass AwesomePerson extends ActiveRecord\\Model\n{\n\tstatic $belongs_to = array('author');\n}\n?>\n"
  },
  {
    "path": "test/models/Book.php",
    "content": "<?php\nclass Book extends ActiveRecord\\Model\n{\n\tstatic $belongs_to = array('author');\n\tstatic $has_one = array();\n\tstatic $use_custom_get_name_getter = false;\n\n\tpublic function upper_name()\n\t{\n\t\treturn strtoupper($this->name);\n\t}\n\n\tpublic function name()\n\t{\n\t\treturn strtolower($this->name);\n\t}\n\n\tpublic function get_name()\n\t{\n\t\tif (self::$use_custom_get_name_getter)\n\t\t\treturn strtoupper($this->read_attribute('name'));\n\t\telse\n\t\t\treturn $this->read_attribute('name');\n\t}\n\n\tpublic function get_upper_name()\n\t{\n\t\treturn strtoupper($this->name);\n\t}\n\n\tpublic function get_lower_name()\n\t{\n\t\treturn strtolower($this->name);\n\t}\n};\n?>\n"
  },
  {
    "path": "test/models/BookAttrAccessible.php",
    "content": "<?php\nclass BookAttrAccessible extends ActiveRecord\\Model\n{\n\tstatic $pk = 'book_id';\n\tstatic $table_name = 'books';\n\n\tstatic $attr_accessible = array('author_id');\n\tstatic $attr_protected = array('book_id');\n};\n?>"
  },
  {
    "path": "test/models/BookAttrProtected.php",
    "content": "<?php\nclass BookAttrProtected extends ActiveRecord\\Model\n{\n\tstatic $pk = 'book_id';\n\tstatic $table_name = 'books';\n\tstatic $belongs_to = array(\n\t\tarray('author', 'class_name' => 'AuthorAttrAccessible', 'primary_key' => 'author_id')\n\t);\n\n\t// No attributes should be accessible\n\tstatic $attr_accessible = array(null);\n};\n?>\n"
  },
  {
    "path": "test/models/Employee.php",
    "content": "<?php\nclass Employee extends ActiveRecord\\Model\n{\n\tstatic $has_one;\n};\n?>"
  },
  {
    "path": "test/models/Event.php",
    "content": "<?php\nclass Event extends ActiveRecord\\Model\n{\n\tstatic $belongs_to = array(\n\t\t'host',\n\t\t'venue'\n\t);\n\n\tstatic $delegate = array(\n\t\tarray('state', 'address', 'to' => 'venue'),\n\t\tarray('name', 'to' => 'host', 'prefix' => 'woot')\n\t);\n};\n?>\n"
  },
  {
    "path": "test/models/Host.php",
    "content": "<?php\nclass Host extends ActiveRecord\\Model\n{\n\tstatic $has_many = array(\n\t\t'events',\n\t\tarray('venues', 'through' => 'events')\n\t);\n}\n?>\n"
  },
  {
    "path": "test/models/JoinAuthor.php",
    "content": "<?php\nclass JoinAuthor extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'authors';\n\tstatic $pk = 'author_id';\n};\n?>"
  },
  {
    "path": "test/models/JoinBook.php",
    "content": "<?php\nclass JoinBook extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'books';\n\n\tstatic $belongs_to = array();\n};\n?>"
  },
  {
    "path": "test/models/NamespaceTest/Book.php",
    "content": "<?php\nnamespace NamespaceTest;\n\nclass Book extends \\ActiveRecord\\Model\n{\n\tstatic $belongs_to = array(\n\t\tarray('parent_book', 'class_name' => '\\NamespaceTest\\Book'),\n\t\tarray('parent_book_2', 'class_name' => 'Book'),\n\t\tarray('parent_book_3', 'class_name' => '\\Book'),\n\t);\n\n\tstatic $has_many = array(\n\t\tarray('pages', 'class_name' => '\\NamespaceTest\\SubNamespaceTest\\Page'),\n\t\tarray('pages_2', 'class_name' => 'SubNamespaceTest\\Page'),\n\t);\n}\n?>\n"
  },
  {
    "path": "test/models/NamespaceTest/SubNamespaceTest/Page.php",
    "content": "<?php\nnamespace NamespaceTest\\SubNamespaceTest;\n\nclass Page extends \\ActiveRecord\\Model\n{\n\tstatic $belong_to = array(\n\t\tarray('book', 'class_name' => '\\NamespaceTest\\Book'),\n\t);\n}\n?>\n"
  },
  {
    "path": "test/models/Position.php",
    "content": "<?php\nclass Position extends ActiveRecord\\Model\n{\n\tstatic $belongs_to;\n};\n?>"
  },
  {
    "path": "test/models/Property.php",
    "content": "<?php\nclass Property extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'property';\n\tstatic $primary_key = 'property_id';\n\n\tstatic $has_many = array(\n\t\t'property_amenities',\n\t\tarray('amenities', 'through' => 'property_amenities')\n\t);\n};\n?>\n"
  },
  {
    "path": "test/models/PropertyAmenity.php",
    "content": "<?php\nclass PropertyAmenity extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'property_amenities';\n\tstatic $primary_key = 'id';\n\n\tstatic $belongs_to = array(\n\t\t'amenity',\n\t\t'property'\n\t);\n};\n?>\n"
  },
  {
    "path": "test/models/Publisher.php",
    "content": "<?php\nclass Publisher extends ActiveRecord\\Model\n{\n\tstatic $pk = 'publisher_id';\n\tstatic $cache = true;\n\tstatic $cache_expire = 2592000; // 1 month. 60 * 60 * 24 * 30\n\n\tstatic $has_many = array(\n\t\t'authors'\n\t);\n}\n"
  },
  {
    "path": "test/models/RmBldg.php",
    "content": "<?php\r\nclass RmBldg extends ActiveRecord\\Model\r\n{\r\n\tstatic $table = 'rm-bldg';\r\n\r\n\tstatic $validates_presence_of = array(\r\n\t\tarray('space_out', 'message' => 'is missing!@#'),\r\n\t\tarray('rm_name')\r\n\t);\r\n\r\n\tstatic $validates_length_of = array(\r\n\t\tarray('space_out', 'within' => array(1, 5)),\r\n\t\tarray('space_out', 'minimum' => 9, 'too_short' => 'var is too short!! it should be at least %d long')\r\n\t);\r\n\r\n\tstatic $validates_inclusion_of = array(\r\n\t\tarray('space_out', 'in' => array('jpg', 'gif', 'png'), 'message' => 'extension %s is not included in the list'),\r\n\t);\r\n\r\n\tstatic $validates_exclusion_of = array(\r\n\t\tarray('space_out', 'in' => array('jpeg'))\r\n\t);\r\n\r\n\tstatic $validates_format_of = array(\r\n\t\tarray('space_out', 'with' => '/\\A([^@\\s]+)@((?:[-a-z0-9]+\\.)+[a-z]{2,})\\Z/i' )\r\n\t);\r\n\r\n\tstatic $validates_numericality_of = array(\r\n\t\tarray('space_out', 'less_than' => 9, 'greater_than' => '5'),\r\n\t\tarray('rm_id', 'less_than' => 10, 'odd' => null)\r\n\t);\r\n}\r\n?>\r\n"
  },
  {
    "path": "test/models/Venue.php",
    "content": "<?php\nclass Venue extends ActiveRecord\\Model\n{\n\tstatic $use_custom_get_state_getter = false;\n\tstatic $use_custom_set_state_setter = false;\n\t\n\t\n\tstatic $has_many = array(\n\t\t'events',\n\t\tarray('hosts', 'through' => 'events')\n\t);\n\n\tstatic $has_one;\n\n\tstatic $alias_attribute = array(\n\t\t'marquee' => 'name',\n\t\t'mycity' => 'city'\n\t);\n\t\n\tpublic function get_state()\n\t{\n\t\tif (self::$use_custom_get_state_getter)\n\t\t\treturn strtolower($this->read_attribute('state'));\n\t\telse\n\t\t\treturn $this->read_attribute('state');\n\t}\n\t\n\tpublic function set_state($value)\n\t{\n\t\tif (self::$use_custom_set_state_setter)\n\t\t\treturn $this->assign_attribute('state', $value . '#');\n\t\telse\n\t\t\treturn $this->assign_attribute('state', $value);\n\t}\n\t\n};\n?>\n"
  },
  {
    "path": "test/models/VenueAfterCreate.php",
    "content": "<?php\nclass VenueAfterCreate extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'venues';\n\tstatic $after_create = array('change_name_after_create_if_name_is_change_me');\n\n\t\n\tpublic function change_name_after_create_if_name_is_change_me()\n\t{\n\t\tif($this->name == 'change me')\n\t\t{\n\t\t\t$this->name = 'changed!';\n\t\t\t$this->save();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/models/VenueCB.php",
    "content": "<?php\nclass VenueCB extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'venues';\n\tstatic $before_save;\n\tstatic $before_update;\n\tstatic $before_create;\n\tstatic $before_validation;\n\tstatic $before_destroy = 'before_destroy_using_string';\n\tstatic $after_destroy = array('after_destroy_one', 'after_destroy_two');\n\tstatic $after_create;\n\n\t// DO NOT add a static $after_construct for this. we are testing\n\t// auto registration of callback with this\n\tpublic function after_construct() {}\n\n\tpublic function non_generic_after_construct() {}\n\n\tpublic function after_destroy_one() {}\n\tpublic function after_destroy_two() {}\n\n\tpublic function before_destroy_using_string() {}\n\n\tpublic function before_update_halt_execution()\n\t{\n\t\treturn false;\n\t}\n\n\tpublic function before_destroy_halt_execution()\n\t{\n\t\treturn false;\n\t}\n\n\tpublic function before_create_halt_execution()\n\t{\n\t\treturn false;\n\t}\n\n\tpublic function before_validation_halt_execution()\n\t{\n\t\treturn false;\n\t}\n}\n?>"
  },
  {
    "path": "test/sql/mysql.sql",
    "content": "CREATE TABLE authors(\n\tauthor_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,\n\tparent_author_id INT,\n\tpublisher_id INT,\n\tname VARCHAR(25) NOT NULL DEFAULT 'default_name',\n\tupdated_at datetime,\n\tcreated_at datetime,\n\tsome_Date date,\n\tsome_time time,\n\tsome_text text,\n\tsome_enum enum('a','b','c'),\n\tencrypted_password varchar(50),\n\tmixedCaseField varchar(50)\n) ENGINE=InnoDB;\n\nCREATE TABLE books(\n\tbook_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,\n\tAuthor_Id INT,\n\tsecondary_author_id INT,\n\tname VARCHAR(50),\n\tnumeric_test VARCHAR(10) DEFAULT '0',\n\tspecial NUMERIC(10,2) DEFAULT 0\n);\n\nCREATE TABLE publishers(\n\tpublisher_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,\n\tname VARCHAR(25) NOT NULL DEFAULT 'default_name'\n) ENGINE=InnoDB;\n\nCREATE TABLE venues (\n\tId int NOT NULL AUTO_INCREMENT PRIMARY KEY,\n\tname varchar(50),\n\tcity varchar(60),\n\tstate char(2),\n\taddress varchar(50),\n\tphone varchar(10) default NULL,\n\tUNIQUE(name,address)\n);\n\nCREATE TABLE events (\n\tid int NOT NULL auto_increment PRIMARY KEY,\n\tvenue_id int NULL,\n\thost_id int NOT NULL,\n\ttitle varchar(60) NOT NULL,\n\tdescription varchar(50),\n\ttype varchar(15) default NULL\n);\n\nCREATE TABLE hosts(\n\tid INT NOT NULL PRIMARY KEY AUTO_INCREMENT,\n\tname VARCHAR(25)\n);\n\nCREATE TABLE employees (\n\tid INT NOT NULL AUTO_INCREMENT PRIMARY KEY,\n\tfirst_name VARCHAR(255) NOT NULL,\n\tlast_name VARCHAR(255) NOT NULL,\n\tnick_name VARCHAR(255) NOT NULL\n);\n\nCREATE TABLE positions (\n\tid int NOT NULL AUTO_INCREMENT PRIMARY KEY,\n\temployee_id int NOT NULL,\n\ttitle VARCHAR(255) NOT NULL,\n\tactive SMALLINT NOT NULL\n);\n\nCREATE TABLE `rm-bldg`(\n    `rm-id` INT NOT NULL,\n    `rm-name` VARCHAR(10) NOT NULL,\n    `space out` VARCHAR(1) NOT NULL\n);\n\nCREATE TABLE awesome_people(\n\tid int not null primary key auto_increment,\n\tauthor_id int,\n\tis_awesome int default 1\n);\n\nCREATE TABLE amenities(\n  `amenity_id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,\n  `type` varchar(40) NOT NULL DEFAULT ''\n);\n\nCREATE TABLE property(\n  `property_id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY\n);\n\nCREATE TABLE property_amenities(\n  `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,\n  `amenity_id` int(11) NOT NULL DEFAULT '0',\n  `property_id` int(11) NOT NULL DEFAULT '0'\n);\n\nCREATE TABLE users (\n    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY\n) ENGINE=InnoDB;\n\nCREATE TABLE newsletters (\n    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY\n) ENGINE=InnoDB;\n\nCREATE TABLE user_newsletters (\n    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,\n    user_id INT NOT NULL,\n    newsletter_id INT NOT NULL\n) ENGINE=InnoDB;\n\nCREATE TABLE valuestore (\n  `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,\n  `key` varchar(20) NOT NULL DEFAULT '',\n  `value` varchar(255) NOT NULL DEFAULT ''\n) ENGINE=InnoDB;"
  },
  {
    "path": "test/sql/oci-after-fixtures.sql",
    "content": "DROP SEQUENCE authors_seq;\nCREATE SEQUENCE authors_seq START WITH 100;\n\nDROP SEQUENCE books_seq;\nCREATE SEQUENCE books_seq START WITH 100;\n\nDROP SEQUENCE publishers_seq;\nCREATE SEQUENCE publishers_seq START WITH 100;\n\nDROP SEQUENCE venues_seq;\nCREATE SEQUENCE venues_seq START WITH 100;\n\nDROP SEQUENCE events_seq;\nCREATE SEQUENCE events_seq START WITH 100;\n\nDROP SEQUENCE hosts_seq;\nCREATE SEQUENCE hosts_seq START WITH 100;\n\nDROP SEQUENCE employees_seq;\nCREATE SEQUENCE employees_seq START WITH 100;\n\nDROP SEQUENCE positions_seq;\nCREATE SEQUENCE positions_seq START WITH 100;\n\nDROP SEQUENCE awesome_people_seq;\nCREATE SEQUENCE awesome_people_seq START WITH 100;\n\nDROP SEQUENCE amenities_seq;\nCREATE SEQUENCE amenities_seq START WITH 100;\n\nDROP SEQUENCE property_seq;\nCREATE SEQUENCE property_seq START WITH 100;\n\nDROP SEQUENCE property_amenities_seq;\nCREATE SEQUENCE property_amenities_seq START WITH 100;\n\nDROP SEQUENCE valuestore_seq;\nCREATE SEQUENCE valuestore_seq START WITH 100;\n"
  },
  {
    "path": "test/sql/oci.sql",
    "content": "CREATE SEQUENCE authors_seq;\nCREATE TABLE authors(\n\tauthor_id INT NOT NULL PRIMARY KEY,\n\tparent_author_id INT,\n\tpublisher_id INT,\n\tname VARCHAR(25) DEFAULT 'default_name' NOT NULL,\n\tupdated_at timestamp,\n\tcreated_at timestamp,\n\tsome_date date,\n\t--some_time time,\n\tsome_text varchar2(100),\n\tencrypted_password varchar(50),\n\t\"mixedCaseField\" varchar(50)\n);\n\nCREATE SEQUENCE books_seq;\nCREATE TABLE books(\n\tbook_id INT NOT NULL PRIMARY KEY,\n\tAuthor_Id INT,\n\tsecondary_author_id INT,\n\tname VARCHAR(50),\n\tnumeric_test VARCHAR(10) DEFAULT '0',\n\tspecial NUMERIC(10,2) DEFAULT 0);\n\nCREATE SEQUENCE publishers_seq;\nCREATE TABLE publishers(\n\tpublisher_id INT NOT NULL PRIMARY KEY,\n\tname VARCHAR(25) DEFAULT 'default_name' NOT NULL,\n);\n\nCREATE SEQUENCE venues_seq;\nCREATE TABLE venues (\n  Id INT NOT NULL PRIMARY KEY,\n  name varchar(50),\n  city varchar(60),\n  state char(2),\n  address varchar(50),\n  phone varchar(10) default NULL,\n  UNIQUE(name,address)\n);\n\nCREATE SEQUENCE events_seq;\nCREATE TABLE events (\n  id INT NOT NULL PRIMARY KEY,\n  venue_id int NULL,\n  host_id int NOT NULL,\n  title varchar(60) NOT NULL,\n  description varchar(10),\n  type varchar(15) default NULL\n);\n\nCREATE SEQUENCE hosts_seq;\nCREATE TABLE hosts(\n\tid INT NOT NULL PRIMARY KEY,\n\tname VARCHAR(25)\n);\n\nCREATE SEQUENCE employees_seq;\nCREATE TABLE employees (\n\tid INT NOT NULL PRIMARY KEY,\n\tfirst_name VARCHAR( 255 ) NOT NULL ,\n\tlast_name VARCHAR( 255 ) NOT NULL ,\n\tnick_name VARCHAR( 255 ) NOT NULL\n);\n\nCREATE SEQUENCE positions_seq;\nCREATE TABLE positions (\n  id INT NOT NULL PRIMARY KEY,\n  employee_id int NOT NULL,\n  title VARCHAR(255) NOT NULL,\n  active SMALLINT NOT NULL\n);\n\nCREATE SEQUENCE awesome_people_seq;\nCREATE TABLE awesome_people(\n\tid int not null primary key,\n\tauthor_id int,\n\tis_awesome int default 1\n);\n\nCREATE SEQUENCE amenities_seq;\nCREATE TABLE amenities(\n  amenity_id int primary key,\n  type varchar(40) NOT NULL\n);\n\nCREATE SEQUENCE property_seq;\nCREATE TABLE property(\n  property_id int primary key\n);\n\nCREATE SEQUENCE property_amenities_seq;\nCREATE TABLE property_amenities(\n  id int primary key,\n  amenity_id int not null,\n  property_id int not null\n);\n\nCREATE SEQUENCE valuestore_seq;\nCREATE TABLE valuestore(\n  id int primary key,\n `key` varchar(20) NOT NULL DEFAULT '',\n `value` varchar(255) NOT NULL DEFAULT ''\n);"
  },
  {
    "path": "test/sql/pgsql-after-fixtures.sql",
    "content": "SELECT setval('authors_author_id_seq', max(author_id)) FROM authors;\nSELECT setval('books_book_id_seq', max(book_id)) FROM books;\nSELECT setval('publishers_publisher_id_seq', max(publisher_id)) FROM publishers;\nSELECT setval('venues_id_seq', max(id)) FROM venues;\nSELECT setval('events_id_seq', max(id)) FROM events;\nSELECT setval('hosts_id_seq', max(id)) FROM hosts;\nSELECT setval('employees_id_seq', max(id)) FROM employees;\nSELECT setval('positions_id_seq', max(id)) FROM positions;\nSELECT setval('\"rm-bldg_rm-id_seq\"', max(\"rm-id\")) FROM \"rm-bldg\";\nSELECT setval('awesome_people_id_seq', max(id)) FROM awesome_people;\nSELECT setval('amenities_amenity_id_seq', max(amenity_id)) FROM amenities;\nSELECT setval('property_property_id_seq', max(property_id)) FROM property;\nSELECT setval('property_amenities_id_seq', max(id)) FROM property_amenities;\nSELECT setval('users_id_seq', max(id)) FROM users;\nSELECT setval('newsletters_id_seq', max(id)) FROM newsletters;\nSELECT setval('user_newsletters_id_seq', max(id)) FROM user_newsletters;\nSELECT setval('valuestore_id_seq', max(id)) FROM valuestore;\n"
  },
  {
    "path": "test/sql/pgsql.sql",
    "content": "CREATE TABLE authors(\n\tauthor_id SERIAL PRIMARY KEY,\n\tparent_author_id INT,\n\tpublisher_id INT,\n\tname VARCHAR(25) NOT NULL DEFAULT 'default_name',\n\tupdated_at timestamp,\n\tcreated_at timestamp,\n\t\"some_Date\" date,\n\tsome_time time,\n\tsome_text text,\n\tencrypted_password varchar(50),\n\t\"mixedCaseField\" varchar(50)\n);\n\nCREATE TABLE books(\n\tbook_id SERIAL PRIMARY KEY,\n\tauthor_id INT,\n\tsecondary_author_id INT,\n\tname VARCHAR(50),\n\tnumeric_test VARCHAR(10) DEFAULT '0',\n\tspecial NUMERIC(10,2) DEFAULT 0.0\n);\n\nCREATE TABLE publishers(\n\tpublisher_id SERIAL PRIMARY KEY,\n\tname VARCHAR(25) NOT NULL DEFAULT 'default_name'\n);\n\nCREATE TABLE venues (\n\tid SERIAL PRIMARY KEY,\n\tname varchar(50),\n\tcity varchar(60),\n\tstate char(2),\n\taddress varchar(50),\n\tphone varchar(10) default NULL,\n\tUNIQUE(name,address)\n);\n\nCREATE TABLE events (\n\tid SERIAL PRIMARY KEY,\n\tvenue_id int NULL,\n\thost_id int NOT NULL,\n\ttitle varchar(60) NOT NULL,\n\tdescription varchar(10),\n\ttype varchar(15) default NULL\n);\n\nCREATE TABLE hosts(\n\tid SERIAL PRIMARY KEY,\n\tname VARCHAR(25)\n);\n\nCREATE TABLE employees (\n\tid SERIAL PRIMARY KEY,\n\tfirst_name VARCHAR(255) NOT NULL,\n\tlast_name VARCHAR(255) NOT NULL,\n\tnick_name VARCHAR(255) NOT NULL\n);\n\nCREATE TABLE positions (\n\tid SERIAL PRIMARY KEY,\n\temployee_id int NOT NULL,\n\ttitle VARCHAR(255) NOT NULL,\n\tactive SMALLINT NOT NULL\n);\n\nCREATE TABLE \"rm-bldg\"(\n    \"rm-id\" SERIAL PRIMARY KEY,\n    \"rm-name\" VARCHAR(10) NOT NULL,\n    \"space out\" VARCHAR(1) NOT NULL\n);\n\nCREATE TABLE awesome_people(\n\tid serial primary key,\n\tauthor_id int,\n\tis_awesome int default 1\n);\n\nCREATE TABLE amenities(\n\tamenity_id serial primary key,\n\ttype varchar(40) NOT NULL\n);\n\nCREATE TABLE property(\n\tproperty_id serial primary key\n);\n\nCREATE TABLE property_amenities(\n\tid serial primary key,\n\tamenity_id int not null,\n\tproperty_id int not null\n);\n\nCREATE TABLE users(\n\tid serial primary key\n);\n\nCREATE TABLE newsletters(\n\tid serial primary key\n);\n\nCREATE TABLE user_newsletters(\n  id serial primary key,\n  user_id int not null,\n  newsletter_id int not null\n);\n\nCREATE TABLE valuestore (\n  id serial primary key,\n  key varchar(20) NOT NULL DEFAULT '',\n  value varchar(255) NOT NULL DEFAULT ''\n);\n\n-- reproduces issue GH-96 for testing\nCREATE INDEX user_newsletters_id_and_user_id_idx ON user_newsletters USING btree(id, user_id);\n"
  },
  {
    "path": "test/sql/sqlite.sql",
    "content": "CREATE TABLE authors(\n\tauthor_id INTEGER NOT NULL PRIMARY KEY,\n\tparent_author_id INT,\n\tpublisher_id INT,\n\tname VARCHAR  (25) NOT NULL DEFAULT default_name, -- don't touch those spaces\n\tupdated_at datetime,\n\tcreated_at datetime,\n\tsome_Date date,\n\tsome_time time,\n\tsome_text text,\n\tencrypted_password varchar(50),\n\tmixedCaseField varchar(50)\n);\n\nCREATE TABLE books(\n\tbook_id INTEGER NOT NULL PRIMARY KEY,\n\tAuthor_Id INT,\n\tsecondary_author_id INT,\n\tname VARCHAR(50),\n\tnumeric_test VARCHAR(10) DEFAULT '0',\n\tspecial NUMERIC(10,2) DEFAULT 0\n);\n\nCREATE TABLE publishers(\n\tpublisher_id INTEGER NOT NULL PRIMARY KEY,\n\tname VARCHAR  (25) NOT NULL DEFAULT default_name -- don't touch those spaces\n);\n\nCREATE TABLE venues (\n  Id INTEGER NOT NULL PRIMARY KEY,\n  name varchar(50),\n  city varchar(60),\n  state char(2),\n  address varchar(50),\n  phone varchar(10) default NULL,\n  UNIQUE(name,address)\n);\n\nCREATE TABLE events (\n  id INTEGER NOT NULL PRIMARY KEY,\n  venue_id int NULL,\n  host_id int NOT NULL,\n  title varchar(60) NOT NULL,\n  description varchar(10),\n  type varchar(15) default NULL\n);\n\nCREATE TABLE hosts(\n\tid INT NOT NULL PRIMARY KEY,\n\tname VARCHAR(25)\n);\n\nCREATE TABLE employees (\n\tid INTEGER NOT NULL PRIMARY KEY,\n\tfirst_name VARCHAR( 255 ) NOT NULL ,\n\tlast_name VARCHAR( 255 ) NOT NULL ,\n\tnick_name VARCHAR( 255 ) NOT NULL\n);\n\nCREATE TABLE positions (\n  id INTEGER NOT NULL PRIMARY KEY,\n  employee_id int NOT NULL,\n  title VARCHAR(255) NOT NULL,\n  active SMALLINT NOT NULL\n);\n\nCREATE TABLE `rm-bldg`(\n    `rm-id` INT NOT NULL,\n    `rm-name` VARCHAR(10) NOT NULL,\n    `space out` VARCHAR(1) NOT NULL\n);\n\nCREATE TABLE awesome_people(\n\tid integer not null primary key,\n\tauthor_id int,\n\tis_awesome int default 1\n);\n\nCREATE TABLE amenities(\n  `amenity_id` INTEGER NOT NULL PRIMARY KEY,\n  `type` varchar(40) DEFAULT NULL\n);\n\nCREATE TABLE property(\n  `property_id` INTEGER NOT NULL PRIMARY KEY\n);\n\nCREATE TABLE property_amenities(\n  `id` INTEGER NOT NULL PRIMARY KEY,\n  `amenity_id` INT NOT NULL,\n  `property_id` INT NOT NULL\n);\n\nCREATE TABLE users (\n    id INTEGER NOT NULL PRIMARY KEY\n);\n\nCREATE TABLE newsletters (\n    id INTEGER NOT NULL PRIMARY KEY\n);\n\nCREATE TABLE user_newsletters (\n    id INTEGER NOT NULL PRIMARY KEY,\n    user_id INTEGER NOT NULL,\n    newsletter_id INTEGER NOT NULL\n);\n\nCREATE TABLE valuestore (\n  `id` INTEGER NOT NULL PRIMARY KEY,\n  `key` varchar(20) NOT NULL DEFAULT '',\n  `value` varchar(255) NOT NULL DEFAULT ''\n);\n"
  }
]