ActiveRecord anti-patterns which you should avoid. Part 1: Conditional validations
Imagine that you develop an e-commerce platform. Your basic model is Order.
You would have some constraints which don't have to be valid before order is placed:
validates :order_items, length: {minimum: 1}, unless: :cart?
validates_presence_of :delivery_address, :delivery_type, :payment_type, unless: :cart?
You would alse need some validations which have to be checked on cart submition:
validates_inclusion_of :delivery_type, in: :available_delivery_types, on: :being_placed_via_cart
validates_inclusion_of :payment_type, in: :available_payment_types, on: :being_placed_via_cart
validates_acceptance_of :shop_rules_acceptance, :terms_of_anti_spam_policy_acceptance, on: :being_placed_via_cart
... but not always:
validates :electronic_order_rules_acceptance, acceptance: true, if: :electronic_order?, on: :being_placed_via_cart
Others should be checked only if the order is passed through an external system.
validates_presence_of :external_id, on: :being_placed_via_api
All the above leads to situations in which you get confused which validations would be triggered on certain save.
So... What to do instead?
I suggest using form/command objects.
class PlaceOrderViaExternalApi < PlaceOrder
validates_presence_of :external_id
end
class PlaceOrder < Command
#...
end
class Command
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveModel::Validations
end
PlaceOrderViaExternalApi.new(order_params).valid?
You can initialize and validate them just like ActiveRecord objects but your model's code keeps cleaner and more maintainable.