I recently had to build an interesting model that stored values for a JWT in order to implement an allow list style revocation strategy. After some feedback from another developer it became clear the interface for that model needed to be optimized. Here’s a quick description of the “behavior” of that model:

  • All of the columns are read only after creation
  • It’s dependent on a User record assocation - thus requires a validation
  • It has an expiration time that is also stored, but set to a pre-determined amount of time
  • It’s jti column value is generated by the model itself since it is a “propietary” action per record

Given this set of behavior we can infer that since the expires_at column and jti are both self generated in the model code, the only attribute required for creation is the associated User record.

This made the code for the model drastically simpler and also gave me constraints to artificially impose on the model itself, preventing updates and making attributes read only.

Rails provides a nifty way of doing these things but this principal can be used with any language/framework.

# Model Class Example
class AllowListedToken < ApplicationRecord

  # ...
  attr_readonly :jti, :user_id, :expires_at # prevents update calls on these columns

  EXPIRATION_TIME = 1.day.from_now

  belongs_to :user

  ## after_initialize is called when the object is created but before the `INSERT` is called
  ## allowing for object transformations to take place before the record persists.
  after_initialize :set_generated_values

  # ...


  def set_generated_values
    self.jti = JtiGenerator.new.jti
    self.expires_at = EXPIRATION_TIME

# Usage
user = User.find(id)
AllowListedToken.create!(user: user)

The moral of the story is to take time to consider how your model should behave and what limitations or defaults you can implement to ensure that the constraints you need to fulfill are fulfilled. This helps ensure the maintainability and simplicity of the model and helps to align the expectated behavior and usage.