Wasabi Developer
Wasabi Developer

Reputation: 3553

How do I do :remote location validation with CarrierWave?

I'm using CarrierWave on my Rails 3 sample app. I want to validate the remote location upload so I don't get the standard error exception when a user submits an invalid URL either blank or not an image:

CarrierWave::DownloadError in ImageController#create
trying to download a file which is not served over HTTP

This is my model:

class Painting < ActiveRecord::Base
  attr_accessible :gallery_id, :name, :image, :remote_image_url
  belongs_to :gallery
  mount_uploader :image, ImageUploader

  validates :name,        :presence => true,
                          :length =>  { :minimum => 5, :maximum => 100 }
  validates :image,       :presence => true

end

This is my controller:

class PaintingsController < ApplicationController
  def new
    @painting = Painting.new(:gallery_id => params[:gallery_id])
  end

  def create
    @painting = Painting.new(params[:painting])
    if @painting.save
      flash[:notice] = "Successfully created painting."
      redirect_to @painting.gallery
    else
      render :action => 'new'
    end
  end

  def edit
    @painting = Painting.find(params[:id])
  end

  def update
    @painting = Painting.find(params[:id])
    if @painting.update_attributes(params[:painting])
      flash[:notice] = "Successfully updated painting."
      redirect_to @painting.gallery
    else
      render :action => 'edit'
    end
  end

  def destroy
    @painting = Painting.find(params[:id])
    @painting.destroy
    flash[:notice] = "Successfully destroyed painting."
    redirect_to @painting.gallery
  end
end

I'm not really sure how to tackle this problem so any insight would be great.

Upvotes: 5

Views: 5812

Answers (4)

Michael Fagan
Michael Fagan

Reputation: 86

The solution on the CarrierWave wiki is not working for me. As Peter Hulst mentioned, CarrierWave loads the file prior to validation. I found a way around this by capturing the exception when it gets thrown, and adding it back as a validation error later. For some reason when an exception is thrown, all the other record's attributes become nil, so they also have to be captured and re-added before validation. This code all goes in your model.

This still needs a little rework to use error messages from config, rather than hardcoded.

attr_accessor :additional_error_message, :original_attributes

def initialize(*args)
  self.original_attributes = args[0]
  begin
    super
  rescue CarrierWave::IntegrityError # bad file type
    self.additional_error_message = 'must be a PNG, JPEG, or GIF file' # depends on your whitelist
  rescue OpenURI::HTTPError # 404
    self.additional_error_message = 'could not be found'
  rescue RuntimeError # redirection
    self.additional_error_message = 'could not be loaded'
  rescue CarrierWave::DownloadError
    self.additional_error_message = 'could not be loaded'
  rescue
    self.additional_error_message = 'could not be loaded'
  end
end

before_validation do |image|
  if additional_error_message.present?
    errors.add(remote_image_url, additional_error_message)
    self.name = original_attributes[:name] # replace this with re-adding all of your original attributes other than the remote_image_url
  end
end

# the image will have an "is blank" error, this removes that
after_validation do |image|
  errors.delete(:image) if additional_error_message.present?
end

Upvotes: 0

dft
dft

Reputation: 648

This is very annoying issue. I did the rescue_from in my application_controller.rb for now and just flash messages stating the issue. It's about the best I could come up with. I am not a fan of clogging up the controller and having to use that duplicate code if you have multiple models that need those validations.

  rescue_from CarrierWave::DownloadError, :with => :carrierwave_download_error
  rescue_from CarrierWave::IntegrityError, :with => :carrierwave_integrity_error

  def carrierwave_download_error
    flash[:error] = "There was an error trying to download that remote file for upload. Please try again or download to your computer first."
    redirect_to :back
  end

  def carrierwave_integrity_error
    flash[:error] = "There was an error with that remote file for upload. It seems it's not a valid file."
    redirect_to :back
  end

Upvotes: 1

David Tuite
David Tuite

Reputation: 22643

The solution to this problem has been added to the CarrierWave Wiki on Github.

Edit:
I'm trying to implement the proposed solution now but I can't get it working. I'm using AR on Rails 3.1.3.

Implementing the code the way it is on the wiki results in the validation actually happening fine. When I try to upload gibberish I get a nice validation message. The problem is that normal uploads are prevented also.

Upvotes: 0

Peter Hulst
Peter Hulst

Reputation: 431

I ran into this same problem. Unfortunately it looks like this is a design flaw of CarrierWave... it does not allow for proper validation of a remote url. CarrierWave will attempt to download the resource right away when the attribute is set and will throw an exception if the url is invalid, can't be accessed, or if the resource doesn't have the expected type. DownloadError or IntegrityErrors are always thrown before any validation occurs.

Therefore I couldn't find a good workaround that uses other validators. My solution ended up looking like this:

valid = false
begin
  par = params[:image].except(:remote_upload_url)
  @image = Image.new(par)
  # this may fail:
  @image.remote_upload_url = params[:image][:remote_upload_url]
  valid = true
rescue CarrierWave::DownloadError
  @image.errors.add(:remote_upload_url, "This url doesn't appear to be valid")
rescue CarrierWave::IntegrityError
  @image.errors.add(:remote_upload_url, "This url does not appear to point to a valid image")
end 

# validate and save if no exceptions were thrown above
if valid && @image.save
  redirect_to(images_configure_path)
else
 render :action => 'new'
end

basically, I wrap the constructor in a rescue block and initially set all parameters except for the remote url. When I set that, an exception may occur which I handle by manually setting an error in the model. Note that no other validations are performed in this scenario. It's a hack but worked for me.

I hope that this can be addressed in a future release, by delaying download of the resource until the model validation stage or after.

Upvotes: 8

Related Questions