Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: model settings #7

Merged
merged 3 commits into from
Nov 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions docs/basic-usage/model-settings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: Model Settings
sort: 3
---

## Introduction

Starting with version `2.1.0`, models can easily have their own settings by using the `\Rawilk\Settings\Models\HasSettings` trait. This trait will automatically create a new `\Rawilk\Settings\Support\Context` object with properties that uniquely identify the model. See the [context](#context) section for more information.

## Usage

First, use the `HasSettings` trait in your Eloquent model.

```php
// ...
use Rawilk\Settings\Models\HasSettings;

class User extends Model
{
use HasSettings;
}
```

Now whenever you need to interact with settings that are specific to that model, you can call `settings()`, which will return an instance of `\Rawilk\Settings\Settings`. This is essentially the same as calling `\Rawilk\Settings\Facades\Settings::context(...)`. This will allow you to do anything you could on the facade, but specifically for the model.

To store a setting:

```php
$user->settings()->set('foo', 'bar');
```

To retrieve a setting:

```php
$user->settings()->get('foo');
```

> {tip} You are able to specify a default value when retrieving a setting just like you can with the facade.

## Context

By default, when `context()` is called on a model, it will create a new `\Rawilk\Settings\Support\Context` object with the model's class and ID. If you need to override this behavior, you may override the `context` method on your model, but make sure you return a `Context` object. If you just need to add additional uniquely identifying properties, you may implement a `contextArguments` method on your model that returns an array of key/value pairs of data that is unique to a a model instance. These key/value pairs will be merged into the context object with your model's class and ID.

```php
protected function contextArguments(): array
{
return [
'email' => $this->email,
];
}
```
2 changes: 1 addition & 1 deletion docs/basic-usage/settings-helper.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Settings Helper
sort: 3
sort: 4
---

## Introduction
Expand Down
37 changes: 37 additions & 0 deletions src/Models/HasSettings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace Rawilk\Settings\Models;

use Rawilk\Settings\Facades\Settings as SettingsFacade;
use Rawilk\Settings\Settings;
use Rawilk\Settings\Support\Context;

/**
* @mixin \Illuminate\Database\Eloquent\Model
*/
trait HasSettings
{
public function context(): Context
{
return new Context([
'model' => static::class,
'id' => $this->getKey(),
...$this->contextArguments(),
]);
}

public function settings(): Settings
{
return SettingsFacade::context($this->context());
}

/**
* Additional arguments that uniquely identify this model.
*/
protected function contextArguments(): array
{
return [];
}
}
2 changes: 1 addition & 1 deletion src/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function getDriver(): Driver
return $this->driver;
}

