lib/refile/attachment.rb
module Refile
module Attachment
# Macro which generates accessors for the given column which make it
# possible to upload and retrieve previously uploaded files through the
# generated accessors.
#
# The `raise_errors` option controls whether assigning an invalid file
# should immediately raise an error, or save the error and defer handling
# it until later.
#
# Given a record with an attachment named `image`, the following methods
# will be added:
#
# - `image`
# - `image=`
# - `remove_image`
# - `remove_image=`
# - `remote_image_url`
# - `remote_image_url=`
# - `image_url`
# - `image_presigned_url`
#
# @example
# class User
# extend Refile::Attachment
#
# attachment :image
# attr_accessor :image_id
# end
#
# @param [String] name Name of the column which accessor are generated for
# @param [#to_s] cache Name of a backend in {Refile.backends} to use as transient cache
# @param [#to_s] store Name of a backend in {Refile.backends} to use as permanent store
# @param [true, false] raise_errors Whether to raise errors in case an invalid file is assigned
# @param [Symbol, nil] type The type of file that can be uploaded, see {Refile.types}
# @param [String, Array<String>, nil] extension Limit the uploaded file to the given extension or list of extensions
# @param [String, Array<String>, nil] content_type Limit the uploaded file to the given content type or list of content types
# @return [void]
def attachment(name, cache: :cache, store: :store, raise_errors: true, type: nil, extension: nil, content_type: nil)
definition = AttachmentDefinition.new(name,
cache: cache,
store: store,
raise_errors: raise_errors,
type: type,
extension: extension,
content_type: content_type
)
define_singleton_method :"#{name}_attachment_definition" do
definition
end
mod = Module.new do
attacher = :"#{name}_attacher"
define_method :"#{name}_attachment_definition" do
definition
end
define_method attacher do
ivar = :"@#{attacher}"
instance_variable_get(ivar) or instance_variable_set(ivar, Attacher.new(definition, self))
end
define_method "#{name}=" do |value|
send(attacher).set(value)
end
define_method name do
send(attacher).get
end
define_method "remove_#{name}=" do |remove|
send(attacher).remove = remove
end
define_method "remove_#{name}" do
send(attacher).remove
end
define_method "remote_#{name}_url=" do |url|
send(attacher).download(url)
end
define_method "remote_#{name}_url" do
end
define_method "#{name}_url" do |*args|
Refile.attachment_url(self, name, *args)
end
define_method "presigned_#{name}_url" do |expires_in = 900|
attachment = send(attacher)
attachment.store.object(attachment.id).presigned_url(:get, expires_in: expires_in) unless attachment.id.nil?
end
define_method "#{name}_data" do
send(attacher).data
end
define_singleton_method("to_s") { "Refile::Attachment(#{name})" }
define_singleton_method("inspect") { "Refile::Attachment(#{name})" }
end
include mod
end
# Macro which generates accessors in pure Ruby classes for assigning
# multiple attachments at once. This is primarily useful together with
# multiple file uploads. There is also an Active Record version of
# this macro.
#
# The name of the generated accessors will be the name of the association
# (represented by an attribute accessor) and the name of the attachment in
# the associated class. So if a `Post` accepts attachments for `images`, and
# the attachment in the `Image` class is named `file`, then the accessors will
# be named `images_files`.
#
# @example in associated class
# class Document
# extend Refile::Attachment
# attr_accessor :file_id
#
# attachment :file
#
# def initialize(attributes = {})
# self.file = attributes[:file]
# end
# end
#
# @example in class
# class Post
# extend Refile::Attachment
# include ActiveModel::Model
#
# attr_accessor :documents
#
# accepts_attachments_for :documents, accessor_prefix: 'documents_files', collection_class: Document
#
# def initialize(attributes = {})
# @documents = attributes[:documents] || []
# end
# end
#
# @example in form
# <%= form_for @post do |form| %>
# <%= form.attachment_field :documents_files, multiple: true %>
# <% end %>
#
# @param [Symbol] collection_name Name of the association
# @param [Class] collection_class Associated class
# @param [String] accessor_prefix Name of the generated accessors
# @param [Symbol] attachment Name of the attachment in the associated class
# @param [Boolean] append If true, new files are appended instead of replacing the entire list of associated classes.
# @return [void]
def accepts_attachments_for(collection_name, collection_class:, accessor_prefix:, attachment: :file, append: false)
include MultipleAttachments.new(
collection_name,
collection_class: collection_class,
name: accessor_prefix,
attachment: attachment,
append: append
)
end
end
end