Search code examples
ruby-on-railsamazon-s3carrierwaveminimagick

Carrierwave, MiniMagick, and S3


In my Rails app, I enable users to upload images using Carrierwave and Amazon S3. I want to implement a feature that lets users edit existing images by rotating it 90 degrees.

I'm confused about where this code would go. Does it go in the image uploader file, or the image controller? And how is it called? I believe it should look something like this:

image = Image.find(params[:id])
image_obj = MiniMagick::Image.read(image.file)
image_obj.rotate(-90)
image_obj.write(image.file)

But I haven't been able to find examples to help me. If anyone can give me a pointer in the right direction, I would really appreciate it!

Edit

Thanks to deep for their thorough response! Here is what I ended up doing:

In my view:

# image.html.erb:
<%= link_to rotate_image_path(:id => image.id), :remote => true %>

In my controller:

# image_controller.rb:
def rotate
    @image = Image.find(params[:id])
    @image.rotated = true
    @image.save
    respond_to do |format|
      format.js { render :nothing => true }
    end
end

In my model:

  # image.rb
  attr_accessible :rotated
  after_save :rotate_image, if: ->(obj){obj.rotated.present? && obj.rotated?}

  def rotate_image
    self.image_path.recreate_versions!
  end

In my uploader:

  # image_uploader.rb
  process :rotate_img

  def rotate_img
  if model.rotated.present? && model.rotated?
      manipulate! do |img|
        img.rotate '-90'
        img
      end
  end
end 

The only real change I made was in the uploader, where I ran into errors trying to do a condition process. I put the conditional within the rotate_img method.


Solution

  • Here's my solution

    First define a attribute accessor in your model and on update set it to true.

    In your model

    #image.rb
    attr_accessor :rotate
    

    In your controller

    #images_controller.rb
    def update
      @image = Image.find(params[:id])
      @image.rotate = true
      @image.save
      redirect_to root_path, :notice => "Bla bla bla"
    end
    

    Carrierwave provides a recreate_versions! method which will process and re-upload the image. In you case you can add a after_save callback that will trigger recreate_versions! method only if the rotate attribute is set to true.

    In your model

    #image.rb
    after_save :rotate_image, if: ->(obj){ obj.rotate.present? and obj.rotate? }
    
    def rotate_image
      self.file.recreate_versions!
    end
    

    Now in your image uploader you can write the code to rotate a image.

    #image_uploader.rb
    .......
    # It will replace the original image with rotated version 
    process :rotate_img, :if => model.rotate.present and model.rotate?
    
    def rotate_img
      manipulate! do |img|
        img.rotate "90" 
        img #returns the manipulated image
      end
    end
    

    If you don't want to replace the original image then all you have to do is to call process inside a version like

    # Create different versions of your uploaded files:
    version :rotated_img do
      process :rotate_img
    end