I have 2 models users , companies
User model:
has_attached_file :avatar,
...
:whiny=>false
validates_with ImageSizeValidator
validates_with ImageTypeValidator
validates_with ImageConvertionValidator
Company model:
has_attached_file :logo,
#the rest is similar
I have done validation for users and put it in validation_helper
class ImageSizeValidator < ActiveModel::Validator
def validate(record)
if record.avatar_file_name.present?
record.errors[:base] << (I18n.t :in_between, scope: "activerecord.errors.models.user.attributes.avatar_file_size") unless record.avatar_file_size.to_i < 200000
end
end
end
class ImageTypeValidator < ActiveModel::Validator
def validate(record)
if record.avatar_file_name.present?
record.errors[:base] << (I18n.t :file_type, scope: "activerecord.errors.models.user.attributes") unless ['image/jpeg', 'image/gif','image/png'].include?(record.avatar_content_type)
end
end
end
My problem is that the names will be different so avatar_file_name for users and logo for companies.
Do I have to do a specific method for each?
How can I work this around?
you just need to add options. If you take a look at documentation, you can pass arguments in block:
#model
validates_with ImageSizeValidator, paperclip_field_name: :avatar
#validator
def validate(record)
if record.send(options[:paperclip_field_name].to_s+"_file_name").present?
record.errors[:base] << (I18n.t :in_between, scope: "activerecord.errors.models.user.attributes.#{options[:paperclip_field_name]}_file_size") unless record.send(options[:paperclip_field_name].to_s+"_file_name").to_i < 200000
end
end
but much easier to use validate_each method
#model
validates :avatar, image_size: true, image_type: true, image_conversion: true
#validator
def validate_each(record, attribute, value)
if record.send(attribute.to_s+"_file_name").present?
record.errors[:base] << (I18n.t :in_between, scope: "activerecord.errors.models.user.attributes.#{attribute}_file_name)") unless record.send(attribute.to_s+"_file_name").to_i < 200000
end
end
Paperclip has built-in support for validations (https://github.com/thoughtbot/paperclip#validations). If their validation are not a fit for your problem you can look on how they are doing it: https://github.com/thoughtbot/paperclip/tree/master/lib/paperclip/validators
Related
I am attempting to use the validates_with custom validations helper with Rails 4.
The following code is working in my application:
class Photo
validates_with CleanValidator
include ActiveModel::Validations
end
class CleanValidator < ActiveModel::Validator
def validate(record)
if record.title.include? "foo"
record.errors[:title] << "Photo failed! restricted word"
end
end
end
However I want to pass this helper to multiple attributes in multiple models, not just :title.
There is an example in validates_with section of guide that contains the following example:
class GoodnessValidator < ActiveModel::Validator
def validate(record)
if options[:fields].any?{|field| record.send(field) == "Evil" }
record.errors[:base] << "This person is evil"
end
end
end
class Person < ApplicationRecord
validates_with GoodnessValidator, fields: [:first_name, :last_name]
end
This is what I want to achieve, substituting [:fields] for [:title] in my code example so that I can use CleanValidator for multiple models and multiple attributes (User.name, Photo.title etc).
I think you want the other example from the guides, each validator. You should be able to do
class CleanValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless ["Evil", "Other", "Restricted", "Words"].include?(value)
record.errors[attribute] << (options[:message] || "is a restricted word")
end
end
end
class Photo
include ActiveModel::Validations
attr_accessor :title
validates :title, clean: true
end
Here is my example
class User < ActiveRecord::Base
validates_with EmailValidator
end
class EmailValidator < ActiveModel::Validator
def validate(record)
if record != someregex
record.errors.add(:email, 'invalid email')
end
end
end
Now I can use this EmailValidator for any model. But my requirement is to validate its uniqueness and presence also for that particular model.
If I can achieve this I can use this EmailValidator for any model email validation with functionalities unique, presence.
Then I can achieve more reusable Validator.
You can do this by rewriting your validator to be EachValidator:
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if attribute !~ someregex
record.errors.add attribute, (options[:message] || 'invalid email')
end
end
end
Then in your model:
validates :email, email: true, uniqueness: true, presence: true
Hope that helps.
Hi I need help and all insight appreciated. I have two models Auctions and Bids and I want to retrieve the All auctions current_user won, the ones s/he has been outbid on and the ones s/he's winning
Here are the two models:
class Auction < ActiveRecord::Base
extend FriendlyId
friendly_id :guid, use: :slugged
before_save :populate_guid
mount_uploaders :images, ImageUploader
belongs_to :client
has_many :bids, dependent: :destroy
has_one :order, dependent: :destroy
validates_presence_of :title, :lien_price,
:end_time, :collateral_value,
:redemption_date, :current_interest_rate,
:additional_tax, :collateral_details,
:location, :client_id, :starting_bid
validate :end_time_in_the_future, :on => :update
validates_uniqueness_of :guid, case_sensitive: false
def end_time_in_the_future
errors.add(:end_time, "can't be in the past") if self.end_time && self.end_time < Time.now
end
def self.get_active_auctions
where("end_time > ?", Time.now)
end
def self.closed_auctions
where("end_time < ?", Time.now)
end
def highest_bid
self.bids.maximum("amount")
end
def highest_bid_object
self.bids.order(:amount => :desc).limit(1).first
end
def highest_bidder
self.highest_bid_object.user if highest_bid_object
end
def closed?
self.end_time < Time.now
end
private
def populate_guid
if new_record?
while !valid? || self.guid.nil?
self.guid = SecureRandom.random_number(1_000_000_000).to_s(36)
end
end
end
end
and
class Bid < ActiveRecord::Base
extend FriendlyId
friendly_id :guid, use: :slugged
belongs_to :auction
belongs_to :user
before_save :populate_guid
validates_presence_of :amount, :user_id,
:auction_id
#validate :higher_than_current?
validates :amount, :numericality => true
validates_uniqueness_of :guid, case_sensitive: false
def higher_than_current?
if !Bid.where("amount > ? AND auction_id = ?", amount, self.auction.id).empty?
errors.add(:amount, "is too low! It can't be lower than the current bid, sorry.")
end
end
private
def populate_guid
if new_record?
while !valid? || self.guid.nil?
self.guid = SecureRandom.random_number(1_000_000_000).to_s(36)
end
end
end
end
I thought
#auctions = Auction.closed_auctions.where(highest_bidder: current_user)
or
#auctions = Auction.closed_auctions.joins(:bids).where(highest_bidder: current_user)
would work but they both raise an error.
Edit this works
#auctions = Auction.closed_auctions.references(highest_bidder: current_user)
But there's probably a better way.
You probably can't access current_user from controller (devise?). So you need to pass the user as a parameter to the class or instance method. What you should look into are scopes and especially scopes that accept parameters. Scopes could really help you refactor your Auction model (you really don't need any methods that only return a where()), but also solve the inaccessible current_user.
Use it like this in your Auction model:
scope: :highest_bidder -> (current_user) { where(highest_bidder: current_user) }
And call it like this from your controller:
#auctions = Auction.closed_auctions.highest_bidder(current_user)
Is it possible to use the roles used for attr_accessible and attr_protected? I'm trying to setup a validation that only executes when not an admin (like this sort of http://launchware.com/articles/whats-new-in-edge-scoped-mass-assignment-in-rails-3-1). For example:
class User < ActiveRecord::Base
def validate(record)
unless # role.admin?
record.errors[:name] << 'Wrong length' if ...
end
end
end
user = User.create({ ... }, role: "admin")
After looking into this and digging through the source code, it appears that the role passed in when creating an Active Record object is exposed through a protected method mass_assignment_role. Thus, the code in question can be re-written as:
class User < ActiveRecord::Base
def validate(record)
unless mass_assignment_role.eql? :admin
record.errors[:name] << 'Wrong length' if ...
end
end
end
user = User.create({ ... }, role: "admin")
Sure can would be something like this:
class User < ActiveRecord::Base
attr_accessible :role
validates :record_validation
def record_validation
unless self.role == "admin"
errors.add(:name, "error message") if ..
end
end
You could do this
class User < ActiveRecord::Base
with_options :if => :is_admin? do |admin|
admin.validates :password, :length => { :minimum => 10 } #sample validations
admin.validates :email, :presence => true #sample validations
end
end
5.4 Grouping conditional validations
I am new to coding - and have not enough reputation to comment this answer:
Rails 3: Uniqueness validation for nested fields_for
So I am creating this question as "Part 2" :)
I am a web designer but curious to learn coding, held with this from my days.
# app/validators/nested_attributes_uniqueness_validator.rb
class NestedAttributesUniquenessValidator < ActiveModel::EachValidator
record.errors[attribute] << "Products names must be unique" unless value.map(&:name).uniq.size == value.size
end
end
above code with "ActiveModel::EachValidator" throw this error:
"undefined method `map' for "Area 1":String"
# app/validators/nested_attributes_uniqueness_validator.rb
class NestedAttributesUniquenessValidator < ActiveModel::Validator
record.errors[attribute] << "Products names must be unique" unless value.map(&:name).uniq.size == value.size
end
end
above code with "ActiveModel::Validator" throw this error:
"Subclasses must implement a validate(record) method. "
this is model file:
class Area < ActiveRecord::Base
validates :name,
:presence => true,
:uniqueness => {:scope => :city_id},
:nested_attributes_uniqueness => {:field => :name}
belongs_to :city
end
You can find complete code over here:
https://github.com/syed-haroon/rose
#Syed: I think you are trying to do this. else reply to my comment.
# app/models/city.rb
class City < ActiveRecord::Base
has_many :areas
validates :areas, :area_name_uniqueness => true
end
# app/models/area.rb
class Area < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name
end
# config/initializers/area_name_uniqueness_validator.rb
class AreaNameUniquenessValidator < ActiveModel::Validator
def validate_each(record, attribute, value)
record.errors[attribute] << "Area names must be unique" unless value.map(&:name).uniq.size == value.size
end
end
I found the answer over here :
https://rails.lighthouseapp.com/projects/8994/tickets/2160-nested_attributes-validates_uniqueness_of-fails
&
validates_uniqueness_of in destroyed nested model rails
This is for rails 2, one line need to me modified over here:
add_to_base has been deprecated and is unavailable in 3.1. Use self.errors.add(:base, message)