Skip to content
camelpunch edited this page Nov 13, 2011 · 2 revisions

Config

Copy the example from config/ungulate.rb. This could go in e.g. config/initializers/ungulate.rb

You only need to set queue_name if you want to use thumbnailing with an SQS queue. If you're using your own queuing system, you don't need to set queue_name.

Controller(s)

You should read http://doc.s3.amazonaws.com/proposals/post.html first to understand the conditions section.

class UploadsController < ApplicationController
  def new
    @boring_image = BoringImage.find(params[:boring_image_id])

    config = YAML.load_file(Rails.root.join('config', 's3.yml'))[Rails.env]

    expiration = 10.hours.from_now

    # use a predefined key - here we use a timestamp so we don't have to wait for CloudFront to update 
    key = "boring_images/#{Time.now.strftime('%Y%m%d%H%M%S')}/#{@boring_image.to_param}"

    @upload = Ungulate::FileUpload.new(
      :bucket_url => "http://#{config['bucket']}.s3.amazonaws.com/",
      :key => key,
      :policy => {
        'expiration' => expiration,
        'conditions' => [
          {'bucket' => config['bucket']},
          {'key' => key},
          {'acl' => 'private'},
          {'success_action_redirect' => new_item_url}
        ]
      }
    )
  end
end

class ItemsController < ApplicationController
  def new
    @some_item = Item.new
    @some_item.key = params[:key]
    @some_item.save!
    redirect_to some_other_url
  end
end

Using starts-with

If you want to include the uploaded filename in the key that is stored on S3, you should use ${filename} in the key. However, to restrict the form to only permit uploading to a particular prefix before this filename, you must use starts-with, like so:

key = "some/prefix/to/${filename}"

@upload = Ungulate::FileUpload.new(
  :bucket_url => "http://#{config['bucket']}.s3.amazonaws.com/",
  :key => key,
  :policy => {
    'expiration' => expiration,
    'conditions' => [
      {'bucket' => config['bucket']},
      ['starts-with', '$key', 'some/prefix/to/'],
      {'acl' => 'private'},
      {'success_action_redirect' => new_item_url}
    ]
  }
)

View

e.g. for uploads/new action, first controller action above:

<% ungulate_upload_form_for(@upload) do %>
  <%= label_tag :file, 'Upload File' %>
  <%= file_field_tag :file %>
  <%= submit_tag 'Upload' %>
<% end %>

This outputs something like:

<form action="http://my-bucket.s3.amazonaws.com/" enctype="multipart/form-data" method="post">
<div>
<input name="key" type="hidden" value="boring_images/201004250000/123" />

<input name="AWSAccessKeyId" type="hidden" value="ASDFASDFASDF" />
<input name="acl" type="hidden" value="private" />
<input name="policy" type="hidden" value="SOMEGOBBLEDYNONSENSE=" />
<input name="signature" type="hidden" value="MORENONSENSEASDFASDF=" />
<input name="success_action_redirect" type="hidden" value="http://my.domain/items/new" />

  <label for="file">Upload File</label>
  <input id="file" name="file" type="file" />
  <input name="commit" type="submit" value="Upload" />

</div>
</form>

Model

You'll only need an enqueue method if you intend to do some image processing, such as thumbnailing and watermarking. Here's an example model with an enqueue method, for thumbnailing and watermarking:

class Item < ActiveRecord::Base
  after_create :ungulate_enqueue

  # send the image processing job to SQS
  def ungulate_enqueue
    config = YAML.load_file(Rails.root.join('config', 's3.yml'))[Rails.env]

    Ungulate::FileUpload.enqueue(
      :bucket => config['bucket'],
      :key => key,
      :notification_url => 'http://some.url/that/receives/a/put',
      :versions => {
        # Chained resize then watermark from URL.
        # See http://studio.imagemagick.org/RMagick/doc/image1.html#composite
        # RMagick constants are expressed as symbols, then converted when queue job is run
        # URLs are assumed to be the location of image blobs, and are converted to Magick::Image objects
        :large => [
          [ :resize_to_fill, 713, 600 ],
          [ :composite, 'https://some.host/someimage.png', :center_gravity, :soft_light_composite_op ]
        ],
        :thumbnail => [ :resize_to_fill, 224, 156 ],
        :large_thumbnail => [ :resize_to_fill, 231, 159 ],
      }
    )
  end
end

Here's an example Document class, that could be used to upload arbitrary documents. If you want to produce signed links to content with private ACL set, you'll need a method to produce URLs, such as in the following example, which makes the link available for two hours.

class Document < ActiveRecord::Base
  belongs_to :owner, :polymorphic => true
  validates_presence_of :key
  validates_uniqueness_of :key

  def url
    RightAws::S3Generator.new(S3_CONFIG['access_key_id'], 
                              S3_CONFIG['secret_access_key'],
                              :port => 80,
                              :protocol => 'http').
      bucket(S3_CONFIG['bucket']).
      get(key, 2.hours)
  end
end
Clone this wiki locally