Skip to content

krystal/attach

Repository files navigation

Attach

Attach allow you to attach files/images/documents to Active Record models with ease. Just define which attachments you wish to add and you can easily upload them to your server (either file system or database).

Installation

In order to get started, add the gem to your Gemfile:

gem 'attach', '~> 2.0'

Once included, add the database table which will store your attachments.

$ rake attach:install:migrations
$ rake db:migrate

Getting started

You can define the attachments which you wish to use on any of your models as such:

class Person < ActiveRecord::Base
  attachment :cover_photo
  attachment :profile_picture
end

Uploading attachments

You'll have a reader and a writer for the attachment that you've created which allows you to set the file to be uploaded. For example:

person = Person.new

# Set the photo from some data you have
person.cover_photo = some_binary_data
person.cover_photo.file_name = "cover-photo.jpg"
person.cover_photo.file_type = "image/jpg"

# Or you can pass in an ActionDispatch::Http::UploadedFile
person.cover_photo = params[:person][:cover_photo]

# You can also pass a pre-constructed `Attach::File` object
file = Attach::File.new(binary)
file.name = "some-name.pdf"
file.type = "application/pdf"
person.cover_photo = file

It's worth noting that calling your reader will always return an Attach::Attachment object regardless of what you pass in. If you pass in an uploaded file it will be converted to the Attach::Attachment object at the point it is set.

Uploading from a form

<% form_for @person, :html => {:multipart => true} do |f| %>
  <%= f.file_field :profile_picture %>
  <%= f.file_field :cover_photo %>
  <%= f.submit "Upload Attachments" %>
<% end %>

Accessing attachments

You can access any of your attachments easily through the methods as shown below.

# Accessing attachments
person = Person.find(person.id)
person.cover_photo              #=> Attach::Attachment
person.cover_photo.url          #=> "/attachment/145d17ed-d5e3-4b55-8c89-ecad9521ad73/snom-mm2.jpg"
person.cover_photo.file_name    #=> "snom-mm2.jpg"
person.cover_photo.digest       #=> "c4de7fd75a7e2ec37bde3a5ef9fa53a1ce9228c0"
person.cover_photo.blob.read    #=> <Binary data>

To download the stored asset, you can use the value of the url. Attach has a middleware that will render these files for you automatically. By default, the middleware will serve all attachments as long as the user has the UUID of the attachment. If you wish to disable the serving of certain attachments (i.e. secure files that should be authenticated first), you should set the serve option to false.

attachment :passport_scan, :serve => false

Preloading attachments

If you're obtaining an array of objects and wish to have attachment information ready to go, you can include it as follows:

# This will include the details about the attachment (not including the binary)
people = Person.includes_attachments(:cover_photo)

Deleting images

If you wish to remove an image you can simply call destroy. If you want to do this from a form, you can add a checkbox with the name {name}_delete.

Backends

You can choose between storing your images in your database or on the file system. The method you choose will depend on your environment and usage requirements. By default, files are stored in the database.

To use the file system, just use the following:

# Stores files in an 'attachments' directory in the root of your app
Attach.use_filesystem!
# Stores files in the directory you specify
Attach.use_filesystem!(:root => 'path/to/root/dir')

You can also write your own backends. Check out the abstract backend for instructions. Once you've written one, just set it as the backend.

Attach.backend = MyApp::MyCleverAttachBackend.new

Caching & Disposition

When you serve assets out through the included middleware, by default they will be served with a private, max-age=<1 year> cache control header. This can be changed to suit the needs of each type of attachment.

attachment :cover_photo, :cache_type => 'public', :cache_max_age => 5.days

The disposition of a file served by the middleware will be attachment by default. You can change this:

attachment :cover_photo, :disposition => 'inline'

Validation

To validate an image before persisting it to your backend you can include a validation block.

attachment :image do
  validator do |attachment, errors|
    unless Lizard::Image.is_image?(attachment.blob.read)
      errors << "must be an image"
    end
  end
end

Custom Data

Attachments have a custom attribute which allows you to store data with an attachment. You might use this to store the width/height of an image in a processor.

attachment :image do
  processor do |attachment|
    image = Lizard::Image.new(attachment.blob.read)
    attachment.custom['width'] = image.width
    attachment.custom['height'] = image.height
  end
end

Children

Attachments can have child attachments which are associated with the first one. This is useful if you're uploading images and wish to generate different thumbnails for it automatically. It works like this:

Creating children

The easiest place to create children is in the processing block for an attachment. You should call the add_child method with the role for the new item. This should be unique across all children in the parent image. If you upload a new child with the same name later, the original will be removed.

attachment :cover_photo do
  processor do |attachment|
    image = Lizard::Image.new(attachment.blob.read)
    attachment.add_child(:thumb500) do |c|
      c.blob = Attach::BlobTypes::Raw.new(image.resize(500, 500).data)
      c.file_name = "thumb500x500.jpg"
    end
  end
end

Accessing children

If you have a single object you wish to find a child for, the easiest way is like such...

post = Post.find(31)
post.cover_photo                    # => The original attachment
post.cover_photo.child(:thumb500)   # => The child attachment

If you're loading multiple objects though you may wish to preload the images that you desire in a single query rather than looking up each one in turn.

Post.includes_attachments(:cover_photo => [:thumb500]).each do |post|
  post.cover_photo.child(:thumb500) # => No additional database queries
end

CDNs

If you have an origin pull CDN and would like the url attribute for your attachments to include the appropriate CDN host, you can set it.

Attach.asset_host = "https://cdn.exampleapp.com"