One quick question. I have two fields pick_up and delivery. I want user to enter one of the two fields while submitting form. Means:
validates pick_up or delivery, :presence => :true.
At least one should be present.
How to write this validation?
validate :pickup_or_delivery
def pickup_or_delivery
if [self.pick_up, self.delivery].compact.blank.size == 0
errors[:base] << ("Please select one option")
end
end
This is a simple enough validation that I'd just do a one-liner:
validate { errors.add(:base, 'Please select one option') if pick_up.blank? && delivery.blank? }
Related
I would like to show some extra info in error messages resulting from a failed validation. For example suppose I have a class Book with the following validation
validates :name, presence: true, uniqueness: true
When someone tries to insert a book by the same name the following error message is returned
{"name":["has already been taken"]}
Instead I wanna show
{"name":["Book 'Great Expectaions' has already been taken at id:7"]}
Right now to make this happen I have to remove the uniqueness validation that I mentioned above and do the following
validate do |book|
existing_book = Book.find_by_name(book.name)
if existing_book
book.errors.add(:name, "#{existing_book.name} already exists at id: #{existing_book.id}")
end
end
Is there a way to get custom error messages like above without writing a custom uniqueness validation? I was thinking something along the lines of
validates :name, presence: true, uniqueness: {message: "#{self.name} already exists at id: #{Book.find_by_name(self.name).id}"
But this does not seem to work as self.name returns 'Book'. Is there a way to access the passed parameters in this context?
You'll have to do this as a custom validation. I would do it like so:
validate :name_is_unique
private
def name_is_unique
errors.add(:name, "#{other_book.name} already exists at id: #{other_book.id}") if other_book = Book.find_by_name(name)
end
The issue isn't really that you can't include the current model attributes in your validation, its that there's no 'one-liner' way to include another model. The good news is, that's what the validate method is for.
If it bothers you to have this in your model, just write a custom validator so it can be re-used application-wide.
Anyone know if there is anything built into rails to the effect of validates_signularity_of :string? I can't find any documentation of anything like this, but just wanted to check. I want to validate that a string the user can enter in is always a singular word.
One way would be to leverage the singularize method.
If singularizing a string results in the same string, the string is already singular. Otherwise, it is plural.
A custom validator like the following might work:
class SingularValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
unless value.to_s.singularize == value
object.errors[attribute] << (options[:message] || "is not singular")
end
end
end
Then in your model:
validates :column_name, :singular => true
Credit:
Basic structure of a custom validator extracted from Ryan's Railscast #211
I am developing an app in Rails 3 and upon signup I need the user to enter their email address and I need it to be unique and case sensitive. I.e. no one should be able to sign up with myEmail#yahoo.com when MyEmail#yahoo.com already exists in the database.
This is my code and it crashes the app:
validates :email, :presence => true, :uniqueness => true, :case_sensitive => true,
:format => {:with => /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i}
What is wrong with it?
Please dont use case sensitive there!!!. It will fetch all the users! So if you have 100.000 users. first it will fetch them all with LOWER(email). This can be VERY slow and it wont use your index on email.
Here an article that i found just now about this topic: http://techblog.floorplanner.com/post/20528527222/case-insensitive-validates-uniqueness-of-slowness
My suggesting is: Run a query to make all the emails downcased and make a before validation filter to downcase the email attribute so you dont have any uppercased characters in that column.
User.update_all('email = LOWER(email)')
before filter:
before_validation :downcase_email
private
def downcase_email
self.email = email.downcase if email.present?
end
For the Rails 3 type of validation you need to nest the casse insensitive block like so
validates :email, :uniqueness => { :case_sensitive => false }
I don't have the reputation to comment on the accepted answer, but #medBo asked about how this behaves in Rails 4. For reference, when using Rails 4.2 + MySQL, if I specify
validates :username, uniqueness: { case_sensitive: true }
ActiveRecord performs this query:
SELECT 1 AS one FROM `users` WHERE `users`.`username` = 'TEST_USER' LIMIT 1
In this case the search is not case sensitive. But when I set:
validates :username, uniqueness: { case_sensitive: false }
it performs:
SELECT 1 AS one FROM `users` WHERE `users`.`username` = BINARY 'TEST_USER'
The BINARY operator ensures the search is case sensitive without fetching all users, meaning for my setup at least, the case_sensitive flag doesn't suffer from the performance issue that #Michael Koper notes for earlier versions of Rails. I can't comment on how ActiveRecord performs for other database setups.
I'm not sure if it is possible to do case insensitive validations using that syntax (at least, I haven't found any documentation for it).
You should be able to validate case insensitive uniqueness like this though:
validates_uniqueness_of :email, :case_sensitive => false
Without more detail on the crash you get, I can't help more than that.
You can use a callback in your model like "before_validation" on email attribute to make it lowercased like this:
before_validation { self.email = email.downcase }
This will make the email input lowercased, after that try uniqueness validation without case sensitive:
validates :email, uniqueness: true
For more info about callbacks: here is ruby guide
https://guides.rubyonrails.org/active_record_callbacks.html
I'd like to have a maximum number of associated records on a model.
E.g. a project has_many tasks, but not more then twenty.
How can I enforce this rule?
The only solution that I've been able to come up with so far is an
INSERT INTO...SELECT query like this:
INSERT INTO
tasks (`id`,`project_id`,`title`,`body`)
SELECT
NULL, ?, ?, ?
FROM
tasks
HAVING
count(id) < MAX_NUMBER_OF_TASKS
LIMIT 1;
As far as I can tell, this will guarantee a maximum number of tasks being inserted. Am I correct in this?
Is there a 'Rails way' to do this?
Is it possible to override ActiveRecord/the Task model so that it uses the query above
to insert a new record?
I'm currently using a custom method with ActiveRecord::Base.connection
and calling that instead of .create or .save when new_record? == true.
I haven't been able to try this, but I can't see why it shouldn't work.
Step one: Define a validator on the parent object (this is a simple implementation - could/should be made more generic):
class Project < ActiveRecord::Base
validate :max_tasks
def max_tasks
if tasks.count > 20
errors.add_to_base("Should not have more than 20 tasks")
end
end
end
Step two: Turn on validation of project from tasks:
class Task < ActiveRecord::Base
validates_associated :project
end
And I think you should be in business. When you try and save a new task, it'll validate the associated project, and the validation will fail if there are (now) more than 20 tasks associated.
Just in case you fancy making this more generic, you could do something like:
class NumberOfAssociatedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if options[:maximum] && record.send(attribute).count > options[:maximum]
record.errors[attribute] << "must not have more than #{options[:maximum]}"
end
if options[:minimum] && record.send(attribute).count < options[:minimum]
record.errors[attribute] << "must not have less than #{options[:minimum]}"
end
end
end
class MyModel < ActiveRecord::Base
validates :my_association, :number_of_associated => {:maxiumum => 20}
end
May be you can add some pre save validation to your model, that checks how many associated models it already have, and throw a validation error if it exceeds your max number of associations.
I am using Rails 3 with Mongoid.
I have two documents:
class MyUser
include Mongoid::Document
field ......
references_many :statuses, :class_name => "MyStatus"
end
class MyStatus
include Mongoid::Document
field ......
referenced_in :user, :class_name => "MyUser"
end
The problem is, I can get the user of any given status, but I cannot get the list of statuses from a user!
ie.
status = MyStatus.first
status.user # the output is correct here
user = MyUser.first
user.statuses # this one outputs [] instead of the list of statuses...
Please tell me what have I done wrong? I am just a few days with mongo......
Your code looks correct to me.
Are you sure that MyStatus.first.user == MyUser.first ?
It's possible that you have multiple users in your db.. where the first user has no statuses, and the second user has status1 in his list.
To test this, try doing:
status = MyStatus.first
user = status.user
user.statuses # Should return at least one status