Got the Chas and Dave itch again. Love these guys and the music just instantly makes me happy.
Month: February 2023
Incredible shot from The Last of Us episode 6
Another incredible episode of The Last of Us.
The references were so great too. Shimmer; Dina in the background; farm with sheep.
Next week will be the episode with David and it’s gonna be so very dark and have one of the best episode endings so far I reckon.
Inventory app — saving inventory items.
This is the absolute bare bones minimum implementation for my inventory keeping: saving items to my inventory list.
Super simple, but meant only as an example of how I’d work when working on an API.
Here are the changes made to my Inventory Manager. Those changes include the test and logic for the initial index endpoint too. I may blog about that part in a separate post soon.
Writing the store test
One of Laravel’s many strengths is how well it is set up for testing and just how nice those tests can read. Especially now that I’ve started using Pest.
Here is the test I wrote for the store
endpoint I was yet to write:
test('inventory items can be created', function () {
$response = $this->postJson(route(name: 'inventory.store'), [
'name' => 'My Special Item',
]);
$response->assertStatus(201);
$this->assertDatabaseHas(Inventory::class, [
'name' => 'My Special Item',
]);
});
Firstly I post to an endpoint, that I am yet to create, with the most minimal payload I want: an item’s name:
$response = $this->postJson(route(name: 'inventory.store'), [
'name' => 'My Special Item',
]);
Then I can check I have the correct status code: an HTTP Created 201 status:
$response->assertStatus(201);
Finally I check that the database table where I will be saving my inventory items has the item I have created in the test:
$this->assertDatabaseHas(Inventory::class, [
'name' => 'My Special Item',
]);
The first argument to the assertDatabaseHas
method is the model class, which Laravel will use to determine the name of the table for that model. Either by convention, or by the value you override it with on the model.
The second argument is an array that should match the table’s column name and value. Your model can have other columns and still pass. It will only validate that the keys and values you pass to it are correct; you don’t need to pass every column and value — that would become tedious.
Writing the store logic
There is a term I’ve heard in Test-driven development called “sliming it out”. If I remember correctly, this is when you let the test feedback errors dictate every single piece of code you add.
You wouldn’t add any code at all until a test basically told you too.
I wont lie – I actually love this idea, but it soon becomes tiresome. It’s great to do when you start out in TDD, in my opinion, but soon you’ll start seeing things you can add before running the test.
For example, you know you’ll need a database table and a model class, and most likely a Model Factory for upcoming tests, so you could run the artisan command to generate those straight away:
php artisan make:model -mf Inventory
# or with sail
./vendor/bin/sail artisan make:model -mf Inventory
I dont tend to generate my Controller classes with these, as I now use single-action controllers for personal projects.
Store Route
Within the routes/web.php
file, I add the following:
use App\Http\Controllers\Inventory\StoreController;
Route::post('inventory', StoreController::class)->name('inventory.store');
Using a single-action class here to keep logic separated. Some would see this as over-engineering, especially if keeping controller code to a minimum anyway, but I like the separation.
Adding an explicit “name” to the endpoint, means I can just refer to it throughout the app with that name. Like in the test code above where I generate the endpoint with the “route” helper function:
route(name: 'inventory.store')
Store Controller
<?php
declare(strict_types = 1);
namespace App\Http\Controllers\Inventory;
use App\Http\Requests\InventoryStoreRequest;
use App\Models\Inventory;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Http\Response;
class StoreController
{
public function __invoke(InventoryStoreRequest $request): Response|ResponseFactory
{
Inventory::create([
'name' => $request->get(key: 'name'),
]);
return response(content: 'Inventory item created', status: 201);
}
}
Super straight forward at the moment. After receiving the request via the custom request class (code below), I just create an inventory item with the name on the request.
I then return a response with a message and an HTTP Created 201 status.
This code does assume that it was created fine so I might look at a better implementation of this down the line…
…but not before I have a test telling me it needs to change.
InventoryStoreRequest class
This is a standard generated request class with the following rules
method:
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules(): array
{
return [
'name' => 'required',
];
}
Again, nothing much to it. It makes sure that a name is required to be passed.
Its not saying anything about what that value could be. We could pass a date time or a mentally-long string.
I’ll fix that in a future post.
An extra test for the required name
In order to be “belt and braces”, I have also added a test that proves that we require a name to be passed. Pest makes this laughable simple:
test('inventory items require a name', function () {
$this->postJson(route(name: 'inventory.store'))
->assertJsonValidationErrorFor('name');
});
This just performs a post request to the store endpoint, but passes no data. We then just chain the assertJsonValidationErrorFor
method, giving it the parameter that should have caused the failed validation. In this case “name”.
As the validation becomes more sophisticated I will look at adding more of these tests, and even possibly running all “required” fields through the some test method with Pests data functionality. Essentially the same as how PHPUnit’s Data Providers work.
Useful Links
Complete changes in git for when I added the store and the index endpoints to my Inventory app.
Connecting to a VPN in Arch Linux with nmcli
nmcli
is the command line tool for interacting with NetworkManager.
For work I sometimes need to connect to a vpn using an .ovpn
(openvpn) file.
This method should work for other vpn types (I’ve only used openvpn
)
Installing the tools
All three of the required programs are available via the official Arch repositories.
networkmanager
(with contains the nmcli tool). You’ve probably already got this installed if you’re reading this.- openvpn
- networkmanager-openvpn (to allow networkmanager to manage OpenVPN connections)
Importing the ovpn
file into your Network Manager
Once you’ve got the openvpn file on your computer, you can import it into your Network Manager configuration with the following command:
# Replace the file path with your own correct one.
nmcli connection import type openvpn file /path/to/your-file.ovpn
You should see a message saying that the connection was succesfully added.
Activate the connection
Activating the connection will connect you to the VPN specified with that .ovpn
file.
nmcli connection up your-file
If you need to provide a password to your vpn connection, you can add the --ask
flag, which will make the connection up
command ask you for a password:
nmcli connection up your-file --ask
Disconnect
To disconnect from the VPN, just run the down
command as follows:
nmcli connection down you-file