attach 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/lib/attach.rb +23 -0
 - data/lib/attach/attachment.rb +116 -0
 - data/lib/attach/attachment_binary.rb +11 -0
 - data/lib/attach/backends/abstract.rb +36 -0
 - data/lib/attach/backends/database.rb +27 -0
 - data/lib/attach/backends/file_system.rb +37 -0
 - data/lib/attach/middleware.rb +29 -0
 - data/lib/attach/model_extension.rb +126 -0
 - data/lib/attach/processor.rb +48 -0
 - data/lib/attach/railtie.rb +19 -0
 - data/lib/attach/version.rb +3 -0
 - metadata +75 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA1:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: bba016e24c62143435904ab7adec8d742dfc2cff
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 7fd96092ed4196d2eb765788adf376bbb8caec46
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 1728d88028c90ca2701943199a9fecfe5cbe4b6f3358a3533f617aa279942e05974f94d3bb7089e31e99af1082781a2f9979cb2857d617c07d6399f590ef2e09
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: abb0b019475de3fb68a433aa99a80facdd95c86701e76ba5fea90c7b097e0bb507337a1404f67571f2aceb9aae8cf1f2ef0248fedc87ca37764e007f7d5f33fa
         
     | 
    
        data/lib/attach.rb
    ADDED
    
    | 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'records_manipulator'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'attach/processor'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'attach/railtie' if defined?(Rails)
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Attach
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              def self.backend
         
     | 
| 
      
 8 
     | 
    
         
            +
                @backend ||= begin
         
     | 
| 
      
 9 
     | 
    
         
            +
                  require 'attach/backends/database'
         
     | 
| 
      
 10 
     | 
    
         
            +
                  Attach::Backends::Database.new
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              def self.backend=(backend)
         
     | 
| 
      
 15 
     | 
    
         
            +
                @backend = backend
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              def self.use_filesystem!(config = {})
         
     | 
| 
      
 19 
     | 
    
         
            +
                require 'attach/backends/file_system'
         
     | 
| 
      
 20 
     | 
    
         
            +
                @backend = Attach::Backends::FileSystem.new(config)
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,116 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'securerandom'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'digest/sha1'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'attach/attachment_binary'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Attach
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Attachment < ActiveRecord::Base
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                # Set the table name
         
     | 
| 
      
 9 
     | 
    
         
            +
                self.table_name = 'attachments'
         
     | 
| 
      
 10 
     | 
    
         
            +
                self.inheritance_column = 'sti_type'
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                # This will be the ActionDispatch::UploadedFile object which be diseminated
         
     | 
| 
      
 13 
     | 
    
         
            +
                # by the class on save.
         
     | 
| 
      
 14 
     | 
    
         
            +
                attr_accessor :uploaded_file
         
     | 
| 
      
 15 
     | 
    
         
            +
                attr_writer :binary
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # Relationships
         
     | 
| 
      
 18 
     | 
    
         
            +
                belongs_to :owner, :polymorphic => true
         
     | 
| 
      
 19 
     | 
    
         
            +
                belongs_to :parent, :class_name => 'Attach::Attachment', :optional => true
         
     | 
| 
      
 20 
     | 
    
         
            +
                has_many :children, :class_name => 'Attach::Attachment', :dependent => :destroy, :foreign_key => :parent_id
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                # Validations
         
     | 
| 
      
 23 
     | 
    
         
            +
                validates :file_name, :presence => true
         
     | 
| 
      
 24 
     | 
    
         
            +
                validates :file_type, :presence => true
         
     | 
| 
      
 25 
     | 
    
         
            +
                validates :file_size, :presence => true
         
     | 
| 
      
 26 
     | 
    
         
            +
                validates :digest, :presence => true
         
     | 
| 
      
 27 
     | 
    
         
            +
                validates :token, :presence => true, :uniqueness => true
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                # All attachments should have a token assigned to this
         
     | 
