Module: Refile

Defined in:
lib/refile.rb,
lib/refile/app.rb,
lib/refile/file.rb,
lib/refile/type.rb,
lib/refile/rails.rb,
lib/refile/version.rb,
lib/refile/attacher.rb,
lib/refile/download.rb,
lib/refile/signature.rb,
lib/refile/attachment.rb,
lib/refile/file_double.rb,
lib/refile/custom_logger.rb,
lib/refile/random_hasher.rb,
lib/refile/backend_macros.rb,
lib/refile/backend/file_system.rb,
lib/refile/attachment_definition.rb,
lib/refile/rails/attachment_helper.rb,
lib/refile/attachment/active_record.rb,
lib/refile/attachment/multiple_attachments.rb

Defined Under Namespace

Modules: ActiveRecord, Attachment, AttachmentHelper, Backend Classes: App, Download, File, FileDouble, InvalidUrl, RandomHasher, Signature, TooManyRedirects, Type

Constant Summary collapse

VERSION =
"0.7.0"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.allow_downloads_fromArray[String], :all

A list of names which identify backends in the global backend registry. The Rack application allows GET requests to only the backends specified in this config option. This defaults to :all, allowing files from all backends to be downloaded.

Returns:

  • (Array[String], :all)

103
104
105
# File 'lib/refile.rb', line 103

def allow_downloads_from
  @allow_downloads_from
end

.allow_originString

Value for Access-Control-Allow-Origin header

Returns:

  • (String)

113
114
115
# File 'lib/refile.rb', line 113

def allow_origin
  @allow_origin
end

.allow_uploads_toArray[String], :all

A list of names which identify backends in the global backend registry. The Rack application allows POST requests to only the backends specified in this config option. This defaults to ["cache"], only allowing direct uploads to the cache backend.

Returns:

  • (Array[String], :all)

95
96
97
# File 'lib/refile.rb', line 95

def allow_uploads_to
  @allow_uploads_to
end

.appRefile::App?

A shortcut to the instance of the Rack application. This should be set when the application is initialized. refile/rails sets this value.

Returns:


43
44
45
# File 'lib/refile.rb', line 43

def app
  @app
end

.app_hostString?

The host name that the Rack application can be reached at. If not set, Refile will use an absolute URL without hostname. You should only change this setting if you are running the Refile app on a different domain than your main application.

If you are simply running the Refile app behind a CDN you'll want to change cdn_host instead.

The difference between app_host and cdn_host is that the latter only affects URLs generated by file_url and the Refile::AttachmentHelper#attachment_url and Refile::AttachmentHelper#attachment_image_tag helpers, whereas the former also affects upload_url, presign_url and the Refile::AttachmentHelper#attachment_field helper.

Returns:

  • (String, nil)

75
76
77
# File 'lib/refile.rb', line 75

def app_host
  @app_host
end

.automountBoolean

Should the rack application be automounted in a Rails app?

If set to false then Refile.app should be mounted in the Rails application routes.rb with the options at: Refile.mount_point, as: :refile_app

The default is true.

Returns:

  • (Boolean)

133
134
135
# File 'lib/refile.rb', line 133

def automount
  @automount
end

.cdn_hostString?

The host name of a CDN distribution that the Rack application can be reached at. If not set, Refile will use an absolute URL without hostname. It is strongly recommended to run Refile behind a CDN and to set this to the hostname of the CDN distribution.

The cdn_host setting is used when retrieving files, but not when uploading new files, since uploads should normally not go through the CDN.

A protocol relative URL is recommended for this value.

Returns:

  • (String, nil)

57
58
59
# File 'lib/refile.rb', line 57

def cdn_host
  @cdn_host
end

.content_max_ageInteger

Value for Cache-Control: max-age= header

Returns:

  • (Integer)

118
119
120
# File 'lib/refile.rb', line 118

def content_max_age
  @content_max_age
end

.loggerLogger

Logger that should be used by rack application

Returns:

  • (Logger)

108
109
110
# File 'lib/refile.rb', line 108

def logger
  @logger
end

.mount_pointString

Where should the rack application be mounted? The default is 'attachments'.

Returns:

  • (String)

123
124
125
# File 'lib/refile.rb', line 123

def mount_point
  @mount_point
end

.secret_keyString

Value for generating signed attachment urls to protect from DoS

Returns:

  • (String)

138
139
140
# File 'lib/refile.rb', line 138

def secret_key
  @secret_key
end

Class Method Details

.app_url(host: nil, prefix: nil) ⇒ String

Generates a URL to the Refile application.

The host defaults to app_host. You can also override the host via the host option. Normally the Refile app will not be mounted at the root but rather at some other path, the prefix option allows you to override this setting, and if not set it will fall back to mount_point.

