-
Notifications
You must be signed in to change notification settings - Fork 3
Add PolicySeeder #1178
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
base: main
Are you sure you want to change the base?
Add PolicySeeder #1178
Changes from all commits
b5f898d
695a643
3c34946
3077811
68e1d54
19db0ce
f57f71c
4f36d78
c0dc419
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| <?php | ||
|
|
||
| namespace App; | ||
|
|
||
| use Carbon\CarbonImmutable; | ||
| use Eloquent; | ||
| use Illuminate\Database\Eloquent\Builder; | ||
| use Illuminate\Database\Eloquent\Model; | ||
|
|
||
| /** | ||
| * @property int $id | ||
| * @property string $policy_type | ||
| * @property CarbonImmutable|null $active_from | ||
| * @property string $content_vue_file | ||
| * @property CarbonImmutable|null $created_at | ||
| * @property CarbonImmutable|null $updated_at | ||
| * | ||
| * @method static Builder<static>|Policy newModelQuery() | ||
| * @method static Builder<static>|Policy newQuery() | ||
| * @method static Builder<static>|Policy query() | ||
| * @method static Builder<static>|Policy whereActiveFrom($value) | ||
| * @method static Builder<static>|Policy whereContentVueFile($value) | ||
| * @method static Builder<static>|Policy whereCreatedAt($value) | ||
| * @method static Builder<static>|Policy whereId($value) | ||
| * @method static Builder<static>|Policy wherePolicyType($value) | ||
| * @method static Builder<static>|Policy whereUpdatedAt($value) | ||
| * | ||
| * @mixin Eloquent | ||
| */ | ||
| class Policy extends Model { | ||
| // define which attributes are mass assignable | ||
| protected $fillable = [ | ||
| 'policy_type', | ||
| 'active_from', | ||
| 'content_vue_file', | ||
| ]; | ||
|
|
||
| // define the default value of model attributes when a new instance is created | ||
| protected $attributes = [ | ||
| 'active_from' => null, | ||
| ]; | ||
|
|
||
| protected function casts(): array { | ||
| return [ | ||
| // cast `active_from` to a CarbonImmutable instance rather than a string | ||
| 'active_from' => 'immutable_date', | ||
|
|
||
| 'created_at' => 'immutable_datetime', | ||
| 'updated_at' => 'immutable_datetime', | ||
| ]; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| <?php | ||
|
|
||
| namespace App; | ||
|
|
||
| use Carbon\CarbonImmutable; | ||
| use Eloquent; | ||
| use Illuminate\Database\Eloquent\Builder; | ||
| use Illuminate\Database\Eloquent\Model; | ||
|
|
||
| /** | ||
| * This model uses a separate `accepted_at` property rather than renaming the default `created_at` property because: | ||
| * - it reduces confuses by remaining consistent with other models that use the default timestamps | ||
| * - `accepted_at` will be before `created_at` when backfilling the terms-of-use acceptances | ||
| * | ||
| * @property int $id | ||
| * @property int $user_id | ||
| * @property int $policy_id | ||
| * @property CarbonImmutable|null $created_at | ||
| * @property CarbonImmutable|null $updated_at | ||
| * @property CarbonImmutable $accepted_at | ||
| * | ||
| * @method static Builder<static>|PolicyAcceptance newModelQuery() | ||
| * @method static Builder<static>|PolicyAcceptance newQuery() | ||
| * @method static Builder<static>|PolicyAcceptance query() | ||
| * @method static Builder<static>|PolicyAcceptance whereAcceptedAt($value) | ||
| * @method static Builder<static>|PolicyAcceptance whereCreatedAt($value) | ||
| * @method static Builder<static>|PolicyAcceptance whereId($value) | ||
| * @method static Builder<static>|PolicyAcceptance wherePolicyId($value) | ||
| * @method static Builder<static>|PolicyAcceptance whereUpdatedAt($value) | ||
| * @method static Builder<static>|PolicyAcceptance whereUserId($value) | ||
| * | ||
| * @mixin Eloquent | ||
| */ | ||
| class PolicyAcceptance extends Model { | ||
| protected $fillable = [ | ||
| 'user_id', | ||
| 'policy_id', | ||
|
|
||
| ]; | ||
|
|
||
| protected $guarded = [ | ||
| // Don't allow `accepted_at` to be mass assigned. | ||
| // Most of the time this will be set to the current timestamp by the database. | ||
| 'accepted_at', | ||
| ]; | ||
|
|
||
| protected function casts(): array { | ||
| return [ | ||
| // cast `accepted_at` to a `CarbonImmutable` instance rather than a string | ||
| 'accepted_at' => 'immutable_datetime', | ||
|
|
||
| 'created_at' => 'immutable_datetime', | ||
| 'updated_at' => 'immutable_datetime', | ||
| ]; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| <?php | ||
|
|
||
| use Illuminate\Database\Migrations\Migration; | ||
| use Illuminate\Database\Schema\Blueprint; | ||
| use Illuminate\Support\Facades\Schema; | ||
|
|
||
| return new class() extends Migration { | ||
| /** | ||
| * Run the migrations. | ||
| */ | ||
| public function up(): void { | ||
| Schema::create('policies', function (Blueprint $table) { | ||
| $table->id(); | ||
| $table->enum('policy_type', ['terms-of-use', 'hosting-policy']); | ||
| $table->date('active_from')->nullable()->default(null); | ||
| $table->string('content_vue_file', 255); | ||
|
|
||
| // Use Eloquent built in to create nullable `created_at` and `updated_at` | ||
| // timestamp fields | ||
| $table->timestamps(); | ||
|
|
||
| // This prevents two upcoming policies of the same type with `active_from` set to `null`, | ||
| $table->unique(['policy_type', 'active_from']); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Reverse the migrations. | ||
| */ | ||
| public function down(): void { | ||
| Schema::dropIfExists('policies'); | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| <?php | ||
|
|
||
| use Illuminate\Database\Migrations\Migration; | ||
| use Illuminate\Database\Schema\Blueprint; | ||
| use Illuminate\Support\Facades\Schema; | ||
|
|
||
| return new class() extends Migration { | ||
| /** | ||
| * Run the migrations. | ||
| */ | ||
| public function up(): void { | ||
| Schema::create('policy_acceptances', function (Blueprint $table) { | ||
| $table->id(); | ||
|
|
||
| // Can't use the `foreignId()` method because the `users.id` column isn't an unsigned big integer | ||
| $table->unsignedInteger('user_id'); | ||
| $table->foreign('user_id')->references('id')->on('users')->restrictOnUpdate()->restrictOnDelete(); | ||
|
|
||
| $table->foreignId('policy_id')->constrained()->restrictOnUpdate()->restrictOnDelete(); | ||
|
|
||
| // Use Eloquent built in to create nullable `created_at` and `updated_at` | ||
| // timestamp fields | ||
| $table->timestamps(); | ||
|
|
||
| // Using a separate `accepted_at` column rather than renaming the default `created_at` column because: | ||
| // * it reduces confusion by remaining consistent with other tables that use the default columns | ||
| // * `accepted_at` will be before `created_at` when backfilling the terms-of-use acceptances | ||
| $table->timestamp('accepted_at')->useCurrent(); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Reverse the migrations. | ||
| */ | ||
| public function down(): void { | ||
| Schema::dropIfExists('policy_acceptances'); | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <?php | ||
|
|
||
| namespace Database\Seeders; | ||
|
|
||
| use App\Policy; | ||
| use Carbon\CarbonImmutable; | ||
| use Illuminate\Database\Seeder; | ||
|
|
||
| class PolicySeeder extends Seeder { | ||
| public function run() { | ||
| Policy::create( | ||
| [ | ||
| 'policy_type' => 'terms-of-use', | ||
| 'active_from' => CarbonImmutable::createFromDate(2026, 06, 01), | ||
| 'content_vue_file' => 'terms-of-use/example.vue', | ||
| ] | ||
| ); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this will create a new policy, or fail if the policy already exists. If multiple seeders are run in the future, I think a failed Seeder will prevent subsequent seeders from running, which is likely not what we want. From my quick searching, it's often recommended to create seeders in an idempotent way (i.e. running the seeder multiple times will result in the same database state). https://oneuptime.com/blog/post/2026-02-03-laravel-database-seeding/view recommends using Alternatively/additionally, you could use the |
||
| } | ||
|
Comment on lines
+10
to
+18
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For testing purposes, would it be useful to have more than one current policy? Maybe also a superseded policy? I'm also happy if we add these at a later stage, though. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| <?php | ||
|
|
||
| namespace Tests; | ||
|
|
||
| use App\Policy; | ||
| use App\PolicyAcceptance; | ||
| use App\User; | ||
| use Carbon\CarbonImmutable; | ||
| use Illuminate\Foundation\Testing\RefreshDatabase; | ||
|
|
||
| class PolicyAcceptanceTest extends TestCase { | ||
| use RefreshDatabase; | ||
|
|
||
| protected int $user_id; | ||
|
|
||
| protected int $policy_id; | ||
|
|
||
| protected function setUp(): void { | ||
| parent::setUp(); | ||
| $user = User::factory()->create(); | ||
| $this->user_id = $user->id; | ||
| $policy = Policy::create( | ||
| [ | ||
| 'policy_type' => 'terms-of-use', | ||
| 'active_from' => CarbonImmutable::yesterday(), | ||
| 'content_vue_file' => 'terms-of-use/example.vue', | ||
| ]); | ||
| $policy->save(); | ||
| $this->policy_id = $policy->id; | ||
| } | ||
|
|
||
| public function testCreatesAndSavesSuccessfully(): void { | ||
| $policyAcceptance = new PolicyAcceptance( | ||
| [ | ||
| 'user_id' => $this->user_id, | ||
| 'policy_id' => $this->policy_id, | ||
| ] | ||
| ); | ||
| $policyAcceptance->save(); | ||
| $policyAcceptance->refresh(); | ||
|
|
||
| $this->assertDatabaseHas('policy_acceptances', [ | ||
| 'user_id' => $this->user_id, | ||
| 'policy_id' => $this->policy_id, | ||
| ]); | ||
|
|
||
| $this->assertNotEmpty($policyAcceptance->accepted_at); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| <?php | ||
|
|
||
| namespace Tests; | ||
|
|
||
| use App\Policy; | ||
| use Carbon\Carbon; | ||
| use Illuminate\Foundation\Testing\RefreshDatabase; | ||
|
|
||
| class PolicyTest extends TestCase { | ||
| use RefreshDatabase; | ||
|
|
||
| public function testCreatesAndSavesSuccessfully(): void { | ||
| $yesterday = Carbon::yesterday(); | ||
| $policy = Policy::create( | ||
| [ | ||
| 'policy_type' => 'terms-of-use', | ||
| 'active_from' => $yesterday, | ||
| 'content_vue_file' => 'terms-of-use/example.vue', | ||
| ] | ||
| ); | ||
| $policy->save(); | ||
|
|
||
| $this->assertDatabaseHas('policies', [ | ||
| 'policy_type' => 'terms-of-use', | ||
| 'active_from' => $yesterday, | ||
| 'content_vue_file' => 'terms-of-use/example.vue', | ||
| ]); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a
CarbonImmutableinstance here works, but makes it trickier to usefirstOrCreate()orupdateOrCreate()methods (see other comment as to why you would want to use those methods). This is because (emphasis mine):And explained using tinker:
To address this you could either:
'active_from' => '2026-06-01',)CarbonImmutable::createMidnightDate()methodThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did some more testing of creating
CarbonImmutableobjects while writing a test for another PR and thought I would paste the findings here as well.