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)
Related
This is my model class:
class Availability < ActiveRecord::Base
attr_accessible :beginning_date, :end_date
validates :beginning_date, :end_date :presence => true
# custom validators
validate :dates_cant_be_in_the_past
def dates_cant_be_in_the_past
if Date.parse(beginning_date) < Date.today
errors.add(:beginning_date, "cant be in the past")
end
if Date.parse(end_date) < Date.today
errors.add(:end_date, "cant be in the past")
end
end
end
Now two things should happen: At first validate the presence of the beginning_date and end_date attributes and than run my dates_cant_be_in_the_pastvalidator.
Sadly this approach doesn't work. If I leave a field empty the Date.parsemethod throws an exception, because the argument is obviously empty.
Is it possible to define the order of default and custom validations? Or do I have to implement the presence validator myself, so I would do something like:
validate :dates_cant_be_blank, :dates_cant_be_in_the_past
The guide at least says:
You can pass more than one symbol for each class method and the respective validations will be run in the same order as they were registered.
Thank in advance
It's much simpler if you create a validator for that:
class DateValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if Date.parse(value) < Date.today
record.errors.add(attribute, "cant be in the past")
end
end
end
And at your model you would use it like this:
class Availability < ActiveRecord::Base
attr_accessible :beginning_date, :end_date
validates :beginning_date, :end_date :presence => true
validates :beginning_date, :end_date, :date => true, :allow_blank => true
end
The :allow_blank piece if the one prevents the validation from running if the value is empty. Using a real validator object also removes the code form your model making it much simpler and removing the duplication you currently have.
You could try something like this
class Availability < ActiveRecord::Base
attr_accessible :beginning_date, :end_date
validates :beginning_date, :end_date : presence => true
# custom validators
validate :valid_dates
def valid_dates
if valid_string(beginning_date)
errors.add(:beginning_date, "Can't be in the past") unless Date.parse(beginning_date) > Date.today
end
if valid_string(end_date)
errors.add(:end_date, "Can't be in the past") unless Date.parse(end_date) > Date.today
end
end
def valid_string(test_value)
test.value.is_a? String
end
end
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
A model Country has a attribute code which is automatically converted to lowercase by a before_save callback. Is it possible to force this behaviour on "magic" methods without rewriting large chunks of ActiveRecord::Base?
class Country < ActiveRecord::Base
attr_accessible :code
validates :code, :presence => true
validates_uniqueness_of :code, :case_sensitive => false
before_save do |country|
country.code.downcase! unless country.code.nil?
end
end
RSpec
describe Country do
describe 'data normalization'
before :each do
#country = FactoryGirl.create(:country, :code => 'DE')
end
# passes
it 'should normalize the code to lowercase on insert' do
#country.code.should eq 'de'
end
# fails
it 'should be agnostic to uppercase finds' do
country = Country.find_by_code('DE')
country.should_not be_nil
end
# fails
it 'should be agnostic to uppercase finds_or_creates' do
country = Country.find_or_create_by_code('DE')
country.id.should_not be_nil # ActiveRecord Bug?
end
end
This is what I came up with, altough I really hate that approach (as mentioned in the question). An easy alternative would be to set the column, table or whole database up to ignore case (but this is db dependendt).
class Country < ActiveRecord::Base
attr_accessible :code
validates :code, :presence => true
validates_uniqueness_of :code, :case_sensitive => false
before_save do |country|
country.code.downcase! unless country.code.nil?
end
class ActiveRecord::Base
def self.method_missing_with_code_finders(method_id, *arguments, &block)
if match = (ActiveRecord::DynamicFinderMatch.match(method_id) || ActiveRecord::DynamicScopeMatch.match(method_id))
attribute_names = match.attribute_names
if code_index = attribute_names.find_index('code')
arguments[code_index].downcase!
end
end
method_missing_without_code_finders(method_id, *arguments, &block)
end
class << self
alias_method_chain(:method_missing, :code_finders)
end
end
end
Has "def validate" been taken out in Rails 3.1? I'm on Rails 3.1 pre and it doesn't seem to be working
class Category < ActiveRecord::Base
validates_presence_of :title
private
def validate
errors.add(:description, "is too short") if (description.size < 200)
end
end
The "title" validation works but the "description" validation doesn't.
Does something like this work for you?
class Category < ActiveRecord::Base
validates_presence_of :title
validate :description_length
def description_length
errors.add(:description, "is too short") if (description.size < 200)
end
end
class Category < ActiveRecord::Base
validates_presence_of :title
private
validate do
errors.add(:description, "is too short") if (description.size < 200)
end
end
For other types of validations, you can also add 'Validators' like the one listed here:
http://edgeguides.rubyonrails.org/3_0_release_notes.html#validations
class TitleValidator < ActiveModel::EachValidator
Titles = ['Mr.', 'Mrs.', 'Dr.']
def validate_each(record, attribute, value)
unless Titles.include?(value)
record.errors[attribute] << 'must be a valid title'
end
end
end
class Person
include ActiveModel::Validations
attr_accessor :title
validates :title, :presence => true, :title => true
end
# Or for Active Record
class Person < ActiveRecord::Base
validates :title, :presence => true, :title => true
end
I'm having this model:
class Vote
include Mongoid::Document
include Mongoid::Timestamps
field :vote, :type=>Integer
embedded_in :voteable, :inverse_of => :votes
referenced_in :user
attr_accessible :vote, :user, :voteable
validates :vote,:inclusion => [-1, 1]
validates :user ,:presence=> true,:uniqueness=>true
end
The problem is that the validation for user uniqueness per vote is not working, and the same user can create several votes, which is not what I want. Any ideas how to solve that?
Looks like this is a known problem.
http://groups.google.com/group/mongoid/browse_thread/thread/e319b50d87327292/14ab7fe39337418a?lnk=gst&q=validates#14ab7fe39337418a
https://github.com/mongoid/mongoid/issuesearch?state=open&q=validates#issue/373
It is possible to write a custom validation to enforce uniqueness. Here is a quick test:
class UserUniquenessValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << "value #{value} is not unique" unless is_unique_within_votes(record, attribute, value)
end
def is_unique_within_votes(vote, attribute, value)
vote.voteable.votes.each do |sibling|
return false if sibling != vote && vote.user == sibling.user
end
true
end
end
class Vote
...
validates :user ,:presence => true, :user_uniqueness => true
end