| 
      
 30 
     | 
    
         
            +
                before_validation { self.token = SecureRandom.uuid if self.token.blank? }
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                # Copy values from the `uploaded_file` and set them as the appropriate
         
     | 
| 
      
 33 
     | 
    
         
            +
                # fields on this model
         
     | 
| 
      
 34 
     | 
    
         
            +
                before_validation do
         
     | 
| 
      
 35 
     | 
    
         
            +
                  if self.uploaded_file
         
     | 
| 
      
 36 
     | 
    
         
            +
                    self.binary = self.uploaded_file.tempfile.read
         
     | 
| 
      
 37 
     | 
    
         
            +
                    self.file_name = self.uploaded_file.original_filename
         
     | 
| 
      
 38 
     | 
    
         
            +
                    self.file_type = self.uploaded_file.content_type
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  self.digest = Digest::SHA1.hexdigest(self.binary)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  self.file_size = self.binary.bytesize
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                # Write the binary to the backend storage
         
     | 
| 
      
 46 
     | 
    
         
            +
                after_create do
         
     | 
| 
      
 47 
     | 
    
         
            +
                  Attach.backend.write(self, self.binary)
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                # Remove any old images for this owner/role when this is added
         
     | 
| 
      
 51 
     | 
    
         
            +
                after_create do
         
     | 
| 
      
 52 
     | 
    
         
            +
                  self.owner.attachments.where.not(:id => self).where(:parent_id => self.parent_id, :role => self.role).destroy_all
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                # Run any post-upload processing after the record has been committed
         
     | 
| 
      
 56 
     | 
    
         
            +
                after_commit do
         
     | 
| 
      
 57 
     | 
    
         
            +
                  unless self.processed? || self.parent_id
         
     | 
| 
      
 58 
     | 
    
         
            +
                    self.processor.queue_or_process
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                # Remove the file from the backends
         
     | 
| 
      
 63 
     | 
    
         
            +
                after_destroy do
         
     | 
| 
      
 64 
     | 
    
         
            +
                  Attach.backend.delete(self)
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                # Return the attachment for a given role
         
     | 
| 
      
 68 
     | 
    
         
            +
                def self.for(role)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  self.where(:role => role).first
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                # Return the binary data for this attachment
         
     | 
| 
      
 73 
     | 
    
         
            +
                def binary
         
     | 
| 
      
 74 
     | 
    
         
            +
                  @binary ||= persisted? ? Attach.backend.read(self) : nil
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                # Return the path to the attachment
         
     | 
| 
      
 78 
     | 
    
         
            +
                def url
         
     | 
| 
      
 79 
     | 
    
         
            +
                  Attach.backend.url(self)
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                # Is the attachment an image?
         
     | 
| 
      
 83 
     | 
    
         
            +
                def image?
         
     | 
| 
      
 84 
     | 
    
         
            +
                  file_type =~ /\Aimage\//
         
     | 
| 
      
 85 
     | 
    
         
            +
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                # Return a processor for this attachment
         
     | 
| 
      
 88 
     | 
    
         
            +
                def processor
         
     | 
| 
      
 89 
     | 
    
         
            +
                  @processor ||= Processor.new(self)
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                # Return a child process
         
     | 
| 
      
 93 
     | 
    
         
            +
                def child(role)
         
     | 
| 
      
 94 
     | 
    
         
            +
                  @cached_children ||= {}
         
     | 
| 
      
 95 
     | 
    
         
            +
                  @cached_children[role.to_sym] ||= begin
         
     | 
| 
      
 96 
     | 
    
         
            +
                    self.children.where(:role => role).first
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                # Add a child attachment
         
     | 
| 
      
 101 
     | 
    
         
            +
                def add_child(role, &block)
         
     | 
| 
      
 102 
     | 
    
         
            +
                  attachment = self.children.build
         
     | 
