module Paperclip::Storage::Database
Store files in a database.
Usage is identical to the file system storage version, except:
-
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.
-
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
-
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'
-
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
# 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
# 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
# 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
# File lib/paperclip/storage/database.rb, line 140 def database_table @database_table end
# 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 lib/paperclip/storage/database.rb, line 190 def file_contents(style = default_style) file_for(style).file_contents end
# 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
# File lib/paperclip/storage/database.rb, line 180 def files instance.send("#{@paperclip_files_association_name}") end
# 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
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
Private Instance Methods
# 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
# 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
# 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
# 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