Latest Posts (12 found)
tekin.co.uk 1 months ago

Overriding Rails’ default validation error message format

This is a followup to my recent lightning talk and writeup about i18n in Rails and how it can be useful even when we’re not translating our applications. In the talk and writeup I describe how we can change the Active Record error message format from the default of and drop the attribute prefix, giving us more flexibility in how we phrase our error messages. The downside to this is that it results in the default Rails error messages being output as incomplete sentences. It’s since occurred to me that it’s possible to avoid this by replicating the default validation error messages in our app’s locale file with the attribute prefix as part of the message , rather than the format! So we end up with a locale file that looks something like this: With that in place, we’re free to define bespoke validation errors for specific attributes and models without the attribute prefix, whilst also preserving the default error messages as a fallback.

0 views
tekin.co.uk 1 months ago

10 Things You Might Not Know About Rails i18n

This is mostly a transcript of my recent lightning talk on i18n in Rails , go watch that if you prefer your content in video form. Otherwise read on to find out why I believe understanding i18n in Rails can be useful to you, a Rails developer, even if you’re not translating your applications into other languages. I’m going to share ten things I think are interesting in i18n, and explain how we can take advantage of them in our single language app code. But first a brief primer on i18n… i18n is shorthand for Internationalisation When we’re talking about i18n, it’s useful to understand the difference between Internationalisation and Localisation : The take away here is that although i18n is primarily a framework for adapting applications to support other languages, the core idea of separating content from applications code has useful properties that we can take advantage of it to improve our Rails applications, even if they only support a single language. Rails ships with the i18n gem , and there are two main components: Rails aliases these helpers in views and helper modules to the convenient shorthands of and respectively. So the most basic usage of i18n would look something like this: Primer over! On to the ten things… Even if you’re not localising your app you’re almost certainly still using i18n. That’s because it’s baked into the very fabric of the framework. For example Active Record uses i18n under the hood to generate error messages for its validations. It does so using a YAML file that ship with Active Model : Whenever you see something like “Title cannot be blank”, this is the i18n plumbing that was used to construct it. Rails i18n makes it easy to override these default errors messages. Just replicate the same key structure in your application’s locale file with your chosen phrasing: Rails will check your application’s locale file first, falling back to the defaults only if the message isn’t defined there. As well as overriding this global phrasing, it’s also possible to override the error messages for a given attribute name: Or you can get even more granular and override specific attributes on a given model. For example if we have the following model and validation: It’s possible to set a bespoke error message for this specific model and attribute: As well as overriding individual validation messages globally and for specific attributes, it’s also possible to change the actual format of error messages. By default, errors are constructed with the familiar format of . This is defined in the same YAML that ships with Active Model alongside the default error messages: This format works as a reasonable default to ship with the framework as it gives us mostly coherent error messages out of the box without us having to do anything, but it lacks fidelity and can result in some clunky and unfriendly sounding messages. For example, take this validation: If we go with the default format we end up with . Which is fine, but we can definitely do better. My personal preference on the apps I work with is to remove the attribute prefix entirely: This frees me from having to make my error messages work with attribute’s name upfront, allowing me to write more user-friendly and coherent error messages: Now changing the default format does come at a cost: without the attribute name prefix, the default messages that ship with Rails no longer form complete sentences. This forces me to write bespoke error messages for each validation I add to my app: Personally I’m happy to pay this cost for the benefit of improving the user experience. So whilst you may not want to do this on an existing app with thousands of error messages to backfill, it might be something you consider next time you run and are starting from a clean slate. Update : There’s actually a fairly neat way to avoid this cost, check out my follow-up post on how . As well using i18n to manage and override the error messages for the built-in validations, you can also use it to manage the messages for your custom validations. Let’s look at a custom model validation: Here the error message is inlined as part of the validation code. Instead of doing this, we can pass in a symbol identifying the error, and define the corresponding message in the locale file using the same key structure as the built-in validations: This results in more concise and compact model code, but also equally as nice is that it removes a presentational concern — the specific phrasing of the message — our the business logic. It’s also possible to interpolate both the attribute name and the value of the attribute directly into the error message, which is useful for custom validations that are used across multiple attributes and/or models: Another place where Rails makes use of i18n under the hood is the form helpers: specifically, the label helper methods. By default the label helper methods humanize the attribute name to arrive at the label text: One way to override the label text is to pass in a string to the helper call like so: I think a better way is to define label overrides in the application’s locale file: Not only does this keep the view code more concise and less noisy, but it has the added advantage of defining this label override in one place: now any other forms that display the same label will automatically get the overridden text without you having to manually copy the literal string to every template. And as with form errors, label overrides can be defined per-model as well as globally: i18n has sophisticated support for pluralisation that we can leverage to simplify our app code. As an example, we can display the stock availability of a product by calling out to i18n like so: And then define the phrasing based on the number of items in stock in our locale file: Much neater than a bunch of conditional logic in a helper method! As well as being available in Rails controllers and views, i18n is also integrated into Action Mailer, allowing you to organise content for mailer templates in local files. Mailer get one extra feature of controllers: the ability to specify email subject lines in locale files: Again, this enables us to move another presentational concern out of our application code and put it somewhere more appropriate. There is also a meta benefit to defining all these bits of content (mailer subject lines, form labels, validation error messages) in an application’s locale file: it puts them all in one centralised place, making them easier to change, check for consistency and generally manage. It also makes the content more accessible to non-developers such as designers and product folks. Right at the top I mentioned the method that is used to output localised times and dates. The way this works is by defining our own time and date formats in our locale file using the same format specification as Ruby’s : Here we’ve defined a short date format that will be familiar to my UK-based audience: day/month/year. To render a date using this format we simply pass in the format identifier to (or its alias): or with the shorthand: Now my American readers may be confused by this short format, as across the pond the preferred way to display dates is actually month/day/year. So whilst you may not need to localise your entire application, you might want to consider localising your date rendering, especially if you have customers both sides of the Atlantic. To do this you would define a US region specific English locale file: Then update your controller code to automatically switch locale so the user gets dates formatted appropriately for them. There are many ways to do actually do the locale switching, but at a high-level, you identify the user’s specific locale and then make sure your controller actions are executed with that locale. Here’s an example of doing that by sniffing the user’s locale from their browser headers: (The exact mechanics of the method is left as a separate exercise, but you could do worse than use the http_accept_language library) Little known fact: the i18n library supports locale files defined using Ruby! Now why would you do this, other than a burning hatred for YAML? Well one reason is that Ruby-based locale files allows you to make use of procs to dynamically generate content. Below is an excerpt from a Ruby-based locale file from one of my applications. It uses a proc to format dates as academic years, changing the output depending on which side of September the date is: Now this could just as easily be achieved using a standard Rails helper method. But for me I like the consistency and clarity of using a single mechanism ( ) for formatting dates across my application code, rather than with a mishmash of calls to bespoke helpers, , , etc. This is the part of the lightning talk where I make a meta point about what I’ve covered. I won’t write that up here, but if you’re interested go watch the video from minute 9. Internationalisation — the process of abstracting content and other locale-specific things away from application code itself Localisation — the process of adapting software to support different languages and regions (maybe using i18n) locale files where textual content is organised and stored per-language (normally as YAML) two helper methods: for outputting text content from the locale files for localising dates and times

