Gem for managing active record attachments that are to be uploaded directly to Amazon S3.
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
app/assets/javascripts
lib
spec
vendor/assets
.document
.gitignore
.rspec
CHANGELOG.md
Gemfile
LICENSE
README-Rails-4.md
README.md
Rakefile
anaconda.gemspec

README.md

Anaconda

Dead simple direct-to-s3 file uploading for your rails app.

Requirements

Rails 5+. For Rails 3 & 4, see the Rails 4 Documentation

Installation

  1. Add to your Gemfile

    gem 'anaconda', '>= 5.0.0'
    
  2. bundle install

  3. Add the following to your application.js

    //= require anaconda
    
  4. Finally, run the installer to install the configuration initializer into config/initializers/anaconda.rb

    $ rails g anaconda:install
    

Configuration

AWS S3 Setup

Create a bucket where you want your uploads to go. If you already have a bucket in place, you can certainly use it.

IAM

For best security we recommend creating a user in IAM that will just be used for file uploading. Once you create that user you can apply a security policy to it so they can only access the specified resources. Here is an example IAM policy that will restrict this user to only have access to the one bucket specified (be sure to replace 'your.bucketname'). Be sure to generate security credentials for this user. These are the S3 credentials you will use.

{
  "Version": "2012-10-17",
  "Statement": [
        {
          "Effect": "Allow",
          "Action": "s3:*",
          "Resource": [
              "arn:aws:s3:::[your.bucketname]",
              "arn:aws:s3:::[your.bucketname]/*"
            ]
        }
    ]
}

CORS

You will need to set up CORS permissions on the bucket so users can upload to it from your website. Below is a sample CORS configuration.

If users will only upload from one domain, you can put that in your AllowedOrigin. If they will upload from multiple domains you may either add an AllowedOrigin for each of them, or use a wildcard * origin as in our example below.

<?xml version="1.0" encoding="UTF-8"?>
  <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
</CORSConfiguration>

Initializer

The initializer installed into config/initializers/anaconda.rb contains the settings you need to get anaconda working.

You must set these to your S3 credentials/settings in order for anaconda to work.

We highly recommend the figaro gem https://github.com/laserlemon/figaro to manage your environment variables in development and production.

Usage

  • Controller changes

    You must add these parameters to your permitted parameters. In Rails 4+ this is done via strong parameters in the controller.

    We add two Class methods on the model that return an array of columns used by each anaconda_for column. One is scoped per anaconda field, and the other returns all of the columns anaconda needs for the entire model. This is useful if you have multiple anaconda columns in your model.

    You can use these methods in your strong parameter list directly. Ex:

     PostMediasController < Application Controller
     ...
     	def post_media_params
     		params.require(:post_media).permit(
     		:name,
     		:foobar,
     		PostMedia.anaconda_fields_for( :asset )
     		)
     	end
     end
    

    This keeps your strong parameter list clean and dry. If you have multiple anaconda models in your model and you wish for all of the params for all of the models to be permitted, you may use PostMedia.anaconda_fields_for_all_columns instead.

    If you prefer to do this manually the fields this permit are listed below.

    For each anaconda_for (assuming anaconda_for :asset):

    • :asset_filename
    • :asset_file_path
    • :asset_size
    • :asset_original_filename
    • :asset_stored_privately
    • :asset_type
  • Migrations

    We provide a migration generator. Assuming anaconda_for :asset inside of PostMedia model:

     $ rails g anaconda:migration PostMedia asset
    
  • Model setup

     class PostMedia < ActiveRecord::Base
       belongs_to :post
     	
       anaconda_for :asset, base_key: :asset_key
     	
       def asset_key
     	o = [('a'..'z'), ('A'..'Z')].map { |i| i.to_a }.flatten
     	s = (0...24).map { o[rand(o.length)] }.join
     	"post_media/#{s}"
       end
     end
    

    At this time the available options on anaconda_for are:

    • base_key default: %{plural model}/%{plural column}/%{random string}
    • aws_access_key default: aws_access_key specified in Anaconda config
    • aws_secret_key default: aws_secret_key specified in Anaconda config
    • aws_bucket default: aws_bucket specified in Anaconda config
  • aws_endpoint default: aws_endpoint specified in Anaconda config

    • acl default public-read
    • max_file_size default: 500.megabytes
    • allowed_file_types default: all
    • host String. If specified, this will be used to access publically stored objects instead of the S3 bucket. Useful for CloudFront integration. Note: At this time privately stored objects will still be requested via S3. Default: false
    • protocol https, http, or :auto. If :auto, // will be used as the protocol. Note: At this time, all privately stored objects are requested over https. Default: http
    • remove_previous_s3_files_on_change Boolean. If true, files will be removed from S3 when a new file is uploaded. Default: true
    • remove_previous_s3_files_on_destroy Boolean. If true, files will be removed from S3 when a record is destroyed. Default: true
    • expiry_length - If supplied, this is the length in seconds that a signed URL is valid for. Default: 1.hour

