module Paperclip::Storage::Database

Store files in a database.

Usage is identical to the file system storage version, except:

  1. In your model specify the “database” storage option; for example:

has_attached_file :avatar, :storage => :database

The files will be stored in a new database table named with the plural attachment name by default, “avatars” in this example.

  1. You need to create this new storage table with at least these columns:

- file_contents
- style
- the primary key for the parent model (e.g. user_id)

Note the “binary” migration will not work for the LONGBLOG type in MySQL for the file_cotents column. You may need to craft a SQL statement for your migration, depending on which database server you are using. Here's an example migration for MySQL:

create_table :avatars do |t|

t.string :style
t.integer :user_id
t.timestamps

end execute 'ALTER TABLE avatars ADD COLUMN file_contents LONGBLOB'

You can optionally specify any storage table name you want and whether or not deletion is done by cascading or not as follows:

has_attached_file :avatar, :storage => :database, :database_table => 'avatar_files', :cascade_deletion => true
  1. By default, URLs will be set to this pattern:

/:relative_root/:class/:attachment/:id?style=:style

Example:

/app-root-url/users/avatars/23?style=original

The idea here is that to retrieve a file from the database storage, you will need some controller's code to be executed.

Once you pick a controller to use for downloading, you can add this line to generate the download action for the default URL/action (the plural attachment name), “avatars” in this example:

downloads_files_for :user, :avatar

Or you can write a download method manually if there are security, logging or other requirements.

If you prefer a different URL for downloading files you can specify that in the model; e.g.:

has_attached_file :avatar, :storage => :database, :url => '/users/show_avatar/:id/:style'
  1. Add a route for the download to the controller which will handle downloads, if necessary.

The default URL, /:relative_root/:class/:attachment/:id?style=:style, will be matched by the default route: :controller/:action/:id

Public Class Methods

extended(base) click to toggle source
# File lib/paperclip/storage/database.rb, line 59
def self.extended(base)
  base.instance_eval do
    setup_attachment_class
    setup_paperclip_file_model
    setup_paperclip_files_association
    override_default_options base
  end
  Paperclip.interpolates(:database_path) do |attachment, style|
    attachment.database_path(style)
  end
  Paperclip.interpolates(:relative_root) do |attachment, style|
    begin
      if ActionController::AbstractRequest.respond_to?(:relative_url_root)
        relative_url_root = ActionController::AbstractRequest.relative_url_root
      end
    rescue NameError
    end
    if !relative_url_root && ActionController::Base.respond_to?(:relative_url_root)
      ActionController::Base.relative_url_root
    end
  end

  ActiveRecord::Base.logger.info("[paperclip] Database Storage Initalized.")
end

Public Instance Methods

copy_to_local_file(style, dest_path) click to toggle source
# File lib/paperclip/storage/database.rb, line 128
def copy_to_local_file(style, dest_path)
  File.open(dest_path, 'wb+'){|df| to_file(style).tap{|sf| File.copy_stream(sf, df); sf.close;sf.unlink} }
end
database_path(style) click to toggle source
# File lib/paperclip/storage/database.rb, line 144
def database_path(style)
  paperclip_file = file_for(style)
  if paperclip_file
    "#{database_table}(id=#{paperclip_file.id},style=#{style.to_s})"
  else
    "#{database_table}(id=new,style=#{style.to_s})"
  end
end
database_table() click to toggle source
# File lib/paperclip/storage/database.rb, line 140
def database_table
  @database_table
end
exists?(style = default_style) click to toggle source
# File lib/paperclip/storage/database.rb, line 153
def exists?(style = default_style)
  if original_filename
    instance.send("#{@paperclip_files_association_name}").where(:style => style).exists?
  else
    false
  end
end
file_contents(style = default_style) click to toggle source
# File lib/paperclip/storage/database.rb, line 190
def file_contents(style = default_style)
  file_for(style).file_contents
end
file_for(style) click to toggle source
# File lib/paperclip/storage/database.rb, line 184
def file_for(style)
  db_result = instance.send("#{@paperclip_files_association_name}").send(:file_for, style.to_s)
  raise RuntimeError, "More than one result for #{style}" if db_result.size > 1
  db_result.first
