Thoughts on Interfaces for Models
100 Days to Offload ChallengeThis is post 2 as part of the #100DaysToOffload challenge. The point is to write 100 posts on a personal blog in a year. Quality isn't as important as quantity so some posts may be a little messy. Read other posts in this challenge.
I recently had to build an interesting model that stored values for a JWT in order to implement an allow list style revocation strategy1. 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
Userrecord assocation - thus requires a validation
- It has an expiration time that is also stored, but set to a pre-determined amount of time
jticolumn 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
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 # ... private def set_generated_values self.jti = JtiGenerator.new.jti self.expires_at = EXPIRATION_TIME end end # 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.
 Allow List Revocation Strategy: This is a token revocation strategy that uses stored values to validate that a token is valid. Look ups for the token are used to ensure it's valid. If a token is not found in the table, then authentication fails. Revoking is essentially destroying a record in the token table. Devise based resource.