diff --git a/.travis.yml b/.travis.yml index e3dfa07..5e0c259 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,3 +58,6 @@ script: notifications: email: false + +addons: + postgresql: "9.4" diff --git a/src/Model/Behavior/VersionBehavior.php b/src/Model/Behavior/VersionBehavior.php index 86d3bbe..2c2ac01 100644 --- a/src/Model/Behavior/VersionBehavior.php +++ b/src/Model/Behavior/VersionBehavior.php @@ -16,6 +16,7 @@ use ArrayObject; use Cake\Collection\Collection; +use Cake\Database\Type; use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\Event\EventManager; @@ -26,6 +27,7 @@ use Cake\Utility\Hash; use Cake\Utility\Inflector; use DateTime; +use InvalidArgumentException; /** * This behavior provides a way to version dynamic data by keeping versions @@ -183,11 +185,13 @@ public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $o continue; } + $converted = $this->_convertFieldsToType([$field => $content], 'toDatabase'); + $data = [ 'version_id' => $versionId, 'model' => $this->_config['referenceName'], 'field' => $field, - 'content' => $content, + 'content' => $converted[$field], 'created' => $created, ] + $this->_extractForeignKey($entity); @@ -309,6 +313,8 @@ public function groupVersions($results) $versionField => $versionId ]; + $keys = $this->_convertFieldsToType($keys, 'toPHP'); + /* @var \Cake\Datasource\EntityInterface $versionRow */ $versionRow = $grouped->match(['version_id' => $versionId])->first(); @@ -428,4 +434,29 @@ protected function _referenceName() return $name; } + + /** + * Converts fields to the appropriate type to be stored in the version, and + * to be converted from the version record to the entity + * + * @param array $fields Fields to convert + * @param string $direction Direction (toPHP or toDatabase) + * @return array + */ + protected function _convertFieldsToType(array $fields, $direction) + { + if (!in_array($direction, ['toPHP', 'toDatabase'])) { + throw new InvalidArgumentException(sprintf('Cannot convert type, Cake\Database\Type::%s does not exist', $direction)); + } + + $driver = $this->_table->getConnection()->getDriver(); + foreach ($fields as $field => $content) { + $column = $this->_table->getSchema()->getColumn($field); + $type = Type::build($column['type']); + + $fields[$field] = $type->{$direction}($content, $driver); + } + + return $fields; + } } diff --git a/tests/Fixture/ArticlesFixture.php b/tests/Fixture/ArticlesFixture.php index f6eb3b0..ee51f59 100644 --- a/tests/Fixture/ArticlesFixture.php +++ b/tests/Fixture/ArticlesFixture.php @@ -36,6 +36,7 @@ class ArticlesFixture extends TestFixture 'title' => ['type' => 'string', 'null' => true], 'body' => 'text', 'published' => ['type' => 'string', 'length' => 1, 'default' => 'N'], + 'settings' => ['type' => 'json', 'null' => true], '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]] ]; diff --git a/tests/TestCase/Model/Behavior/VersionBehaviorTest.php b/tests/TestCase/Model/Behavior/VersionBehaviorTest.php index bba5909..0ef0243 100644 --- a/tests/TestCase/Model/Behavior/VersionBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/VersionBehaviorTest.php @@ -8,8 +8,10 @@ use Cake\ORM\Entity; use Cake\ORM\TableRegistry; use Cake\TestSuite\TestCase; +use InvalidArgumentException; use Josegonzalez\Version\Model\Behavior\VersionBehavior; use Josegonzalez\Version\Model\Behavior\Version\VersionTrait; +use ReflectionObject; class TestEntity extends Entity { @@ -67,7 +69,7 @@ public function testSaveNew() ->toArray(); $this->assertEquals(3, $article->version_id); - $this->assertCount(12, $results); + $this->assertCount(13, $results); } /** @@ -390,4 +392,110 @@ public function testGetVersionId() $this->assertEquals(2, $article->version_id); $this->assertEquals(3, $table->getVersionId($article)); } + + /** + * tests saving a non scalar db type, such as JSON + * + * @return void + */ + public function testSaveNonScalarType() + { + $table = TableRegistry::get('Articles', [ + 'entityClass' => 'Josegonzalez\Version\Test\TestCase\Model\Behavior\TestEntity', + ]); + $schema = $table->getSchema(); + $schema->setColumnType('settings', 'json'); + $table->setSchema($schema); + $table->addBehavior('Josegonzalez/Version.Version'); + + $data = ['test' => 'array']; + $article = $table->get(1); + $article->settings = $data; + $table->saveOrFail($article); + + $version = $article->version($article->version_id); + $this->assertSame($data, $version->settings); + } + + /** + * tests versions convert types + * + * @return void + */ + public function testVersionConvertsType() + { + $table = TableRegistry::get('Articles', [ + 'entityClass' => 'Josegonzalez\Version\Test\TestCase\Model\Behavior\TestEntity', + ]); + $table->addBehavior('Josegonzalez/Version.Version'); + + $article = $table->get(1); + $version = $article->version($article->version_id); + $this->assertInternalType('int', $version->author_id); + } + + /** + * tests _convertFieldsToType + * + * @return void + */ + public function testConvertFieldsToType() + { + $table = TableRegistry::get('Articles', [ + 'entityClass' => 'Josegonzalez\Version\Test\TestCase\Model\Behavior\TestEntity', + ]); + $schema = $table->getSchema(); + $schema->setColumnType('settings', 'json'); + $table->setSchema($schema); + $behavior = new VersionBehavior($table); + + $reflection = new ReflectionObject($behavior); + $method = $reflection->getMethod('_convertFieldsToType'); + $method->setAccessible(true); + + $data = ['test' => 'array']; + $fields = [ + 'settings' => json_encode($data), + 'author_id' => '1', + 'body' => 'text', + ]; + $fields = $method->invokeArgs($behavior, [$fields, 'toPHP']); + $this->assertInternalType('array', $fields['settings']); + $this->assertSame($data, $fields['settings']); + $this->assertInternalType('int', $fields['author_id']); + $this->assertInternalType('string', $fields['body']); + + $data = ['test' => 'array']; + $fields = [ + 'settings' => ['test' => 'array'], + 'author_id' => 1, + 'body' => 'text', + ]; + $fields = $method->invokeArgs($behavior, [$fields, 'toDatabase']); + $this->assertInternalType('string', $fields['settings']); + $this->assertSame(json_encode($data), $fields['settings']); + $this->assertInternalType('int', $fields['author_id']); + $this->assertInternalType('string', $fields['body']); + } + + /** + * tests passing an invalid direction to _convertFieldsToType + * + * @return void + */ + public function testConvertFieldsToTypeInvalidDirection() + { + $this->expectException(InvalidArgumentException::class); + + $table = TableRegistry::get('Articles', [ + 'entityClass' => 'Josegonzalez\Version\Test\TestCase\Model\Behavior\TestEntity', + ]); + $behavior = new VersionBehavior($table); + + $reflection = new ReflectionObject($behavior); + $method = $reflection->getMethod('_convertFieldsToType'); + $method->setAccessible(true); + + $method->invokeArgs($behavior, [[], 'invalidDirection']); + } }