TIL #1 - Cleaning up pagination

May 07 2020 • 2 min read

TIL is going to be a series of posts where I share the details of new information (mostly code-related) that I recently encountered and deem worthy of writing about.

This post, the very first of the aforementioned series is about meta-programming in Ruby. I learnt how to define a Ruby class at runtime while trying to clean up my implementation of list pagination for a GraphQL API.

Before clean-up

class PaginatedUsersType < Types::BaseObject
  field :data, [Types::UserType], null: false
  field :pagination, Types::PaginationType, null: false
end

Before learning about how to define a class at runtime, to paginate a list I had to manually write several classes like the one above for each list being paginated. I kept thinking it was tedious and that there had to be a better way.

Defining a class at runtime is made possible with Object.const_set which allows you to define constants. The first argument is the name of the constant you're setting. The second is the value of that constant.

After clean-up

def paginate(type)
  type = "Paginated#{type.demodulize.gsub("Type", "").pluralize}Type"
  Object.const_set(type, Class.new(Types::BaseObject) do
    field :data, [data_type], null: false
    field :pagination, Types::PaginationType, null: false
  end)
end

# example usage
paginate Types::UserType

For my use-case, instead of manually having to write near similar classes for each of the lists I wanted to paginate I could instead call paginate, pass in the underlying type of the list and have the class generated for me saving me time and eliminating unneeded code.

A gotcha

Object.const_set does not overwrite previous definitions, it creates new ones. To prevent duplicate definitions you can use Object.const_defined? to check if a constant is defined and Object.const_get to get an already defined constant.

That's all for today! 😁