3scale/porta

View on GitHub
app/models/account/states.rb

Summary

Maintainability
A
25 mins
Test Coverage
module Account::States
  PERIOD_BEFORE_DELETION   = 15.days.freeze
  STATES = %i[created pending approved rejected suspended scheduled_for_deletion].freeze
  extend ActiveSupport::Concern

  included do
    include AfterCommitQueue

    # XXX: This state machine smells. The :created state does not feel right. I seems it would
    # be better to start in :pending state, but then it's difficult to send the confirmed emails,
    # because hooking them to after_created won't work since there is no user to send it to yet.

    state_machine :initial => :created do

      STATES.each { |state_name| state state_name }

      around_transition do |account, _transition, block|
        previous_state = account.state

        block.call

        account.publish_state_changed_event(previous_state) unless previous_state == account.state
      end

      before_transition :to => :pending do |account|
        account.deliver_confirmed_notification
      end

      before_transition :to => :approved do |account|
        account.deliver_approved_notification
      end

      before_transition :to => :rejected do |account|
        account.deliver_rejected_notification
      end

      after_transition to: :suspended do |account|
        account.run_after_commit(:notify_account_suspended)
        account.bought_account_contract&.suspend if account.provider?
      end

      after_transition any - :scheduled_for_deletion => :scheduled_for_deletion do |account|
        account.run_after_commit(:schedule_backend_sync_worker)
      end

      after_transition :scheduled_for_deletion => any - :scheduled_for_deletion do |account, _transition|
        account.run_after_commit(:schedule_backend_sync_worker)
      end

      after_transition to: :approved, from: :suspended do |account|
        account.run_after_commit(:notify_account_resumed)
        account.bought_account_contract&.resume if account.provider?
      end

      after_transition to: :approved, from: [:created, :pending] do |account|
        account.bought_account_contract&.accept
      end

      after_transition any => any do |account|
        account.update(state_changed_at: Time.zone.now)
      end

      event :make_pending do
        transition :created => :pending
        transition :approved => :pending
        transition :rejected => :pending
      end

      event :approve do
        transition :created => :approved
        transition :pending => :approved
        transition :rejected => :approved
      end

      event :reject do
        transition :created => :rejected
        transition :pending => :rejected
        transition :approved => :rejected
      end

      event :suspend do
        transition all => :suspended, if: :ready_to_be_suspended?
      end

      event :resume do
        transition :suspended => :approved, if: :provider?
        transition :scheduled_for_deletion => :approved
      end

      event :schedule_for_deletion do
        transition all => :scheduled_for_deletion, unless: :master?
      end
    end

    scope :without_suspended, -> { where.not(state: :suspended) }
    scope :without_deleted, ->(without = true) { where.has { state != :scheduled_for_deletion } if without }
    scope :without_to_be_deleted, ->(without = true) { without_deleted.where.not(provider_account: unscoped.scheduled_for_deletion) if without }

    scope :by_state, ->(state) do
      where(:state => state.to_s) if state.to_s != "all"
    end

    scope :deleted_since, ->(deletion_time = PERIOD_BEFORE_DELETION.ago) do
      scheduled_for_deletion.where.has { state_changed_at <= deletion_time }
    end

    scope :suspended_since, ->(suspension_time) do
      suspended.where.has { state_changed_at <= suspension_time }
    end

    scope :inactive_since, ->(inactivity_time) do
      where.has { created_at <= inactivity_time }.without_traffic_since(inactivity_time)
    end

    scope :without_traffic_since, ->(inactivity_time) do
      where.has do
        not_exists Cinstance.where.has { user_account_id == BabySqueel[:accounts].id }.active_since(inactivity_time)
      end
    end

    def deletion_date
      return if state_changed_at.blank? || !scheduled_for_deletion?
      (state_changed_at + PERIOD_BEFORE_DELETION)
    end

    def enabled?
      !scheduled_for_deletion? && (provider_account && !provider_account.scheduled_for_deletion?)
    end

    def should_be_deleted?
      suspended? || will_be_deleted?
    end

    def should_not_be_deleted?
      !should_be_deleted?
    end

    def will_be_deleted?
      scheduled_for_deletion? || (buyer? && provider_account.try(:should_be_deleted?))
    end

    def ready_to_be_suspended?
      tenant? || marked_for_suspension
    end

    attr_accessor :marked_for_suspension

    def suspended_or_scheduled_for_deletion?
      suspended? || scheduled_for_deletion?
    end
  end

  module ClassMethods
    STATES.each { |state_name| define_method(state_name) { by_state(state_name) } }
  end

  def upgrade_state!
    if buyer? && approval_required?
      make_pending!
    else
      approve!
    end
  end

  def publish_state_changed_event(previous_state)
    Accounts::AccountStateChangedEvent.create_and_publish!(self, previous_state)
    PublishEnabledChangedEventForProviderApplicationsWorker.perform_later(self, previous_state)
  end

  def deliver_confirmed_notification
    if admins.present? && provider_account
      run_after_commit do
        AccountMessenger.new_signup(self).deliver_now
        AccountMailer.confirmed(self).deliver_later
      end
    end
  end

  def deliver_approved_notification
    if buyer? && approval_required?
      unless admins.empty? || admins.first.created_by_provider_signup?
        run_after_commit do
          AccountMailer.approved(self).deliver_later
        end
      end
    end
  end

  def deliver_rejected_notification
    unless admins.empty? || self.provider_account.nil?
      run_after_commit do
        AccountMailer.rejected(self).deliver_later
      end
    end
  end

  def notify_account_suspended
    ThreeScale::Analytics.track_account(self, 'Account Suspended')
    ThreeScale::Analytics.group(self)
    ReverseProviderKeyWorker.enqueue(self)
  end

  def notify_account_resumed
    ThreeScale::Analytics.track_account(self, 'Account Resumed')
    ThreeScale::Analytics.group(self)
    ReverseProviderKeyWorker.enqueue(self)
  end
end