0 views
tekin.co.uk 11 months ago

The Ruby community has a DHH problem

David Celis recently published a thoughtful piece on Rails governance in response to the latest troubling blog post from DHH, the creator of Rails. Like David, I’ve also been troubled by DHH’s recent output and the harm it is causing to the Ruby community. I think it’s worth taking a moment to analyse DHH’s post in more detail and make it clear exactly why it’s so problematic. In his post, DHH complains that London is no longer a city he wants to live in because it is now only a third “native Brit”. His use of “native Brit” is as a proxy for “White British”. The implication is clear: if you are not White, you are not British. In the same post he praises Tommy Robinson (actual name Stephen Christopher Yaxley-Lennon), a right-wing agitator with several convictions for violent offences and a long history of association with far-right groups such as the English Defence League and the British Nationalist Party . He then goes on to describe those that attended last weekend’s far-right rally in London as “perfectly normal, peaceful Brits” protesting against the “demographic nightmare” that has enveloped London, despite the violence and disorder they caused . To all of that he ads a dash of Islamophobia, citing “Pakistani rape gangs” as one of the reasons for the unrest, repeating a weaponised trope borne from a long since discredited report from the Quilliam Foundation, an organisation with ties to both the the US Tea Party , and Tommy Robinson himself. A trope that exists despite the fact that the overwhelming majority of convicted child sex offenders are white men , with Asian men in fact under-represented.

