Paperclip override validates_attachment in subclass - ruby-on-rails

I am trying to override validates_attachment in Subclass but I notice it only works well with Superclass validation; I wonder why my validates_attachment in subclass doesn't work. Has anybody faced this problem? and how have you solved this problem? Here is an example code:
class Superclass
validates_attachment :logo, :image_ratio => { :ratio => {"1:1" => "28", "4:1" => "50", "5:1" => "40"} }
end
class Subclass < Superclass
validates_attachment :logo, :image_ratio => { :ratio => {"1:1" => "40", "2:1" => "60"} }
end

I suggest that you should put both the class's fields in different tables. It could be possible that you are getting problems because of that.
However if you really want to have only one table for both the classes then I believe that you could use something like this:
validates_attachment :logo, :image_ratio => { :ratio => {"1:1" => "40", "2:1" => "60"} }, :unless => Proc.new {|attach| attach.type == "SubClass"}
I assumed that you have a attach_type column but depending on how you are determining whether the attachment type is a SubClass, it is left upto you to change it.
You could also try to remove your validates_attachment from the Subclass and instead try with_options in your model, like the following:
with_options :unless => :attach_type == "SubClass" do |attach|
attach.validates_attachment :logo, :image_ratio => { :ratio => {"1:1" => "40", "2:1" => "60"}}
end

This works for me... rails 4
validates :photo, :presence => true,
:attachment_content_type => { :content_type => "image/jpg" },
:attachment_size => { :in => 0..10.kilobytes }

Incase anyone else runs into an issue where they need instance access before they can validate I used the following:
class AttachmentDynamicContentTypeValidator < Paperclip::Validators::AttachmentContentTypeValidator
def validate_each(record, attribute, value)
#record = record
super
end
def allowed_types
#record.my_valid_types_array || ["text/plain"]
end
def check_validity!; end
end
And in the actual asset instance I added the following:
class Asset < ActiveRecord::Base
validates :asset, attachment_dynamic_content_type: :asset_content_type
end
Hope that helps someone.

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?

Rails: Cannot set option by using method

For example here is my paperclip configuration. Here is my model file:
class File < ApplicationRecord
belongs_to :attachable, polymorphic: true, optional: true
has_attached_file(:attach_file, local_option)
end
In this model file, I have defined two option named local_option and s3_option.
def local_option
{
:storage => :filesystem,
:path => 'public/:class/:id/:basename.:extension'
}
end
def s3_option
{
:storage => :s3,
:s3_headers => lambda { |attachment| {'Content-Type' => attachment.attach_file_content_type} }
}
end
When I run, I meet this error:
method_missing': undefined local variable or method `local_option' for
Class:0x007fc9be09dac8> (NameError)
I don't know why ruby/rails doesn't see local_option method. Please tell me how.
Thanks
It should be class instance method, not instance method.
def self.local_option
{
storage: :filesystem,
path: 'public/:class/:id/:basename.:extension'
}
end
And voila - error is gone.
Advice: since it is non-changing data, I'd put it into a constant:
LOCAL_FILE_OPTIONS = {
storage: :filesystem,
path: 'public/:class/:id/:basename.:extension'
}.freeze
Now
has_attached_file(:attach_file, LOCAL_FILE_OPTIONS)

How to Re-size Images that are Too Large on-the-fly with Paperclip and Rails 3