| 
      
 103 
     | 
    
         
            +
                  attachment.role = role
         
     | 
| 
      
 104 
     | 
    
         
            +
                  attachment.owner = self.owner
         
     | 
| 
      
 105 
     | 
    
         
            +
                  attachment.file_name = self.file_name
         
     | 
| 
      
 106 
     | 
    
         
            +
                  attachment.file_type = self.file_type
         
     | 
| 
      
 107 
     | 
    
         
            +
                  attachment.disposition = self.disposition
         
     | 
| 
      
 108 
     | 
    
         
            +
                  attachment.cache_type = self.cache_type
         
     | 
| 
      
 109 
     | 
    
         
            +
                  attachment.cache_max_age = self.cache_max_age
         
     | 
| 
      
 110 
     | 
    
         
            +
                  attachment.type = self.type
         
     | 
| 
      
 111 
     | 
    
         
            +
                  block.call(attachment)
         
     | 
| 
      
 112 
     | 
    
         
            +
                  attachment.save!
         
     | 
| 
      
 113 
     | 
    
         
            +
                end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
              end
         
     | 
| 
      
 116 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Attach
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Backends
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Abstract
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  def initialize(config = {})
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @config = config
         
     | 
| 
      
 7 
     | 
    
         
            +
                  end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  #
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # Return the data for the given attachment
         
     | 
| 
      
 11 
     | 
    
         
            +
                  #
         
     | 
| 
      
 12 
     | 
    
         
            +
                  def read(attachment)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  #
         
     | 
| 
      
 16 
     | 
    
         
            +
                  # Write data for the given attachment
         
     | 
| 
      
 17 
     | 
    
         
            +
                  #
         
     | 
| 
      
 18 
     | 
    
         
            +
                  def write(attachment, data)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  #
         
     | 
| 
      
 22 
     | 
    
         
            +
                  # Delete the data for the given attachment
         
     | 
| 
      
 23 
     | 
    
         
            +
                  #
         
     | 
| 
      
 24 
     | 
    
         
            +
                  def delete(attachment)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  #
         
     | 
| 
      
 28 
     | 
    
         
            +
                  # Return the URL that this attachment can be accessed at
         
     | 
| 
      
 29 
     | 
    
         
            +
                  #
         
     | 
| 
      
 30 
     | 
    
         
            +
                  def url(attachment)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    "/attachment/#{attachment.token}/#{attachment.file_name}"
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'attach/backends/abstract'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Attach
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Backends
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Database < Abstract
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  def read(attachment)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    if binary = AttachmentBinary.find_by_attachment_id(attachment.id)
         
     | 
| 
      
 9 
     | 
    
         
            +
                      binary.data
         
     | 
| 
      
 10 
     | 
    
         
            +
                    else
         
     | 
| 
      
 11 
     | 
    
         
            +
                      nil
         
     | 
| 
      
 12 
     | 
    
         
            +
                    end
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def write(attachment, data)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    binary = AttachmentBinary.where(:attachment_id => attachment.id).first_or_initialize
         
     | 
| 
      
 17 
     | 
    
         
            +
                    binary.data = data
         
     | 
| 
      
 18 
     | 
    
         
            +
                    binary.save!
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def delete(attachment)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    AttachmentBinary.where(:attachment_id => attachment.id).destroy_all
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'attach/backends/abstract'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Attach
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Backends
         
     | 
| 
      
 6 
     | 
    
         
            +
                class FileSystem < Abstract
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def read(attachment)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    File.read(path_for_attachment(attachment))
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def write(attachment, data)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    path = path_for_attachment(attachment)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    FileUtils.mkdir_p(File.dirname(path))
         
     | 
| 
      
 15 
     | 
    
         
            +
                    File.open(path, 'wb') do |f|
         
     | 