1 views
tekin.co.uk 1 years ago

Yearnotes 2024

After a four year absence it’s time to get back on the yearnotes train! 2024 was a good year for Join Together . Our main focus was a chunky project for the National Education Union (NEU). As well as replacing their online join process, we also built them a bespoke service for upgrading existing student members to full membership after graduation. The NEU are one of the largest (and more tech-savvy) unions in the UK, so it was a pretty big deal for us to win the project. And despite a fair amount of complexity and esoteric requirements, we totally nailed it, leaving the folks at the NEU over the moon with what we delivered. We also shipped projects and updates of varying sizes for a good number of our existing union clients. It’s become clear that updates and ongoing development with existing clients will make up a healthy chunk of our revenue, which is helpful, as bringing new unions onboard can be a long and arduous process (it took a whole year from first contact to signed contract with NEU). By the tail end of the year Join Together’s projects were wrapping up and a previous client of mine got in touch asking if I’d be available for a short contract. They needed urgent help shipping some major updates in a short space of time. With no new Join Together projects starting until the new year I was able to take them up on their offer, and spent the last couple months of 2024 working on their mission. It turned into quite an intense project, working mostly solo to make significant changes to their user modelling (primarily separating admin-related code/authentication from their generic model (1) ). In the end it all went smoothly, which was very pleasing, and I was wrapped up in time for Christmas.

0 views
tekin.co.uk 2 years ago

Different ways to use “–patch” in Git

I’ve written previously about using to interactively stage changes . But did you know that you can use (aka ) to similar effect with other Git commands? Let’s take a look… is great for temporarily stashing changes that you want to apply later, and handily it also supports selectively stashing changes with the flag: Bonus tip: you can also selectively stash entire files using to disambiguate the command from the paths you want stashing: You can use the command to discard local changes and restore files to their last committed state. It can also be called with the flag to interactively select specific hunks to discard: Note the different phrasing of the prompt on the last line: Here we are choosing the changes we want to discard . Be careful, this is a destructive change, and because these are unstaged and uncommitted changes git won’t be able to help you recover the changes once they’ve been discarded!

0 views
tekin.co.uk 2 years ago

Appearance on the Maintainable Software podcast

I recently appeared on the Maintainable Software podcast with Robby Russell. It was fun chatting with Robby about some of the things that help keep software maintainable. Check it out !

0 views
tekin.co.uk 3 years ago

How to use introspection to discover what is exhausting your ActiveRecord connection pool

This week I wrote about the reasons why you might need an ActiveRecord connection pool larger than the number of configured Puma threads . Since then Ben Sheldon has pointed out that apps running embedded job workers (for example Sidekiq in embedded mode , GoodJob in async mode or Sucker Punch ) will also be creating extra threads and therefor may need a larger thread pool. In this follow-up post I’m going to describe the technique I used to uncover the source of the connection pool contention for the app I’m working on, and how you can do the same if your seeing mysterious exceptions and don’t know where they’re coming from. These connection timeouts were a long-standing mystery in our app. Although they didn’t happen with great frequency, they were happening often enough to warrant some further digging rather than letting them become another broken window. To figure out what was going on I employed some introspection on the connection pool. Whenever a thread asks for a connection from ActiveRecord, it is assigned one from the connection pool. The connection itself stores a reference to the assigned thread as its . We can inspect the assigned threads in the connection pool to learn a bit more about them:

0 views
tekin.co.uk 3 years ago

Why the advice to have a connection pool the same size as your Puma threads is (probably) wrong for you

The standard advice goes: set your Rails database’s connection pool to have as many connections as you have Puma threads . The idea being that you should only need as many connections as you have concurrent threads. This advice is coming from a good place, as most of the time you will be constrained on the number of connections you have available to your database. The nuance missing from that advice is that it assumes no additional threads are ever spawned during your apps operation! Now although you might know your code inside and out and be 100% certain that you don’t create additional threads anywhere in your code, chances are Rails is creating additional threads without you realising it… On a basic level, the way ActiveRecord’s connection pool works is that it assigns each thread that asks for a connection its own separate connection , which the thread then releases once it’s finished. Using a pool like this means that many threads can be querying the database at the same time, so many more requests can be processed in parallel. If your app’s Puma config sets the maximum number of threads to 5, then you can normally expect there to be at most 5 threads asking for their own database connection, hence the advice to set the pool size to the same as size as the number of threads.

