Capistrano (as of version 3.19.1) still defaults to deploying the master
branch, even though GitHub, GitLab and BitBucket have all changed their default branch name to main
.
The simplest fix for this is to add a line to config/deploy.rb
which sets the branch to main
:
# config/deploy.rb
set :branch, "main"
You can easily override that branch setting on a per-environment basis. For example, if you want to always deploy the currently checked-out (HEAD
) branch when deploying to staging
, override the :branch
setting in config/deploy/staging.rb
. The current branch can be obtained with git rev-parse --abbrev-ref HEAD
so the command becomes:
# config/deploy/staging.rb
set :branch, `git rev-parse --abbrev-ref HEAD`.chomp
Setting a global default and then overriding it per-environment is probably sufficient 95+% of the time, but what if you ever want to do something different?
If it’s a one-off thing, you could edit the local copy of config/deploy.rb
or config/deploy/<env>.rb
and deploy. As long as you don’t commit and push the changes to Git, Capistrano on your machine will use the branch you set, and everyone else will happily keep using the original setting from Git.
However, I found a better way (on StackOverflow) that doesn’t rely on you having to remember not to commit your changes. We will set up a workflow that gives us:
First the code, then the explanation (lightly modified from my project’s README).
# config/deploy.rb
# config valid for current version and patch releases of Capistrano
lock "~> 3.19.1"
def branch_name(default_branch)
branch = ENV.fetch('BRANCH', default_branch)
if branch == '.'
`git rev-parse --abbrev-ref HEAD`.chomp
else
branch
end
end
# Uncomment one of these to set an app-wide default for all environments
# set :branch, "main"
# set :branch, branch_name("main")
set :application, "my_app"
# etc, etc
# config/deploy/production.rb
server "<hostname>", user: "deploy", roles: %w[app db web]
set :branch, branch_name("main")
# config/deploy/staging.rb
server "<hostname>", user: "deploy", roles: %w[app db web]
set :branch, branch_name(".")
And here’s the relevant section from my project’s README.md
:
Capistrano is used for deployment. By default the main
branch will be deployed in production (more info in next section), so make sure all code has been committed (and pushed), then run:
> cap production deploy
A typical deployment takes less than 30 seconds and will finish with:
00:19 deploy:log_revision
01 echo "Branch main (at a087b4c40d8ef0031a0b90773c8511d8e873fa59) deployed as release 20240921040843 b…
✔ 01 deploy@<hostname> 0.067s
The last five releases are kept on the server (in theory allowing for easy rollback) but in practice it’s generally safer to revert the changes and deploy a new release (reference).
The default branch has been set to “main” in config/deploy/production.rb
(using set :branch, branch_name("main")
), but you can override it by setting the BRANCH
environment variable when running Capistrano:
> BRANCH=my-new-feature cap production deploy
Set the variable to .
to deploy the current branch (a bit like the current working directory on *nix systems):
> BRANCH=. cap production deploy
The .
shortcut also works in deploy files. We default to using the current branch in staging using:
# config/deploy/staging.rb
set :branch, branch_name(".")
P.S. On a bigger team you might not want to make it so easy to deploy a different branch to production. In that case, don’t include the banch_name
method in production.rb
:
# config/deploy/production.rb
server "<hostname>", user: "deploy", roles: %w[app db web]
set :branch, "main"
P.P.S. I deliberately commented out set :branch
from config/deploy.rb
. That way if I forget to set the branch for a new environment, Capistrano will attempt to use master
and the deploy will fail. If you want new environments to default to something else, uncomment one of the set :branch
lines in config/deploy.rb
FWIW, I have a 1 year old iPhone 15 Pro Max and its battery capacity is at 98% after 261 cycles.
I too kept mine limited to 80% all year, but not rigorously like Juli Clover… I would occasionally charge mine to 100% when I knew I’d be using it a lot the next day, maybe 4 or 5 times? 🤷♂️
Saw the scary screen recording message for Microsoft Teams on macOS Sequoia but Google Meet worked fine in Safari.
Making native apps clunkier may encourage even more web apps… if so, that could lead more and more users to choose a Surface Book or Chromebook over a MacBook for their next device 🤔
In episode 605 of Core Intuition, @manton made a claim that past me would have completely agreed with but that I’m less convinced about today.
Because if you actually think about the stuff that the EU is finding Apple in non-compliance over, if you just look at it… Like, just let apps link to the web! This really should be uncontroversial.
— Manton Reece, starting around the 11 minute mark
I think there’s a bit more nuance to the discussion than Manton allows for here and I wonder if it’s a matter of perspective.
For instance, it used to be when I was setting up a new Mac and I saw the Analytics screen, I would think “Yes, I do want to share analytics with Manton and Daniel, Gus Mueller, Brent Simmons, Ken Case, David _Smith and all the other developers that make great Mac or iOS software”.
Now when I see that screen I think “No, I don’t want to share analytics with Google, Facebook, Taboola, casino.mindthebet.co.uk or the data brokers who trick or pay developers to bundle their SDK”.
When asked the question “Should iOS developers be allowed to link to their own website for account creation and payments?” I have a similarly bifurcated response…
On the one hand, yes, it would be great if iOS users didn’t have to jump through so many hoops to transact with trustworthy indie developers and reputable large companies.
On the other hand, there are so many bad actors out there who will exploit any opportunity to make a quick buck (or lazy ones who will bundle a sketchy SDK to avoid a bit of work). So no, I don’t want to give them additional tools to trick me into making unwanted payments or handing over my credit card details.
I know there are a bunch of kind, trustworthy, generous people on the open web (particularly among proponents of the open web!) but for every one of them it seems like there’s 10 scammers, spammers, hustlers and thieves.
I don’t know what the “right” answer is1. For all the nuance I’ve tried to include in this post, I’ve still vastly oversimplified things. And so I’ll finish with another oversimplification: Apple’s restrictions on linking out to the web benefit Apple, but I think they benefit most users just as much or more 🤷♂️
At work we use a legacy web app from the late 90s that doesn’t play nicely with Safari’s modern privacy practices (you may have heard of Salesforce?). Salesforce is the only website I frequently need another browser for and I always want Salesforce links to open in Brave, a fork of Chrome that I hope and assume doesn’t send all my browsing history to Google.
I previously purchased a fantastic little utility app called Bumpr getbumpr.com that lets you choose which app to use for web and mail links (including letting you use web based email to handle mailto: links).
In normal use it opens a graphical menu right near your mouse but 99% of the time I just want it to open Safari so the extra click was starting to bug me. Thankfully, there’s a super simple workaround thanks to Bumpr’s clever design (as in how it works, not just how it looks).
Step 1 is to remove other browsers from the Bumpr menu. If there’s only one browser listed, Bumpr will not show you a one-item menu… links will just open in that one browser without an extra click.
Step 2 is to add your exceptions to the “Custom Browser Rules…”. Even though step 1 removed Brave from the interactive menu, it’s still available to be used for per-domain rules so you can tell your legacy web app to always open in a more permissive browser.
P.S. If you’re doing this specifically for Salesforce, there are some other changes you’ll probably want to make in Brave/Chrome/Edge to support Salesforce’s old fashioned design; allowing third party cookies and allowing pop ups:
TLDR: To count related records (and get a zero when there are none) use a LEFT JOIN
. To count related records that match a certain criteria (and get a zero when there are none) use a CASE
statement in the SELECT
fields.
I had a table of school terms and a table of enrolments. Here’s a super simplified example using an INNER JOIN
:
SELECT term.year, enrol.id
FROM term INNER JOIN enrol on term.term_id = enrol.term_id
year | id |
---|---|
… | … |
2021 | 69423 |
2021 | 694170 |
2023 | 69423 |
2023 | 69584 |
2024 | 69456 |
To count the enrolments per year was a simple matter of adding a COUNT
, changing it to a LEFT JOIN
and adding the GROUP BY
:
SELECT term.year, COUNT(enrol.id)
FROM term LEFT JOIN enrol on term.term_id = enrol.term_id
GROUP BY term.year
ORDER BY term.year DESC
year | |
---|---|
2024 | 1 |
2023 | 2 |
2022 | 0 |
2021 | 2 |
… | … |
Note the count of 0 in 2022, that’s what I want! But then when I tried to add a WHERE
clause to only get the enrolments where there was a possible flaw in the data, I stopped getting a zero count for the missing years:
SELECT term.year, COUNT(enrol.id)
FROM term LEFT JOIN enrol on term.term_id = enrol.term_id
WHERE enrol.raw_mark <> enrol.final_mark
GROUP BY term.year
ORDER BY term.year DESC
year | |
---|---|
2023 | 1 |
If I understand correctly, the WHERE
clause is removing the rows (including the rows that only contain a term.year) before they get counted.
The solution (thanks to my DB guru friend TC) is to move the logic from the WHERE
clause up into the SELECT
. Also, now that there are blanks instead of nulls in the second column, we can go back to a regular INNER JOIN
and still get the zero counts:
SELECT term.year,
COUNT(CASE WHEN enrol.raw_mark <> enrol.final_mark THEN 1 END)
FROM term INNER JOIN enrol on term.term_id = enrol.term_id
GROUP BY term.year
ORDER BY term.year DESC
year | |
---|---|
2024 | 0 |
2023 | 1 |
2022 | 0 |
2021 | 0 |
… | … |
I’m using Alfred and cliclick to perform rudimentary UI automation to overcome a broken Salesforce implementation (can’t bulk delete):
Following Micro.blog’s pricing changes, rather than retiring an old blog hosted with a basic account, I decided to upgrade my main account to premium and migrate the old blog to the premium account. I get to access the advanced features of Micro.blog and the Micro.blog team doesn’t lose any recurring revenue, win-win!
Here’s the process I went through:
1. Logged into Micro.blog for Mac with the old account:
1. Export an archive of the blog - File → Export → Blog Archive
2. Renamed the archive from Micro.blog.bar to Micro.blog.zip and expanded it to check the contents… the only thing I could see that was missing was the Pages
3. Manually copied the text from our 2 custom Pages in Micro.Blog for Mac to a text file (ignored /archive
and /photos
)
4. Signed out of (“removed”) the old account from Micro.blog for Mac
2. Signed in to my old account on the web:
1. Deleted the blog
2. Changed the username on the old account (I don’t think this was necessary… later I changed it back)
3. Reset my Fediverse/Mastodon name
4. Downloaded a copy of my invoices
5. Canceled my old subscription
3. Logged in to my main account on the web:
1. Upgraded to premium
2. Added a new blog (same domain as the old one)
3. Added the two pages (only one visible in nav)
4. Deleted unwanted pages
4. Relaunched Micro.blog for Mac
1. Confirmed the new (old 😉) blog was available on the main account
2. Imported the posts and images File → Import → Blog Archive
The whole process took less than an hour (including writing this post). The slowest part was the blog import which took about 13 minutes to import 52 posts and 104 images (on a 280/20Mbps wifi connection… would have been 1/1Gbps if I’d plugged into Ethernet 🤦♂️).
My only other regret is that I didn’t think to make note of the “Design” section of the old blog… the new version looks very different and I can’t remember what theme I chose or customisations I made.
Overall, the whole process went shockingly smoothly and my only (very mild) complaint is that the Blog Archive format (or some suitable alternative) doesn’t include the pages or design info. Not being able to export and import pages might have been a bigger complaint from me if I’d had more than 2 pages to copy (or if I’d forgotten to make a manual copy before deleting the blog).
Once again I’m impressed with just how well designed and executed Micro.blog is despite being bootstrapped and being developed and supported by a very small team!
If you’re in the Apple ecosystem1, Text Replacements are a powerful tool on Mac and iOS/iPadOS. If you’re using both, you can even sync your text replacements between all your devices via iCloud.
Here’s some tips to help you make the most of them:
z
or zz
for most of mine but choose what works for youzmls
→ Matthew Lindfield Seager
zacme
→ Acme Corporation LLC GmbH Pty Ltd
zaddw
→ 432 Cheerful Drive, Happyton NSW 2345
(My mnemonic is z address work)zaddh
→ 123 Long Avenue, Pleasantville NSW 2222
(z address home)zmapw
→ https://maps.app.goo.gl/HYXmwHvjLGXzYvEU6
(z map work)zmaph
→ https://maps.app.goo.gl/NtFrs1Qzn5uv1z886
(z map home)@@e
with matthew.lindfieldseager@example.com
. Even if you don’t have a long name like me this can still be a timesaver (I have 5 email addresses set up this way)zeuro
→ €
, zbaht
→ ฿
, etc (or use a code like zGBP
→ £
)zdiv
→ ÷
, zsqrt
→ √
, etczcommand
→ ⌘
, zshift
→ ⇧
, ztab
→ ⇥
, etczsquare
→ □
, ->
→ →
(typing that last one felt a bit meta)zspew
with 🥴🤢🤮
or zlove
with 🥰😍😘
zshrug
with ¯\_(ツ)_/¯
ta
→ ta
to be able to informally thank people (Note: The shortcut and replacement can be the same… Adding the “misspelling” to your dictionary might also help in this scenario);)
with ;)
to stop autocorrect changing it to 😉
;)
with 😘
if you want it to use a different winking emojimu
→ my
and Mu
to Mu
..plist
file
Microsoft have really upped their game in their efforts to repulse long-time Mac users. Apparently it isn’t enough to make their apps behave like visitors from another planet, now they are calling attention to their alienness in every way they can
Took me quite a while to figure out how to access the records in a collection
object returned by the restforce Gem…
It implements Ruby’s Enumerable
module so you can map
/collect
, reject
, slice
, inject
, etc to your heart’s content.
Seems obvious in hindsight 🤦♂️
Changing banks in Australia is a hassle, but here’s a before and after story that might help persuade you to make the effort to switch.
Without calling them out by name, let’s just say we were with a large, national Australian bank (perhaps we can call them nAb for short 😉). We had our mortgage with them along with a savings offset account and a credit card we paid off in full each month. They were our bank from late 2013 to late 2023.
Their mobile app was reasonable and they eventually came to the party and supported Apple Pay (despite pushing hard to avoid it for a while). We didn’t love their interest rate rises or fees (see below) but we didn’t think our overall experience could be much better anywhere else, banks are banks after all.
When interest rates dropped, our big bank was slow to update its mortgage rates. When interest rates rose, they were quick to update their mortgage rates and they gave us no notice. The only way to find out about rate rises was to regularly log in and check through all the transactions for a routine looking transaction with a message like “Your interest rate is w% from xx/yy/zz”. Looking back, in a period where official interest rates rose by 3.5%, our mortgage rate went up by 4.69%. When I complained about the excessive rate rises with no clear notice, they told me that according to their general terms, they only need to disclose interest rate rises in a newspaper(!!!) and on our statements (which they only issue every 6 months). Not cool.
In theory we had low-fee (maybe even fee-free) accounts but in practice we got slugged regularly. Even after they abolished the $30 fee for overdrawn accounts, they found other ways to extract their pound of flesh and keep their record profits growing:
To add insult to injury, rather than send a message saying “your scheduled transfer is about to fail due to insufficient funds”, they would wait until the next day and send a message saying “your credit card payment is late”! They have the messaging infrastructure but it’s like they deliberately choose what messages to send to maximise customer fees and bank profits. 🤦♂️
Back in 2018 I signed up with a “neobank” called Up. They added a number of clever features over time like virtual cards, “round-ups”, savers, auto-splitting and more but when I signed up (during their closed beta) they didn’t offer joint accounts and they didn’t offer home loans. I basically only used them for their fee-free overseas transactions and for Apple Pay (until our main bank finally caved and added support in mid-2019).
Up steadily improved over the subsequent 5 years and when I took a look at them again in 2023 they had added a unique take on joint accounts (you can add a “Player 2” and have a mixture of individual and joint accounts) plus they offer a home loan with no fees and a very attractive interest rate. With those two blockers removed I checked with my wife (“Whatever makes sense to you dear”) and then took the plunge.
I sent my wife a link to join and a few minutes later I received an invite to be her “Player 2”, she had already signed up, verified her identity, opened a personal account and figured out how to invite me to open a joint account. I accepted and within seconds we had a new joint savings account and new virtual debit cards linked to that account. We then made a joint application to refinance our mortgage and the whole process was shockingly easy! It was all done online (mostly on my phone) and the only slight hiccough we encountered during the process was having to cancel our home insurance and get it re-issued in both our names.
I thought I was going to talk about rates and fees (which are both significantly better with Up) but it turns out the biggest difference I’ve noticed since we switched in October is around communication. That may sound a little nebulous but there’s been lots of little things like:
Thanks to the proactive communication, I don’t have to worry about sneaky rate rises, surprise fees or dodgy transactions. I can now rely on my bank to be honest, up-front and transparent. It’s a really refreshing change!
I realise Up might not be suitable for everyone. Their offering suits us but our banking is quite straight forward. One thing that might be a deal breaker for some is that, as a neobank, they don’t have any branches. If you still deal with cash you’ll have to bank it at a post office. On the other hand, these days there’s more post offices than there are bank branches so maybe that’s not such a big deal?
They also don’t offer credit cards. For the time being we’ve kept our big bank credit card as a back-up in case we ever have problems with our Up debit cards but we haven’t used it in over a month and I suspect we’ll cancel it, probably the next time our old bank charges us an annual fee and we remember we still have it.
So, if neither of those are dealbreakers and you’re sick of your big bank, I encourage you to investigate Up (Aussies only sorry). If you decide to join, use my referral code and we will both get a $15 bonus. Or just sign up without it.
You don’t even have to go all-in straight away. I was a member for 5 years before we switched (almost) all our banking.
Enjoyed another Aussie crime novel by Chris Hammer: The Tilt 📚🎧
Well written as always and liked the way he introduced a new main character by crossing over with some supporting ones from the previous books.
Thoroughly enjoyed reading The Last Devil to Die by Richard Osman 📚
Finished Silver by Chris Hammer 🎙️📚
I’m enjoying listening to audio books while working on the house and grateful for our local library making the service available.
Finished reading: Scrublands by Chris Hammer 📚🎧
While procrastinating working on a new Rails app, I didn’t want object IDs to start at 1 but neither did I want to deal with the various hassles of using a UUID instead of an integer for the ID. My compromise was to start the ID numbering with a multiple digit number.
This requires adding an execute
statement (which is technically non-reversible) to your create_table
migration:
class CreateUsers < ActiveRecord::Migration[7.1]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false
t.timestamps
end
reversible do |direction|
direction.up do
# Set the starting ID on the users table so IDs don't start at 1
execute <<-SQL
INSERT INTO sqlite_sequence ('name', 'seq') VALUES('users', 2345);
SQL
end
direction.down do
# Do nothing (entry is automatically removed when table is dropped)
end
end
end
end
Note: The relevant sequence row gets dropped when the table gets dropped, so in this case there’s no need to define a down
action. I’ve included an empty one anyway to make it clear this isn’t an oversight.
Now when I create my first user, their ID is 1 higher than the sequence I assigned… app.example.com/users/2346
.
You can choose whatever starting number you want. If you’re running this on a different table, replace the ‘users’ portion with your own table name:
INSERT INTO sqlite_sequence ('name', 'seq') VALUES('<table>', 1734);
The above example is specific to SQLite (I’m using Litestack to simplify deployment) but presumably you could do something very similar with other databases.
I haven’t tested this but for PostgreSQL it should be as simple as changing the execute
command above to:
SELECT setval('users_id_seq', 2345);
-- '<table>_id_seq' for other table names
If you try this with PostgreSQL, make you sure test the migration works in both directions.
Couldn’t figure out why ruby debug was crashing on one machine but another “identical” machine was fine. I was about to redact my username from the crash reports when I started to wonder about its length.
Sure enough, a username longer than 20 characters triggers the bug 🐛
Can sleep easy now 😝
Australia will vote tomorrow on a modest proposal to give Aboriginal and Torres Strait Islander peoples a voice to the parliament and the executive “on matters relating to Aboriginal and Torres Strait Islander peoples”. Unfortunately, it looks like the No campaign has sown enough fear, uncertainty and doubt to prevent us from taking this small step forward as a federation.
Australia has a long and dark history when it comes to our treatment of our First Peoples. It wasn’t until 1967 that they were included in the census and it was just 40 short years ago, in 1983, that our First Peoples finally received equal voting rights and responsibilities, after more than 80 long years of being denied a fair go and pushed to the margins. The No campaign say that this proposed change introduces race into the constitution, while conveniently ignoring the fact that racial prejudice has been baked into our constitution and our culture from the very beginning.
Our First People’s have spent years gathering consensus on a reasonable path forward towards reform that might go some way towards a more just and equitable future after centuries of injustice. They are asking for voice, treaty and truth and more than 80% of First Nations people support the change we are voting on (according to the only large-scale, representative survey that has been published).
Tomorrow we have a chance to vote yes and do what Proverbs 31:8 says:
Speak out on behalf of the voiceless, and for the rights of all who are vulnerable.
In 1903, a member of the first Australian National Parliament stated that “it is impossible for the average aboriginal to understand any political question, or to vote with intelligence”. If the No vote succeeds tomorrow, I fear it will be because, 120 years later, that sentiment carries the day amongst a majority of Australians.
I’m enjoying the conversation, talks, food and location of RubyConf Thailand. Rubyists may even recognise a certain someone who gave the keynote talk, 30 years of Ruby History 🤩
Love that the organisers leaned into the “competition” with Rails World 😉
#rubyconfth
It’s not much to look at but I’m very glad for this little national treasure park just 5km from where we are living.
Final day (day 30) of #mbsept
(Is it cheating to use a collage?)
There’s a bit of a contrast between the undeveloped parts of our suburb and the new estates popping up
Day 29 of #mbsept
Combining a workout with a commute
Day 28 of #mbsept
This little guy just wants an embrace.
Day 27 of #mbsept
I love it when people build useful, thoughtful and bookmarkable tools like this highly configurable countdown timer. I was looking for a timer that would continue into the negatives; this one does that and so much more.
Don’t be put off by the plain appearance of the configuration page.