Examples:

Refile.app_url

With host and prefix

Refile.app_url(host: "http://some.domain", prefix: "/refile")

Parameters:

  • host (String, nil) (defaults to: nil)

    Override the host

  • prefix (String, nil) (defaults to: nil)

    Adds a prefix to the URL if the application is not mounted at root

Returns:

  • (String)

    The generated URL


284
285
286
287
288
289
290
291
# File 'lib/refile.rb', line 284

def app_url(host: nil, prefix: nil)
  host ||= Refile.app_host
  prefix ||= Refile.mount_point

  uri = URI(host.to_s)
  uri.path = prefix || "/"
  uri.to_s
end

.attachment_presign_url(object, name, host: nil, prefix: nil) ⇒ String

Receives an instance of a class which has used the Refile::Attachment#attachment macro to generate an attachment column, and the name of this column, and based on this generates a URL to a App where a presign object for the backend can be retrieved.

Examples:

Refile.attachment_presign_url(@post, :document)

Parameters:

  • object (Refile::Attachment)

    Instance of a class which has an attached file

  • name (Symbol)

    The name of the attachment column

  • host (String, nil) (defaults to: nil)

    Override the host

  • prefix (String, nil) (defaults to: nil)

    Adds a prefix to the URL if the application is not mounted at root

Returns:

  • (String)

    The generated URL


443
444
445
446
447
# File 'lib/refile.rb', line 443

def attachment_presign_url(object, name, host: nil, prefix: nil)
  backend = object.send(:"#{name}_attachment_definition").cache

  presign_url(backend, host: host, prefix: prefix)
end

.attachment_upload_url(object, name, host: nil, prefix: nil) ⇒ String

Receives an instance of a class which has used the Refile::Attachment#attachment macro to generate an attachment column, and the name of this column, and based on this generates a URL to a App where files can be uploaded.

Examples:

Refile.attachment_upload_url(@post, :document)

Parameters:

  • object (Refile::Attachment)

    Instance of a class which has an attached file

  • name (Symbol)

    The name of the attachment column

  • host (String, nil) (defaults to: nil)

    Override the host

  • prefix (String, nil) (defaults to: nil)

    Adds a prefix to the URL if the application is not mounted at root

Returns:

  • (String)

    The generated URL


424
425
426
427
428
# File 'lib/refile.rb', line 424

def attachment_upload_url(object, name, host: nil, prefix: nil)
  backend = object.send(:"#{name}_attachment_definition").cache

  upload_url(backend, host: host, prefix: prefix)
end

.attachment_url(object, name, *args, expires_at: nil, host: nil, prefix: nil, filename: nil, format: nil, force_download: nil) ⇒ String?

Generate a URL to an attachment. Receives an instance of a class which has used the Refile::Attachment#attachment macro to generate an attachment column, and the name of this column, and based on this generates a URL to a App.

Optionally the name of a processor and arguments to it can be appended.

If the filename option is not given, the filename is taken from the metadata stored in the attachment, or eventually falls back to the name.

The host defaults to cdn_host, which is useful for serving all attachments from a CDN. You can also override the host via the host option.

Returns nil if there is no file attached.

Examples:

Refile.attachment_url(@post, :document)

With processor

Refile.attachment_url(@post, :image, :fill, 300, 300, format: "jpg")

Parameters:

  • object (Refile::Attachment)

    Instance of a class which has an attached file

  • name (Symbol)

    The name of the attachment column

  • filename (String, nil) (defaults to: nil)

    The filename to be appended to the URL

  • format (String, nil) (defaults to: nil)

    A file extension to be appended to the URL

  • host (String, nil) (defaults to: nil)

    Override the host

  • prefix (String, nil) (defaults to: nil)

    Adds a prefix to the URL if the application is not mounted at root

  • expires_at (String, nil) (defaults to: nil)

    Adds a sulfix to the URL that sets the expiration time of the URL

  • force_download (String, nil) (defaults to: nil)

    Adds a sulfix to the URL to force the download of the file when URL is accessed

Returns:

  • (String, nil)

    The generated URL


400
401
402
403
404
405
406
407
408
409
# File 'lib/refile.rb', line 400

def attachment_url(object, name, *args, expires_at: nil, host: nil, prefix: nil, filename: nil, format: nil, force_download: nil)
  attacher = object.send(:"#{name}_attacher")
  file = attacher.get
  return unless file

  filename ||= attacher.basename || name.to_s
  format ||= attacher.extension

  file_url(file, *args, expires_at: expires_at, host: host, prefix: prefix, filename: filename, format: format, force_download: force_download)
end

.backendsHash{String => Backend}

