Module: Paperclip::Storage::Database

Defined in:
lib/paperclip/storage/database.rb

Overview

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

Defined Under Namespace

Modules: ControllerClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(base) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# 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

Instance Method Details

#copy_to_local_file(style, dest_path) ⇒ Object



128
129
130
# 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) ⇒ Object



144
145
146
147
148
149
150
151
# 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_tableObject



140
141
142
# File 'lib/paperclip/storage/database.rb', line 140

def database_table
  @database_table
end

#exists?(style = default_style) ⇒ Boolean

Returns:

  • (Boolean)


153
154
155
156
157
158
159
# 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) ⇒ Object



190
191
192
# File 'lib/paperclip/storage/database.rb', line 190

def file_contents(style = default_style)
  file_for(style).file_contents
end

#file_for(style) ⇒ Object

Raises:

  • (RuntimeError)


184
185
186
187
188
# 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

#filesObject



180
181
182
# File 'lib/paperclip/storage/database.rb', line 180

def files
  instance.send("#{@paperclip_files_association_name}")
end

#flush_deletesObject

:nodoc:



212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/paperclip/storage/database.rb', line 212

def flush_deletes #:nodoc:
  ActiveRecord::Base.logger.info("[paperclip] Deleting files for #{name}")
  @queued_for_delete.uniq! ##This is apparently necessary for paperclip v 3.x
  @queued_for_delete.each do |path|
    /id=([0-9]+)/.match(path)
    if @options[:cascade_deletion] && !instance.class.exists?(instance.id)
      raise RuntimeError, "Deletion has not been done by through cascading." if @paperclip_file_model.exists?($1)
    else
      @paperclip_file_model.destroy $1
    end
  end
  @queued_for_delete = []
end

#flush_writesObject



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# 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) ⇒ Object Also known as: to_io

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



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# 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