Can't see rotated picture after reloading page - ruby-on-rails

I've made a rotate action for uploaded pictures using carrierwave. Pictures are rotated and saved correctly, BUT after they're saved, page is reloaded and I can't see rotated pictures. (in development this works, but not in production - Heroku) From S3 bucket I can see pictues are rotated correctly instantly. Only after I do in browser:
Open image in new tab > manually reload page > then I see correctly rotated picture.
How can I solve this? Thanks.
class Ad:
attr_accessor :rotate_picture_side
attr_accessor :picture_index
after_save {rotate_selected_picture if should_rotate_picture?}
private
def rotate_selected_picture
self.pictures[picture_index].recreate_versions!
end
def should_rotate_picture?
self.rotate_picture_side.present? && self.picture_index.present?
end
class PicturesController:
def update
#ad.rotate_picture_side = params[:rotate_picture_side]
#ad.picture_index = params[:id].to_i
#ad.save
redirect_to edit_ad_path(#ad, anchor: "rotate-picture #
{#ad.picture_index}")
end
class PicturesUploader:
def rotate_picture
manipulate! do |pic|
if model.rotate_picture_side == "right"
pic.rotate "90"
elsif model.rotate_picture_side == "left"
pic.rotate "270"
end
pic #returns the manipulated picture
end
end

Related

Upload image through Paperclip without saving object

I'm working in Rails and I have two models, a prelaunch and an initiative. Basically I want a user to be able to create an initiative using the attributes of the prelaunch. Basically what I want to have happen is when a user visit's their prelaunch and is ready to turn it into an initiative, it brings them to a form that has their prelaunch information already populated and they can just add the additional info. I've managed to do this for every attribute so far except for the attached image, called :cover_image.
I think the problem is that I'm setting the initiative's cover_image to the prelaunch's cover_image on the new action of my controller, but because this is the new action and not create, I'm not saving the initiative yet. I think this means the cover_image isn't getting reuploaded yet, so #iniative.cover_image.url doesn't point to anything. It also doesn't appear to be prepopulating the file field of my form with anything.
I'm not entirely sure how feasible all of this is, but it's what the client asked for so I'm trying to make it work for them.
Here's my controller:
def new
#initiative = Initiative.new
populate_defaults(#initiative)
#initiative.build_location
3.times{ #initiative.rewards.build }
#initiative.user = current_user
if !params[:prelaunch_id].nil? && !params[:prelaunch_id].empty?
# if user is transferring a prelaunch, assign its attributes to the intiative
#prelaunch = Prelaunch.find(params[:prelaunch_id])
#initiative.assign_attributes(title: #prelaunch.title,
teaser: #prelaunch.teaser,
category: #prelaunch.category,
funding_goal: #prelaunch.funding_goal,
term: #prelaunch.campaign.term,
story: #prelaunch.story,
location: #prelaunch.campaign.location,
video_url: #prelaunch.video_url,
EIN: #prelaunch.campaign.EIN,
nonprofit: #prelaunch.nonprofit,
organization_name: #prelaunch.campaign.organization.name)
end
end
Edit:
Thanks to peterept's answer below I've managed to get the prelaunch cover_image into the form and into the create action of the initiatives controller. The problem now is that everything seems to work perfectly in the create action: the initiative gets the prelaunch's cover image, it saves without error, and it redirects to the show action.
UNFORTUNATELY, By the time it reaches the show action of the controller, #initiative.cover_image is set to the default again. I can't figure out what could possibly be happening between the successful create action and the show action.
Here are the create and show actions of the initiatives controller:
def create
if !params[:initiative][:prelaunch_id].nil? && !params[:initiative][:prelaunch_id].empty?
#prelaunch = Prelaunch.find(params[:initiative][:prelaunch_id]) # find the prelaunch if it exists
end
#initiative = Initiative.new(initiatives_params)
#initiative.user = current_user
begin
#payment_processor.create_account(#initiative)
if #initiative.save
# #prelaunch.destroy # destroy the prelaunch now that the user has created an initiative
flash[:alert] = "Your initiative will not be submitted until you review the initiative and then press 'Go Live' on the initiative page"
redirect_to initiative_path(#initiative)
else
flash[:alert] = "Initiative could not be saved: " + #initiative.errors.messages.to_s
render :new
end
rescue Exception => e
logger.error e.message
flash[:error] = "Unable to process request - #{e.message}"
render :new
end
end
def show
#initiative = Initiative.find(params[:id])
#other_initiatives = Initiative.approved.limit(3)
end
And here is the initiatives_params method from the same controller:
def initiatives_params
initiative_params = params.require(:initiative).permit(
:terms_accepted,
:title,
:teaser,
:term,
:category,
:funding_goal,
:funding_type,
:video_url,
:story,
:cover_image,
:nonprofit,
:EIN,
:role,
:send_receipt,
:organization_name,
:crop_x, :crop_y, :crop_h, :crop_w,
location_attributes: [:address],
rewards_attributes: [:id, :name, :description, :donation, :arrival_time, :availability, :_destroy, :estimated_value])
if #prelaunch.media.cover_image
initiative_params[:cover_image] = #prelaunch.media.cover_image
end
initiative_params
end
You can pass the Image URL and display it on the page.
The user can then override this by uploading a new image (as per normal).
In you're create action, if they have not supplied a new image, then set it to the one in the assocoiated prelaunch - you'd want to copy the original so it doesn't get replaced if they upload a new one. (If you don't know which was the prelaunch, you could pass the ID down to the page).
I was able to make it work by saving the Paperclip object only. This is my model:
class Grade < ActiveRecord::Base
has_attached_file :certificate
end
If I run the following:
#grade.certificate = new_file
#grade.certificate.save
It saves/overwrite the file, but don't update the Grade object.
Versions: ruby-2.3.8, Rails 4.2.11.3 and paperclip (4.3.6)

ImageMagick scales down the processed images on Heroku

Here is a simple raw Rails model with single url attribute.
class Photo < ActiveRecord::Base
validates :url, presence: true
def watermark
remote_photo = MiniMagick::Image.open(url).write("#{Rails.public_path}/photos/photo-#{id}.jpg")
photo = MiniMagick::Image.open("#{Rails.public_path}/photos/photo-#{id}.jpg")
marked_photo = photo.composite(mark, 'jpg') do |c|
c.gravity 'SouthEast'
c.geometry '+15'
end
marked_photo.write(marked_path)
File.delete("#{Rails.public_path}/photos/photo-#{id}.jpg")
end
def marked_path
"#{Rails.public_path}/photos/photo-marked-#{id}.jpg"
end
private
def mark(mark = 'mark.jpg')
#mark ||= MiniMagick::Image.open("#{Rails.public_path}/#{mark}")
end
end
The #watermark method puts a mark image to the bottom right of the photo and writes the file into public/photos/photo-marked-#{id}.jpg. It works perfectly on my local machine, however on Heroku the returned images that are ~600x800px become 10x15px.
Ok, the problem was in the way I've specified the geometry attribute value. In case of c.geometry '+15' ImageMagick takes the 15 as a width and resizes the image preserving the aspect ratio. In order to keep the original size and apply a horizontal offset the value should look like 'x+15', which is for widthxheight+offset.
All of the above is about the version 6.5.7 of ImageMagick, which is the one installed on Heroku. The initial '+15' works fine in the latest version.
Thanks to #mschoening, who helped me out with this.

How do I ask Paperclip if I'm uploading a new file?

I want to do some processing on an image the first time it's uploaded. The model can be updated without updating the image and I want to avoid extra processing in those situations. How can I write a check to only process the image if the image is new, like being replaced?
Something like:
def update
#model = Model.find(params[:id])
#model.process_image if #model.image_is_different_from_existing?
...
end
There's one way if the photos you are uploading have unique name, Let's say paperclip attribute name is image, paperclip creates a column named image_file_name, before saving you can check whether the image file name present in your database matches with the name of image file you are uploading or not. I've implemented something like this, below is the snippet
product_images = product.product_attachments.pluck(:photo_file_name)
unless product_images.include?("name_of_the_image_i_am_uploading")
product.product_attachments.create!(:photo => File.open("/home/rajdeepb/Photo/#{data_array[1]}")) if all_images.include?("/home/rajdeepb/Photo/#{name_of_the_image_i_am_uploading}")
end
THis might not be the perfect solution but it may be helpful to you
I've written a solution that looks for relevant params in the update or create method.
Something like this:
user.rb
def attachments
%w(banner footer logo)
end
users_controller.rb
def new_resources?
attaching = []
#user.attachments.each do |a|
attaching << a if params[:user][a].present?
end
return true unless attaching.empty?
end
def attaching
[]
end
def update
...
if #user.request_necessary? && new_resources?
action_response << "Server updated: new #{attaching} uploaded."
redirect_to users_path, notice: action_response.join(' ')

Why won't Carrierwave and MiniMagick images composite correctly?

I am using carrierwave (0.9.0) with fog (1.18.0), mini_magick (3.6.0), and Rackspace CloudFiles. I want to composite images.
I have two models: Products and Emails. In the products uploader I am trying to create a process that loops through all the emails and composites the product image on top of each email.background_image. I also want to control the name of this image so that is:
"#{email.name}_#{product.product_number}.jpg"
I have included my process and method below. Test result in a file being uploaded to cloudfiles named "email_123.png" and the original image. The name doesn't currently include the email's name and images are not compositing, I just receive the original back. Anything obvious I am missing here? Thanks!
version :email do
process :email_composite
def email_composite
Email.all.each do |email|
email_image = email.secondary.url
email_name = email.name
merge(email_name, email_image)
end
end
def merge(email_name, email_image)
manipulate! do |img|
#product = Product.find(model.id)
email_image = ::MiniMagick::Image.open(email_image)
img = email_image.composite(img, "jpg") do |c|
c.gravity "Center"
end
# img = yield(img) if block_given?
img = img.name "#{email_name}_#{#product.product_number}.png"
img
end
end
end
UPDATED
First I wanted to thank you for your thorough answer, the amount of effort involved is obvious and appreciated.
I had a few more questions when trying to implement this solution.
Inside the #compose method you have:
email_image = ::MiniMagick::Image.open(email_image)
Should this be:
email_image = ::MiniMagick::Image.open(email_image_url)
I assume this from seeing the define_method call included below ( which was awesome by the way - I didn't know you could do that):
define_method(:email_image_url) { email.secondary.url }
After getting Undefined method "versions_for" I switched the #versions_for method as I will always want all email to have a secondary version.
versions_for before
def self.versions_for emails
reset_versions!
email.find_each { |e| version_for_email(e) }
end
versions_for after changes
def self.versions_for
reset_versions!
Email.all.find_each { |e| version_for_email(e) }
end
This change allowed me get rid of the error.
The end result of all of this is that everything appears as if it works, I can upload imagery without error, but now I have no resulting image for the emails versions. I still get my other versions like thumb etc. Any idea what could cause this? Again thanks for all the help you have provided thus far.
Calling manipulate! multiple times within the same version applies the changes in its block to the same image; what you want is to create many versions, one corresponding to each Email. This is tricky because CarrierWave really wants you to have a small set of statically defined versions in your code; it actually builds a new, anonymous Uploader class to handle each version.
We can trick it to build versions dynamically, but it's pretty ugly. Also, we have to be careful to not keep references to stale uploader classes around, or we'll accumulate classes endlessly and eventually run out of memory!
# in ProductUploader:
# CarrierWave stores its version classes in two places:
#
# 1. In a #versions hash, stored within the class; and
# 2. As a constant in your uploader, with a name based on its #object_id.
#
# We have to clean out both of them to be sure old versions get garbage
# collected!
def self.reset_versions!
versions.keys.select { |k| k =~ /^email_/ }.each do |k|
u = versions.delete(k)[:uploader]
remove_const("Uploader#{u.object_id}".gsub('-', '_'))
end
end
def self.version_name_for email
"email_#{email.name}".to_sym
end
# Dynamically generate the +version+ that corresponds to the image composed
# with the image from +email+.
def self.version_for_email email
version(version_name_for(email)) do
process :compose
# Use +define_method+ here so that +email+ in the block refers to the
# argument "email" from the outer scope.
define_method(:email_image_url) { email.secondary.url }
# Use the same trick to override the filename for this version.
define_method(:filename) { "#{email_name}_#{model.product_number}.png" }
# Compose +email_image+ on top of the product image.
def compose
manipulate! do |img|
email_image = ::MiniMagick::Image.open(email_image_url)
# Do the actual composition.
img = email_image.composite(img, "jpg") do |c|
c.gravity "Center"
end
img
end
end
end
end
# Clear out any preexisting versions and generate new ones based on the
# contents of +emails+.
def self.versions_for emails
reset_versions!
email.find_each { |e| version_for_email(e) }
end
# After you call Product.versions_for(emails), you can use this to fetch
# the version for a specific email:
def version_for_email email
versions[self.class.version_name_for(email)]
end
To use this, be sure to call Product.versions_for(Email.all) (possibly in a before_filter), Then, you can access the version for a specific email with #p.product.version_for(email):
# in your controller:
def action
# Recreate a +version+ for each email.
# If you don't want to overlay *every* email's image, you can also pass a
# subset here.
ProductUploader.versions_for(Email.all)
# Upload a file and its versions to Cloud Files...
#p = Product.find(params[:id])
#p.product = File.open(Rails.root.join('clouds.jpg'), 'r')
#p.save!
end
In your views, you can use url or any other helpers as usual:
# in a view:
<% Email.all.find_each do |email| %>
<%= image_tag #p.product.version_for_email(email).url %>
<% end %>

save_and_process post processing 403 Forbidden Carrierwave_direct S3 Fog

I'm trying to develop direct fileuploads to S3 for my app. I'm following the github tutorial for this and everything is more or less ok but get an error message when trying to make the post processing.
I did the following:
I have an activerecord model called clip.rb:
class Clip < ActiveRecord::Base
belongs_to :attachable, :polymorphic => true
mount_uploader :avatar, AvatarUploader
attr_accessible :id, :avatar, :name, :clipat_file_name, :attachable_id, :attachable_type, :clipat, :project_id, :user_id, :path, :parent_id,
def save_and_process_avatar(options = {})
if options[:now] or 1==1
self.remote_avatar_url = avatar.direct_fog_url(:with_path => true)
save
else
Resque.enqueue(AvatarProcessor, attributes)
end
end
Then I have an uploader: avatar_uploader.rb
class AvatarUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
include CarrierWaveDirect::Uploader
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}" #I removed /#{model.id} from the template because it creates an empty directory on the server. But If I put it back, the same problem remains
end
version :thumb do
process :resize_to_limit => [50, 50]
end
end
and an avatar controller:
class AvatarsController < ApplicationController
def new
#uploader = Clip.new.avatar
#uploader.success_action_redirect = 'http://localhost:3000/clips'
end
end
and finally my clip_controller:
class ClipsController < ApplicationController
def index
if params[:key]
key=params[:key].split("/")
clip = Clip.new
clip.attachable_id = key[3]
clip.attachable_type = "Pmdocument"
clip.key = params[:key]
# clip.save
clip.save_and_process_avatar
end
#clips = Clip.where("avatar is not null")
respond_to do |format|
format.html # index.html.erb
format.json { render json: #clips.collect { |p| p.to_jq_upload }.to_json }
end
end
When I upload a file, if I just save my "clip", everything is ok. If I use the save_and_process method however, an error arises at line:
self.remote_avatar_url = avatar.direct_fog_url(:with_path => true)
This is the error message:
OpenURI::HTTPError (403 Forbidden):
app/models/clip.rb:38:in `save_and_process_avatar'
app/controllers/clips_controller.rb:22:in `index'
Rendered /Users/nico/.rvm/gems/ruby-1.9.2-p290/gems/actionpack-3.1.3/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.4ms)
Rendered /Users/nico/.rvm/gems/ruby-1.9.2-p290/gems/actionpack-3.1.3/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (1.2ms)
Rendered /Users/nico/.rvm/gems/ruby-1.9.2-p290/gems/actionpack-3.1.3/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (5.2ms)
I've been hanging on this for two days so, any help would be greatly appreciated!!! Thanks!!! Nicolas.
My bet is that the URL supplied to self.remote_avatar_url is incorrect. I had this same problem and the code that CWDirect gem provides didn't work for me and gave me an incorrect URL, thus CarrierWave couldn't download and process the image. The whole 403 Forbidden error message was a crap message from Amazon--this leads one to believe that there is something wrong with Permissions; in my case there was absolutely nothing wrong with permissions. It was just that there was no image there. Here is the code that got this working for me, notice I've changed how the URL is formed:
def save_and_process_image(options = {})
if options[:now]
# debugger
self.remote_image_url = image.direct_fog_url+self.key # OLD CODE THAT AINT WORKIN! --> image.direct_fog_url(:with_path => true)
save
else
# Resque.enqueue(AvatarProcessor, attributes)
# TODO: Implement background processing
end
end
Note that the name of my mounted field is image and not avatar.
How I got to this point and fixed it--try this out, use the rails debugger (just uncomment the debugger line above) to freeze the program just before the self.remote_image_url line, then while in debug mode type irb to start up the console. Then you can print out and see really what value 'image.direct_fog_url(:with_path => true)' is giving you. You can copy and paste this into a browser. If it's wrong (probably is) then you will get the silly Permissions error (even though it's not a permissions problem), but when it's correct either you will see the uploaded image or the image will download.
It's a good idea to have your Amazon S3 console open and viewing your dev bucket so you can find the image that just uploaded. Find the image in the console and go to its properties and you can see the web address/url that you are supposed to be using.
Hope this helps. Because of the misleading error this was hard to track down for me, I spent a bunch of time trying to correct permissions on my S3 bucket but this wasn't the problem, just that code from the CWDirect github page doesn't work for me (gem version??).

Resources