multiple paperclip attachements with watermark processor

I'm having a ton of trouble with uploading multiple attachments with paperclip and processing them with a watermark.

I have 2 models, Ad and Photo. The Ad has_many :photos and the Photo belongs_to :ad.

To be more exact in /models/ad.rb I have:

class Ad < ActiveRecord::Base

  has_many :photos, :dependent => :destroy
  accepts_nested_attributes_for :photos, :allow_destroy => true  
end

and my photo.rb file looks like this:

class Photo < ActiveRecord::Base

belongs_to :ad

has_attached_file :data,
:styles => {
  :thumb => "100x100#",
  :first => {
    :processors => [:watermark],
    :geometry => '300x250#',
    :watermark_path => ':rails_root/public/images/watermark.png',
    :position => 'SouthEast' },
  :large => {
    :processors => [:watermark],
    :geometry => '640x480#',
    :watermark_path => ':rails_root/public/images/watermark.png',
    :position => 'SouthEast' }
}
end

In my view I use this to add the file fields

<% f.fields_for :photos do |p| %>

  <%= p.label :data, 'Poza:' %> <%= p.file_field :data %>

<% end %>

In my controller, in the edit action i use 4.times {@ad.photos.build} to generate the file fields.

It all works fine and dandy if I don't use the watermark processor, if I use a normal has_attached_file declaration, like this:

has_attached_file :data,
:styles => {
  :thumb => "100x100#",
  :first => '300x250#',
  :large => '640x480#'
}

But when I use the watermark processor I always get this error:

 NoMethodError in PublicController#update_ad

 You have a nil object when you didn't expect it!
 You might have expected an instance of ActiveRecord::Base.
 The error occurred while evaluating nil.[]
  ..............................
 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/nested_attributes.rb:350:in `assign_nested_attributes_for_collection_association'
 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/nested_attributes.rb:345:in `each'
 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/nested_attributes.rb:345:in `assign_nested_attributes_for_collection_association'
 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/nested_attributes.rb:243:in `photos_attributes='
 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2746:in `send'
 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2746:in `attributes='
 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2742:in `each'
 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2742:in `attributes='
 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2628:in `update_attributes'
 /home/alexg/Sites/vandmasina/app/controllers/public_controller.rb:217
 /home/alexg/Sites/vandmasina/app/controllers/public_controller.rb:216:in `update_ad'

