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
-
Add to your
Gemfilegem 'anaconda', '>= 5.0.0' -
bundle install -
Add the following to your
application.js//= require anaconda -
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 endThis 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_columnsinstead.If you prefer to do this manually the fields this permit are listed below.
For each
anaconda_for(assuminganaconda_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 :assetinside 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 endAt this time the available options on anaconda_for are:
-
base_keydefault: %{plural model}/%{plural column}/%{random string} -
aws_access_keydefault: aws_access_key specified in Anaconda config -
aws_secret_keydefault: aws_secret_key specified in Anaconda config -
aws_bucketdefault: aws_bucket specified in Anaconda config
-
-
aws_endpointdefault: aws_endpoint specified in Anaconda config-
acldefault public-read -
max_file_sizedefault:500.megabytes -
allowed_file_typesdefault: all -
hostString. 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 -
protocolhttps,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_changeBoolean. If true, files will be removed from S3 when a new file is uploaded. Default:true -
remove_previous_s3_files_on_destroyBoolean. 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.submitRails Form Builder:
= form_with model: @post, class: "form" do |f| = f.anaconda :file, auto_upload: true, auto_submit: trueForm 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>_detailsex: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 withauto_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_urlandasset_download_url.asset_urlwill return a signed S3 URL if the file is stored with an ACL ofprivateand will return a non-signed URL if the file is stored with public access. This accepts a single optional hash argument with possible parametersprotocolandexpires.asset_download_urlwill 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 areexpiresandfilename.filenameis the name the file will be downloaded as.protocol, if specified here, will override the default value set in the model.
expiresis 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-constructorfired when the first upload element constructs an upload manager for a form -
anaconda:manager:upload-field-registeredfired when an upload field registers itself with an upload manager -
anaconda:manager:uploads-startingfired when the form is submitted and Anaconda starts uploading the selected files -
anaconda:manager:upload-completedfired each time an upload is completed -
anaconda:manager:all-uploads-completedfired once all uploads have completed -
anaconda:file-selectedfired when a file is selected -
anaconda:file-upload-failedfired when an upload fails -
anaconda:file-upload-startedfired for each upload when it is started -
anaconda:invalid-file-type-selectedfired when a non-permitted file type is selected -
anaconda:file-upload-completedfired when an upload is completed
If you return false to the following events it will prevent the default behavior:
-
anaconda:invalid-file-type-selectedDefault behavior is an alert with content_filename_ is a _filetype_ file. Only _allowed file types_ files are allowed. -
anaconda:file-upload-failedDefault 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
bucketin theanaconda_fordeclaration.
- Fix bug causing files to upload to the wrong bucket if you overwrote
-
2.1.1
- Handle passing "://" as part of the protocol in
asset_download_urlmagic method
- Handle passing "://" as part of the protocol in
-
2.1.0
- Add
import_file_to_anaconda_column(file, column_name)method. Needs documenting.
- Add
-
2.0.2
- Fix
asset_urlmethod. There's tests coming. I swear.
- Fix
-
2.0.1 YANKED
- Fix
asset_download_urlmethod
- Fix
-
2.0.0 YANKED Breaking Changes!
- The options you can pass to
anaconda_forhave changed. - Add ability for
anaconda_foroptions to be procs so we can have instance specific data there. - Clean the
filenamethat is passed to theasset_download_urlmethod
- The options you can pass to
-
1.0.11
- Add ability to pass
filenameto theasset_download_urlmethod.
- Add ability to pass
-
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_urlmethod.
- Fix another bug breaking the
-
1.0.8 (yanked)
- Fix bug breaking the
asset_download_urlmethod.
- Fix bug breaking the
-
1.0.7
- Add support for
expiresoption in signed AWS URLs. - Add option for setting default
expiry_lengthfor signed AWS URLs.
- Add support for
-
1.0.6
- Loosen dependency requirement on
simple_form
- Loosen dependency requirement on
-
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()andanaconda_form_data_for_all_columnsinstance methods that return the raw data needed to upload to AWS
- Add
-
1.0.3
- Properly define dependencies so they are included
- Add support for non US Standard region buckets. See new
aws_endpointoption 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_completemethod in AnacondaUploadManager - Remove unused
upload_helper.rband other old code. - Add a bunch of JavaScript events
- Fix incorrect return value from
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.