module Attach::ModelExtension::ClassMethods

Public Instance Methods

attachment(name, options = {}, &block) click to toggle source
# File lib/attach/model_extension.rb, line 108
def attachment(name, options = {}, &block)
  unless self.reflect_on_all_associations(:has_many).map(&:name).include?(:attachments)
    has_many :attachments, :as => :owner, :dependent => :destroy, :class_name => 'Attach::Attachment'
  end

  dsl = AttachmentDSL.new(&block)

  dsl.processors.each do |processor|
    Processor.register(self, name, &processor)
  end

  if dsl.validators.size > 0
    validate do
      attachment = @pending_attachments && @pending_attachments[name] ? @pending_attachments[name] : send(name)
      file_errors = []
      dsl.validators.each do |validator|
        validator.call(attachment, file_errors)
      end
      file_errors.each { |e| errors.add("#{name}_file", e) }
    end
  end

  define_method name do
    var = instance_variable_get("@#{name}")
    if var
      var == :nil ? nil : var
    else
      if attachment = self.attachments.where(:role => name, :parent_id => nil).first
        instance_variable_set("@#{name}", attachment)
      else
        instance_variable_set("@#{name}", :nil)
        nil
      end
    end
  end

  define_method "#{name}=" do |file|
    if file.is_a?(Attach::Attachment)
      @replaced_attachment = self.try(name)
      attachment = file
      attachment.owner = self
      attachment.role = name
      attachment.processed = false
    elsif file
      attachment = Attachment.new({:owner => self, :role => name}.merge(options))
      case file
      when ActionDispatch::Http::UploadedFile
        attachment.binary = file.tempfile
        attachment.file_name = file.original_filename
        attachment.file_type = file.content_type
      when Attach::File
        attachment.binary = file.data
        attachment.file_name = file.name
        attachment.file_type = file.type
      else
        attachment.binary = file
        attachment.file_name = "untitled"
        attachment.file_type = "application/octet-stream"
      end
    end

    if attachment
      @pending_attachments ||= {}
      @pending_attachments[name] = attachment
    end
    instance_variable_set("@#{name}", attachment)
  end

  define_method "#{name}_delete" do
    instance_variable_get("@#{name}_delete")
  end

  define_method "#{name}_delete=" do |delete|
    delete = delete.to_i
    instance_variable_set("@#{name}_delete", delete)
    if delete == 1
      @pending_attachment_deletions ||= []
      @pending_attachment_deletions << name
    end
  end
end
includes_attachments(*options) click to toggle source
# File lib/attach/model_extension.rb, line 26
def includes_attachments(*options)
  manipulate do |records|
    if records.empty?
      # Nothing to do
    else

      if options.first.is_a?(Hash)
        options = options.first
        binaries_to_include = options.delete(:_include_binaries) || {}
      else
        binaries_to_include = {}
        options = options.each_with_object({}) do |opt, hash|
          if opt.is_a?(Symbol) || opt.is_a?(String)
            hash[opt.to_sym] = []
          elsif opt.is_a?(Hash)
            opt.each do |key, value|
              if key == :_include_binaries
                binaries_to_include = value
              else
                hash[key.to_sym] = value
              end
            end
          end
        end

      end

      options.keys.each do |key|
        if options[key].is_a?(Symbol)
          options[key] = [options[key]]
        elsif options[key] == true
          options[key] = []
        end
      end

      attachments_for_binary_preload = []
      root_attachments = {}
      Attachment.where(:owner_id => records.map(&:id), :owner_type => records.first.class.to_s, :role => options.keys).each do |attachment|
        root_attachments[[attachment.owner_id, attachment.role]] = attachment
        if binaries_to_include[attachment.role.to_sym] && binaries_to_include[attachment.role.to_sym].include?(:_self)
          attachments_for_binary_preload << attachment
        end
      end

      child_roles = options.values.flatten
      unless child_roles.empty?
        child_attachments = {}
        Attachment.where(:parent_id => root_attachments.values.map(&:id), :role => child_roles).each do |attachment|
          child_attachments[[attachment.parent_id, attachment.role]] = attachment
        end

        root_attachments.values.each do |attachment|
          options[attachment.role.to_sym].each do |role|
            child_attachment = child_attachments[[attachment.id, role.to_s]]

            if child_attachment && binaries_to_include[attachment.role.to_sym] && binaries_to_include[attachment.role.to_sym].include?(role)
              attachments_for_binary_preload << child_attachment
            end

            attachment.instance_variable_set("@cached_children", {}) if attachment.instance_variable_get("@cached_children").nil?
            attachment.instance_variable_get("@cached_children")[role.to_sym] = child_attachments[[attachment.id, role.to_s]] || :nil
          end
        end
      end

      if binaries = Attach.backend.read_multi(attachments_for_binary_preload)
        attachments_for_binary_preload.each do |attachment|
          attachment.instance_variable_set("@binary", binaries[attachment.id] || :nil)
        end
      else
        # Preloading binaries isn't supported by the backend
      end

      records.each do |record|
        options.keys.each do |role|
          record.instance_variable_set("@#{role}", root_attachments[[record.id, role.to_s]] || :nil)
        end
      end
    end
  end
end