end
files() click to toggle source
# File lib/paperclip/storage/database.rb, line 180
def files
  instance.send("#{@paperclip_files_association_name}")
end
flush_writes() click to toggle source
# File lib/paperclip/storage/database.rb, line 194
def flush_writes
  ActiveRecord::Base.logger.info("[paperclip] Writing files for #{name}")
  @queued_for_write.each do |style, file|
      case ActiveModel::VERSION::MAJOR
      when 3
        paperclip_file = instance.send(@paperclip_files_association_name).send(:find_or_create_by_style, style.to_s)
      when 4,5,6
        paperclip_file = instance.send(@paperclip_files_association_name).send(:find_or_create_by, style: style.to_s)
      else
        raise "ActiveModel version #{ActiveModel::VERSION::STRING} is not supported (yet)"
      end
    paperclip_file.file_contents = file.read
    paperclip_file.save!
    instance.reload
  end
  @queued_for_write = {}
end
to_file(style = default_style) click to toggle source

Returns representation of the data of the file assigned to the given style, in the format most representative of the current storage.

# File lib/paperclip/storage/database.rb, line 163
def to_file style = default_style
  if @queued_for_write[style]
    @queued_for_write[style]
  elsif exists?(style)
    tempfile = Tempfile.new instance_read(:file_name)
    tempfile.binmode
    tempfile.write file_contents(style)
    tempfile.flush
    tempfile.rewind
    tempfile
  else
    nil
  end

end
Also aliased as: to_io
to_io(style = default_style)
Alias for: to_file

Private Instance Methods

override_default_options(base) click to toggle source
# File lib/paperclip/storage/database.rb, line 132
def override_default_options(base)
  if @options[:url] == base.class.default_options[:url]
    @options[:url] = ":relative_root/:class/:attachment/:id?style=:style"
  end
  @options[:path] = ":database_path"
end
setup_attachment_class() click to toggle source
# File lib/paperclip/storage/database.rb, line 116
def setup_attachment_class
  instance.class.ancestors.each do |ancestor|
    # Pick the top-most definition like
    # Paperclip::AttachmentRegistry#definitions_for
    names_for_ancestor = ancestor.attachment_definitions.keys rescue []
    if names_for_ancestor.member?(name)
      @attachment_class = ancestor
    end
  end
end
setup_paperclip_file_model() click to toggle source
# File lib/paperclip/storage/database.rb, line 96
def setup_paperclip_file_model
  class_name = "#{instance.class.table_name.singularize}_#{name.to_s}_paperclip_file".classify
  if @attachment_class.const_defined?(class_name, false)
    @paperclip_file_model = @attachment_class.const_get(class_name, false)
  else
    @paperclip_file_model = @attachment_class.const_set(class_name, Class.new(::ActiveRecord::Base))
    @paperclip_file_model.table_name = @options[:database_table] || name.to_s.pluralize
    @paperclip_file_model.validates_uniqueness_of :style, :scope => instance.class.table_name.classify.underscore + '_id'
    case ActiveModel::VERSION::MAJOR
    when 3, 4
      @paperclip_file_model.scope :file_for, lambda {|style| @paperclip_file_model.where('style = ?', style) }
    when 5,6
      @paperclip_file_model.scope :file_for, lambda {|style| where('style = ?', style) }
    else
      raise "ActiveModel version #{ActiveModel::VERSION::STRING} is not supported (yet)"
    end
  end
end
setup_paperclip_files_association() click to toggle source
# File lib/paperclip/storage/database.rb, line 84
def setup_paperclip_files_association
  @paperclip_files_association_name = @paperclip_file_model.name.demodulize.tableize
  @database_table = @paperclip_file_model.table_name
  #FIXME: This fails when using  set_table_name "<myname>" in your model
  #FIXME: This should be fixed in ActiveRecord...
  instance.class.has_many(@paperclip_files_association_name.to_sym,
                          :class_name => @paperclip_file_model.name,
                          :foreign_key => instance.class.table_name.classify.underscore + '_id'
                          )
end