Paperclip image dimensions custom validator - ruby-on-rails

This is my Image model, in which I've implemented a method for validating the attachment's dimensions:
class Image < ActiveRecord::Base
attr_accessible :file
belongs_to :imageable, polymorphic: true
has_attached_file :file,
styles: { thumb: '220x175#', thumb_big: '460x311#' }
validates_attachment :file,
presence: true,
size: { in: 0..600.kilobytes },
content_type: { content_type: 'image/jpeg' }
validate :file_dimensions
private
def file_dimensions(width = 680, height = 540)
dimensions = Paperclip::Geometry.from_file(file.queued_for_write[:original].path)
unless dimensions.width == width && dimensions.height == height
errors.add :file, "Width must be #{width}px and height must be #{height}px"
end
end
end
This works fine, but it's not reusable since the method takes fixed values for width & height. I want to transform this to a Custom Validator, so I can use it in other models too. I've read the guides about this, I know it'll be something like this in app/models/dimensions_validator.rb:
class DimensionsValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
dimensions = Paperclip::Geometry.from_file(record.queued_for_write[:original].path)
unless dimensions.width == 680 && dimensions.height == 540
record.errors[attribute] << "Width must be #{width}px and height must be #{height}px"
end
end
end
but I know I'm missing something cause this code doesn't work. The thing is that I want to call the validation like this in my model:
validates :attachment, dimensions: { width: 300, height: 200}.
Any idea on how this validator should be implemented?

Put this in app/validators/dimensions_validator.rb:
class DimensionsValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# I'm not sure about this:
dimensions = Paperclip::Geometry.from_file(value.queued_for_write[:original].path)
# But this is what you need to know:
width = options[:width]
height = options[:height]
record.errors[attribute] << "Width must be #{width}px" unless dimensions.width == width
record.errors[attribute] << "Height must be #{height}px" unless dimensions.height == height
end
end
Then, in the model:
validates :file, :dimensions => { :width => 300, :height => 300 }

There is a gem that does this called paperclip-dimension-validator.

Related

paperclip resize not working for dynamic columns on attachment model

I have a two columns on my attachment model on which the user sets the dimension to which they want to resize the image.
However, the variables when the resize happens are nil, but set to actual values after the resize happens.
below is the code
has_attached_file :file, :styles => lambda { |a|
{ :logo => ["200x50>",:png],
:user_defined => ["#{a.instance.custom_width}x#{a.instance.custom_height}>",:png] }
}
the custom_width & custom_height are nil when conversion happens however the logo conversion works as expected.
I am using ruby 2.2.4p230 & Rails 4.2.4
below is the full mode code
class Attachment < ActiveRecord::Base
belongs_to :model_one
belongs_to :attachable, polymorphic: true
#has_attached_file :file, styles: { logo: ['200x50>',:png] }
has_attached_file :file, styles: lambda { |attachment| attachment.instance.styles }
def styles
Rails.logger.info self.inspect
Rails.logger.info self.attachable
styles = {}
m = "200x50>"
l = "#{self.custom_width}x#{self.custom_height}>"
styles[:logo] = [m, :png]
styles[:user_defined] = [l, :png]
styles
end
end
Can anyone please help and let me know if i am doing something wrong?

Paperclip dimension validation error in Rails 4

I want in my app user to be able to upload files with
at least width:800px and height: 550px
I created in app/models/dimensions_validator.rb file
and the code
class DimensionsValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
dimensions = Paperclip::Geometry.from_file(value.queued_for_write[:original].path)
width = options[:width]
height = options[:height]
record.errors[attribute] << "Width must be at least #{width}px" if dimensions.width < width
record.errors[attribute] << "Height must be at least #{height}px" if dimensions.height < height
end
end
and in my app/models/gig.rb model
validates :image, :dimensions => { :width => 800, :height => 550 }
Question: When i click on the submit button,without actually selecting any picture,it throughs an error saying undefined method
"path" for nil:NilClass
and it marks in red color the line 4 which is dimensions = Paperclip::Geometry.from_file(value.queued_for_write[:original].path)
Maybe i need a code,to check if the image is present,something like if image.present? but where would i include that? i already use in the gig model validates_attachment_presence :image
This is the error
And this is my GigsController#update
def update
if #gig.update(gig_params)
redirect_to #gig, notice: "Gig was successfully updated"
else
render "edit"
end
end
Try this.
validates :image, :unless => "image.queued_for_write[:original].blank?", dimensions: { width: 800, height: 550 }
I believe you can add other conditions to your validation. So, you might try adding allow_blank or an if condition:
validates :image, dimensions: { width: 800, height: 550 }, allow_blank: true
or maybe:
validates :image, dimensions: { width: 800, height: 550 }, if: Proc.new {|gig| gig.image? }
It is possible to use this gem for validation of image width and height with Paperclip: https://github.com/evedovelli/image_validators
Add it to your bundle:
gem 'image_validators'
And add the validation rules to your Model:
validates :image, dimensions: { greater_than_or_equal_to: { width: 800, height: 550 } }

