Paperclip and MongoMapper

29 Aug 2009

I started a new project recently that deals with a lot of imported data, so it seemed like a good excuse to try out MongoDB and MongoMapper. The imported data also has a associated images, so I wanted to get the Paperclip plugin working with my Mongo-based models. Fortunately for me, it actually didn’t take too much work to pull out the ActiveRecord-specific bits. Here’s what I did…

The first hurdle was the has_attached_file method. This class method sets up some callbacks, defines a few instance methods, and sets up some validations. Since classes that include MongoMapper::Document don’t have the same validation code that classes descended from ActiveRecord::Base do, I had to tweak how the validations are set up.

The next problem was with one of the interpolations. I always like to use the id_partition interpolation, but the default Paperclip code assumes the id of the instance to which the file is attached is an integer. MongoDB uses alphanumeric strings as ids, though, so I had to check for that to determine which approach to take to create a partitioned path.

Here is the contents of a file that I put in config/initalizers/paperclip.rb to override the bits that needed to be changed:


write_inheritable_attribute(:attachment_definitions, {}) if attachment_definitions.nil?
attachment_definitions[name] = {:validations => []}.merge(options)

after_save :save_attached_files
before_destroy :destroy_attached_files

define_callbacks :before_post_process, :after_post_process
define_callbacks :"before_#{name}_post_process", :"after_#{name}_post_process"

define_method name do |*args|
a = attachment_for(name)
(args.length > 0) ? a.to_s(args.first) : a
end

define_method "#{name}=" do |file|
attachment_for(name).assign(file)
end

define_method "#{name}?" do
attachment_for(name).file?
end

validates_each name, :logic => lambda {
attachment = attachment_for(name)
attachment.send(:flush_errors) unless attachment.valid?
}
end
end

module Interpolations
# Handle string ids (mongo)
def id_partition attachment, style
if (id = attachment.instance.id).is_a?(Integer)
("%09d" % id).scan(/\d{3}/).join("/")
else
id.scan(/.{3}/).first(3).join("/")
end
end
end
end

The call to validates_each ought to be smarter about whether it’s dealing with AR’s validations or whether it’s dealing with John Nunemaker’s Validatable gem, but I’ll leave that for another day.

Finally, Paperclip depends on the instance having a logger method, as classes that inherit from ActiveRecord::Base have. To work around this, I just cheated and defined a logger method in my class that simply returned Rails.logger.

In the end, it didn’t take much work, so it should be fairly simple to get these changes back into Paperclip, should the interest be there. I’m also going to check out Carrierwave, which recently added MongoMapper support.


Actions

Informations

6 responses to “Paperclip and MongoMapper”

Tom (13:01:26) :

Hey Ben, I’m adding MongoDB to a production app this week for a particular feature implementation.

Have you looked into how the app works when a MongoDB server goes away?

Double Shot #529 « A Fresh Cup (03:03:48) :

[...] Paperclip and MongoMapper – Just in case you want to use Paperclip with one of those newfangled non-SQL databases. [...]

Ben (05:32:58) :

I haven’t gotten that far along, yet. I imagine it isn’t pretty. :)

Dan Croak (12:16:24) :

It’s about time for a storage adapter concept in Paperclip. file, s3, mongo, couch, etc.

Bring ideas to the mailing list and let’s get started on this!

Getting Paperclip to work with MongoMapper | Anlek's Blog (09:13:54) :

[...] GridFS, I wanted to use Paperclip with MongoMapper with the normal file system and I found this article from Ben Curtis, however with the latest version of Paperclip (version 2.3.1.1) and MongoMapper (version 0.7.6) I [...]

Mike (01:42:15) :

Thanks for this, it’s been really helpful. Just one thing (which I suspect has been caused by using newer versions) I also had to fix was paperclip seems to confuse itself areound different date and time classes and call to_f on a updated_at while it is saved as a Date which leads to errors. I’ve added a bit to this file which prevents the errors (although doesn’t fix the underlying confusion); gist linked

Leave a comment