public function context(Context $context = null): self
public function context(?Context $context = null): self
{
$this->context = $context;

Expand Down
79 changes: 79 additions & 0 deletions tests/Feature/HasSettingsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

use Rawilk\Settings\Facades\Settings;
use Rawilk\Settings\Support\Context;
use Rawilk\Settings\Tests\Support\Models\Company;
use Rawilk\Settings\Tests\Support\Models\CustomUser;
use Rawilk\Settings\Tests\Support\Models\User;

beforeEach(function () {
$migration = include __DIR__ . '/../Support/database/migrations/create_test_tables.php';
$migration->up();

User::factory(2)->create();
});

test('a model can have its own custom context', function () {
$user1 = User::first();
$user2 = User::where('id', '>', $user1->getKey())->first();

$expectedUser1Context = new Context([
'model' => $user1::class,
'id' => $user1->getKey(),
]);
$actualUser1Context = $user1->context();
expect($actualUser1Context)->toBeInstanceOf(Context::class)
->and($actualUser1Context->has('model'))->toBeTrue()
->and($actualUser1Context->has('id'))->toBeTrue()
->and($actualUser1Context->get('model'))->toBe($expectedUser1Context->get('model'))
->and($actualUser1Context->get('id'))->toBe($expectedUser1Context->get('id'));

$user2Context = $user2->context();
expect($user2Context->get('id'))->not()->toBe($actualUser1Context->get('id'))
->and($user2Context->get('id'))->toBe($user2->getKey());
});

test('a model can add its own custom properties to its context', function () {
// Custom user adds in an "email" key in the "contextArguments" method.
$user = CustomUser::first();
$context = $user->context();

expect($context->has('email'))->toBeTrue()
->and($context->get('email'))->toBe($user->email)
->and($context->has('id'))->toBeTrue()
->and($context->get('id'))->toBe($user->getKey());
});

test('models can have their own settings', function () {
$user1 = User::first();
$user2 = User::where('id', '>', $user1->getKey())->first();
$company = Company::factory()->create();

$company->settings()->set('program.name', $company->name);
$context = new Context([
'model' => $company::class,
'id' => $company->getKey(),
]);

expect(Settings::context($context)->get('program.name'))->toBe($company->name)
->and($user1->settings()->has('program.name'))->toBeFalse();

$user1->settings()->set('program.name', $user1->email);
$user2->settings()->set('program.name', $user2->email);

expect($user1->settings()->has('program.name'))->toBeTrue();

$user1Context = new Context([
'model' => $user1::class,
'id' => $user1->getKey(),
]);
$user2Context = new Context([
'model' => $user2::class,
'id' => $user2->getKey(),
]);

expect(Settings::context($user1Context)->get('program.name'))->toBe($user1->email)
->and(Settings::context($user2Context)->get('program.name'))->toBe($user2->email);
});
19 changes: 19 additions & 0 deletions tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,22 @@
use Rawilk\Settings\Tests\TestCase;

uses(TestCase::class)->in(__DIR__);

// Helpers...
if (! function_exists('fake') && class_exists(\Faker\Factory::class)) {
/**
* Ensure the fake method exists. If we ever drop laravel 8 support, we can remove this helper.
*/
function fake($locale = null)
{
$locale ??= app('config')->get('app.faker_locale') ?? 'en_US';

$abstract = \Faker\Generator::class . ':' . $locale;

if (! app()->bound($abstract)) {
app()->singleton($abstract, fn () => \Faker\Factory::create($locale));
}

return app()->make($abstract);
}
}
21 changes: 21 additions & 0 deletions tests/Support/Models/Company.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Rawilk\Settings\Tests\Support\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Rawilk\Settings\Models\HasSettings;
use Rawilk\Settings\Tests\Support\database\factories\CompanyFactory;

class Company extends Model
{
use HasSettings;
use HasFactory;

protected static function newFactory(): CompanyFactory
{
return new CompanyFactory;
}
}
17 changes: 17 additions & 0 deletions tests/Support/Models/CustomUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Rawilk\Settings\Tests\Support\Models;

class CustomUser extends User
{
protected $table = 'users';

protected function contextArguments(): array
{
return [
'email' => $this->email,
];
}
}
21 changes: 21 additions & 0 deletions tests/Support/Models/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Rawilk\Settings\Tests\Support\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Rawilk\Settings\Models\HasSettings;
use Rawilk\Settings\Tests\Support\database\factories\UserFactory;

class User extends Model
{
use HasSettings;
use HasFactory;

protected static function newFactory(): UserFactory
{
return new UserFactory;
}
}
23 changes: 23 additions & 0 deletions tests/Support/database/factories/CompanyFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Rawilk\Settings\Tests\Support\database\factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Rawilk\Settings\Tests\Support\Models\Company;

final class CompanyFactory extends Factory
{
protected $model = Company::class;

/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->company(),
];
}
}
26 changes: 26 additions & 0 deletions tests/Support/database/factories/UserFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Rawilk\Settings\Tests\Support\database\factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Rawilk\Settings\Tests\Support\Models\User;

/**
* @extends Factory<\Rawilk\Settings\Tests\Support\Models\User>
*/
final class UserFactory extends Factory
{
protected $model = User::class;

/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'email' => fake()->unique()->safeEmail(),
];
}
}
25 changes: 25 additions & 0 deletions tests/Support/database/migrations/create_test_tables.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('email');
$table->timestamps();
});

Schema::create('companies', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
};