| 
      
 16 
     | 
    
         
            +
                      f.write(data)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def delete(attachment)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    path = path_for_attachment(attachment)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    FileUtils.rm(path) if File.file?(path)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  private
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def root_dir
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @config[:root] ||= Rails.root.join('attachments')
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def path_for_attachment(attachment)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    File.join(root_dir, attachment.token[0,2], attachment.token[2,2], attachment.token[4,40])
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,29 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'attach/attachment'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Attach
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Middleware
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(app)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  if env['PATH_INFO'] =~ /\A\/attachment\/([a-f0-9\-]{36})\/(.*)/
         
     | 
| 
      
 12 
     | 
    
         
            +
                    if attachment = Attach::Attachment.find_by_token($1)
         
     | 
| 
      
 13 
     | 
    
         
            +
                      [200, {
         
     | 
| 
      
 14 
     | 
    
         
            +
                        'Content-Length' => attachment.file_size.to_s,
         
     | 
| 
      
 15 
     | 
    
         
            +
                        'Content-Type' => attachment.file_type,
         
     | 
| 
      
 16 
     | 
    
         
            +
                        'Cache-Control' => "#{attachment.cache_type || 'private'}, immutable, maxage=#{attachment.cache_max_age || 30.days.to_i}",
         
     | 
| 
      
 17 
     | 
    
         
            +
                        'Content-Disposition' => "#{attachment.disposition || 'attachment'}; filename=\"#{attachment.file_name}\","
         
     | 
| 
      
 18 
     | 
    
         
            +
                        },
         
     | 
| 
      
 19 
     | 
    
         
            +
                      [attachment.binary]]
         
     | 
| 
      
 20 
     | 
    
         
            +
                    else
         
     | 
| 
      
 21 
     | 
    
         
            +
                      [404, {}, ["Attachment not found"]]
         
     | 
| 
      
 22 
     | 
    
         
            +
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
                  else
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @app.call(env)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,126 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'attach/attachment'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'attach/processor'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Attach
         
     | 
| 
      
 5 
     | 
    
         
            +
              module ModelExtension
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def self.included(base)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  base.extend ClassMethods
         
     | 
| 
      
 9 
     | 
    
         
            +
                  base.after_save do
         
     | 
| 
      
 10 
     | 
    
         
            +
                    if @pending_attachment_deletions
         
     | 
| 
      
 11 
     | 
    
         
            +
                      self.attachments.where(:role => @pending_attachment_deletions).destroy_all
         
     | 
| 
      
 12 
     | 
    
         
            +
                    end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                    if @pending_attachments
         
     | 
| 
      
 15 
     | 
    
         
            +
                      @pending_attachments.each do |pa|
         
     | 
| 
      
 16 
     | 
    
         
            +
                        attachment = self.attachments.build(:uploaded_file => pa[:file], :role => pa[:role])
         
     | 
| 
      
 17 
     | 
    
         
            +
                        if pa[:options]
         
     | 
| 
      
 18 
     | 
    
         
            +
                          pa[:options].each do |key, value|
         
     | 
| 
      
 19 
     | 
    
         
            +
                            attachment.send("#{key}=", value)
         
     | 
| 
      
 20 
     | 
    
         
            +
                          end
         
     | 
| 
      
 21 
     | 
    
         
            +
                        end
         
     | 
| 
      
 22 
     | 
    
         
            +
                        attachment.save!
         
     | 
| 
      
 23 
     | 
    
         
            +
                      end
         
     | 
| 
      
 24 
     | 
    
         
            +
                      @pending_attachments = nil
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                module ClassMethods
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def includes_attachments(*options)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    manipulate do |records|
         
     | 
| 
      
 33 
     | 
    
         
            +
                      if records.empty?
         
     | 
| 
      
 34 
     | 
    
         
            +
                        # Nothing to do
         
     | 
| 
      
 35 
     | 
    
         
            +
                      else
         
     | 
| 
      
 36 
     | 
    
         
            +
                        if options.first.is_a?(Hash)
         
     | 