A global registry of backends.

Returns:


143
144
145
# File 'lib/refile.rb', line 143

def backends
  @backends ||= {}
end

.cacheBackend

A shortcut to retrieving the backend named "cache" from the global registry.

Returns:


217
218
219
# File 'lib/refile.rb', line 217

def cache
  backends["cache"]
end

.cache=(backend) ⇒ Object

A shortcut to setting the backend named "cache" in the global registry.

Parameters:


224
225
226
# File 'lib/refile.rb', line 224

def cache=(backend)
  backends["cache"] = backend
end

.configure { ... } ⇒ Object

Yield the Refile module as a convenience for configuring multiple config options at once.

Yields:

  • Refile


232
233
234
# File 'lib/refile.rb', line 232

def configure
  yield self
end

.extract_content_type(uploadable) ⇒ String?

Extract the content type from an uploadable object. If the content type cannot be determined, this method will return nil.

Parameters:

  • uploadable (IO)

    The uploadable object to extract the content type from

Returns:

  • (String, nil)

    The extracted content type


255
256
257
258
259
260
261
262
263
264
265
# File 'lib/refile.rb', line 255

def extract_content_type(uploadable)
  if uploadable.respond_to?(:content_type)
    uploadable.content_type
  else
    filename = extract_filename(uploadable)
    if filename
      content_type = MIME::Types.of(filename).first
      content_type.to_s if content_type
    end
  end
end

.extract_filename(uploadable) ⇒ String?

Extract the filename from an uploadable object. If the filename cannot be determined, this method will return nil.

Parameters:

  • uploadable (IO)

    The uploadable object to extract the filename from

Returns:

  • (String, nil)

    The extracted filename


241
242
243
244
245
246
247
248
# File 'lib/refile.rb', line 241

def extract_filename(uploadable)
  path = if uploadable.respond_to?(:original_filename)
    uploadable.original_filename
  elsif uploadable.respond_to?(:path)
    uploadable.path
  end
  ::File.basename(path) if path
end

.file_url(file, *args, expires_at: nil, host: nil, prefix: nil, filename:, format: nil, force_download: nil) ⇒ String?

Receives a File and generates a URL to it.

Optionally the name of a processor and arguments to it can be appended.

The filename option must be given.

The host defaults to cdn_host, which is useful for serving all attachments from a CDN. You can also override the host via the host option.

Returns nil if the supplied file is nil.

Examples:

Refile.file_url(Refile.store.get(id))

With processor

Refile.file_url(Refile.store.get(id), :image, :fill, 300, 300, format: "jpg")

Parameters:

  • file (Refile::File)

    The file to generate a URL for

  • filename (String)

    The filename to be appended to the URL

  • format (String, nil) (defaults to: nil)

    A file extension to be appended to the URL

  • host (String, nil) (defaults to: nil)

    Override the host

  • prefix (String, nil) (defaults to: nil)

    Adds a prefix to the URL if the application is not mounted at root

  • expires_at (String, nil) (defaults to: nil)

    Adds a sulfix to the URL that sets the expiration time of the URL

  • force_download (String, nil) (defaults to: nil)

    Adds a sulfix to the URL to force the download of the file when URL is accessed

Returns:

  • (String, nil)

    The generated URL


319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/refile.rb', line 319

def file_url(file, *args, expires_at: nil, host: nil, prefix: nil, filename:, format: nil, force_download: nil)
  return unless file

  host ||= Refile.cdn_host
  backend_name = Refile.backends.key(file.backend)

  filename = Rack::Utils.escape(filename)
  filename << "." << format.to_s if format && !filename.downcase.end_with?(format.to_s.downcase)

  base_path = ::File.join("", backend_name, *args.map(&:to_s), file.id.to_s, filename)
  if expires_at
    base_path += "?expires_at=#{expires_at.to_i}" # UNIX timestamp
  end

  base_path += "?force_download=true" if force_download

  ::File.join(app_url(prefix: prefix, host: host), token(base_path), base_path)
end

.hostObject

Deprecated.

use cdn_host instead


78
79
80
81
# File 'lib/refile.rb', line 78

def host
  warn "Refile.host is deprecated, please use Refile.cdn_host instead"
  cdn_host
end

.host=(host) ⇒ Object

Deprecated.

use cdn_host instead


84
85
86
87
# File 'lib/refile.rb', line 84

def host=(host)
  warn "Refile.host is deprecated, please use Refile.cdn_host instead"
  self.cdn_host = host
end

.presign_url(backend, host: nil, prefix: nil) ⇒ String

Receives a Refile backend and returns a URL to the Refile application where a presign object for the backend can be retrieved.