Validate image size in carrierwave uploader

All uploads should be at least 150x150 pixels. How to validate it with Carrierwave?
Why not to use MiniMagick? Modified DelPiero's answer:
validate :validate_minimum_image_size
def validate_minimum_image_size
image = MiniMagick::Image.open(picture.path)
unless image[:width] > 400 && image[:height] > 400
errors.add :image, "should be 400x400px minimum!"
end
end
I made a slightly more complete validator based on #skalee's answer
class ImageSizeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.blank?
image = MiniMagick::Image.open(value.path)
checks = [
{ :option => :width,
:field => :width,
:function => :'==',
:message =>"Image width must be %d px."},
{ :option => :height,
:field => :height,
:function => :'==',
:message =>"Image height must be %d px."},
{ :option => :max_width,
:field => :width,
:function => :'<=',
:message =>"Image width must be at most %d px."},
{ :option => :max_height,
:field => :height,
:function => :'<=',
:message =>"Image height must be at most %d px."},
{ :option => :min_width,
:field => :width,
:function => :'>=',
:message =>"Image width must be at least %d px."},
{ :option => :min_height,
:field => :height,
:function => :'>=',
:message =>"Image height must be at least %d px."},
]
checks.each do |p|
if options.has_key?(p[:option]) and
!image[p[:field]].send(p[:function], options[p[:option]])
record.errors[attribute] << p[:message] % options[p[:option]]
end
end
end
end
end
Use it like validates :image, :image_size => {:min_width=>400, :min_height => 400}.
It surprised me just how difficult it was to search around for a clear-cut way to validate image width & height with CarrierWave. #Kir's solution above is right on, but I wanted to go a step further in explaining what he did, and the minor changes I made.
If you look at his gist https://gist.github.com/1239078, the answer lies in the before :cache callback he has in the Uploader class. The magic line is
model.avatar_upload_width, model.avatar_upload_height = `identify -format "%wx %h" #{new_file.path}`.split(/x/).map { |dim| dim.to_i }
in his case, avatar_upload_width & avatar_upload_height are attributes of his User model. I didn't want to have to store width&height in the database, so in my model I said:
attr_accessor :image_width, :image_height
Remember, you can use attr_accessor for attributes you want to have on hand when messing with a record, but just don't want to persist them to the db. So my magic line actually turned into
model.image_width, model.image_height = `identify -format "%wx %h" #{new_file.path}`.split(/x/).map { |dim| dim.to_i }
So now I have the width & height of my image stored in the model object. The last step is to write a custom validation for the dimensions, so in your model you need something like
validate :validate_minimum_image_size
And then below it define your custom validation method, same as in the gist
# custom validation for image width & height minimum dimensions
def validate_minimum_image_size
if self.image_width < 400 && self.image_height < 400
errors.add :image, "should be 400x400px minimum!"
end
end
I just made a custom validator that aims to be more Rails 4+ syntax friendly.
I took ideas from the others responses on this thread.
Here is the gist: https://gist.github.com/lou/2881f1aa183078687f1e
And you can use it like this:
validates :image, image_size: { width: { min: 1024 }, height: { in: 200..500 } }
In this particular case it should be:
validates :image, image_size: { width: 150, height: 150 }

Paperclip obtain real image size