| 
      
 37 
     | 
    
         
            +
                          options = options.first
         
     | 
| 
      
 38 
     | 
    
         
            +
                        else
         
     | 
| 
      
 39 
     | 
    
         
            +
                          options = options.each_with_object({}) do |role, hash|
         
     | 
| 
      
 40 
     | 
    
         
            +
                            hash[role.to_sym] = []
         
     | 
| 
      
 41 
     | 
    
         
            +
                          end
         
     | 
| 
      
 42 
     | 
    
         
            +
                        end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                        options.keys.each do |key|
         
     | 
| 
      
 45 
     | 
    
         
            +
                          if options[key].is_a?(Symbol)
         
     | 
| 
      
 46 
     | 
    
         
            +
                            options[key] = [options[key]]
         
     | 
| 
      
 47 
     | 
    
         
            +
                          end
         
     | 
| 
      
 48 
     | 
    
         
            +
                        end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                        root_attachments = {}
         
     | 
| 
      
 51 
     | 
    
         
            +
                        Attachment.where(:owner_id => records.map(&:id), :owner_type => records.first.class.to_s, :role => options.keys).each do |attachment|
         
     | 
| 
      
 52 
     | 
    
         
            +
                          root_attachments[[attachment.owner_id, attachment.role]] = attachment
         
     | 
| 
      
 53 
     | 
    
         
            +
                        end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                        child_attachments = {}
         
     | 
| 
      
 56 
     | 
    
         
            +
                        child_roles = options.values.flatten
         
     | 
| 
      
 57 
     | 
    
         
            +
                        Attachment.where(:parent_id => root_attachments.values.map(&:id), :role => child_roles).each do |attachment|
         
     | 
| 
      
 58 
     | 
    
         
            +
                          child_attachments[[attachment.id, attachment.role]] = attachment
         
     | 
| 
      
 59 
     | 
    
         
            +
                        end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                        root_attachments.values.each do |attachment|
         
     | 
| 
      
 62 
     | 
    
         
            +
                          options[attachment.role.to_sym].each do |role|
         
     | 
| 
      
 63 
     | 
    
         
            +
                            attachment.instance_variable_set("@cached_children", {}) if attachment.instance_variable_get("@cached_children").nil?
         
     | 
| 
      
 64 
     | 
    
         
            +
                            attachment.instance_variable_get("@cached_children")[role.to_sym] = attachment
         
     | 
| 
      
 65 
     | 
    
         
            +
                          end
         
     | 
| 
      
 66 
     | 
    
         
            +
                        end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                        records.each do |record|
         
     | 
| 
      
 69 
     | 
    
         
            +
                          options.keys.each do |role|
         
     | 
| 
      
 70 
     | 
    
         
            +
                            if a = root_attachments[[record.id, role.to_s]]
         
     | 
| 
      
 71 
     | 
    
         
            +
                              record.instance_variable_set("@#{role}", a)
         
     | 
| 
      
 72 
     | 
    
         
            +
                            end
         
     | 
| 
      
 73 
     | 
    
         
            +
                          end
         
     | 
| 
      
 74 
     | 
    
         
            +
                        end
         
     | 
| 
      
 75 
     | 
    
         
            +
                      end
         
     | 
| 
      
 76 
     | 
    
         
            +
                    end
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  def attachment(name, options = {}, &block)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    unless self.reflect_on_all_associations(:has_many).map(&:name).include?(:attachments)
         
     | 
| 
      
 81 
     | 
    
         
            +
                      has_many :attachments, :as => :owner, :dependent => :destroy, :class_name => 'Attach::Attachment'
         
     | 
| 
      
 82 
     | 
    
         
            +
                    end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                    if block_given?
         
     | 
| 
      
 85 
     | 
    
         
            +
                      Processor.register(self, name, &block)
         
     | 
| 
      
 86 
     | 
    
         
            +
                    end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                    define_method name do
         
     | 