Parameters:

  • backend (Refile::Backend)

    The backend to generate a URL for

  • host (String, nil) (defaults to: nil)

    Override the host

  • prefix (String, nil) (defaults to: nil)

    Adds a prefix to the URL if the application is not mounted at root

Returns:

  • (String)

    The generated URL


364
365
366
# File 'lib/refile.rb', line 364

def presign_url(backend, host: nil, prefix: nil)
  ::File.join(upload_url(backend, host: host, prefix: prefix), "presign")
end

.processor(name, processor = nil) {|Refile::File| ... }

This method returns an undefined value.

Adds a processor. The processor must respond to call, both receiving and returning an IO-like object. Alternatively a block can be given to this method which also receives and returns an IO-like object.

An IO-like object is recommended to be an instance of the IO class or one of its subclasses, like File or a StringIO, or a Refile::File. It can also be any other object which responds to size, read, eof? rewind and close and mimics the behaviour of IO objects for these methods.

Examples:

With processor class

class Reverse
  def call(file)
    StringIO.new(file.read.reverse)
  en
end
Refile.processor(:reverse, Reverse)

With block

Refile.processor(:reverse) do |file|
  StringIO.new(file.read.reverse)
end

Parameters:

  • name (#to_s)

    The name of the processor

  • processor (Proc, nil) (defaults to: nil)

    The processor, must respond to call and.

Yields:

Yield Returns:

  • (IO)

    An IO-like object representing the processed file


193
194
195
196
# File 'lib/refile.rb', line 193

def processor(name, processor = nil, &block)
  processor ||= block
  processors[name.to_s] = processor
end

.processorsHash{String => Proc}

A global registry of processors. These will be used by the Rack application to manipulate files prior to serving them up to the user, based on options sent trough the URL. This can be used for example to resize images or to convert files to another file format.

Returns:

  • (Hash{String => Proc})

153
154
155
# File 'lib/refile.rb', line 153

def processors
  @processors ||= {}
end

.storeBackend

A shortcut to retrieving the backend named "store" from the global registry.

Returns:


202
203
204
# File 'lib/refile.rb', line 202

def store
  backends["store"]
end

.store=(backend) ⇒ Object

A shortcut to setting the backend named "store" in the global registry.

Parameters:


209
210
211
# File 'lib/refile.rb', line 209

def store=(backend)
  backends["store"] = backend
end

.token(path) ⇒ String?

Generate a signature for a given path concatenated with the configured secret token.

Raises an error if no secret token is configured.

Examples:

Refile.token('/store/f5f2e4/document.pdf')

Parameters:

  • path (String)

    The path to generate a token for

Returns:

  • (String, nil)

    The generated token

Raises:


459
460
461
462
463
464
465
466
467
468
469
# File 'lib/refile.rb', line 459

def token(path)
  if secret_key.nil?
    error = "Refile.secret_key was not set.\n\n"
    error << "Please add the following to your Refile configuration and restart your application:\n\n"
    error << "```\nRefile.secret_key = '#{SecureRandom.hex(64)}'\n```\n\n"

    raise error
  end

  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha1"), secret_key, path)
end

.typesHash{Symbol => Refile::Type}

A global registry of types. Currently, types are simply aliases for a set of content types, but their functionality may expand in the future.

Returns:


161
162
163
# File 'lib/refile.rb', line 161

def types
  @types ||= {}
end

.upload_url(backend, host: nil, prefix: nil) ⇒ String

Receives a Refile backend and returns a URL to the Refile application where files can be uploaded.

Examples:

Refile.upload_url(Refile.store)

Parameters:

  • backend (Refile::Backend)

    The backend to generate a URL for

  • host (String, nil) (defaults to: nil)

    Override the host

  • prefix (String, nil) (defaults to: nil)

    Adds a prefix to the URL if the application is not mounted at root

Returns:

  • (String)

    The generated URL


348
349
350
351
352
# File 'lib/refile.rb', line 348

def upload_url(backend, host: nil, prefix: nil)
  backend_name = Refile.backends.key(backend)

  ::File.join(app_url(host: host, prefix: prefix), backend_name)
end

.valid_token?(path, token) ⇒ Boolean

Check if the given token is a valid token for the given path.

Examples:

Refile.valid_token?('/store/f5f2e4/document.pdf', 'abcd1234')

Parameters:

  • path (String)

    The path to check validity for

  • token (String)

    The token to check

Returns:

  • (Boolean)

    Whether the token is valid

Raises:


480
481
482
483
484
485
# File 'lib/refile.rb', line 480

def valid_token?(path, token)
  expected = Digest::SHA1.hexdigest(token(path))
  actual = Digest::SHA1.hexdigest(token)

  expected == actual
end