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



121
122
123
# File 'lib/paperclip/storage/database.rb', line 121

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



137
138
139
140
141
142
143
144
# File 'lib/paperclip/storage/database.rb', line 137

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



133
134
135
# File 'lib/paperclip/storage/database.rb', line 133

def database_table
  @database_table
end

#exists?(style = default_style) ⇒ Boolean

Returns:

  • (Boolean)


146
147
148
149
150
151
152
# File 'lib/paperclip/storage/database.rb', line 146

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



183
184
185
# File 'lib/paperclip/storage/database.rb', line 183

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

#file_for(style) ⇒ Object

Raises:

  • (RuntimeError)


177
178
179
180
181
# File 'lib/paperclip/storage/database.rb', line 177

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



173
174
175
# File 'lib/paperclip/storage/database.rb', line 173

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

#flush_deletesObject

:nodoc:



205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/paperclip/storage/database.rb', line 205

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



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/paperclip/storage/database.rb', line 187

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
        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.



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/paperclip/storage/database.rb', line 156

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