| 
      
 89 
     | 
    
         
            +
                      instance_variable_get("@#{name}") || begin
         
     | 
| 
      
 90 
     | 
    
         
            +
                        attachment = self.attachments.where(:role => name, :parent_id => nil).first
         
     | 
| 
      
 91 
     | 
    
         
            +
                        instance_variable_set("@#{name}", attachment)
         
     | 
| 
      
 92 
     | 
    
         
            +
                      end
         
     | 
| 
      
 93 
     | 
    
         
            +
                    end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                    define_method "#{name}_file" do
         
     | 
| 
      
 96 
     | 
    
         
            +
                      instance_variable_get("@#{name}_file")
         
     | 
| 
      
 97 
     | 
    
         
            +
                    end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                    define_method "#{name}_file=" do |file|
         
     | 
| 
      
 100 
     | 
    
         
            +
                      instance_variable_set("@#{name}_file", file)
         
     | 
| 
      
 101 
     | 
    
         
            +
                      if file.is_a?(ActionDispatch::Http::UploadedFile)
         
     | 
| 
      
 102 
     | 
    
         
            +
                        @pending_attachments ||= []
         
     | 
| 
      
 103 
     | 
    
         
            +
                        @pending_attachments << {:role => name, :file => file, :options => options}
         
     | 
| 
      
 104 
     | 
    
         
            +
                      else
         
     | 
| 
      
 105 
     | 
    
         
            +
                        nil
         
     | 
| 
      
 106 
     | 
    
         
            +
                      end
         
     | 
| 
      
 107 
     | 
    
         
            +
                    end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                    define_method "#{name}_delete" do
         
     | 
| 
      
 110 
     | 
    
         
            +
                      instance_variable_get("@#{name}_delete")
         
     | 
| 
      
 111 
     | 
    
         
            +
                    end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                    define_method "#{name}_delete=" do |delete|
         
     | 
| 
      
 114 
     | 
    
         
            +
                      delete = delete.to_i
         
     | 
| 
      
 115 
     | 
    
         
            +
                      instance_variable_set("@#{name}_delete", delete)
         
     | 
| 
      
 116 
     | 
    
         
            +
                      if delete == 1
         
     | 
| 
      
 117 
     | 
    
         
            +
                        @pending_attachment_deletions ||= []
         
     | 
| 
      
 118 
     | 
    
         
            +
                        @pending_attachment_deletions << name
         
     | 
| 
      
 119 
     | 
    
         
            +
                      end
         
     | 
| 
      
 120 
     | 
    
         
            +
                    end
         
     | 
| 
      
 121 
     | 
    
         
            +
                  end
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
              end
         
     | 
| 
      
 126 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,48 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Attach
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Processor
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                def self.background(&block)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @background_block = block
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def self.background_block
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @background_block
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def self.register(model, attribute, &block)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @processors ||= {}
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @processors[[model.to_s, attribute.to_s]] ||= []
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @processors[[model.to_s, attribute.to_s]] = block
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def self.processor(model, attribute)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @processors && @processors[[model.to_s, attribute.to_s]]
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def initialize(attachment)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @attachment = attachment
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def process
         
     | 
| 
      
 27 
     | 
    
         
            +
                  call_processors(@attachment)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @attachment.update_column(:processed, true)
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def queue_or_process
         
     | 
| 
      
 32 
     | 
    
         
            +
                  if self.class.background_block
         
     | 
| 
      
 33 
     | 
    
         
            +
                    self.class.background_block.call(@attachment)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  else
         
     | 
| 
      
 35 
     | 
    
         
            +
                    process
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                private
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def call_processors(attachment)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  if p = self.class.processor(attachment.owner_type, attachment.role)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    p.call(attachment)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
              end
         
     | 
| 
      
 48 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Attach
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Railtie < Rails::Engine #:nodoc:
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                engine_name 'attach'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                initializer 'attach.initialize' do |app|
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  require 'attach/middleware'
         
     | 
