Caching Rendered PDFs in Rails with Active Storage

As I was working on easyclientlog.com, building its invoice system that allows freelancers/consultants to generates PDFs. Working with PDF generating is that, its takes time and CPU cycles. But most of the time the pdf only needs to be generated once, and the content seldom changes. So rendering the same thing over and over just didn’t feel right. So I used a simple trick that I’ve followed in many of my prior Rails apps: upload the PDF on first render, store it using Active Storage, and reuse that file on the next access.

The Idea: Cache on First Render

When the PDF is generated the first time, we attach it to the record using Active Storage. This could be an invoice, report, or any other object. The next time we need to show or download the PDF, we skip the rendering step and simply serve the uploaded file.

This avoids unnecessary rendering and speeds up response times, especially for large documents or when generating in bulk.

A Complete Guide to Rails.current_attributes (ActiveSupport::CurrentAttributes)

Managing context like the current user, store, client, or request ID across controllers, models, jobs, and services in a Rails app has always been a little messy. Before Rails 5.2, you might’ve reached for Thread.current, class_attribute, or even @@class_variables to store this kind of state. But these are not thread-safe and can cause hard-to-debug issues in concurrent environments.

Rails 5.2 introduced a clean solution: ActiveSupport::CurrentAttributes.

This article covers what it is, why it exists, how it works internally, how to use it safely across web and background jobs, and how to test it properly.


📜 A Quick History

ActiveSupport::CurrentAttributes was introduced by DHH in Rails 5.2 to simplify access to per-request or per-job context in a thread-safe way. It replaced older, hackier methods like:

1
2
Thread.current[:current_user] = user
ApplicationRecord.class_attribute :current_client

These approaches were shared across threads — fine in development, dangerous in production.

CurrentAttributes changed that by giving you a dedicated, Rails-friendly API for storing context that’s isolated per request/job.

How I Use Tailscale to Host a Public App From My Laptop

I live in India, and getting a static IP at home isn’t straightforward. You usually have to go through customer care, sometimes upgrade to a more expensive plan, and even then it’s not always guaranteed. I wanted a simple setup to make one of my apps publicly accessible without paying a lot or dealing with all that.

So here’s what I did. I used Tailscale, AWS Lightsail, Docker, and Nginx Proxy Manager to expose my laptop to the internet, safely and securely. And right now, I’m running https://easyclientlog.com - next.js application and https://api.easyclientlog.com - ruby on rails application, using this exact setup.

Getting a Static IP

I signed up on AWS and went to Lightsail. Picked Mumbai as the region and selected the cheapest Ubuntu server. AWS gives you a static IP for free (in lightsail) as long as it’s attached to a running instance, and the server itself is free for the first three months.

This server acts like a relay. It’s the public-facing machine. I don’t run the actual app on it, it just receives the request and forwards it to my laptop at home.

Ruby `Data` Class – A Convenient Way to Create Value Objects

Ruby’s Data class was introduced in Ruby 3.2, offering a convenient way to define value objects. The concept of value objects was popularized by Martin Fowler and Eric Evans through their books and articles. In the real world, we often represent properties like coordinates on a map (x, y) or the speed of a car—using primitives such as integers or strings. While these work, they lack the semantic clarity and behavior of custom types. This is where value objects shine. They encapsulate meaning and behavior around a set of values.

Ruby’s Data class was created to provide a native way to represent such concepts. Struct does fullfil this requirement, except the part of immutability. Ruby Structs are mutable. Some example of use case for value objects are to represent money, email address, co-ordinates, etc.

Lets now explore ruby Data, keeping in my the characteristics of a value object as shared by Marin Fowler. A value object should have:

  • No Identity
  • Immutable
  • Equality by Value
  • Small and Simple
  • Reusable

Let’s explore how Ruby’s Data class supports these characteristics.

class Data

Data is a core Ruby class, so no external gems are needed.

Here’s a simple example:

1
MarsRover = Data.define(:name, :x, :y)

Understanding Ruby’s `tap` — A Powerful Debugging and Configuration Tool

Ruby’s Object#tap is a small but powerful method that often goes unnoticed. It allows you to “tap into” a method chain, perform some operation, and return the original object—regardless of what the block inside returns. This makes it particularly useful for debugging, configuration, or inserting side effects without breaking the flow of your code.

A Simple Use Case: Debugging

Take the following example:

1
n = [1,2,3].map { _1 % 2 }.map { _1 * 10 }

This returns [10, 0, 10]. But what if that’s not what you expected? You might want to inspect the result of the first map:

1
2
3
4
n = [1,2,3].map { _1 % 2 }
puts n.inspect
n = n.map { _1 * 10 }
puts n.inspect

Using tap, we can make this more elegant:

1
2
3
4
n = [1, 2, 3].map { _1 % 2 }
             .tap { puts _1.inspect }
             .map { _1 * 10 }
             .tap { puts _1.inspect }