diff --git a/modules/system/database/migrations/2015_10_01_000017_Db_System_Revisions.php b/modules/system/database/migrations/2015_10_01_000017_Db_System_Revisions.php new file mode 100644 index 000000000..b98fd1219 --- /dev/null +++ b/modules/system/database/migrations/2015_10_01_000017_Db_System_Revisions.php @@ -0,0 +1,30 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->integer('user_id')->unsigned()->nullable()->index(); + $table->string('field')->nullable()->index(); + $table->string('cast')->nullable(); + $table->text('old_value')->nullable(); + $table->text('new_value')->nullable(); + $table->string('revisionable_type'); + $table->integer('revisionable_id'); + $table->timestamps(); + $table->index(['revisionable_id', 'revisionable_type']); + }); + } + + public function down() + { + Schema::dropIfExists('system_revisions'); + } +} diff --git a/modules/system/models/Revision.php b/modules/system/models/Revision.php new file mode 100644 index 000000000..ede558a72 --- /dev/null +++ b/modules/system/models/Revision.php @@ -0,0 +1,17 @@ + 'Database\Tester\Models\Author', ]; +} + +class SluggablePost extends Post +{ + + use \October\Rain\Database\Traits\Sluggable; + + /** + * @var array Guarded fields + */ + protected $guarded = []; + + /** + * @var array List of attributes to automatically generate unique URL names (slugs) for. + */ + protected $slugs = [ + 'slug' => 'title' + ]; + +} + +class RevisionablePost extends Post +{ + + use \October\Rain\Database\Traits\Revisionable; + use \October\Rain\Database\Traits\SoftDeleting; + + /** + * @var array Guarded fields + */ + protected $guarded = []; + + /** + * @var array Dates + */ + protected $dates = ['published_at', 'deleted_at']; + + /** + * @var array Monitor these attributes for changes. + */ + protected $revisionable = [ + 'title', + 'slug', + 'description', + 'is_published', + 'published_at', + 'deleted_at' + ]; + + /** + * @var int Maximum number of revision records to keep. + */ + public $revisionableLimit = 8; + + /** + * @var array Relations + */ + public $morphMany = [ + 'revision_history' => ['System\Models\Revision', 'name' => 'revisionable'] + ]; + + /** + * The user who made the revision. + */ + public function getRevisionableUser() + { + return 7; + } + } \ No newline at end of file diff --git a/tests/fixtures/plugins/database/tester/models/SluggablePost.php b/tests/fixtures/plugins/database/tester/models/SluggablePost.php deleted file mode 100644 index 86ed609d6..000000000 --- a/tests/fixtures/plugins/database/tester/models/SluggablePost.php +++ /dev/null @@ -1,30 +0,0 @@ - 'title' - ]; - -} \ No newline at end of file diff --git a/tests/fixtures/plugins/database/tester/updates/create_posts_table.php b/tests/fixtures/plugins/database/tester/updates/create_posts_table.php index 37dcd6ca7..38bc65a73 100644 --- a/tests/fixtures/plugins/database/tester/updates/create_posts_table.php +++ b/tests/fixtures/plugins/database/tester/updates/create_posts_table.php @@ -18,6 +18,7 @@ class CreatePostsTable extends Migration $table->boolean('is_published')->default(false); $table->timestamp('published_at')->nullable(); $table->integer('author_id')->unsigned()->index()->nullable(); + $table->softDeletes(); $table->timestamps(); }); } diff --git a/tests/unit/plugins/database/RevisionableModelTest.php b/tests/unit/plugins/database/RevisionableModelTest.php new file mode 100644 index 000000000..792c325d0 --- /dev/null +++ b/tests/unit/plugins/database/RevisionableModelTest.php @@ -0,0 +1,141 @@ +runPluginRefreshCommand('Database.Tester'); + } + + public function testUpdateNothing() + { + $post = RevisionablePost::create(['title' => 'Hello World!']); + $this->assertEquals('Hello World!', $post->title); + $this->assertEquals(0, $post->revision_history()->count()); + + $post->revisionsEnabled = false; + $post->title = 'Helloooooooooooo!'; + $post->save(); + $this->assertEquals(0, $post->revision_history()->count()); + } + + public function testUpdateSingleField() + { + $post = RevisionablePost::create(['title' => 'Hello World!']); + $this->assertEquals('Hello World!', $post->title); + $this->assertEquals(0, $post->revision_history()->count()); + + $post->title = 'Helloooooooooooo!'; + $post->save(); + $this->assertEquals(1, $post->revision_history()->count()); + + $item = $post->revision_history()->first(); + + $this->assertEquals('title', $item->field); + $this->assertEquals('Hello World!', $item->old_value); + $this->assertEquals('Helloooooooooooo!', $item->new_value); + $this->assertEquals(7, $item->user_id); + } + + public function testUpdateMultipleFields() + { + $post = RevisionablePost::create([ + 'title' => 'Hello World!', + 'slug' => 'hello-world', + 'description' => 'Good day, Commander', + 'is_published' => true, + 'published_at' => new DateTime + ]); + + $this->assertEquals(0, $post->revision_history()->count()); + + $post->title = "Gday mate"; + $post->slug = "gday-mate"; + $post->description = 'Wazzaaaaaaaaaaaaap'; + $post->is_published = false; + $post->published_at = Carbon::now()->addDays(1); + $post->save(); + + $history = $post->revision_history; + + $this->assertEquals(5, $history->count()); + $this->assertEquals([ + 'title', + 'slug', + 'description', + 'is_published', + 'published_at' + ], $history->lists('field')); + } + + public function testExceedingRevisionLimit() + { + $post = RevisionablePost::create([ + 'title' => 'Hello World!', + 'slug' => 'hello-world', + 'description' => 'Good day, Commander', + 'is_published' => true, + 'published_at' => new DateTime + ]); + + $this->assertEquals(0, $post->revision_history()->count()); + + $post->title = "Gday mate"; + $post->slug = "gday-mate"; + $post->description = 'Wazzaaaaaaaaaaaaap'; + $post->is_published = false; + $post->published_at = Carbon::now()->addDays(1); + $post->save(); + + $post->title = 'The Boss'; + $post->slug = 'the-boss'; + $post->description = 'Paid the cost to be the boss'; + $post->is_published = true; + $post->published_at = Carbon::now()->addDays(10); + $post->save(); + + // Count 10 changes above, limit is 8 + $this->assertEquals(8, $post->revision_history()->count()); + } + + public function testSoftDeletes() + { + $post = RevisionablePost::create(['title' => 'Hello World!']); + $this->assertEquals('Hello World!', $post->title); + $this->assertEquals(0, $post->revision_history()->count()); + + $post->title = 'Helloooooooooooo!'; + $post->save(); + $this->assertEquals(1, $post->revision_history()->count()); + + $post->delete(); + $this->assertEquals(2, $post->revision_history()->count()); + } + + public function testRevisionDateCast() + { + $post = RevisionablePost::create([ + 'title' => 'Hello World!', + 'published_at' => Carbon::now() + ]); + $this->assertEquals(0, $post->revision_history()->count()); + + $post->published_at = Carbon::now()->addDays(1); + $post->save(); + + $this->assertEquals(1, $post->revision_history()->count()); + + $item = $post->revision_history()->first(); + $this->assertEquals('published_at', $item->field); + $this->assertEquals('date', $item->cast); + $this->assertInstanceOf('Carbon\Carbon', $item->old_value); + $this->assertInstanceOf('Carbon\Carbon', $item->new_value); + } +} \ No newline at end of file diff --git a/tests/unit/plugins/database/SluggableModelTest.php b/tests/unit/plugins/database/SluggableModelTest.php index 0518ae75f..c15b18f05 100644 --- a/tests/unit/plugins/database/SluggableModelTest.php +++ b/tests/unit/plugins/database/SluggableModelTest.php @@ -8,7 +8,7 @@ class SluggableModelTest extends PluginTestCase { parent::setUp(); - include_once base_path().'/tests/fixtures/plugins/database/tester/models/SluggablePost.php'; + include_once base_path().'/tests/fixtures/plugins/database/tester/models/Post.php'; $this->runPluginRefreshCommand('Database.Tester'); }