| 
      
 9 
     | 
    
         
            +
                  app.config.middleware.use Attach::Middleware
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  ActiveSupport.on_load(:active_record) do
         
     | 
| 
      
 12 
     | 
    
         
            +
                    require 'attach/model_extension'
         
     | 
| 
      
 13 
     | 
    
         
            +
                    ::ActiveRecord::Base.send :include, Attach::ModelExtension
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,75 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: attach
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.0.0
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Adam Cooke
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 9 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 10 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2017-03-10 00:00:00.000000000 Z
         
     | 
| 
      
 12 
     | 
    
         
            +
            dependencies:
         
     | 
| 
      
 13 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 14 
     | 
    
         
            +
              name: records_manipulator
         
     | 
| 
      
 15 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 16 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 17 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 18 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 19 
     | 
    
         
            +
                    version: '1.0'
         
     | 
| 
      
 20 
     | 
    
         
            +
                - - "<"
         
     | 
| 
      
 21 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 22 
     | 
    
         
            +
                    version: '2.0'
         
     | 
| 
      
 23 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 24 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 25 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 26 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 27 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 28 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 29 
     | 
    
         
            +
                    version: '1.0'
         
     | 
| 
      
 30 
     | 
    
         
            +
                - - "<"
         
     | 
| 
      
 31 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 32 
     | 
    
         
            +
                    version: '2.0'
         
     | 
| 
      
 33 
     | 
    
         
            +
            description: Attach documents & files to Active Record models
         
     | 
| 
      
 34 
     | 
    
         
            +
            email:
         
     | 
| 
      
 35 
     | 
    
         
            +
            - me@adamcooke.io
         
     | 
| 
      
 36 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 37 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 38 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 39 
     | 
    
         
            +
            files:
         
     | 
| 
      
 40 
     | 
    
         
            +
            - lib/attach.rb
         
     | 
| 
      
 41 
     | 
    
         
            +
            - lib/attach/attachment.rb
         
     | 
| 
      
 42 
     | 
    
         
            +
            - lib/attach/attachment_binary.rb
         
     | 
| 
      
 43 
     | 
    
         
            +
            - lib/attach/backends/abstract.rb
         
     | 
| 
      
 44 
     | 
    
         
            +
            - lib/attach/backends/database.rb
         
     | 
| 
      
 45 
     | 
    
         
            +
            - lib/attach/backends/file_system.rb
         
     | 
| 
      
 46 
     | 
    
         
            +
            - lib/attach/middleware.rb
         
     | 
| 
      
 47 
     | 
    
         
            +
            - lib/attach/model_extension.rb
         
     | 
| 
      
 48 
     | 
    
         
            +
            - lib/attach/processor.rb
         
     | 
| 
      
 49 
     | 
    
         
            +
            - lib/attach/railtie.rb
         
     | 
| 
      
 50 
     | 
    
         
            +
            - lib/attach/version.rb
         
     | 
| 
      
 51 
     | 
    
         
            +
            homepage: https://github.com/adamcooke/attach
         
     | 
| 
      
 52 
     | 
    
         
            +
            licenses:
         
     | 
| 
      
 53 
     | 
    
         
            +
            - MIT
         
     | 
| 
      
 54 
     | 
    
         
            +
            metadata: {}
         
     | 
| 
      
 55 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 56 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 57 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 58 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 59 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 60 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 61 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 62 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 63 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 64 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 65 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 66 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 67 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 68 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 69 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 70 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 71 
     | 
    
         
            +
            rubygems_version: 2.5.1
         
     | 
| 
      
 72 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 73 
     | 
    
         
            +
            specification_version: 4
         
     | 
| 
      
 74 
     | 
    
         
            +
            summary: Attach documents & files to Active Record models
         
     | 
| 
      
 75 
     | 
    
         
            +
            test_files: []
         
     |