# frozen_string_literal: true module Maquina ## # A concern that implements blocking functionality for models. # Supports both permanent and temporary blocking mechanisms. # # == Usage # # Include this concern in models that need blocking capability: # # class User < ApplicationRecord # include Maquina::Blockeable # end # # == Required Attributes # # Models must have one or both of: # - +blocked_at+:: Timestamp for permanent blocks # - +temporary_blocked_at+:: Timestamp for temporary blocks # # == Configuration # # Temporary blocking duration is controlled by: # - +Maquina.configuration.temporary_block+:: Duration of temporary blocks # # == Special Behavior for User Model # # When included in Maquina::User: # - Adds +memberships_blocked?+ method to check if all user's memberships are blocked # - Excludes management users from membership blocking checks # # == Scopes # # === unblocked # # Returns records that are not blocked (either permanently or temporarily) # # With both blocked_at and temporary_blocked_at: # scope :unblocked, -> { where(blocked_at: nil).where("(coalesce(temporary_blocked_at + interval '? minutes', now())) <= now()", ...) } # # With only blocked_at: # scope :unblocked, -> { where(blocked_at: nil) } # # == Instance Methods # # === blocked? # # Returns true if the record is blocked by any mechanism: # - Has permanent block (blocked_at present) # - Has active temporary block # - All memberships are blocked (User model only) # # === memberships_blocked? # # Only available for User model. Returns true if user has no active unblocked memberships. # # == Example # # class User < ApplicationRecord # include Maquina::Blockeable # end # # user.update(blocked_at: Time.current) # user.blocked? # => true # User.unblocked # => excludes blocked users # module Blockeable extend ActiveSupport::Concern included do |base| if base.eql?(Maquina::User) define_method(:memberships_blocked?) do return false if management? return @memberships_blocked if !@memberships_blocked.nil? @memberships_blocked = !memberships.includes(:organization).where(blocked_at: nil).where(maquina_organizations: {active: true}).exists? end end if has_attribute?(:blocked_at) && has_attribute?(:temporary_blocked_at) define_method(:blocked?) do temporary_blocked_until = nil if Maquina.configuration.temporary_block.present? && temporary_blocked_at.present? temporary_blocked_until = temporary_blocked_at.since(Maquina.configuration.temporary_block) end blocked_at.present? || (temporary_blocked_until.present? && temporary_blocked_until > Time.zone.now) || (respond_to?(:memberships_blocked?) && memberships_blocked?) end scope :unblocked, -> { where(blocked_at: nil).where("(coalesce(temporary_blocked_at + interval '? minutes', now())) <= now()", Maquina.configuration.temporary_block&.in_minutes&.to_i || 0) } elsif has_attribute(:blocked_at) define_method(:blocked?) do blocked_at.present? || (respond_to?(:memberships_blocked?) && memberships_blocked?) end scope :unblocked, -> { where(blocked_at: nil) } end end end end