Zebra stripes

image by David Clarke

A Active Model validator for Stripe Ids

In my article on using validators, the example was pretty simple.

RailsConf 2024

I'm co-chairing RailsConf 2024 in Detroit May 7–9. Come and join us 

In this example, we discuss the real production use of a more complex validator extracted from the billing code in AnswerThePublic. We use Stripe exclusively for billing in our applications.

Every object on Stripe—a transaction, an invoice, a customer—has a unique and similarly-patterned identifier. We often need to store these ids locally in our application to interact with Stripe so that they can interact with Stripe and we can collect payments from customers.

We want to make sure we don’t store incorrectly-formatted Stripe ids in our application’s database or we might introduce errors in our eventual API calls.

Instead of…

…using a regex in validates when setting a format for the stripe_id:

class Subscription < ActiveRecord
  validates :stripe_id, format: { with: /\Asub\_[A-Za-z0-9]{8,}\z/ }
end

Use…

…a validator class to ensure the format of a Stripe id

# app/validators/stripe_validator.rb
class StripeValidator < ActiveModel::EachValidator
  VALID_PREFIXES = %w[ch cus in plan sub txr].freeze

  def validate_each(record, attribute, value)
    record.errors[attribute] << (options[:message] || 'is not a valid Stripe ID') unless regex(options[:prefix]).match?(value)
  end

  def check_validity!
    raise ArgumentError, 'Requires :prefix' unless options.include?(:prefix)
    raise ArgumentError, 'Invalid :prefix supplied' unless VALID_PREFIXES.include?(options[:prefix].to_s)
  end

  private

  def regex(value)
    /\A#{value}\_[A-Za-z0-9]{8,}\z/
  end
end

…then use it in models like so:

class Subscription < ActiveRecord
  validates :stripe_id, stripe: { prefix: :sub }
end

class Invoice < ActiveRecord
  validates :stripe_id, stripe: { prefix: :in }
end

Why?

I wanted to provide a useful, concrete example where extracting a validator (in this case for a popular and well-used service) can help keep your code clear and concise.

This validator can be reused across multiple models in our own domain that have records on Stripe accessible via their API, reducing potential errors, and ensuring our application data matches up to our data on Stripe.

The extra structure improves the clarity of your code when you, or another developer, come back to it. There’s now one place in our app to validate the format of all our Stripe identifiers.

Why not?

You could use a specific regex for each model’s validation, but you’d lose the extra expressiveness in the model from naming and extracting the validator.

“This field must be a stripe id with the prefix ‘in’” is clearer to read and understand than “This field must match a confusing regular expression”.

Brighton Ruby 2024

Still running UK’s friendliest, Ruby event on Friday 28th June. Ice cream + Ruby 


Last updated on September 22nd, 2019 by @andycroll

An email newsletter, with one Ruby/Rails technique delivered with a ‘why?’ and a ‘how?’ every two weeks. It’s deliberately brief, focussed & opinionated.