module User::Avatar extend ActiveSupport::Concern ALLOWED_AVATAR_CONTENT_TYPES = %w[ image/jpeg image/png image/gif image/webp ].freeze MAX_AVATAR_DIMENSIONS = { width: 4096, height: 4096 }.freeze included do has_one_attached :avatar do |attachable| attachable.variant :thumb, resize_to_fill: [ 256, 256 ], process: :immediately end scope :with_avatars, -> { preload(:account, :avatar_attachment) } validate :avatar_content_type_allowed, :avatar_dimensions_allowed, if: :avatar_attached? end def avatar_attached? avatar.attached? end def avatar_thumbnail avatar.variable? ? avatar.variant(:thumb) : avatar end private def avatar_content_type_allowed if !ALLOWED_AVATAR_CONTENT_TYPES.include?(avatar.content_type) errors.add(:avatar, "must be a JPEG, PNG, GIF, or WebP image") end end def avatar_dimensions_allowed return unless avatar.blob.analyzed? || avatar.blob.analyze width = avatar.blob.metadata[:width] height = avatar.blob.metadata[:height] if width && width > MAX_AVATAR_DIMENSIONS[:width] errors.add(:avatar, "width must be less than #{MAX_AVATAR_DIMENSIONS[:width]}px") end if height && height > MAX_AVATAR_DIMENSIONS[:height] errors.add(:avatar, "height must be less than #{MAX_AVATAR_DIMENSIONS[:height]}px") end end end