ksylvest/attached

View on GitHub
lib/attached.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'attached/definition'
require 'attached/attachment'
require 'attached/attatcher'
require 'attached/railtie'

module Attached

  def self.mock!
    Fog.mock!
  end

  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods

    # Add an attachment to a class.
    #
    # Options:
    #
    # * :styles    - a hash containing style names followed by parameters passed to processor
    # * :storage   - a symbol for a predefined storage or a custom storage class
    # * :processor - a symbol for a predefined processor or a custom processor class
    #
    # Usage:
    #
    #   has_attached :video
    #   has_attached :video, storage: :aws
    #   has_attached :video, styles: { mov: { size: "480p", format: "mov" } }

    def has_attached(name, options = {})
      Attatcher.define(self, name, options)
    end

    # Validates an attached size in a specified range or minimum and maximum.
    #
    # Options:
    #
    # * :message - string to be displayed with :minimum and :maximum variables
    # * :minimum - integer for the minimum byte size of the attached
    # * :maximum - integer for the maximum byte size of teh attached
    # * :in - range of bytes for file
    #
    # Usage:
    #
    #   validates_attached_size :avatar, range: 10.megabytes .. 20.megabytes
    #   validates_attached_size :avatar, minimum: 10.megabytes, maximum: 20.megabytes
    #   validates_attached_size :avatar, message: "size must be between :minimum and :maximum bytes"

    def validates_attached_size(name, options = {})

      zero = (0.0 / 1.0)
      infi = (1.0 / 0.0)

      minimum = options[:minimum] || options[:in] && options[:in].first || zero
      maximum = options[:maximum] || options[:in] && options[:in].last  || infi

      message = case
      when options[:message] then options[:message]
      when minimum == zero && maximum == infi then "size must be specified"
      when maximum == infi then "size must be a minimum of :minimum" 
      when minimum == zero then "size must be a maximum of :maximum"
      else "size must be between :minimum and :maximum"
      end

      range = minimum..maximum

      message.gsub!(/:minimum/, number_to_size(minimum)) unless minimum == zero
      message.gsub!(/:maximum/, number_to_size(maximum)) unless maximum == infi

      validates_inclusion_of :"#{name}_size", in: range, message: message,
        if: options[:if], unless: options[:unless]

    end

    # Validates an attached extension in a specified set.
    #
    # Options:
    #
    # * :in - allowed values for attached
    #
    # Usage:
    #
    #   validates_attached_extension :avatar, is: 'png'
    #   validates_attached_extension :avatar, in: %w(png jpg)
    #   validates_attached_extension :avatar, in: [:png, :jpg]
    #   validates_attached_extension :avatar, in: %w(png jpg), message: "extension must be :in"
    #   validates_attached_extension :avatar, in: %w(png jpg), message: "extension must be :in"

    def validates_attached_extension(name, options = {})

      message = options[:message] || "extension is invalid"

      options[:in] ||= [options[:is]] if options[:is]

      range = options[:in].map { |element| ".#{element}" }

      validates_inclusion_of :"#{name}_extension", in: range, message: message,
        if: options[:if], unless: options[:unless]
    end

    # Validates that an attachment is included.
    #
    # Options:
    #
    # * :message - string to be displayed
    #
    # Usage:
    #
    #   validates_attached_presence :avatar
    #   validates_attached_presence :avatar, message: "must be attached"

    def validates_attached_presence(name, options = {})

      message = options[:message] || "must be attached"

      validates_presence_of :"#{name}_identifier", message: message,
        if: options[:if], unless: options[:unless]

    end

  private

    # Convert a number to a human readable size.
    #
    # Usage:
    #
    #   number_to_size(1) # 1 byte
    #   number_to_size(2) # 2 bytes
    #   number_to_size(1024) # 1 kilobyte
    #   number_to_size(2048) # 2 kilobytes

    def number_to_size(number, options = {})
      return if number == 0.0 / 1.0
      return if number == 1.0 / 0.0

      singular = options[:singular] || 1
      base     = options[:base]     || 1024
      units    = options[:units]    || ["byte", "kilobyte", "megabyte", "gigabyte", "terabyte", "petabyte"]

      exponent = (Math.log(number) / Math.log(base)).floor

      number /= base ** exponent
      unit = units[exponent]

      number == singular ?  unit.gsub!(/s$/, '') : unit.gsub!(/$/, 's')

      "#{number} #{unit}"
    end

  end

end