Any anaconda_for option may also be a proc that will be evaluated in the context of the current instance.

  • Form setup

    Anaconda fields are supported inside of either a simple_form form builder, or the default rails form builder.

    SimpleForm Builder:

      = simple_form_for post_media do |f|
     	   = f.anaconda :asset
     	   = f.name
     	   = f.other_field
     	   = f.submit
    

    Rails Form Builder:

     = form_with model: @post, class: "form" do |f|
       = f.anaconda :file, auto_upload: true, auto_submit: true
    

    Form helper options

    There are a variety of options available on the form helper. At this time they are:

    • upload_details_container - An element id you would like the upload details located in. Defaults to <resource>_<attribtue>_details ex: post_media_asset_details
    • auto_upload - If set to true, upload will begin as soon as a file is selected. Default: false
    • auto_submit - If set to true, form will submit automatically when upload is completed. Useful when mixed with auto_upload: true, especially if the file field is the only field on the form. Default: true when auto_upload is false; false when auto_upload is true.
    • base_key - If supplied, this will be the base_key used for this upload
  • Fields

    At this point you will have these methods available on a post_media instance:

    • :asset_filename
    • :asset_file_path
    • :asset_size
    • :asset_original_filename
    • :asset_stored_privately
    • :asset_type
    • :asset_url
    • :asset_download_url

    The magic methods are asset_url and asset_download_url.

    asset_url will return a signed S3 URL if the file is stored with an ACL of private and will return a non-signed URL if the file is stored with public access. This accepts a single optional hash argument with possible parameters protocol and expires.

    asset_download_url will return a signed S3 URL with content-disposition set to attachment so the file will be downloaded instead of opened in the browser. This accepts a single optional hash argument. The currently supported parameters in this hash are expires and filename. filename is the name the file will be downloaded as.

    protocol, if specified here, will override the default value set in the model.
    expires is a DateTime object when a signed URL will be valid until. On a file stored publically, this has no effect.

    Example usages:

     # Assuming the files are stored privately.
     
     asset_url(expires: 3.hours.from_now)
     # Generates a URL that is valid for 3 hours
     
     asset_url(protocol: "https", expires: 5.minutes.from_now)
     # Generates a URL that is valid for 5 minutes, and uses the https protocol
     
     asset_download_url(expires: 5.minutes.from_now)
     # Generates a URL that is valid for 5 minutes, and will cause the browser to download the object.
     # This is most useful for things like images that most browsers try to display.
    

Advanced Usage

Events

There are several events fired throughout the upload process that you can subscribe to. Many of them contain useful data along with the event. The documentation needs expanding here.

  • anaconda:manager:upload-manager-constructor fired when the first upload element constructs an upload manager for a form
  • anaconda:manager:upload-field-registered fired when an upload field registers itself with an upload manager
  • anaconda:manager:uploads-starting fired when the form is submitted and Anaconda starts uploading the selected files
  • anaconda:manager:upload-completed fired each time an upload is completed
  • anaconda:manager:all-uploads-completed fired once all uploads have completed
  • anaconda:file-selected fired when a file is selected
  • anaconda:file-upload-failed fired when an upload fails
  • anaconda:file-upload-started fired for each upload when it is started
  • anaconda:invalid-file-type-selected fired when a non-permitted file type is selected
  • anaconda:file-upload-completed fired when an upload is completed

If you return false to the following events it will prevent the default behavior:

  • anaconda:invalid-file-type-selected Default behavior is an alert with content _filename_ is a _filetype_ file. Only _allowed file types_ files are allowed.
  • anaconda:file-upload-failed Default behavior is an alert with content _filename_ failed to upload.

Versioning

From version 1.0.0 on we have used Semantic Versioning.

Changelog

  • 5.0.3

    • Fix bug where multiple anaconda forms on the same page would not properly display the upload progress in the right container.
  • 5.0.2

    • Fix bug where multiple anaconda forms on the same page would not properly submit the right data to the controller.
  • 5.0.1

    • Bugfix
  • 5.0.0

    • Support Rails 5
  • 2.1.2

    • Fix bug causing files to upload to the wrong bucket if you overwrote bucket in the anaconda_for declaration.
  • 2.1.1

    • Handle passing "://" as part of the protocol in asset_download_url magic method
  • 2.1.0

    • Add import_file_to_anaconda_column(file, column_name) method. Needs documenting.
  • 2.0.2

    • Fix asset_url method. There's tests coming. I swear.
  • 2.0.1 YANKED

    • Fix asset_download_url method
  • 2.0.0 YANKED Breaking Changes!

    • The options you can pass to anaconda_for have changed.
    • Add ability for anaconda_for options to be procs so we can have instance specific data there.
    • Clean the filename that is passed to the asset_download_url method
  • 1.0.11

    • Add ability to pass filename to the asset_download_url method.
  • 1.0.10

    • Add 610ms delay after final file is uploaded before submitting the form. Some browsers stop all CSS transitions when the form is submitted and this was preventing the progress bar from reaching 100%. This allows it to reach 100% before submitting the form, so users don't get the impression that the file failed to fully upload.
  • 1.0.9

    • Fix another bug breaking the asset_download_url method.
  • 1.0.8 (yanked)

    • Fix bug breaking the asset_download_url method.
  • 1.0.7

    • Add support for expires option in signed AWS URLs.
    • Add option for setting default expiry_length for signed AWS URLs.
  • 1.0.6

    • Loosen dependency requirement on simple_form
  • 1.0.5

    • Fix bug where we were generating the same base_key for all instances if you didn't define a custom method
  • 1.0.4

    • Add anaconda_form_data_for() and anaconda_form_data_for_all_columns instance methods that return the raw data needed to upload to AWS
  • 1.0.3

    • Properly define dependencies so they are included
    • Add support for non US Standard region buckets. See new aws_endpoint option in the config
  • 1.0.2

    • Refactor S3Uploader into it's own class so it can be used outside of the form helper
  • 1.0.1

    • Use OpenSSL::Digest instead of deprecated OpenSSL::Digest::Digest.
  • 1.0.0

    • Fix incorrect return value from all_uploads_are_complete method in AnacondaUploadManager
    • Remove unused upload_helper.rb and other old code.
    • Add a bunch of JavaScript events

See changelog for previous changes

Contributing to anaconda

  • Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
  • Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
  • Fork the project.
  • Start a feature/bugfix branch.
  • Commit and push until you are happy with your contribution.
  • Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
  • Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.

Copyright

Copyright (c) 2017 Forge Apps, LLC. See LICENSE for further details.