My problem is the next:
I am trying resize a image size depending a proportial size. Example If i have a image whose size is 1440*1000 its new size will be 648*440 (I use a proportion depending a max_size)
NOTE: Then i post my code so you will understand the size relations.
Ok. so I am reading this stackoverflow post:
Getting width and height of image in model in the Ruby Paperclip GEM
Now i post my code and then i will describe my problem.
class ProductImage < ActiveRecord::Base
belongs_to :product, :dependent => :destroy
MAXIMUM_SIZE = 650
has_attached_file :photo, :url => "/:attachment/:class/:id/:style_:basename.:extension", :styles => {:real_size => Proc.new { |instance| instance.real_size }, :original => "400x400>", :medium => "300x300>", :principal => "240x240>", :thumb => "100x100>", :small => "80x50>"}
def real_size
#image = Paperclip::Geometry.from_file(photo.to_file(:maximum_size))
#OBTAIN REAL IMAGE SIZE, NOT ATTACHMENT SIZES
if image_less_than_maximum_size?
return "#{image.width}x#{image.height}"
else
return adjust_image_size(self.width, self.height)
end
end
def adjust_image_size(image_width, image_height)
ratio = (image_width/image_height).to_f
difference_between_size = (image_width - image_height).abs
percentage_difference = ratio > 1 ? difference_between_size * 100.0 / image_width : difference_between_size * 100.0 / image_height
difference_respect_maximum_size = ratio > 1 ? MAXIMUM_SIZE * 100.0 / image_width : MAXIMUM_SIZE * 100.0 / image_height
width = height = 0.0
if ratio > 1
#USE 101.0 FOR INCREMENT OR DECREMENT THE VALUE A LITTLE BIT
width = image_width * difference_respect_maximum_size / 101.0
height = width - (percentage_difference * width / 101.0)
else
heigth = image_height * difference_respect_maximum_size / 101.0
width = height - (percentage_difference * height / 101.0)
end
return "#{width}x#{height}"
end
def image_less_than_maximum_size?
if self.width > self.height
return self.width < MAXIMUM_SIZE
else
return self.height < MAXIMUM_SIZE
end
end
end
My problem is how could i obtain the "real_size"?.
i.e, if image size is "1440*1000" to obtain this size (no attachment size)
UPDATE:
I am thinking a solution. So i think in declare two temp variable to ProductImage model and during initialize method use a before_post_process paperclip callback.
class ProductImage < ActiveRecord::Base
belongs_to :product, :dependent => :destroy
attr_accessor :height, :width
MAXIMUM_SIZE = 650
has_attached_file :photo, :url => "/:attachment/:class/:id/:style_:basename.:extension", :styles => {:real_size => Proc.new { |instance| instance.real_size }, :original => "400x400>", :medium => "300x300>", :principal => "240x240>", :thumb => "100x100>", :small => "80x50>"}
before_post_process :image?
before_post_process :assign_size
...
def assign_size
#width = Paperclip::Geometry.from_file(remote_original_photo_path).width
#height = Paperclip::Geometry.from_file(remote_original_photo_path).height
end
end
Then i could use this size in the other method.
My new problem is how could I determine the remote_original_photo_path in model?
in controller i use the params[:product][:product_images_attributes][index][:photo].
I could save the temp path in model. However because my real_size method during initilize i donĀ“t know how to pass the params info.
Thanks in advance again
With using a gem like image_size ?
[EDIT]
To determine the original upload path may be you can use :
remote_original_photo_path = File.basename(upload['datafile'].original_path)

setting activerecord attribute based on virtual attributes

i have an attribute called dimensions which i want to set based on my width, height, and depth attributes.
for example, i want to do ShippingProfile.find(1).width = 4, and have it save to dimensions as {:width => 4, :height => 0, :depth => 0}`
is this possible?
class ShippingProfile < ActiveRecord::Base
after_initialize :set_default_dimensions
serialize :dimensions, Hash
attr_accessor :width, :height, :depth
attr_accessible :width, :height, :depth, :dimensions
private
def set_default_dimensions
self.dimensions ||= {:width => 0, :height => 0, :depth => 0}
end
end
Very much so, all you need to do is use a callback to set the value of self.dimensions:
class ShippingProfile < ActiveRecord::Base
after_initialize :set_default_dimensions
after_validation :set_dimensions
serialize :dimensions, Hash
attr_accessor :width, :height, :depth
attr_accessible :width, :height, :depth, :dimensions
private
def set_default_dimensions
self.dimensions ||= {:width => 0, :height => 0, :depth => 0}
end
def set_dimensions
self.dimensions = {
:width => self.width || self.dimensions[:width],
:height => self.height || self.dimensions[:height],
:depth => self.depth || self.dimensions[:depth],
}
end
end
You need to use self.foo || self.dimensions[:foo] to ensure that you preserve any existing values already set in the hash. Why? Your dimension attributes (I'm assuming) aren't being persisted in the database - you're using attr_accessor, rather than setting them up as fields in your table.
As an aside, I think you're going about your model design the wrong way. By storing the dimensions as a hash in the database, not only do you lose the ability to query based on those attributes, but you add a level of fragility you don't need.
If you are storing your individual dimension attributes as separate fields, then you're introducing redundancy and complexity. You would be better served by having the three attributes as fields in your database (if you aren't already), then generating the dimensions hash on the fly when it's needed:
class ShippingProfile < ActiveRecord::Base
def dimensions
{ :width => self.width, :height => self.height, :depth => self.depth }
end
end
This way, you retain the functionality and gain some flexibility.
ShippingProfile.find(1).dimensions[:width] = 4
You can use a class in serialize, so
class ShippingProfile < ActiveRecord::Base
serialize :dimensions, Dimensions
end
class Dimensions
attr_accessor :width, :height,:depth
def initialize
#width = 0
#height = 0
#depth = 0
end
def volume
width * height * depth
end
end
Now you can do ShippingProfile.dimensions.width = 1 and later ShippingProfile.dimension.volume etc..
A model would be a richer representation than a Hash

Resources