Data Storage

Each dragonfly app has a key-value datastore to store the content (originals only).

Lets say we have an app

app = Dragonfly[:my_app_name]

Then we can store data like so:

# Can pass in a String, Pathname, File or Tempfile
uid = app.store('SOME CONTENT')

We can also save metadata at the same time, and any other options the configured datastore accepts

uid = app.store('SOME CONTENT',
  :meta => {:time => Time.now, :name => 'content.txt'},
  :some => 'option'
)

We can get content with

content = app.fetch(uid)
content.data         # "SOME CONTENT"

We can also get the extra saved attributes

content.meta         # {:time => Sat Aug 14 12:04:13 +0100 2010, :name => 'content.txt'}
content.name         # 'content.txt'

We can destroy it with

app.destroy(uid)

Serving directly from the datastore

Datastores can optionally serve data directly too, by implementing url_for

app.datastore.url_for(uid, :some => 'option')   # ---> "http://some.url/thing.txt"

or (the same)

app.remote_url_for(uid, :some => 'option')

or

my_model.attachment.remote_url(:some => 'option')

You can create your own datastore, or use one of the provided ones as outlined below.

File datastore

The FileDataStore stores data on the local filesystem.

It is used by default.

If for whatever reason you need to configure it again:

# shouldn't need this - it is the default
app.datastore = Dragonfly::DataStorage::FileDataStore.new

app.datastore.configure do |d|
  d.root_path = '/filesystem/path/public/place'   # defaults to /var/tmp/dragonfly
  d.server_root = '/filesystem/path/public'       # filesystem root for serving from - default to nil
  d.store_meta = false                            # default to true - can be switched off to avoid
                                                  #  saving an extra .meta file if meta not needed
end

You can serve directly from the FileDataStore if the server_root is set.

To customize the storage path (and therefore the uid), use the :path option on store

app.store("SOME CONTENT", :path => 'some/path.txt')

To do this on a per-model basis see Models.

BEWARE!!!! you must make sure the path (which will become the uid for the content) is unique and changes each time the content is changed, otherwise you could have caching problems, as the generated urls will be the same for the same uid.

S3 datastore

To configure with the S3DataStore:

app.datastore = Dragonfly::DataStorage::S3DataStore.new

app.datastore.configure do |c|
  c.bucket_name = 'my_bucket'
  c.access_key_id = 'salfjasd34u23'
  c.secret_access_key = '8u2u3rhkhfo23...'
  c.region = 'eu-west-1'                        # defaults to 'us-east-1'
  c.storage_headers = {'some' => 'thing'}       # defaults to {'x-amz-acl' => 'public-read'}
  c.url_scheme = 'https'                        # defaults to 'http'
  c.url_host = 'some.custom.host'               # defaults to "<bucket_name>.s3.amazonaws.com"
end

You can also pass these options to S3DataStore.new as an options hash.

You can serve directly from the S3DataStore using e.g.

my_model.attachment.remote_url

or with an expiring url:

my_model.attachment.remote_url(:expires => 3.days.from_now)

or with an https url:

my_model.attachment.remote_url(:scheme => 'https')   # also configurable for all urls with 'url_scheme'

or with a custom host:

my_model.attachment.remote_url(:host => 'custom.domain')   # also configurable for all urls with 'url_host'

Extra options you can use on store are :path and :headers

app.store("SOME CONTENT", :path => 'some/path.txt', :headers => {'x-amz-acl' => 'public-read-write'})

To do this on a per-model basis see Models.

BEWARE!!!! you must make sure the path (which will become the uid for the content) is unique and changes each time the content is changed, otherwise you could have caching problems, as the generated urls will be the same for the same uid.

Mongo datastore

To configure with the MongoDataStore:

app.datastore = Dragonfly::DataStorage::MongoDataStore.new

It won't normally need configuring, but if you wish to:

app.datastore.configure do |d|
  c.host = 'http://egg.heads:5000'                  # defaults to localhost
  c.port = '27018'                                  # defaults to mongo default (27017)
  c.database = 'my_database'                        # defaults to 'dragonfly'
  c.username = 'some_user'                          # only needed if mongo is running in auth mode
  c.password = 'some_password'                      # only needed if mongo is running in auth mode
  c.connection_opts = {:name => 'prod'}             # arg gets passed to Mongo::Connection
                                                    #  or Mongo::ReplSetConnection initializer - see http://api.mongodb.org/ruby/current

  c.hosts = ['localhost:30000', 'localhost:30001']  # will use Mongo::ReplSetConnection instead of Mongo::Connection
end

If you already have a mongo database or connection available, you can skip setting these and set db or connection instead.

You can also pass any options to MongoDataStore.new as an options hash.

You can't serve directly from the mongo datastore.

You can optionally pass in a :content_type option to store to tell it the content's MIME type.

Couch datastore

To configure with the CouchDataStore:

app.datastore = Dragonfly::DataStorage::CouchDataStore.new

To configure:

app.datastore.configure do |d|
  c.host = 'localhost'                            # defaults to localhost
  c.port = '5984'                                 # defaults to couchdb default (5984)
  c.database = 'dragonfly'                        # defaults to 'dragonfly'
  c.username = ''                                 # not needed if couchdb is in 'admin party' mode
  c.password = ''                                 # not needed if couchdb is in 'admin party' mode
end

You can also pass these options to CouchDataStore.new as an options hash.

You can serve directly from the couch datastore.

You can optionally pass in a :content_type option to store to tell it what to use for its 'Content-Type' header.

Custom datastore

Data stores are key-value in nature, and need to implement 3 methods: store, retrieve and destroy.

class MyDataStore

  def store(temp_object, opts={})
    # ... use temp_object.data, temp_object.file, temp_object.path, etc.
    # ... also we can use temp_object.meta and store it ...

    # store and return the uid
    'return_some_unique_uid'
  end

  def retrieve(uid)
    # return an array containing
    [
      content,          # either a File, String or Tempfile
               # Hash - :name and :format are treated specially,
    ]                   #  e.g. job.name is taken from job.meta[:name]
  end

  def destroy(uid)
    # find the content and destroy
  end

end

You can now configure the app to use your datastore:

Dragonfly[:my_app_name].datastore = MyDataStore.new

Notice that store takes a second opts argument. Any options, get passed here. :meta is treated specially and is accessible inside MyDataStore#store as temp_object.meta

uid = app.store('SOME CONTENT',
  :meta => {:name => 'great_content.txt'},
  :some_other => :option
)

# ...

You can also optionally serve data directly from the datastore if it implements url_for:

class MyDataStore

  # ...

  def url_for(uid, opts={})
    "http://some.domain/#{uid}"
  end

end