attributesacts as a way to add a typecast layer to a model. The attribute may be backed by a database column, or it can be a "transient" attribute that only exists for the lifetime of a model instance.
JSON columns can be very lightweight and flexible ways to represent data when you are not sure exactly what you schema will look like in the future, and can be a way to defer decision making until more information is known. Adding a custom type and an attribute to represent a JSON column can provide more robustness.
This can look like this:
# In some model...
attribute :my_custom_attribute, Attributes::MyCustomAttribute.new
# The "bridge" between the attribute definition and the custom attribute type
class Attributes::MyCustomAttribute < ActiveRecord::Type::Json
return value unless value.is_a?(String)
parsed = JSON.parse(value)
# An ActiveModel-enabled class that can run validations, define custom comparators, etc.