Making ActiveRecord associations readonly based on state

My use-case:

* Trip has many destinations and travelers
* Trip states are: 
draft -> checked out -> paid
draft -> checked out -> failed (which should allow them to edit again)

* Trip workflow actually is: 
being edited -> for confirmation -> go to payment gateway (set to checked out) -> go back to success/fail
payment gateway sends payment details to system whether payment was success (paid) or failed (failed)

To disable editing on trip:

class Trip < ApplicationRecord
  def readonly?
    !draft?
  end

  def draft?
    !checked_out?
  end

  def checked_out?
    return false if checked_out_on_changed?
    # this is required, otherwise saving the relations will always fail
    # as draft will always return false even during the saving process
    
    !checked_out_on.blank?
  end
end

To disable changes on related records (destination/traveler):

class Destination < ApplicationRecord
  validate :trip_is_in_draft

protected
  def trip_is_in_draft
    errors.add(:trip, "should be in draft") if !trip.draft?
  end
end

How to create a PORO model and use it in Rails Form Helpers

First off, I’ll start with our use-case:

We have a fairly simple form for an item with some complexity. It would have default values derived from the current_user‘s attributes but would still have the field on its own. Overriding it in Item model feels like overkill (and starts to be a violation of SRP) so we set out to create a PORO model for it. Full code and explanation below the fold.

Continue reading “How to create a PORO model and use it in Rails Form Helpers”