In the past I was storing binary data on the filesystem instead of the database. Now I wanted to try the other approach.
The files (images in this particular case) will be stored in a binary typed column as raw data. The following concern will check if they are really images, their size and provides caching of the images.
#app/models/concerns/with_images.rb
class FileTypeValidator < ActiveModel::Validator
def validate(record)
record.class.get_image_fields.each do |f|
field = f[0]
type = f[1].is_a?(Array) ? f[1] : [f[1]]
type = type.map{|t| "image/#{t}" }
size = f[2]
if record[field].is_a? ActionDispatch::Http::UploadedFile then
if not type.include?(record[field].content_type) then
record.errors.add field, "#{field.to_s.humanize} must be an PNG image!"
elsif record.send("#{field}=", record[field].read).size > size then
record.errors.add field, "#{field.to_s.humanize} maximum size can be #{size.to_s(:human_size)}"
end
end
end
end
end
module WithImages
extend ActiveSupport::Concern
include ActionView::Helpers::AssetTagHelper
included do
validates_with FileTypeValidator
def self.get_image_fields
@image_fields
end
def self.image_fields(fields)
@image_fields = fields
fields.map{|f| f[0]}.each do |field|
define_method field do
image_field field
end
end
end
end
protected
def image_field(field)
path = "#{Rails.root}/public/images/app-#{self.id}-#{field}.png"
Dir.mkdir(File.dirname(path)) unless Dir.exists? File.dirname(path)
if not File.exists?(path) or File.mtime(path) < self.updated_at
File.open(path, 'wb') do |f|
f.write self[field]
end
end
image_tag "/images/app-#{self.id}-#{field}.png"
end
end
Now you have, let say, User model where you store the avatar in an avatar:binary
column:
#app/models/user.rb
class User < ActiveRecord::Base
include WithImages
image_fields [[:avatar, 'png', 100.kilobytes]]
end