First, let’s have in mind two case studies that perfectly illustrate this matter:

State machines: you need to run different validations depending on the current state of the record.

Progressive completion: one may fill the form at step n only if all previous steps are completed.

What tools ActiveRecord provides to deal with this?

Create an STI and mutate the type of your record.

— Upside: you can run “always required” validations and “type-specific” validations.

— Downside: you cannot run “type-specific” validations for multiple types all at once, you would have to mutate your object for each type and merge errors.

— Upside: you can run “always required” validations and “type-specific” validations. — Downside: you cannot run “type-specific” validations for multiple types all at once, you would have to mutate your object for each type and merge errors. Use contexts.

— Upside: you can run “always required” validations and “type-specific” validations.

— Downside: your models remain messy/heavy and you need to call valid? for each context you need to validate.

You can also use other tools such as dry-validation.

— Upside: powerful tool.

— Downside: add yet another dependency, learn yet another tool. Also you get out of ActiveRecord and lose many benefits (all errors in record.errors , i18n lazy lookup, …). It’s a great tool but rather for complex cases.

None of the above fits my expectations: a modular, lightweight solution that needs no extra dependency and relies solely on ActiveModel::Validations.

Build an object with only the behavior that is required for the context at hand.

Said differently: instead of having a model with all validations and running only the ones required, have a model with only always-required validations and extend it with context/state -specific validations.

Upsides:

No extra dependency

Completely modular

No extra DSL to learn

Lightens your models

Downsides: