Skip to content

Instantly share code, notes, and snippets.

@jacqui
Created January 12, 2010 22:57
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jacqui/275719 to your computer and use it in GitHub Desktop.
Save jacqui/275719 to your computer and use it in GitHub Desktop.
This plugin for Merb and Rails provides a simple and extremely flexible way to upload files.
ORM Support: ActiveRecord, DataMapper, Sequel, MongoMapper
Storage Support: S3, MongoDB's GridFS, filesystem...
Oh yeah and works with RMagick, ImageScience, MiniMagic
http://github.com/jnicklas/carrierwave/
CarrierWave.configure do |config|
if Rails.env.test?
config.storage = :file
config.enable_processing = false
elsif Rails.env.development? || Rails.env.staging?
config.storage = :s3
config.s3_access_key_id = 'XXXXXXXXXXXXX'
config.s3_secret_access_key = 'XXXXXXXXXXXXX'
config.s3_bucket = 'walrus'
config.ignore_integrity_errors = false
end
end
# It's really this easy!
MongoMapper.database = "fluffy-#{Rails.env}"
class Field
include MongoMapper::EmbeddedDocument
key :name, String, :required => true
key :label, String
key :required, Boolean, :default => false
key :widget, String, :required => true
key :options, Array # => [[value, label], [value, label], [value, label]]
WIDGET_TYPES = ['Text Field', 'Text Area', 'Date Picker', 'GeoCoder', 'Dropdown']
def partial_name
widget.gsub(/\s/, '_').underscore.downcase
end
def name=(value)
self[:name] = value.underscore.downcase.gsub(/\s/, '_')
end
def options
if self[:options].is_a?(Hash)
self[:options].values
elsif self[:options].is_a?(Array) && self[:options].first.is_a?(OrderedHash)
self[:options].map(&:values)
else
self[:options]
end
end
def options=(value)
self[:options] = if value.is_a?(Hash)
value.map(&:values)
else
value
end
end
end
<% @form.fields.each do |field| %>
<%= render :partial => "/forms/#{field.partial_name}", :locals => { :f => f, :field => field } unless field.name.blank? %>
<% end %>
class Form
include MongoMapper::Document
include Versioned
key :name, String, :required => true
key :headline, String, :required => true
key :subject_filter, String
key :submission_email, String
key :slug, String
key :thanks, String
key :notification_emails, Array
key :section, String
key :intro, String
key :header, String
key :nyt_wrapper, Boolean, :default => true
key :image_upload, Boolean, :default => true
key :project_id, Mongo::ObjectID
key :status, String, :default => 'open'
key :keywords, String
key :image_count, Integer, :default => 0
key :text_count, Integer, :default => 0
key :submission_count, Integer, :default => 0
key :pending_submission_count, Integer, :default => 0
key :approved_submission_count, Integer, :default => 0
key :rejected_submission_count, Integer, :default => 0
key :custom_css, String
key :custom_js, String
timestamps!
validates_presence_of :slug, :if => Proc.new { open? }
belongs_to :project
many :submissions
many :fields
before_save :update_counts
after_save :update_project_counts
def self.find_all_not_equal_and_not_nil(column, value)
find(:all, :conditions => { column => { '$exists' => true, '$ne' => value } })
end
end
# encoding: utf-8
require 'tempfile'
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
attr_accessor :height, :width
# Convert all images to JPG
process :convert => 'jpg'
process :colorspace => 'rgb'
# Thumbnails + other resizes
version :admin_thumb do
process :resize_to_fill => [220, 150]
end
version :quilt_thumb do
process :resize_to_fill => [30, 30]
end
version :slideshow do
process :resize_to_limit => [500, 390]
end
# Only allow jpg, gif, or png images
def extension_white_list
%w(jpg jpeg gif png)
end
# Set the max file size of uploaded images
def max_file_size
5242880
end
# Override the directory where uploaded files will be stored
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def colorspace(cs)
manipulate! do |img|
case cs
when 'rgb'
img.colorspace = Magick::RGBColorspace
when 'cmyk'
img.colorspace = Magick::CMYKColorspace
end
img = yield(img) if block_given?
img
end
end
def rotate(degrees)
m = Manipulator.new
m.manipulate(s3_bucket, path) do |img|
img.rotate(degrees.to_f)
end
end
# TODO: determine if we're gonna need this, and if so, fix it.
def current_path_with_s3
return current_path_without_s3 # just return the default for now, see TODO.
base_name = File.basename(current_path_without_s3)
local_file = File.join(RAILS_ROOT, 'public', current_path_without_s3)
if File.exists?(local_file)
local_file
else
s3_image = open(file.url)
tmp_file = File.new(File.join(RAILS_ROOT, 'public', cache_dir, base_name), "wb")
tmp_file.write(s3_image.read)
tmp_file.close
File.join(RAILS_ROOT, 'public', cache_dir, base_name)
end
end
alias_method_chain :current_path, :s3
before :store, :save_dimensions
def save_dimensions(foo = nil)
manipulate! do |img|
self.height = img.columns
self.width = img.rows
img
end
end
end
manipulator
S3 is great for storing image files, but manipulating images after they’ve been saved to S3 (rotating, resizing, cropping, etc.) is a bit of a chore. Usually it goes something like this:
* download file from S3, save to local tmp file
* run imagemagick over local tmp file
* push modified image back to S3
* remember to clean up tmp file
After re-implementing this pattern a few times over (and filling up /tmp by forgetting the last step), we decided to roll this into a gem that makes S3 image manipulation as easy as:
m = Manipulator.new
m.manipulate('my-bucket','some/file.jpg') do |img|
img.rotate(90).resize_to_fit(75,75)
end
The idea is to instantiate a Manipulator and then pass it a bucket name and key to manipulate. Manipulator will download your image to a local tmp file, then yield an RMagick Image wrapping that file. Call methods on the yielded image to make modifications; as soon as the block returns, the modified image is pushed back to S3 and the tmp file is removed.
Awesome gem for modeling your domain and storing it in mongo.
http://github.com/jnunemaker/mongomapper
require 'carrierwave/orm/mongomapper'
class Submission
include MongoMapper::Document
key :received_via, String
key :status, String, :default => 'pending'
key :title, String
key :body, String
key :identifier, String, :default => ''
key :form_id, Mongo::ObjectID
key :project_id, Mongo::ObjectID
key :orphan, Boolean, :default => false
timestamps!
VALID_STATUSES = %w(pending approved rejected)
validates_inclusion_of :status, :within => VALID_STATUSES
belongs_to :form
belongs_to :project
before_save :sanitize_text, :save_identifier
after_save :update_form_counts, :update_project_counts
mount_uploader :image, ImageUploader
def rotate_image(degrees)
return if image.blank?
image.rotate(degrees)
end
protected
def sanitize_text
Submission.keys.each_pair do |name, mongo_obj|
if sanitizeable_field?(mongo_obj)
contents = send("#{name}")
self.send("#{name}=", Sanitize.clean(contents, Sanitize::Config::RESTRICTED)) unless contents.blank?
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment