How do popular gem writers take advantage of blocks?

Do like ActiveRecord, Devise, and Grape by encapsulating inner logic and providing a simple way to define the code that matters dynamically.

You use dynamic methods every day!

Imagine if instead of doing this

You would have to do this

This is the current implementation of ActiveRecord::Base.transaction.

Why would I bother myself to handle ActiveRecord inner mechanics to simply execute an SQL TRANSACTION?

It makes no sense. Isn't it?

Let's have a look at the 2 following case studies to help understand the mindset you must have when writing this kind of code for your fellow developers.

Case Study #001: Products API Wrapper

The list of products on my eCommerce website is maintained by a 3rd Party API.

This 3rd Party API provides a simple way to list new product_ids and fetch information for a specific product.

So, every week, we refresh our product list.

The current code in our weekly task goes as followed

Seems ok. But what are the problems with that?

I developed an API wrapper. You, as a user of this wrapper, must acquire too much knowledge before using it properly (how to parse the response body, what errors to handle, etc...).

Also, let's assume that the Products API evolves and we can now get a list of products to remove from our list.

I would add a Products::APIWrapper.list_products_to_remove.

And you would have to literally deal with the same code on your side to parse the response and handle errors, while you would just want to focus on the main task:

Remove the fetched products from our list.

Now, allow me to rewrite the wrapper to give you the same experience as ActiveRecord::Base.transaction

It seems to be more pleasant to use. Isn't it?

Why is it good to do so?

Because it allows you, as a user of my wrapper, to put your 100% focus on one thing. In this case: What to do with this product data?

This is the kind of reflex that gem writers tend to acquire more rapidly because their code is public, they want to make it as simple as possible for the users of their library.

Case Study #002: Locking Mechanism for background jobs

When your system consumes a consequent amount of background jobs that can be triggered by webhooks, a common problem that this kind of system encounters is duplicate jobs that run concurrently.

This is clearly a risk to your data integrity.

A common pattern to solve this issue is to lock the first running job and unlock it once your data manipulation has been completed.

The duplicates (jobs with the exact same arguments) will then see that the job is locked and will fail silently.. or not (depending on your strategy).

Here is a common implementation that I often see:

The first SyncProductsWorker job with resource_id = 42 is being performed.

It locks the job.

Simultaneously, a second SyncProductsWorker job with resource_id = 42 is performed. In this case, locked?(resource_id) is true and the job returns silently.

Cool. But like the first case study, as a user of my implementation of a background job locking mechanism, you must acquire knowledge to be able to use it in your perform.

Let's have the same approach as in the first case study

Here, the business logic (record manipulation, etc..) is clearly the part that we want the developer to focus on.

By using lock! with a block, your main focus is simply to handle the business logic for SyncProductsWorker.

You don't bother yourself with the manual locking and unlocking of the resource.

Why would you?

Conclusion

When you design a piece of code that will be used and reused, ask yourself:

  • what is the dynamic part of this code?

  • what is the minimum knowledge required that the users of this code must know?

  • Why would they have to manually deal with inner logic that could be easily encapsulated in the method and provide a block to dynamically implement the business logic?

Quick heads-up:

💚