This is my Gig model
class Gig < ActiveRecord::Base
has_attached_file :image, :styles => { :medium => "360x170>", :bigger => "650x459>" }
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
validate :image_size_validation
def image_size_validation
if image.size > 2.megabytes
errors.add(:base, "Image should be less than 2MB")
end
end
end
Everything works great,i can not upload a picture greater than 2Mb as i wanted,and it gives a notice about that(written by me).
Question: When I save the form(of a new product),without selecting any picture,it throws an error saying undefined method >
for nil:NilClass. Instead of just saying to the user,that he should
upload a picture,so the form can be saved.
Gig controller for create is this
def create
#gig = current_user.gigs.build(gig_params)
if #gig.save
redirect_to #gig, notice: "Gig successfully created"
else
render "new"
end
end
That error is clearly thrown by this line:
if image.size > 2.megabytes
obviously, if there isn't an image... it won't have a size.
How about you check for nil there:
def image_size_validation
return if image.blank?
if image.size > 2.megabytes
Related
I am using paperclip with s3 in a rails 4 app. It is working fine everywhere, but I have a specific use case that is requiring some special behavior.
I need to upload an image as an avatar, and have it resize to all the thumbnail sizes, but then I need to be able to update only the original image, while preserving all the thumbnail links.
Currently, I'm using a Proc to determine attachment sizes based on a class variable. This is causing image 1 to be uploaded and resized, then I am setting image 2 with no styles. I was hoping this would create all the thumbs, then replace the original. Unfortunately, it is updating the URLs for each size, but they are empty.
tl;dr - I need to have avatars resized, but I need to be able to update ONLY the original and leave the rest alone.
Controller
class StudentsController < ApplicationController
# POST /students/:id/avatar
def new_avatar
current_student.avatar = params[:avatar]
current_student.set_orginial_only TRUE
current_student.avatar = params[:avatar_orig]
if current_student.save
render json: current_student, serializer: StudentAvatarSerializer, status: 200
else
render json: ErrorSerializer.new(current_student), status: 400
end
end
# DELETE /students/:id/avatar
def destroy_avatar
current_student.avatar.destroy
if current_student.save
render json: {success: true}, status: 200
else
render json: ErrorSerializer.new(current_student), status: 400
end
end
private
# find student by id and cache
def current_student
#student ||= Student.find(params[:id])
end
end
Model
class Student < ActiveRecord::Base
##orginial_only = FALSE
def set_orginial_only value
##orginial_only = value
end
# Paperclip attachements
has_attached_file :avatar, :styles => Proc.new { |clip| clip.instance.attachment_sizes },
path: "/:class/:attachment/:id/:content_type_extension/:style/:filename",
:default_url => "/images/:style/missing.png"
validates_attachment_content_type :avatar, :content_type => /image/
validates_attachment_size :avatar, :in => 0..2.megabytes
def attachment_sizes
if ##orginial_only
styles = {}
else
styles = {
tiny: '50x50#',
tiny_retina: '100x100#',
small: '60x60#',
small_retina: '120x120#',
medium: '108x108#',
medium_retina: '216x216#',
large: '205x205#',
large_retina: '410x410#'
}
end
styles
end
end
Is there a way to only update the original image while keeping all my thumbs?
Solution: I ended up just using paperclip for the resized image, and aws-sdk to manually replace the original image in the paperclip assigned path.
I have a validation method in my Photo Model, which, successfully makes the validation but afterwards when creating the photo gives me a strange error. I've tried to update Minimagick but It doesn't work. I know that without the validation it works correctly...
The Error
Errno::ENOENT in PhotosController#create
No such file or directory
Line in which the error is
image = MiniMagick::Image.open(photo.path)
Model
validate :validate_minimum_image_size
def validate_minimum_image_size
image = MiniMagick::Image.open(photo.path)
if image[:width] <= image[:height]
errors.add :photo, "should be 400x400px minimum!"
end
end
+Crop method*
def crop
if model.crop_x.present?
resize_to_limit(800, 500)
manipulate! do |img|
x = model.crop_x
y = model.crop_y
w = model.crop_w
h = model.crop_h
img.crop "#{w}x#{h}+#{x}+#{y}"
img
end
end
end
EDITED CONTROLLER
def create
#photo = Photo.new(photo_params)
#photo.user = current_user
if #photo.save
redirect_to [current_user, #photo], notice: 'El spot se ha subido correctamente!'
else
if /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.match(request.user_agent) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.match(request.user_agent[0..3])
redirect_to [current_user, #photo], notice: 'El spot se ha subido correctamente!'
else
render :crop
end
end
end
Update
I added the new validation approach in Photo model
private
def validate_minimum_image_size
if self.photo_upload_width <= self.photo_upload_height
errors.add :photo, "Dimensions of uploaded photo should be not less than 400x400 pixels."
end
end
end
But now, it's validating. If photo width <= photo height, it will send the photo to crop, otherwise, it will create it without cropping successfully. But there is a problem, when it sends the photo to crop, and I click on create, the following error is raised:
NoMethodError in PhotosController#create
undefined method `<=' for nil:NilClass
Extracted source (around line #37):
def validate_minimum_image_size
if self.photo_upload_width <= self.photo_upload_height
errors.add :photo, "Dimensions of uploaded photo should be not less than 400x400 pixels."
end
end
Since you're using Carrierwave, try this approach: https://gist.github.com/kirs/1239078
The parts you need to add/change look something like this:
Model
class Photo < ActiveRecord::Base
attr_accessor :photo_upload_width, :photo_upload_height
mount_uploader :photo, PhotoUploader
validate :validate_minimum_image_size
private
def validate_minimum_image_size
if self.photo_upload_width < 400 || self.photo_upload_height < 400
errors.add :photo, "Dimensions of uploaded photo should be not less than 400x400 pixels."
end
end
end
Uploader
class PhotoUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
# for image size validation
# fetching dimensions in uploader, validating it in model
before :cache, :capture_size_before_cache # callback, example here: http://goo.gl/9VGHI
def capture_size_before_cache(new_file)
if model.photo_upload_width.nil? || model.photo_upload_height.nil?
model.photo_upload_width, model.photo_upload_height = `identify -format "%wx %h" #{new_file.path}`.split(/x/).map { |dim| dim.to_i }
end
end
end
I have a preview of attached image in my update form. The problem appears when user gets validation errors on attachment field. In this case image thumbnail url becomes as if an image was uploaded without any errors (it shows name of file that was not saved at server).
Here is how I get image url in my view:
<%= image_tag(#product.photo.url(:medium)) %>.
Controller:
def update
#product = Product.find(params[:id])
#product.update_attributes(params[:product]) ? redirect_to('/admin') : render(:new)
end
def edit
#product = Product.find(params[:id])
render :new
end
Model:
class Product < ActiveRecord::Base
<...>
##image_sizes = {:big => '500x500>', :medium => '200x200>', :thumb=> '100x100>'}
has_attached_file :photo, :styles => ##image_sizes, :whiny => false
validates_attachment_presence :photo
validates_attachment_content_type :photo, :content_type => ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'], :message => I18n.t(:invalid_image_type)
validates_attachment_size :photo, :less_than => 1.megabytes, :message => I18n.t(:invalid_image_size, :max => '1 Mb')
after_post_process :save_image_dimensions
<...>
end
UPD: The simpliest solution is to add #photo_file_url = #product.photo.url(:medium) in controller's update action before #product.update_attributes and <% #photo_file_url ||= #product.photo.url(:medium) %> in the view.
I actually had the same issue and here is what I had done (which seems a bit of a hack) but works and will show the default or previous image on update failure
after_validation :logo_reverted?
def logo_reverted?
unless self.errors[:logo_file_size].blank? or self.errors[:logo_content_type].blank?
self.logo.instance_write(:file_name, self.logo_file_name_was)
self.logo.instance_write(:file_size, self.logo_file_size_was)
self.logo.instance_write(:content_type, self.logo_content_type_was)
end
end
In my app I had similar issues, so I used:
<%= image_tag(#product.photo.url(:medium)) if #photo.valid? %>
That way nothing shows up if the Photo isn't valid (ie successfully created), but when you edit existing records the image is shown.
Hope that helps.
I am working with Rails 3 and Paperclip to attach uploaded files to several object types using a polymorphic association. I have created an Asset model and an inherited Image model (will be adding others, like Video and Documents later) as follows:
# app/models/asset.rb
class Asset < ActiveRecord::Base
# Nothing here yet
end
# app/models/image.rb
class Image < Asset
belongs_to :assetable, :polymorphic => true
has_attached_file :file, {
:styles => {
:small => { :geometry => '23x23#', :format => 'png' },
:medium => { :geometry => '100x100#', :format => 'png' } }
}.merge(PAPERCLIP_STORAGE_OPTIONS).merge(PAPERCLIP_STORAGE_OPTIONS_ASSET_IMAGE) # Variables sent in environments to direct uploads to filesystem storage in development.rb and S3 in production.rb
validates_attachment_presence :file
validates_attachment_size :file, :less_than => 5.megabytes
end
I then have another object type, Unit, which I am attaching multiple images to as follows:
# app/models/unit.rb
class Unit < ActiveRecord::Base
# ...
has_many :images, :as => :assetable, :dependent => :destroy
accepts_nested_attributes_for :images
end
# app/controllers/units_controller.rb
class UnitsController < ApplicationController
# ...
def new
#unit = current_user.units.new
# ...
#unit.images.build
end
def create
#unit = current_user.units.new(params[:unit])
# ...
respond_to do |format|
if #unit.save
format.html { redirect_to(#unit, :notice => 'Unit creation successful!') }
else
format.html { render :action => "new" }
end
end
end
def show
#unit = current_user.units.find(params[:id])
#unit_images = #unit.images
# ...
end
def edit
#unit = current_user.units.find(params[:id])
# ...
#unit.images.build
end
def update
#unit = current_user.units.find(params[:id], :readonly => false)
respond_to do |format|
if #unit.update_attributes(params[:unit])
format.html { redirect_to(#unit, :notice => 'Unit was successfully updated.') }
else
format.html { render :action => "edit" }
end
end
end
def destroy
#unit = current_user.units.find(params[:id])
#unit.destroy
respond_to do |format|
format.html { redirect_to(units_url) }
end
end
end
# app/views/units/_form.html.haml
.field # Display already uploaded images
= f.fields_for :images do |assets|
- unless assets.object.new_record?
= link_to(image_tag(assets.object.file.url(:medium)), assets.object.file.url(:original))
.field # Display field to add new image
= f.fields_for :images do |assets|
- if assets.object.new_record?
= assets.label :images, "Image File"
= assets.file_field :file, :class => 'uploadify'
Using these settings I am able to upload images one at at time, per display of the form.
The issues start when I try to integrate Uploadify to add multi file uploading/previewing. I have satisfied all the Uploadify dependancies, but in order to save the images associated with the Unit model I need to somehow include a reverence to the unit_id so that the polymorphic association can be made properly. Below is my current Uploadify code:
%script
$(document).ready(function() {
$('.uploadify').uploadify({
uploader : '/uploadify/uploadify.swf',
cancelImg : '/uploadify/cancel.png',
auto : true,
multi : true,
script : '#{units_path}',
scriptData : {
"#{key = Rails.application.config.session_options[:key]}" : "#{cookies[key]}",
"#{request_forgery_protection_token}" : "#{form_authenticity_token}",
}
});
});
So while I can easily upload with Paperclip along, Uploadify will not work. Any help would be much appreciated. Thank you in advance.
UPDATE:
After doing more research I ran across this comment to a similar issue: Rails3, S3, Paperclip Attachment as it's own model?. Any thoughts on whether or not that would work in this situation? Is there an easy way of determining the unit.id from the /new method and passing it to the Uploadify-created Asset?
We had solved a very similar problem once by saving the model right away when loading the form in a draft state (using state machine). Like this the model is available when you're trying to attach the files you're uploading and once you're submitting the rest of the form, you're basically just updating the model which changes it's state to e.g. published. It's a little work to update the controllers etc., but it did the trick.
I'm trying to upload to a portfolio app I've built, specifically trying to find where to hook delayed_job into the process. It all works otherwise. Right now it returns undefined method 'call' for #<Class:0xae68750> on app/controllers/portfolio_items_controller.rb:18:in 'create' so here's my model and that portion of the controller... anyone see anything that could be going wrong? The hook I'm using now I got from this blog: http://madeofcode.com/posts/42-paperclip-s3-delayed-job-in-rails
/app/controllers/portfolio_items_controller.rb
def create
#portfolio_item = PortfolioItem.new(params[:portfolio_item])
if #portfolio_item.save
flash[:notice] = "Portfolio item created. As soon as files are uploaded Portfolio item will be made live."
redirect_to #portfolio_item
else
render :action => 'new'
end
end
/app/models/asset.rb
class Asset < ActiveRecord::Base
attr_accessible :image, :image_file_name, :image_content_type, :image_file_size, :portfolio_item_id, :order
belongs_to :portfolio_item
has_attached_file :image,
:styles => {
:thumb => "20x20#",
:small => "100x100",
:large => "600x600>"
},
:storage => :s3,
:s3_credentials => {
:access_key_id => ENV["S3_KEY"],
:secret_access_key => ENV["S3_SECRET"]
},
:bucket => ENV["S3_BUCKET"],
:path => "portfolio/:attachment/:id/:style/:basename.:extension"
before_source_post_process do |image|
if source_changed?
processing = true
false
end
end
after_save do |image|
if image.source_changed?
Delayed::Job.enqueue ImageJob.new(image.id)
end
end
def regenerate_styles!
self.source.reprocess!
self.processing = false
self.save(false)
end
def source_changed?
self.source_file_size_changed? ||
self.source_file_name_changed? ||
self.source_content_type_changed? ||
self.source_update_at_changed?
end
end
class ImageJob < Struct.new(:image_id)
def perform
Image.find(self.image_id).regenerate_styles!
end
end
Edit: thanks to kind people, it's not the missing .new anymore. But now it's that the before_source_post_process is not defined? And I can't find that method in anywhere but that blog post and this SO question. Is there something more appropriate?
The before_source_post_process won't work for you. It only works for:
has_attached_file :source
In your case it should be
before_image_post_process
Similarly, the source_changed? method should be:
def source_changed?
self.image_file_size_changed? ||
self.image_file_name_changed? ||
self.image_content_type_changed? ||
self.image_update_at_changed?
end
I think this:
#portfolio_item = PortfolioItem.(params[:portfolio_item])
should most likely be this:
#portfolio_item = PortfolioItem.new(params[:portfolio_item])