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(' ')
Related
I have an ActiveRecord model named Document and have implemented CRUD operations around it. I just have a problem with persisting a Document instance between requests when validation fails (be cause I wanna redirect to another page when this happens).
First, I tried storing the instance in the flash session:
# documents_controller.rb
def new
#document = flash[:document] || Document.new
end
def create
document = Document.new(document_params)
if document.save
return redirect_to documents_path
end
flash[:document] = document
redirect_to new_document_path
end
With the code above, I was expecting that the actual Document instance was stored in the flash session, but instead it became a string which looks somewhat like #<Document:0xad32368>. After searching online for a while, I found out that for some reasons you cannot store ActiveRecord objects in sessions.
There are a lot of suggestions about just storing the object's id in the flash session, but I can't do that because as you can see, the object is not yet stored in the database.
Next, I tried reconstructing the Document instance after the redirect, taking advantage of the instance's attributes method (which returns a serializeable hash that can be stored in the session):
# documents_controller.rb
def new
#document = Document.new(flash[:document_hash] || {})
end
def create
...
flash[:document_attributes] = document.attributes
redirect_to new_document_path
end
This almost solved the problem, except for the part in which the validation errors (document.errors) are not preserved. Also, if this is used to persist an instance already stored in the database (in the case of failed validations when updating a Document instance), I'm not sure which between the original attributes and the new attributes will get persisted.
Right now I've already run out ideas to try. Anyone who has a decent solution for this?
EDIT:
You might be wondering why I still have to redirect to another page instead of just rendering the new document view template or the new action in the create method. I did so because there are some things in my views that are dependent on the current controller method. For example, I have a tab which needs to be highlighted when you are on the document creation page (done by checking if action_name == "new" and controller_name == "documents"). If I do:
def create
...
render action: "new"
end
the tab will not get highlighted because action_name will now be create. I also can't just add additional condition to highlight the tab if action_name == "create" because documents can also be created from the the index page (documents_path). Documents can also be updated from the index page (documents_path) or from the detail page (document_path(document)), and if validation fails in the update method, I'd like to redirect to the previous page.
If I really need to fake persisting something between requests (all of the variables that you set are lost between requests), I will ususally put the relevant attributes into hidden fields in the new form.
In your case, this is overkill. In your code, you are redirecting, which causes a new request:
def create
document = Document.new(document_params)
if document.save
return redirect_to documents_path
end
flash[:document] = document
redirect_to new_document_path
end
You can easily render the output of another action, instead of redirecting, by using render action: 'action_to_render'. So in your example, this would probably be:
def create
#document = Document.new(document_params)
if #document.save
render action: 'index'
else
render action: 'new'
end
end
Which can be simplified to:
def create
#document = Document.new(document_params)
action_to_render = #document.save ? 'index' : 'new'
render action_to_render
end
If you need extra logic from the action, you can refactor the logic to a method called from both actions, or simply call the other action from the current one.
It is fine once in a while, but I would caution that having to jerk around with the rendering too much is usually indicative of poor architecture.
Edit:
An additional option, given the newly highlighted constraints, could be to make the new and create methods the same. Remove the new action and routes, and make create answer for GET and PATCH requests. The action might look something like:
def create
#document = Document.new(document_params)
request.patch? && #document.save && redirect_to( documents_path )
end
I actually use something very similar to this for almost all of my controllers, as it tends to DRY things significantly (as you can remove the extra probably identical view, as well)
Another option would be to just use an instance variable to keep track of the active tab in this instance, and make the rest of the code a lot cleaner.
SOLVED
I was able to make a workaround for it using ActiveSupport::Cache::Store (as suggested by #AntiFun). First I created a fake_flash method which acts closely like the flash sessions except that it uses the cache to store the data, and it looks like this:
def fake_flash(key, value)
if value
Rails.cache.write key, value
else
object = Rails.cache.read key
Rails.cache.delete key
object
end
end
And then I just used it like the flash session.
# documents_controller.rb
def new
...
#document = fake_flash[:document] || Document.new
...
end
def create
document = Document.new document_params
...
# if validation fails
fake_flash :document, document
redirect_to new_document_page
end
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 %>
I'm using jpegcam to allow a user to take a webcam photo to set as their profile photo. This library ends up posting the raw data to the sever which I get in my rails controller like so:
def ajax_photo_upload
# Rails.logger.info request.raw_post
#user = User.find(current_user.id)
#user.picture = File.new(request.raw_post)
This does not work and paperclip/rails fails when you try to save request.raw_post.
Errno::ENOENT (No such file or directory - ????JFIF???
I've seen solutions that make a temporary file but I'd be curious to know if there is a way to get Paperclip to automatically save the request.raw_post w/o having to make a tempfile. Any elegant ideas or solutions out there?
UGLY SOLUTION (Requires a temp file)
class ApiV1::UsersController < ApiV1::APIController
def create
File.open(upload_path, 'w:ASCII-8BIT') do |f|
f.write request.raw_post
end
current_user.photo = File.open(upload_path)
end
private
def upload_path # is used in upload and create
file_name = 'temp.jpg'
File.join(::Rails.root.to_s, 'public', 'temp', file_name)
end
end
This is ugly as it requires a temporary file to be saved on the server. Tips on how to make this happen w/o the temporary file needing to be saved? Can StringIO be used?
The problem with my previous solution was that the temp file was already closed and therefore could not be used by Paperclip anymore. The solution below works for me. It's IMO the cleanest way and (as per documentation) ensures your tempfiles are deleted after use.
Add the following method to your User model:
def set_picture(data)
temp_file = Tempfile.new(['temp', '.jpg'], :encoding => 'ascii-8bit')
begin
temp_file.write(data)
self.picture = temp_file # assumes has_attached_file :picture
ensure
temp_file.close
temp_file.unlink
end
end
Controller:
current_user.set_picture(request.raw_post)
current_user.save
Don't forget to add require 'tempfile' at the top of your User model file.
I'm trying to add tags to the Image model in RefineryCMS (trying on 1.0.8 and 2.0.4), have added attr_accessible :tag_list, required acts-as-taggable and setup the views, but the problem is that the tags only save when editing/updating a previously uploaded image - not when uploading for first time, even though it uses the same form...
Any ideas?
It happens on every version of rails and Refinery I have tried...
The tags are going through in the post when looking at logs, just not saving...
I had a similar issue and eventually find the cause of the extra-attributes (in your case the :tag_list) not being saved on new image upload.
If you look at ::Refinery::ImageController you'll see that the create action actyally create the image with :
unless params[:image].present? and params[:image][:image].is_a?(Array)
#images << (#image = ::Refinery::Image.create(params[:image]))
else
params[:image][:image].each do |image|
#images << (#image = ::Refinery::Image.create(:image => image))
end
end
params[:image][:image] is an Array when multiple multiple file uploed is enabled (by default it is). But then the action only use take the array values when creating the images, ignoring the other params.
I quickly write the below work-around that allow to save the other params on multiple image upload :
unless params[:image].present? and params[:image][:image].is_a?(Array)
#images << (#image = ::Refinery::Image.create(params[:image]))
else
images_params = params[:image].dup
images_params.delete(:image)
params[:image][:image].each do |image|
#images << (#image = ::Refinery::Image.create({:image => image}.merge(images_params)))
end
end
It's probably not the most elegant solution bu it does the trick.
To use it in your app, you'll have to create a decorator for the ::Refinery::ImageController to copy and edit the create action in it. (see 'Extending a Controller' in Refinery's Guides)
Lets imagine that I have a custom image upload for a particular record and I add two columns into the model.
thumbnail_url
thumbnail_path
Now lets imagine that I have a form with a :file field which is the uploaded file in multipart form. I would need to somehow have the model pickup the file within the given hash and then issue it over to a custom method which performs the upload and saves it as apart of the model.
Right now I'm doing this:
def initialize(options = nil)
if options
if options[:file]
self.upload_thumbnail(options[:file])
options.delete(:file)
end
super options
else
super
end
end
def update_attributes(options = nil)
if options
if options[:file]
self.upload_thumbnail(options[:file])
options.delete(:file)
end
super options
else
super
end
end
It works, but I am doing some unnecessary overriding here. Is there a simpler way of doing this? Something that would only require overriding perhaps one method?
You are looking for virtual attributes. Just define:
def file
# Read from file or whatever
end
def file=(value)
# Upload thumbnail and store file
end
and initialize, update_attributes and cousins will pick the methods for you.
That, or save the hassle and use paperclip as Kandada suggested.
Have you considered using using paperclip gem? It performs the functions you describe in your question.