Skip to content

Commit

Permalink
Merge pull request #1 from TimothePearce/implements-collections
Browse files Browse the repository at this point in the history
Implements collections
  • Loading branch information
timothepearce authored Sep 14, 2021
2 parents 44d4f4b + abac926 commit 267f27c
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public function up()
Schema::create('cargo_projections', function (Blueprint $table) {
$table->id();

$table->string('name');
$table->string('projector_name');
$table->string('key')->nullable();
$table->string('period');
$table->timestamp('start_date');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Exception;

class MissingProjectionNameException extends Exception
class MissingProjectorNameException extends Exception
{
protected $message = "The projection's name is missing from you query.";
}
61 changes: 38 additions & 23 deletions src/Models/Projection.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use Laravelcargo\LaravelCargo\Exceptions\MissingProjectionNameException;
use Laravelcargo\LaravelCargo\Exceptions\MissingProjectionPeriodException;
use Laravelcargo\LaravelCargo\Exceptions\MissingProjectorNameException;
use Laravelcargo\LaravelCargo\ProjectionCollection;

class Projection extends Model
Expand All @@ -31,9 +31,9 @@ class Projection extends Model
];

/**
* The projection's name used in query.
* The projector's name used in query.
*/
protected string | null $queryName = null;
protected string | null $projectorName = null;

/**
* The projection's period used in query.
Expand All @@ -59,11 +59,11 @@ public function from(string $modelName): MorphToMany
/**
* Scope a query to filter by name.
*/
public function scopeName(Builder $query, string $name): Builder
public function scopeFromProjector(Builder $query, string $projectorName): Builder
{
$this->queryName = $name;
$this->projectorName = $projectorName;

return $query->where('name', $name);
return $query->where('projector_name', $projectorName);
}

/**
Expand All @@ -77,14 +77,32 @@ public function scopePeriod(Builder $query, string $period): Builder
}

/**
* Scope a query to filter between dates
* Scope a query to filter by key.
*/
public function scopeKey(Builder $query, array | string | int $keys): Builder
{
if (gettype($keys) === 'array') {
return $query->where(function ($query) use (&$keys) {
collect($keys)->each(function ($key, $index) use (&$query) {
return $index === 0 ?
$query->where('key', (string) $key) :
$query->orWhere('key', (string) $key);
});
});
}

return $query->where('key', (string) $keys);
}

/**
* Scope a query to filter by the given dates
* @throws MissingProjectorNameException
* @throws MissingProjectionPeriodException
* @throws MissingProjectionNameException
*/
public function scopeBetween(Builder $query, Carbon $startDate, Carbon $endDate): Builder
{
if (is_null($this->queryName)) {
throw new MissingProjectionNameException();
if (is_null($this->projectorName)) {
throw new MissingProjectorNameException();
}

if (is_null($this->queryPeriod)) {
Expand All @@ -100,20 +118,17 @@ public function scopeBetween(Builder $query, Carbon $startDate, Carbon $endDate)
}

/**
* Scope a query to filter by key.
* Scope a query to filter by the given dates and fill with empty period if necessary.
*/
public function scopeKey(Builder $query, array | string | int $keys): Builder
public function scopeFillBetween(Builder $query, Carbon $startDate, Carbon $endDate): ProjectionCollection
{
if (gettype($keys) === 'array') {
return $query->where(function ($query) use (&$keys) {
collect($keys)->each(function ($key, $index) use (&$query) {
return $index === 0 ?
$query->where('key', (string) $key) :
$query->orWhere('key', (string) $key);
});
});
}

return $query->where('key', (string) $keys);
$projections = $query->between($startDate, $endDate)->get();

return $projections->fillBetween(
$this->projectorName,
$this->queryPeriod,
$startDate,
$endDate,
);
}
}
56 changes: 55 additions & 1 deletion src/ProjectionCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,64 @@
namespace Laravelcargo\LaravelCargo;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use Laravelcargo\LaravelCargo\Models\Projection;

class ProjectionCollection extends Collection
{
/**
* Fills the collection with empty
* Fills the collection with empty projection between the given dates.
*/
public function fillBetween(string $projectionName, string $period, Carbon $startDate, Carbon $endDate)
{
[$periodQuantity, $periodType] = Str::of($period)->split('/[\s]+/');

$startDate->floorUnit($periodType, $periodQuantity);
$endDate->floorUnit($periodType, $periodQuantity);

$allPeriods = $this->getAllPeriods($startDate, $endDate, $period);
$allProjections = new self([]);

$allPeriods->each(function (string $projectionPeriod) use (&$projectionName, &$period, &$allProjections) {
$projection = $this->firstWhere('start_date', $projectionPeriod);

is_null($projection) ?
$allProjections->push($this->makeEmptyProjection($projectionName, $period, $projectionPeriod)) :
$allProjections->push($projection);
});

return $allProjections;
}

/**
* Get the projections dates.
*/
private function getAllPeriods(Carbon $startDate, Carbon $endDate, string $period): \Illuminate\Support\Collection
{
$cursorDate = clone $startDate;
$allProjectionsDates = collect([$startDate]);
[$periodQuantity, $periodType] = Str::of($period)->split('/[\s]+/');

while ($cursorDate->notEqualTo($endDate)):
$cursorDate->add($periodQuantity, $periodType);
$allProjectionsDates->push(clone $cursorDate);
endwhile;

return $allProjectionsDates;
}

/**
* Makes an empty projection from the given projector name.
*/
private function makeEmptyProjection(string $projectorName, string $period, string $startDate): Projection
{
return Projection::make([
'projector_name' => $projectorName,
'key' => null,
'period' => $period,
'start_date' => $startDate,
'content' => $projectorName::defaultContent(),
]);
}
}
6 changes: 3 additions & 3 deletions src/Projector.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ private function parsePeriod(string $period): void
private function findProjection(string $period, int $quantity, string $periodType): Projection | null
{
return Projection::firstWhere([
['name', $this::class],
['projector_name', $this::class],
['key', $this->hasKey() ? $this->key($this->model) : null],
['period', $period],
['start_date', Carbon::now()->floorUnit($periodType, $quantity)],
Expand All @@ -57,11 +57,11 @@ private function findProjection(string $period, int $quantity, string $periodTyp
private function createProjection(string $period, int $quantity, string $periodType): void
{
$this->model->projections()->create([
'name' => $this::class,
'projector_name' => $this::class,
'key' => $this->hasKey() ? $this->key($this->model) : null,
'period' => $period,
'start_date' => Carbon::now()->floorUnit($periodType, $quantity),
'content' => $this->handle($this->defaultContent(), $this->model),
'content' => $this->handle($this::defaultContent(), $this->model),
]);
}

Expand Down
6 changes: 3 additions & 3 deletions src/WithProjections.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ public function bootProjectors(): void
* Get all the projections of the model.
*/
public function projections(
string | null $projectionName = null,
string | null $projectorName = null,
string | array | null $periods = null,
): MorphToMany {
$query = $this->morphToMany(Projection::class, 'projectable', 'cargo_projectables');

if (isset($projectionName)) {
$query->where('name', $projectionName);
if (isset($projectorName)) {
$query->where('projector_name', $projectorName);
}

if (isset($periods) && gettype($periods) === 'string') {
Expand Down
105 changes: 60 additions & 45 deletions tests/ProjectionCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,63 +21,78 @@ public function setUp(): void
/** @test */
public function it_makes_the_missing_prior_period_when_filled()
{
$startDate = Carbon::now()->subMinute();
$startDate = Carbon::now()->subMinutes(5);
$endDate = now();
Log::factory()->create();

$unfilledProjections = Projection::name(SingleIntervalProjector::class)
$unfilledProjections = Projection::fromProjector(SingleIntervalProjector::class)
->period('5 minutes')
->between($startDate, $endDate)->get();
$this->assertCount(1, $unfilledProjections);

$filledProjections = Projection::name(SingleIntervalProjector::class)
$filledProjections = Projection::fromProjector(SingleIntervalProjector::class)
->period('5 minutes')
->fillBetween($startDate, $endDate)->get();
->fillBetween($startDate, $endDate);
$this->assertCount(2, $filledProjections);

$this->assertEquals($unfilledProjections->first()->id, $filledProjections()->last()->id);
$this->assertEquals($unfilledProjections->first()->id, $filledProjections->last()->id);
}

/** @test */
public function it_makes_the_missing_subsequent_period_when_filled()
{
$startDate = now();
$endDate = Carbon::now()->addMinutes(5);
Log::factory()->create();

$unfilledProjections = Projection::fromProjector(SingleIntervalProjector::class)
->period('5 minutes')
->between($startDate, $endDate)->get();
$this->assertCount(1, $unfilledProjections);

$filledProjections = Projection::fromProjector(SingleIntervalProjector::class)
->period('5 minutes')
->fillBetween($startDate, $endDate);
$this->assertCount(2, $filledProjections);

$this->assertEquals($unfilledProjections->first()->id, $filledProjections->first()->id);
}

/** @test */
public function it_makes_the_missing_between_period_when_filled()
{
$startDate = now();
$endDate = Carbon::now()->addMinutes(10);
Log::factory()->create();
$this->travel(10)->minutes();
Log::factory()->create();

$unfilledProjections = Projection::fromProjector(SingleIntervalProjector::class)
->period('5 minutes')
->between($startDate, $endDate)->get();
$this->assertCount(2, $unfilledProjections);

$filledProjections = Projection::fromProjector(SingleIntervalProjector::class)
->period('5 minutes')
->fillBetween($startDate, $endDate);
$this->assertCount(3, $filledProjections);

$this->assertEquals($unfilledProjections->first()->id, $filledProjections->first()->id);
$this->assertEquals($unfilledProjections->last()->id, $filledProjections->last()->id);
}

/** @test */
public function missing_periods_are_filled_with_default_content()
{
$filledProjections = Projection::fromProjector(SingleIntervalProjector::class)
->period('5 minutes')
->fillBetween(now(), Carbon::now()->addMinutes(10));

$filledProjections->each(function (Projection $filledProjection) {
$this->assertEquals($filledProjection->content, SingleIntervalProjector::defaultContent());
});
}

//
// /** @test */
// public function it_makes_the_missing_subsequent_period_when_filled()
// {
// Log::factory()->create();
//
// $projections = Projection::period('5 minutes')
// ->between(Carbon::now()->addMinutes(6), Carbon::now())
// ->filled();
//
// $this->assertCount(1, $projectionsDB = Projection::all());
// $this->assertCount(2, $projections);
// $this->assertEquals($projectionsDB->first()->id, $projections()->first()->id);
// }
//
// /** @test */
// public function it_makes_the_missing_between_period_when_filled()
// {
// Log::factory()->create();
//
// $projections = Projection::period('5 minutes')
// ->between(Carbon::now()->addMinutes(11), Carbon::now())
// ->filled();
//
// $this->assertCount(1, $projectionsDB = Projection::all());
// $this->assertCount(2, $projections);
// $this->assertEquals($projectionsDB->first()->id, $projections()->first()->id);
// }
//
// /** @test */
// public function missing_periods_are_filled_with_default_content()
// {
// $projections = Projection::period('5 minutes')
// ->between(Carbon::now()->subMinute(), Carbon::now())
// ->filled();
//
// $this->assertCount(1, $projectionsDB = Projection::all());
// $this->assertCount(2, $projections);
// }
//
// /** @test */
// public function it_raises_an_exception_when_a_multiple_periods_collection_is_filled()
// {
Expand Down
Loading

0 comments on commit 267f27c

Please sign in to comment.