I am trying to implement the steps to check and resize images with paperclip based on this blog post: http://www.techdarkside.com/how-to-re-size-images-that-are-too-large-on-the-fly-with-paperclip-and-rails
Here is what I have in place...
class Question < ActiveRecord::Base
# subclasses
class Question::Image < Asset
has_attached_file :attachment,
:url => "/uploads/:class/:attachment/:id_partition/:basename_:style.:extension",
:styles => Proc.new { |attachment| attachment.instance.styles },
:styles => Proc.new { |attachment| attachment.instance.resize }
attr_accessible :attachment
# http://www.ryanalynporter.com/2012/06/07/resizing-thumbnails-on-demand-with-paperclip-and-rails/
def dynamic_style_format_symbol
URI.escape(#dynamic_style_format).to_sym
end
def styles
unless #dynamic_style_format.blank?
{ dynamic_style_format_symbol => #dynamic_style_format }
else
{ :medium => "300x300>", :thumb => "100x100>" }
end
end
def dynamic_attachment_url(format)
#dynamic_style_format = format
attachment.reprocess!(dynamic_style_format_symbol) unless attachment.exists?(dynamic_style_format_symbol)
attachment.url(dynamic_style_format_symbol)
end
def resize
if self.attachment_file_size > 2000000
"300x300>"
else
" "
end
end
end
I'm thinking the issue is with the reuse of the :styles symbol, however I'm not sure how to work both the styles method AND the resize method into a single Proc statement.
Here is what I ended up with thanks to #janfoeh suggestion. I did need to add :originalto the options in style to get this to work. I also bumped the max file size up to 5mb.
class Question < ActiveRecord::Base
# subclasses
class Question::Image < Asset
has_attached_file :attachment,
:url => "/uploads/:class/:attachment/:id_partition/:basename_:style.:extension",
:styles => Proc.new { |attachment| attachment.instance.styles }
attr_accessible :attachment
# http://www.ryanalynporter.com/2012/06/07/resizing-thumbnails-on-demand-with-paperclip-and-rails/
def dynamic_style_format_symbol
URI.escape(#dynamic_style_format).to_sym
end
def styles
unless #dynamic_style_format.blank?
{ dynamic_style_format_symbol => #dynamic_style_format }
else
{ :original => resize, :medium => "300x300>", :thumb => "100x100>" }
end
end
def dynamic_attachment_url(format)
#dynamic_style_format = format
attachment.reprocess!(dynamic_style_format_symbol) unless attachment.exists?(dynamic_style_format_symbol)
attachment.url(dynamic_style_format_symbol)
end
def resize
if self.attachment_file_size > 5000000
"1000x1000>"
else
" "
end
end
end

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 }

Share methods between named scopes

I have a bunch of named scopes and have a method within one of them that I would like to share between the other named scopes. I've sort of accomplished this by using define_method and a lambda. However, there is still some repeated code and I'm wondering is there a better approach?
Here's a simplified example of what I've got. Assume I have a table of projects and each project has many users.
Within the User model I have...
filter_by_name = lambda { |name| detect {|user| user.name == name} }
named_scope :active, :conditions => {:active => true} do
define_method :filter_by_name, filter_by_name
end
named_scope :inactive, :conditions => {:active => false} do
define_method :filter_by_name, filter_by_name
end
named_scope :have_logged_in, :conditions => {:logged_in => true} do
define_method :filter_by_name, filter_by_name
end
Then I would use it like...
active_users = Project.find(1).users.active
some_users = active_users.filter_by_name ["Pete", "Alan"]
other_users = active_users.filter_by_name "Rob"
logged_in_users = Project.find(1).users.logged_in
more_users = logged_in_users.filter_by_name "John"
Here's an entirely different solution that is probably more in spirit with what the question was asking for.
named_scope takes a block, which could be any Proc. So if you create a lambda/Proc which defines the filter_by_name method, you can pass it as the last argument to a named_scope.
filter_by_name = lambda { |name| detect {|user| user.name == name} }
add_filter_by_name = lambda { define_method :filter_by_name, filter_by_name }
named_scope(:active, :conditions => {:active => true}, &add_filter_by_name)
named_scope(:inactive, :conditions => {:active => false}, &add_filter_by_name)
named_scope(:have_logged_in, :conditions => {:logged_in => true}, &add_filter_by_name)
This will do what you're looking for. If you still think it's too repetitive, you can combine it with the techniques in mrjake2's solution to define many named scopes at once. Something like this:
method_params = {
:active => { :active => true },
:inactive => { :active => false },
:have_logged_in => { :logged_in => true }
}
filter_by_name = lambda { |name| detect {|user| user.name == name} }
add_filter_by_name = lambda { define_method :filter_by_name, filter_by_name }
method_params.keys.each do |method_name|
send(:named_scope method_name, :conditions => method_params[method_name],
&add_filter_by_name)
end
Named scopes can be chained, so you're making this harder on your self than you need to.
The following when defined in the user model will get you what you want:
class User < ActiveRecord::Base
...
named_scope :filter_by_name, lambda { |name|
{:conditions => { :name => name} }
}
named_scope :active, :conditions => {:active => true}
named_scope :inactive, :conditions => {:active => false}
named_scope :have_logged_in, :conditions => {:logged_in => true}
end
Then the following snippets will work:
active_users = Project.find(1).users.active
some_users = active_users.filter_by_name( ["Pete", "Alan"]
other_users = active_users.filter_by_name "Rob"
logged_in_users = Project.find(1).users.have_logged_in
more_users = logged_in_users.filter_by_name "John"
I see that you're using detect, probably to avoid excess hits to the DB. But your examples don't use it properly. Detect only returns the first element in a list that the block returns true for. In the above example, some_users will only be a single record, the first user that is named either "Pete" or "Alan". If you're looking to get all users named "Pete" or "Alan" then you want select. And if you want select then you're better off using a named scope.
Named scopes when evaluated return a special object that contains the components necessary to build the SQL statement to generate the results, chaining other named scopes still doesn't execute the statement. Not until you try to access methods on the result set, such as calling each or map.
I would probably use a bit of metaprogramming:
method_params = {
:active => { :active => true },
:inactive => { :active => false },
:have_logged_in => { :logged_in => true }
}
method_params.keys.each do |method_name|
send :named_scope method_name, :conditions => method_params[method_name] do
define_method :filter_by_name, filter_by_name
end
end
This way if you wanted to add more finders in the future, you could just add the method name and conditions to the methods_param hash.
You can also do this with a second named scope.
named_scope :active, :conditions => {:active => true}
named_scope :inactive, :conditions => {:active => false}
named_scope :have_logged_in, :conditions => {:logged_in => true}
named_scope :filter_by_name, lambda {|name| :conditions => ["first_name = ? OR last_name = ?", name, name]}
Then you can do #project.users.active.filter_by_name('Francis').
If you really need to do this with Enumerable#detect, I would define the filter_by_name method in a module which can then extend the named scopes:
with_options(:extend => FilterUsersByName) do |fubn|
fubn.named_scope :active, :conditions => {:active => true}
fubn.named_scope :inactive, :conditions => {:active => false}
fubn.named_scope :have_logged_in, :conditions => {:logged_in => true}
end
module FilterUsersByName
def filter_by_name(name)
detect {|user| user.name == name}
end
end
This adds the filter_by_name method to the class returned by all three named scopes.

Resources