mechanicles/set_as_primary

View on GitHub
lib/set_as_primary.rb

Summary

Maintainability
A
1 hr
Test Coverage
A
100%
# frozen_string_literal: true

require "set_as_primary/version"
require "active_support/concern"

module SetAsPrimary
  class Error < StandardError; end

  extend ActiveSupport::Concern

  included do
    before_save :unset_old_primary
    before_save :force_primary, if: -> { _klass._force_primary }
    after_destroy :force_primary, if: -> { _klass._force_primary }

    instance_eval do
      class_attribute :_primary_flag_attribute, :_owner_key, :_force_primary

      private
        def set_as_primary(primary_flag_attribute = :primary, options = {})
          if primary_flag_attribute.is_a?(Hash)
            options = primary_flag_attribute; primary_flag_attribute = :primary
          end

          configuration = { owner_key: nil, force_primary: true }

          configuration.update(options) if options.is_a?(Hash)

          _handle_setup_errors(primary_flag_attribute, configuration)

          self._primary_flag_attribute = primary_flag_attribute
          self._owner_key = configuration[:owner_key]
          self._force_primary = configuration[:force_primary]
        end

        def _handle_setup_errors(primary_flag_attribute, configuration)
          if !primary_flag_attribute.is_a?(Symbol)
            raise SetAsPrimary::Error, "wrong argument type (expected Symbol)"
          end

          owner_key = configuration[:owner_key]

          if owner_key.present? && reflect_on_association(owner_key).nil?
            raise ActiveRecord::AssociationNotFoundError.new(self, owner_key)
          end
        end
    end
  end

  private
    def unset_old_primary
      return unless public_send(_klass._primary_flag_attribute)

      scope = _klass
      scope = scope.where(_scope_options) if _scope_options.present?
      scope = scope.where("id != ?", id) unless new_record?

      scope.update_all(_klass._primary_flag_attribute => false)
    end

    def force_primary
      scope = _klass
      scope = scope.where(_scope_options) if _scope_options.present?
      count = scope.count

      if count == 1 && destroyed?
        object = scope.first
        object.update_columns(_klass._primary_flag_attribute => true)
      elsif (count == 1 && !new_record?) || (count == 0 && new_record?)
        public_send("#{_klass._primary_flag_attribute}=", true)
      end
    end

    def _scope_options
      return nil if _klass._owner_key.nil?

      @_scope_options ||= if _klass.reflect_on_association(_klass._owner_key).options[:polymorphic]
        _polymorphic_condition_options
      else
        owner_id = "#{_klass._owner_key}_id".to_sym
        { owner_id => public_send(owner_id) }
      end
    end

    def _polymorphic_condition_options
      owner = self.public_send(self.class._owner_key)

      {
        "#{_klass._owner_key}_id".to_sym =>  owner.id,
        "#{_klass._owner_key}_type".to_sym => owner.class.name
      }
    end

    def _klass
      self.class
    end
end