The parameters are ok, as far as I can say

 Parameters:

 {"commit"=>"Salveaza modificarile",
  "ad"=>{"price"=>"6000",
  "oras"=>"9",
  "photos_attributes"=>{"0"=>{"data"=>#<File:/tmp/RackMultipart20100928-5130-b42noe-0>},
  "1"=>{"data"=>#<File:/tmp/RackMultipart20100928-5130-r0ukcr-0>},
  "2"=>{"data"=>#<File:/tmp/RackMultipart20100928-5130-mb23ei-0>},
  "3"=>{"data"=>#<File:/tmp/RackMultipart20100928-5130-1bpkm3b-0>}},

The Watermark processor /lib/paperclip_processors/watermark.rb looks like this:

  module Paperclip
class Watermark < Processor

class InstanceNotGiven < ArgumentError; 
end

def initialize(file, options = {},attachment = nil)
  super
  @file = file
  @current_format = File.extname(@file.path)
  @basename = File.basename(@file.path, @current_format)
  @watermark = ':rails_root/public/images/watermark.png'
  @current_geometry = Geometry.from_file file # This is pretty slow
  @watermark_geometry = watermark_dimensions
end

def watermark_dimensions
  return @watermark_dimensions if @watermark_dimensions
  @watermark_dimensions = Geometry.from_file @watermark
end

def make
  dst = Tempfile.new([@basename, @format].compact.join("."))
  watermark = " \\( #{@watermark} -extract #{@current_geometry.width.to_i}x#{@current_geometry.height.to_i}+#{@watermark_geometry.height.to_i /
                2}+#{@watermark_geometry.width.to_i / 2} \\) "
  command = "-gravity center " + watermark + File.expand_path(@file.path) + " " +File.expand_path(dst.path)

  begin
    success = Paperclip.run("composite", command.gsub(/\s+/, " "))
  rescue PaperclipCommandLineError
    raise PaperclipError, "There was an error processing the watermark for #{@basename}" if @whiny_thumbnails
  end
  dst
end

end
end

I have tried the processor in a normal app, without multiple attachments and it works perfect. It doesn't work with nested_attributes as far as I can tell.

The app is rails 2.3.5 with ruby 1.8.7 and paperclip 2.3.11

If you can provide any help it would be appreciated a lot, since I've been trying to figure this out for 2 days now :)

Answers


Oh, man, that was a tough one!

You have few errors in your code and none is related to nested models. My explanation is for Rails 3 & Paperclip 2.3.3

a) the :rails_root thing doesn't work. This interpolation is used only in url/path and not on custom options. So you should replace it with Rails.root.join("public/images...")

b) you simply ignore the :watermark_path option and you use only hardcoded path (in initialization method). So it doesn't matter what you have in your :styles as it always go for .../images/watermark.png. The :rails_root thingy there again so it cannot work.

c) when you pass a parameter to Paperclip.run("composite", "foo bar there") it actually executes this command:

composite 'foo bar there'

can you see the single quotes? Because of that the composite command see your parameters as one huge parameter and doesn't understand it at all. If you pass it as an array, then every item is enclosed in the quotes, not the array as a whole.

So here is the improved version of watermark processor:

module Paperclip
  class Watermark < Processor

  class InstanceNotGiven < ArgumentError;
  end

  def initialize(file, options = {},attachment = nil)
    super
    @file = file
    @current_format = File.extname(@file.path)
    @basename = File.basename(@file.path, @current_format)
    # PAWIEN: use default value only if option is not specified
    @watermark = options[:watermark_path] || Rails.root.join('public/images/watermark.png')
    @current_geometry = Geometry.from_file file # This is pretty slow
    @watermark_geometry = watermark_dimensions
  end

  def watermark_dimensions
    return @watermark_dimensions if @watermark_dimensions
    @watermark_dimensions = Geometry.from_file @watermark
  end

  def make
    dst = Tempfile.new([@basename, @format].compact.join("."))
    dst.binmode

    begin
      # PAWIEN: change original "stringy" approach to arrayish approach
      # inspired by the thumbnail processor
      options = [
        "-gravity",
        "center",
        "#{@watermark}",
        "-extract",
        "#{@current_geometry.width.to_i}x#{@current_geometry.height.to_i}+#{@watermark_geometry.height.to_i / 2}+#{@watermark_geometry.width.to_i / 2}",
        File.expand_path(@file.path),
        File.expand_path(dst.path)
      ].flatten.compact.join(" ").strip.squeeze(" ")

      success = Paperclip.run("composite", options)
    rescue PaperclipCommandLineError
      raise PaperclipError, "There was an error processing the watermark for #{@basename}" if @whiny_thumbnails
    end
    dst
  end

  end
end

Hope that helped you!

UPDATE: You need to use latest paperclip gem from github

gem 'paperclip', '>= 2.3.3', :git => "http://github.com/thoughtbot/paperclip.git"

In this version the formats of #run were changed once again so I've updated the code. It really should work as I've created test application and it's doing what supposed.

UPDATE 2: Repo with working example:

git://repo.or.cz/paperclip-mass-example.git

If you use rails 3 and paperclip > 2.3.3, try https://gist.github.com/843418 source.


Just from a quick glance it looks like watermark_path should be "#{Rails.root}/..." though it looks like you have a lot going on here.

Also, I don't see your form as in form_for. Make sure you have {:multipart => true}


Need Your Help

How to treat a section of a list as one number in python?

python arrays list

For example in x = [0, 1, 2, 3, 4, 5, 6] if I then wanted to use digits x[0, 1, 2, 3] as 0123 how would I do it?

Google Apps Script to import .eml into my Gmail Inbox

gmail google-apps-script gmail-imap eml gmail-pop

I have several hundred .eml files in GDrive that I would like to import into my Gmail as emails. Can anyone suggest a Google Apps Script that will let me do this? I am fairly proficient in Google A...