Maksym Prokopov personal blog
Idea is a something worth sharing

Adding PDF previews to ActionText with ActiveStorage

15.09.2025

Rails is a great framework, once you stop thinking in Java patterns and embraice DHH way of writing web apps.

Interestingly, Basecamp uses exactly the same ActiveStorage implementation, but shows full-size and download links alone with the PDF attachment. But if you only follow the guide, it’s hard to implement by your own without some gotchas.

These gotchas I want to capture here in this how-to article. It assumes reader followed official guide and stuck with PDF thumbnails preview implementation.

Read More…

Stop writing data migrations

15.09.2025

As many of us, I was writing data migrations as a part of database schema migrations.

That’s a bad practice, and here is how I perform the same nowadays.

Example

Migration from the plain text fields to rich text fields with ActiveText. How?

  1. Create a migration to add action_text_rich_texts table.
  2. Create a migration to rename old column containing plain text to the field prefixed with old_
  3. Write a rake task to convert the data from old column to the new place.
  4. Write a migration to remove old content after everything went smooth

Steps 1-2-3 are done within a single new PR. Once the schema is in the new state, I perform rake tasks for data movement.

Read More…

Vanilla Rails is Enough

06.09.2025

Vanilla Rails is Enough

Argh, some things I should have read earlier! One of these is this brilliant blog post Vanilla Rails is plenty had a great influence on me.

Here is the list of gems i’m ditching out of my Rails projects.

- knockout-rails
- paloma
- simple-form
- rails-timeago
- carrierwave
-- we are here --
- devise
- haml
- resque
- resque-web
- resque-scheduler
- factory-bot
- rspec
- bower-rails
- sprockets
- google-visualr
- recurring-select
- seleect2-rails
- pundit

adding a few

Read More…

Get GitHub Teams CLI Snippet

17.07.2025

GitHub CLI is a nice way to query GitHub API locally

gh api orgs/<org>/teams | jq '.[] | {name,slug}'

Travel Time and Date in Rails Console

28.06.2025

Adding RSpec support to VSCode

in Gemfile, and Ruby-LSP from Shopify

group :development do
  gem "ruby-lsp-rspec", require: false
  gem "ruby-lsp-rails", require: false
end
include ActiveSupport::Testing::TimeHelpers

travel_to 2.days.ago

Legacy Rails project and modern Assets Pipeline

22.05.2025

One of the projects I’ve started a long-long time ago still powers the business. It was quite a journey starting from Rails 2, than Rails 3 and so on up until currently Rails 7 with plans to bump to Rails 8.

The project has live updates with Server Sent Events and a sidecar microservice to keep the connections and push updates. Quite similar to what DHH have done with ActiveCable, but based on different stack.

Read More…

Hosting for Static Assets

05.04.2025

I lived on GitHub Pages for static hosting for a long time. For example, this blog been hosted as https://mprokopov.github.io for years, just with the custom domain https://prokopov.me

But last week I finally moved everything to CloudFlare Pages and quite happy about it!

The major driver for the change was a hassle of management github-pages. It’s either your have to keep everything in a separate branch, with the name like github-pages, or have to use completly different repository. I chose the latter, and for a long time kept using two repositories, one with Hugo sources, second with rendered HTML. It was a bit painful to manage two git repositories in the same working tree, hugo build renders the website to the public folder. In addition, the repository with HTML should be public.

Read More…

Ghostty Remap Cmd to Control under macOS

02.04.2025

We all love the fast software. Ghostty is crazy fast.

What I use with my macos keyboard comes from Emacs. Namely, Cmd+ instead of Control+ for navigation in shell. Jump to a beginning of line, +a, to the end of line +e.

Why?

  1. Because my macOS keyboard doesn’t have TWO Ctrl keys on both sides.
  2. Try to compare ctrl-a vs cmd-a yourself 😀

Here is an example of a config extracted from this discussion.

Read More…

Trigger Jenkins job on EC2 termination event

24.03.2025

Example of the Python code to catch EC2 termination signal and ping Jenkins to run termination job. Useful to execute graceful shutdown if you’re using EC2 spot instaces.

You will need Jenkins Generic Webhook plugin.

import json
import boto3
import urllib3
import os

JENKINS_JOB_TOKEN =  os.environ.get('JENKINS_TOKEN')
JENKINS_URL =  os.environ.get('JENKINS_URL')

def invoke_jenkins(target):
    http = urllib3.PoolManager()
    return http.request('POST', f"{JENKINS_URL}/generic-webhook-trigger/invoke?instance={target}", headers={'Authorization': f"Bearer {JENKINS_JOB_TOKEN}"})

def find_target(instance_id):
    ec2 = boto3.client("ec2")
    tags = ec2.describe_tags(Filters=[{'Name': 'resource-id', 'Values': [instance_id]}])

    for tag in tags['Tags']:
     if tag['Key'] == "Name":
        return tag['Value']

def lambda_handler(event, context):
    instance_id = event["detail"]["instance-id"]

    target = find_target(instance_id)

    if not target:
        return {
            'statusCode': 422,
            'body': f"target for {instance_id} not found"
        }

    print(f"Jenkins termination job called for instance: {instance_id} target {target}")

    resp = invoke_jenkins(target)

    print(f"Jenkins response status {resp.status}")
    print(f"Jenkins decoded response")
    print(resp.data.decode('utf-8'))

    return {
        'statusCode': resp.status,
        'body': json.dumps(resp.data.decode('utf-8'))
    }

HashiCorp Nomad reschedule a Job

22.03.2025

HashiCorp Nomad Job

It’s often the job increases retry counter, but there is no time to waste after the root cause of failing job being removed.

Here is the way to enforce re-run of the job. Unfortunately, I couldn’t find any means to achieve the same from the UI

nomad job eval -force-reschedule rundeck