# frozen_string_literal: true require 'active_support/concern' require 'active_support/number_helper' require 'active_storage/engine' module BetterRecord module ModelConcerns module HasValidatedAvatar extend ActiveSupport::Concern module ClassMethods def has_validated_avatar( avatar_name: :avatar, image_validator: :valid_image, min_image_size: nil, max_image_size: 500.kilobytes, shrink_large_image: false, **opts ) # == Constants ============================================================ # == Attributes =========================================================== attribute :"new_#{avatar_name}?", :boolean # == Extensions =========================================================== # == Relationships ======================================================== has_one_attached :"last_#{avatar_name}" has_one_attached avatar_name # == Validations ========================================================== validate avatar_name, image_validator # == Scopes =============================================================== # == Callbacks ============================================================ after_save :"cache_current_#{avatar_name}", if: :"new_#{avatar_name}?" # == Boolean Class Methods ================================================ # == Class Methods ======================================================== # == Boolean Methods ====================================================== # == Instance Methods ===================================================== define_method :"attach_#{avatar_name}" do |file, **options| __send__(avatar_name).attach(file, **options) __send__ image_validator end define_method :valid_image_format do unless __send__(avatar_name).blob.content_type.start_with? 'image/' errors.add(avatar_name, 'is not an image file') return false end true end define_method :valid_image_size do if max_image_size && __send__(avatar_name).blob.byte_size > max_image_size if shrink_large_image.present? @copy_later = true ResizeBlobImageJob.perform_later( model: self.class.to_s, query: {id: self.id}, attachment: avatar_name.to_s, backup_action: image_validator.to_s, options: shrink_large_image ) else errors.add(avatar_name, "is too large, maximum #{ActiveSupport::NumberHelper.number_to_human_size(max_image_size)}") return false end elsif min_image_size && __send__(avatar_name).blob.byte_size < min_image_size errors.add(avatar_name, "is too small, minimum #{ActiveSupport::NumberHelper.number_to_human_size(min_image_size)}") return false end true end define_method :valid_image do return true unless __send__(avatar_name).attached? if valid_image_format && valid_image_size return true if @copy_later puts "\n\nCOPYING AVATAR\n\n" result = self.class.find_by(id: self.id).__send__(:"cache_current_#{avatar_name}") puts "\n\nDONE COPYING AVATAR: #{result}\n\n" result else r = self.find_by(id: self.id).__send__(avatar_name) r.purge if r.attached? __send__(:"load_last_#{avatar_name}") if __send__(:"last_#{avatar_name}").attached? false end end define_method :"cache_current_#{avatar_name}" do __send__ :"copy_#{avatar_name}" end define_method :"load_last_#{avatar_name}" do __send__ :"copy_#{avatar_name}", :"last_#{avatar_name}", avatar_name end define_method :"copy_#{avatar_name}" do |from = avatar_name, to = :"last_#{avatar_name}"| puts "COPYING #{from} TO #{to}" from_attachment = __send__ from to_attachment = __send__ to purge(to_attachment) if to_attachment.attached? tmp = Tempfile.new tmp.binmode tmp.write(from_attachment.download) tmp.flush tmp.rewind r = self.class.find_by(id: self.id) from_attachment = r.__send__ from to_attachment = r.__send__ to to_attachment.attach(io: tmp, filename: from_attachment.filename, content_type: from_attachment.content_type) tmp.close true end end end end end end