Reading List
The most recent articles from a list of feeds I subscribe to.
Using MySQL `order by` while keeping one value at the end
The other day I needed to sort a dataset in MySQL and ensure one value was always at the end. I never fully understood how order by works, so I did some research on how to solve my problem and how order by behaves.
The data set looks like a list of jobs. I want to present them alphabetically sorted. However, one of them is “Other”, and I want that one to appear at the end of the list in my UI.
| id | name |
| 1 | Backend Developer |
| 2 | Designer |
| 3 | Other |
| 4 | Account Manager |
| 5 | Frontend Developer |
Lets jump straight to the solution:
select * from jobs
order by field(name, "Other"), name
The field(value, …list) function returns the (1-based) index of the value in the list. If the value isn’t in the list, it returns 0.
This is what the result of field(name, "Other") looks like for this dataset:
| id | name | field
| 1 | Backend Developer | 0
| 2 | Designer | 0
| 3 | Other | 1
| 4 | Account Manager | 0
| 5 | Frontend Developer | 0
The second part of the solution is using order by for multiple columns.
I always assumed order by would order by the first clause, then the second. This is not entirely true. When you order by multiple values, MySQL treats the first as the primary value to sort by, and the next one as the secondary. This means once the first clause pushes values down or up, they’ll stay there.
To illustrate, here’s a simple dataset with letters and numbers.
| letter | number |
| d | 2 |
| e | 1 |
| c | 3 |
| a | 3 |
| b | 1 |
When we run order by number, letter, MySQL will order by number first, and keep the subsets on their own little islands:
| letter | number |
| e | 1 |
| b | 1 |
| d | 2 |
| c | 3 |
| a | 3 |
Then, MySQL will order the subsets by letter:
| letter | number |
| b | 1 |
| e | 1 |
| d | 2 |
| a | 3 |
| c | 3 |
Back to our jobs dataset, order by field(name, "Other") will push the “Other” value below.
| id | name | field |
| 1 | Backend Developer | 0 |
| 2 | Designer | 0 |
| 4 | Account Manager | 0 |
| 5 | Frontend Developer | 0 |
| 3 | Other | 1 |
The second part of the order by clause will order the subsets by name.
| id | name | field |
| 4 | Account Manager | 0 |
| 1 | Backend Developer | 0 |
| 2 | Designer | 0 |
| 5 | Frontend Developer | 0 |
| 3 | Other | 1 |
With Eloquent in Laravel
If you want to use the field function to order in Laravel, you need to use orderByRaw:
$jobs = Job::query()
->orderByRaw('field(name, "Other")')
->orderBy('name');
If you want to apply this to all queries to the jobs table in our app, you can consider a global scope:
class Job extends Model
{
protected static function boot()
{
static::addGlobalScope(
'order',
fn (Builder $builder) => $builder
->orderByRaw('FIELD(name, "Other")')
->orderBy('name')
);
}
}
Eloquent findOrFail caveats
I use Model::findOrFail a lot in Laravel. Recently, I realized it’s not always the best option.
findOrFail is great because it ensures you’ll end up with a model instance. No more “Attempt to read property on null” exceptions.
$title = Post::findOrFail($id)->title;
Another benefit is that Laravel returns a 404 response for you when the model doesn’t exist. (This is handled in Laravel’s base exception handler) The 404 response is a double-edged sword. It’s a great feature in controllers and other outer layers of your application, but can be harmful in core business logic.
Validating that a model exists should generally happen in a request object or a controller action. When I’m deep inside the application — like in an action or a service class — a missing model often means there’s an inconsistency in my database state. That’s not the same as a missing resource. Inconsistent database states should generally return a 500 status code, not 404.
In addition, Laravel doesn’t report model not found exceptions to error trackers. This makes sense for 404 responses. But if a model wasn’t found in my core business logic, it means I need to validate earlier or catch the error and deal with it.
I stopped using findOrFail in actions, aggregate roots, and other core business classes. In those places, I’d rather throw a plain exception that my error tracker will pick up.
The Monetization Trap
I want to talk about something I’ve been chewing on for a while: the monetization trap.
Last year, I had a hard time writing a post, sending a newsletter, or researching a topic without considering if it could be part of some grand monetization scheme. Everyone and their dog seemed to have a project that generates passive income; I want a slice too!
Once you start to look at a project through the lens of monetization, the scope blows up. There are practical considerations like pricing and payment infrastructure, but more importantly, accountability towards people paying you. If I start a paid newsletter, I need out enough quality content to give people their money’s worth. Out the door with the writing on a whenever-inspiration-strikes schedule.
The scope blows up before I even get started, and I’m paralyzed from doing anything.
I zoomed out and looked at the grand scheme of things. Why do I write? Why do I code in my spare time? People that bake cakes or tend to their garden aren’t looking to monetize their hobby–the hobby itself is fulfilling. Writing and coding are my creative outlets. I already have a great job. Monetizing a side project wouldn’t affect my life and finances in a significant way, at least not enough to warrant the time and stress.
After reflection, I realized I don’t care about monetization as much as I thought, I got tricked into thinking I did because I see so many people around me building SaaS or selling paid memberships on the side. Does that mean I’ll never monetize anything I do? Not necessarily, but I feel calmer realizing there’s no hurry, and I don’t need to shoehorn anything.
Ebb and flow
I’ve been thinking a lot about work-life balance lately. I used to see it as a balancing scale that should remain on the same position at all times. However, I believe a better mental model is to see it as ebb and flow.
There are times you need to focus on work. There are times you need to focus on life. There are times you can focus on either. Transitioning between these states can happen in a forced way (circumstance), or you can slowly move towards a new area like ebb and flow.
The trick is to learn to pace yourself moving in one direction, or you’ll become overwhelmed. Don’t let ebb become drought, or flow become flood. If you try to maintain an arbitrary, exact balance at all times, you risk not moving forward on any focus area.
This can be applied to other places as well, like working on a main project vs a side project, or maintaining essential vs accidental complexity in a software project.
Use Blink to execute something once and only once
Our Blink package is marketed as a caching solution to memoize data for the duration of a web request. Recently, we came upon another use case for the package: to execute something once and only once.
Blink looks just like Laravel’s cache. You can store things and retrieve them. The Blink cache is stored on a singleton object, so it’s destroyed at the end of the request (similar to Laravel’s array cache driver).
$orders = blink('orders', fn () => Order::all());
$orders = blink('orders');
Now for a different use case. Say we want to generate and store PDF invoices for an order. We want to regenerate them whenever an order or an order line changes. We can use Eloquent events to determine when to dispatch a job.
Order::saved(fn (Order $order) =>
dispatch(new GenerateInvoicePdf($order))
);
OrderLine::saved(fn (OrderLine $order) =>
dispatch(new GenerateInvoicePdf($orderLine->order))
);
If someone modifies an order and an order line in the same request, GenerateInvoicePdf will be dispatched twice, clogging the queue with unnecessary work.
Instead, we want to make sure the job only gets dispatched once. Here’s where Blink comes in. Instead of using Blink to store something, we’ll use it to execute something only once.
Order::saved(fn (Order $order) =>
blink('generate-invoice-pdf:' . $order->id, fn () =>
dispatch(new GenerateInvoicePdf($order))
)
);
OrderLine::saved(fn (OrderLine $order) =>
blink('generate-invoice-pdf:' . $orderLine->order->id, fn () =>
dispatch(new GenerateInvoicePdf($orderLine->order))
)
);
Now it doesn’t matter how many saved events are triggered; the job will only be dispatched once.
Cleaning things up, I like keeping cache calls to the same key in one place.
class Order extends Model
{
public function generateInvoicePdf(): void
{
blink('generate-invoice-pdf:' . $this->id, fn () =>
dispatch(new GenerateInvoicePdf($this))
);
}
}
Order::saved(fn (Order $order) =>
$order->generateInvoicePdf()
);
OrderLine::saved(fn (OrderLine $order) =>
$orderLine->order->generateInvoicePdf()
);
This won’t work when the environment is persisted across requests, like in a Horizon worker or Laravel Octane. In that case, you can fall store a should_regenerate boolean on the orders table and schedule a command to dispatch the GenerateInvoicePdf jobs.