0 views
tekin.co.uk 4 years ago

List your Git branches by recent activity

Even if you’re diligent and regularly delete merged and stale branches you may still find it hard to pick out a particular branch from the alphabetically-sorted output of . How about something more useful, like seeing them listed based on their freshness? The command accepts a option which we can use to list our branches based on the last committer date: We can also use the option to include the exact time and see just how fresh each branch is: Or for something more friendly and easy-to-parse we can ask for a relative date: That’s better! Let’s add this as a alias to our Git config:

0 views
tekin.co.uk 5 years ago

How focused commits make you a better coder

One of the core practices I encourage in developers when joining a new team is shaping changes into small, focused, atomic commits. For folks that are used to committing code in a haphazard, laissez faire manner this is sometimes dismissed as pedantic fussiness: if the code works, it works and that’s what’s important! Well I strongly disagree. Here I want to present a few of the ways taking the time to shape small focused commits help you to be a more effective developer and ship better software. First things first, let’s acknowledge the fact that doing this well does take a certain level of tooling knowledge and practice. That means getting comfortable selectively staging changes ( ) and revising your working history as you go ( ). But once you get the hang of it, it becomes second nature and not doing it will start to feel a bit icky, like skipping test coverage for a new piece of code, or not brushing your teeth before bed. As well as tooling and practice, it also take a level of discipline. That’s because to understand how a piece of work might be delivered as small focused changes, you have to slow down and think about how it could be sliced up and delivered iteratively. This is actually the first benefit in disguise! Figuring out a plan for the work that breaks it down in terms of small, iterative steps is a useful strategy for making a large or difficult problem manageable. I personally do this with a checklist of tasks that I write down before starting a piece of work. I then update and adjust the list as I progress through the work and my understanding grows and changes. The tasks don’t always map one-to-one to commits, but they often do. @tomstuart talks more about this and other strategies for breaking down large and difficult problems in his talk Get Off the Tightrope .

0 views
tekin.co.uk 5 years ago

Why Git blame sucks for understanding WTF code (and what to use instead)

You’re happily working your way through a codebase when you happen upon some code that makes you stop and think: What the…!? Maybe it’s a method that’s doing something surprising. Or perhaps it’s doing something completely unsurprising, it’s just doing it in a surprising way. When this happens you might instinctively reach for to help you figure out what’s going on. After all, gives you the commit that most recently touched the line, and often that’s enough to point you in the right direction. But often it isn’t and you’re none the wiser. That’s because: If you only use you’re limiting yourself to a one-dimensional perspective of the code you’re trying to understand. Wouldn’t it be better to view things in 3D!? Thankfully Git has some pretty powerful search tools built right in. Let’s take a closer look at some of the tools at our disposal. If is entry-level history search, (also known as “the pickaxe”) is how you take things to the next level. It lets you search for all commits that contain a given string:

0 views
tekin.co.uk 5 years ago

Better Git diff output for Ruby, Python, Elixir, Go and more

The regular Git users amongst you will be familiar with the diff output that breaks down into “hunks” like so: The first line (starting ) is known as the hunk header, and is there to help orientate the change. It gives us the line numbers for the change (the numbers between the ), but also a textual description for the enclosing context where the change happened, in this example . Git tries to figure out this enclosing context, whether it’s a function, module or class definition. For C-like languages it’s pretty good at this. But for the Ruby example above it’s failed to show us the immediate context, which is actually a method called . That’s because out of the box Git isn’t able to recognise the Ruby syntax for a method definition, which would be . What we really want to see is: And it’s not just Ruby where Git struggles to figure out the correct enclosing context. Many other programming languages and file formats also get short-changed when it comes to the hunk header context. Thankfully, it’s not only possible to configure a custom regex specific to your language to help Git better orient itself, there’s even a pre-defined set of patterns for many languages and formats right there in Git . All we have to do is tell Git which patterns to use for our file extensions.

0 views