Skip to content

Commit

Permalink
Merge pull request #8 from plank/fix-newest-id-attribute
Browse files Browse the repository at this point in the history
fix: revisionable_id on null
  • Loading branch information
kfriars authored Dec 15, 2021
2 parents 74b2c16 + ff034b8 commit d1dff52
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 8 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,43 @@ withoutRevisions()

This query scope is used to query the models without taking revisioning into consideration.

### Dynamic Relationships

Inspired by https://reinink.ca/articles/dynamic-relationships-in-laravel-using-subqueries, this package supplies a few
dynamic relationships as a convenience for navigating through a model's revision history. The following scopes will run
subqueries to get the additional columns and eagerload the corresponding relations, saving you the hassle of caching
them on each of the tables for your revisionable models. As a fallback when these scopes are not applied, we use get
mutators to run queries and fetch the same columns, making sure the relations are always available but at the expense
of running a bit more queries. *NOTE: when applying these scopes, you will have extra columns in your models attributes,
**any update or insert operations will not work.***

#### withNewestAt($until, $since)
```php
/**
* @param $until Checkpoint|Carbon|string
* @param $since Checkpoint|Carbon|string
*/
withNewestAt($until = null, $since = null)
```
This scope will retrieve the id of the newest model given the until / since constraints. Stored in the newest_id
attribute, this allows you to use `->newest()` relation as a quick way to navigate to that model. Defaults to the
newest model in the revision history.

#### withNewest()
This scope is a shortcut of `withNewestAt` with the default parameters. Uses the same attribute, mutator and relation.

#### withInitial()
This scope will retrieve the id of the initial model from its revision history. Stored in the initial_id attribute,
this allows you to use `->initial()` relation as a quick way to navigate to that first item in the revision history.

#### withPrevious()
This scope will retrieve the id of the previous model from its revision history. Stored in the previous_id attribute,
this allows you to use `->previous()` relation as a quick way to navigate to that previous item in the revision history.

#### withNext()
This scope will retrieve the id of the next model from its revision history. Stored in the next_id attribute,
this allows you to use `->next()` relation as a quick way to navigate to that next item in the revision history.

### Revision Metadata & Uniqueness
As a workaround to some package compatibility issues, this package offers a convenient way to store the values of some
columns as ```metadata``` on the ```revisions``` table. The primary use-case for this feature is to deal with columns or
Expand Down
8 changes: 3 additions & 5 deletions src/Concerns/HasRevisions.php
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,8 @@ public function getNewestIdAttribute($value)
if ($value !== null || array_key_exists('newest_id', $this->attributes)) {
return $value;
}
// dependency on latest boolean column, alternative to using max id
return $this->revisions()->where('latest', true)->first()->revisionable_id;
// when value isn't set by extra subselect scope, fetch from relations
return $this->revision->newest->revisionable_id ?? null;
}


Expand Down Expand Up @@ -413,9 +413,7 @@ public function performRevision()
protected function replicateRelationsTo(Model $copy)
{
$relationHelper = resolve(RelationHelper::class);

$excluded = $this->getExcludedRelations();
$relations = collect($relationHelper::getModelRelations($this))->map->type->except($excluded);
$relations = collect($relationHelper::getModelRelations($this))->map->type;

foreach ($relations as $relation => $type) {
$shortType = substr($type, strrpos($type, '\\') + 1);
Expand Down
9 changes: 7 additions & 2 deletions src/Helpers/RelationHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,12 @@ public static function isChildMultiple(string $relation): bool
* Not just the eager loaded ones present in the $relations Eloquent property.
*
* @param Model|class-string $model
* @param array $except
* @param bool $refresh
* @return array
* @throws ReflectionException
*/
public static function getModelRelations($model, bool $refresh = false): array
public static function getModelRelations($model, array $except = [], bool $refresh = false): array
{
/** @var class-string $class */
$class = ($model instanceof Model) ? get_class($model) : $model;
Expand All @@ -176,6 +177,10 @@ public static function getModelRelations($model, bool $refresh = false): array
return static::$relations[$class];
}

if (method_exists($model, 'getExcludedRelations')) {
$except = array_merge($except, $model->getExcludedRelations());
}

static::$relations[$class] = [];

foreach (get_class_methods($model) as $method) {
Expand All @@ -199,7 +204,7 @@ public static function getModelRelations($model, bool $refresh = false): array
$code = substr($code, $begin, strrpos($code, '}') - $begin + 1);

foreach (static::$relationTypes as $type) {
if (stripos($code, '$this->'.$type.'(')) {
if (stripos($code, '$this->'.$type.'(') && !in_array($method, $except, true)) {
$relation = $model->$method();

if ($relation instanceof Relation) {
Expand Down
13 changes: 13 additions & 0 deletions src/Models/Revision.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* @property-read int|null $all_revisions_count
* @property-read \Illuminate\Database\Eloquent\Collection|Revision[] $otherRevisions
* @property-read int|null $other_revisions_count
* @property-read Revision|null $newest
* @property-read Revision|null $next
* @property-read Revision|null $previous
* @property-read Checkpoint|null $checkpoint
Expand Down Expand Up @@ -234,6 +235,18 @@ public function next(): HasOne
return $this->hasOne(static::class, 'previous_revision_id', $this->getKeyName());
}

/**
* Return latest revision
*
* @return HasOne
*/
public function newest(): HasOne
{
return $this->hasOne(static::class, 'revisionable_type', 'revisionable_type')
->where('original_revisionable_id', $this->original_revisionable_id)
->where('latest', true)->latest();
}

/**
* Returns true if this is the most current revision for an item
*
Expand Down
8 changes: 7 additions & 1 deletion tests/Feature/RevisionObservablesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,14 @@ public function force_deleted_posts_preserves_revision_history(): void
$r1->refresh();
$this->assertEquals(0, $r1->next()->count());
$this->assertNull($r1->previous_revision_id);
$this->assertCount(1, Post::withoutRevisions()->get());
$posts = Post::withoutRevisions()->get();
$this->assertCount(1, $posts);
$this->assertCount(1, Revision::all());
$post = $posts->first();
$post->forceDelete();
$this->assertCount(0, $r1->otherRevisions()->get());
$this->assertNull(null, $post->newest()->get());
$this->assertCount(0, Post::withoutRevisions()->get());
}

/**
Expand Down

0 comments on commit d1dff52

Please sign in to comment.