3scale/porta

View on GitHub
app/lib/switches.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Switches
  extend ActiveSupport::Concern

  SWITCHES = %i[account_plans service_plans finance require_cc_on_signup
                multiple_services multiple_applications multiple_users skip_email_engagement_footer
                groups branding web_hooks iam_tools].freeze

  THREESCALE_VISIBLE_SWITCHES = %i[
    finance branding groups skip_email_engagement_footer web_hooks require_cc_on_signup
  ].freeze

  class Switch
    delegate :hidden?, :visible?, :denied?, to: :status

    attr_reader :name, :settings

    def initialize(settings, name)
      @settings = settings
      @name = name
    end

    def status
      # it has to be read_attribute - calling the method would cause
      # return a Switch object
      ActiveSupport::StringInquirer.new(@status || update_status)
    end

    def hideable?
      !globally_denied? && Settings.basic_hidden_switches.exclude?(name.to_sym)
    end

    def allowed?
      not denied?
    end

    def hide!
      if visible?
        @settings.send("hide_#{@name}!")
      end
    ensure
      update_status
    end

    def show!
      if hidden?
        @settings.send("show_#{@name}!")
      end
    ensure
      update_status
    end

    def allow
      @settings.send("allow_#{@name}")
    ensure
      update_status
    end

    def deny
      @settings.send("deny_#{@name}")
    ensure
      update_status
    end

    def reload
      @settings = @settings.clone.reload
      update_status
      self
    end

    def globally_denied?
      false
    end

    private

    def update_status
      @status = @settings.read_attribute("#{@name}_switch").to_s
    end
  end

  class SwitchDenied < Switch
    def allowed?
      false
    end

    def hidden?
      false
    end

    def visible?
      false
    end

    def denied?
      true
    end

    def hide!
      false
    end

    def show!
      false
    end

    def allow
      false
    end

    def deny
      true
    end

    def globally_denied?
      true
    end
  end

  class Collection
    def initialize(settings, switch_names = SWITCHES)
      @settings = settings
      assign_switches(*switch_names)
    end

    attr_reader :settings, :switches
    delegate *%i[[] each keys values fetch partition map include? any? all? empty? first with_indifferent_access delete stringify_keys], to: :switches

    def slice(*switch_names)
      self.class.new(settings, switch_names)
    end

    def except(*switch_names)
      slice(*(keys - switch_names))
    end

    def select(&block)
      slice(*(switches.select(&block).keys))
    end

    %i[allowed denied visible hidden hideable].each do |method_sym|
      define_method(method_sym) do
        switch_names = switches.select { |_, switch| switch.send("#{method_sym}?") }.keys
        slice(*switch_names)
      end
    end

    def reload
      @settings = settings.clone.reload
      assign_switches(*keys)
      self
    end

    private

    def assign_switches(*switch_names)
      @switches = Hash[switch_names.map { |switch_name| [switch_name, settings.send(switch_name)] }]
    end
  end

  MULTISERVICES_MAX_SERVICES = 3

  included do
    SWITCHES.each do |name|
      attr_name = "#{name}_switch"
      attr_protected attr_name

      # Switches State Machine
      #
      #
      #    +--------------+                             +--------------+
      #    |              +                          |              |
      #    | Visible      |                          |   DENIED     |
      #    |              |       deny               |              |
      #    |              o------------------------->+              |
      #    +---+-----+----+                                 +-----+--------+
      #        |     |                                               ^  |
      #        |     | hide/show                                 |  |
      #        |     |                                             |  |
      #     +--+-----+----+             deny                     |  |
      #     |             |-------------------------------    |
      #     |  Hidden     |          allow                    |
      #     |             |<-----------------------------------
      #     |             |
      #     +-------------+
      #
      state_machine attr_name, initial: :denied, namespace: name do
        before_transition do |settings|
          unless settings.account.provider?
            raise Account::ProviderOnlyMethodCalledError, "cannot change state of #{name} of #{settings.inspect}"
          end
        end

        state :denied, :hidden, :visible

        event :hide do
          transition visible: :hidden
        end

        event :show do
          transition hidden: :visible
        end

        event :deny do
          transition [:hidden, :visible] => :denied
        end

        event :allow do
          transition denied: :hidden
        end
      end

      # Overrides the model's attribute reader
      define_method(name) do
        if globally_denied_switches.include?(name.to_sym)
          SwitchDenied.new(self, name)
        else
          Switch.new(self, name)
        end
      end
    end

    finance_state_machine = state_machines['finance_switch']

    finance_state_machine.after_transition to: :denied, from: %i[hidden visible] do |settings|
      settings.account.billing_strategy.destroy if settings.account.billing_strategy
    end

    finance_state_machine.after_transition to: %i[visible hidden], from: [:denied] do |settings|
      unless settings.account.billing_strategy
        account = settings.account
        account.billing_strategy = Finance::PostpaidBillingStrategy.create(account: account, currency: 'USD')
        account.save!
      end
    end

    state_machines['multiple_applications_switch'].after_transition to: %i[visible hidden], from: [:denied] do |settings|
      SimpleLayout.new(settings.account).create_multiapp_builtin_pages!
    end

    state_machines['multiple_services_switch'].after_transition to: %i[visible hidden], from: [:denied] do |settings|
      SimpleLayout.new(settings.account).create_multiservice_builtin_pages!

      settings.account.update_provider_constraints_to(
        { max_services: MULTISERVICES_MAX_SERVICES },
        'Upgrading max_services because of switch is enabled.'
      )
    end

    state_machines['service_plans_switch'].after_transition to: %i[visible hidden], from: [:denied] do |settings|
      SimpleLayout.new(settings.account).create_service_plans_builtin_pages!
    end
  end

  module ClassMethods
    def hide_basic_switches?
      Rails.configuration.three_scale.hide_basic_switches
    end

    def basic_enabled_switches
      if hide_basic_switches?
        %i(multiple_services multiple_applications multiple_users).freeze
      else
        [].freeze
      end
    end

    def basic_disabled_switches
      if hide_basic_switches?
        %i(skip_email_engagement_footer).freeze
      else
        [].freeze
      end
    end

    def basic_hidden_switches
      basic_disabled_switches
    end
  end

  def switches
    # Hash[SWITCHES.map{ |switch_name| [ switch_name, send(switch_name) ] }]
    Collection.new(self)
  end

  # Using a constant here seems weird as it depends on some parameters
  def globally_denied_switches
    [
      account.master_on_premises? ? :finance : nil
    ].compact
  end

  def visible_ui?(switch)
    attribute = "#{switch}_ui_visible"
    if has_attribute?(attribute)
      self[attribute]
    elsif switch == :require_cc_on_signup # visible only for existing providers as of 2016-07-05
      account.provider_can_use?(switch)
    else
      true
    end
  end
end