I have this following validation in Playlist model with attributes title and is_deleted. The point of the 'is_deleted' column(bool type column with default value 'false') is cause I have to archive
the playlists not delete them.
validates :title, presence: true, uniqueness: true, unless: :is_deleted?
Now if I have a record for example with title 'Playlist 1' and is_deleted false and try to create a record with is_deleted: true it won't validate him, which is good. If I try to update the is_deleted column from the first record to true it won't validate, which is good as well.
But here is the thing now I have two playlists with title: "Playlist 1" and is_deleted: true. If I try to update any of them to is_deleted: false, the validation won't let me. It gives a "Title has already been taken" error. I really don't understand why, cause I don't have any other record that is is_deleted: false with the title: "Playlist 1".
This is because your validation is checking for the uniqueness of :title only. When it validates your model after changing is_deleted to false it is checking that the title is unique. It'll do a query something like:
Playlist.where(title: 'Playlist 1').where.not(id: self.id).exists?
and obviously there is a playlist with that title already.
The key thing is that it doesn't check the is_deleted flag on other playlists. It is only using it on the current playlist to decide whether it should validate or not.
You probably need to us validates_uniqueness_of rather than just validates which means you can add conditions to the validation:
validates_uniqueness_of :title,
unless: :is_deleted?,
conditions: -> { where(is_deleted: false) }
This will ensure that your uniqueness check only checks against other playlists that are also not deleted.
You need to keep your validates :title, presence: true, unless: :is_deleted? to validate that the title is present (or you may have some success with the allow_nil or allow_blank options on validates_uniqueness_of).
This unless runs in ruby-land. And it does what it says on the tin: "validate uniqueness of title if post is not deleted". Note that it doesn't say "validate uniqueness of title among only non-deleted records".
You should be using database tools to enforce this kind of restrictions. Postgres has partial unique indexes that can do this.
Related
class Person < ApplicationRecord
validates :name, uniqueness: { case_sensitive: false }
end
When a model has above definition, what exactly is happening behind the scenes?
My guess is there exists some validates method and a parameter is passed with symbol name. What is second parameter? a hash with a value that is hash?
First validation :name lets know that Person is not valid without a name attribute.
Second validation uniqueness
This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index on that column in your database.
Third { case_sensitive: false }
There is also a case_sensitive option that you can use to define whether the uniqueness constraint will be case sensitive or not. This option defaults to true
Finally validates :name, uniqueness: { case_sensitive: false }
It means in Person model name attribute must be present with uniqueness not be case sensitive.
Simple-Form automatically detects whether there is a validates :xxx, presence: true validation, and displays the field as a required one (e.g. with an asterisk appended to the label).
validates :parent, presence: true
This results in:
Interestingly, as soon as I add a condition to this validation...
validates :parent, presence: true, if: -> { true }
...it doesn't do this no more:
Is this a bug or a feature?
This is the expected behavior, validations are only run when you save the object to the db, so you have no way of knowing whether the lambda returns true or not until then. Of course in your case it always returns true, but imagine you have a time constraint in your lambda or some other more complex condition, e.g.
...., -> { Time.zone.now > Date.new(2017, 1, 1) }
Maybe when you create the object for the form this returns false, but when the form is actually submitted and saved to the db enough time had passed for it to return true.
So there is no way for simple_form to know when the form is created whether the field is required or not.
You could call it a feature, since it's deliberate. If you look at the gem code (especially at calculate_required and conditional_validators?) you'll notice that if the presence validator has a condition, like if or unless, the field is no longer marked as required.
I solved like this for :terms being a checkbox element:
validates :terms, presence: true, acceptance: true
With this, it validates at form level for checking that 'term' checkbox is submitted only after it's checked. Hope it helps someone in the future.
Simple form validations don't work if you have conditions in your validations.
This is intentional as you can put whatever you want in an :if or :unless and calling this could cause unintended side effects.
source: https://github.com/heartcombo/simple_form/issues/322
I have a Ticket and Event model in which the relationship is that an Event has many Tickets. The Ticket model has columns serial starting value and serial ending value which denotes the range of the valid serial nubers for that event.
I want to validate the Ticket on creation so that if a Ticket is created with a serial number beyond that range, the system will spew out errors saying that the range in invalid.
What I currently have in my ticket model validation is another validation to show a valid serial number for all events which is between 140000 and 149999
validates :serial_number,
presence: true,
numericality: {
only_integer: true,
:greater_than_or_equal_to => 140000,
:less_than => 149999,
:message => "is invalid" },
:uniqueness => { :message => "%{value} has already been taken" }
I need to get data from the Event model and place it into the Ticket validation. Does Rails allow something like this in the model? Should I do it in the controller?
Definitely do this in your model. Use custom validations for it:
validate :serial_number_valid
def serial_number_valid
unless event.serial_number_range.include?(serial_number)
errors.add(:serial_number, "must be within range")
end
end
One way to do this is by using rails validation helper of inclusion. This helper validates that the attributes' values are included in a given set. For this particular case you can use:
validates :serial_number, inclusion: { in: 140000..149999 }, presence: true, uniqueness: true
If you want to use the same method in rails 4 way here's a link to that.
In my Rails 4 app I have an email field. My model ensures that no duplicate values are stored using the following:
validates :email, presence: true, uniqueness: { case_sensitive: false }
This has served me well while I wasn't bothered about confirming email addresses but now I want to confirm them before they are able to login. This means that I only want to confirm uniqueness if another field confirmed is true.
Is there an in-built way to tackle this at all or will it be my own validation rule that's required?
You can pass options such as a record set to a uniqueness validator like so:
validates_uniqueness_of :email, conditions: -> { where(confirmed: true) }
Then it will only enforce uniqueness against confirmed records.
I believe this is what you want:
http://edgeguides.rubyonrails.org/active_record_validations.html#using-a-symbol-with-if-and-unless
I'm learning rails with the book Agile Web development with Rails 4e. It uses the following so far as our product model (adapted from a scaffold):
class Product < ActiveRecord::Base
attr_accessible :description, :image_url, :price, :title
validates :description, :title, :image_url, presence: true
validates :price, numericality: {greater_than_or_equal_to: 0.01}
validates :title, uniqueness: true
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png)$}i,
message: 'Must be a valid URL for a gif, png, or jpg..'
}
end
I'm wondering why it tests first for the presence of :image_url, but then in the tertiary validation to make sure the image url is valid, it allows for blank responses which contradicts the first validation. I don't understand why this is supposed to work as is.
As an additional question, if the image_url is empty, how can I test if it is empty in my code? (e.g. in the product view to display a default image.)
Model validations are tested in isolation. A model is valid if and only if it passes validation for each validates statement independently.
It's probably bad-form, and evidently confusing for that allow_blank: true to be in the 4th validation, but that only applies to that single statement. The model must pass all validations to be considered valid, so the 1st statement merely imposes a tighter restriction than the 4th.
A final point, note that presence tests for non-nilness, whereas blank is defined as nil or the empty string. It is therefore possible to be both present and blank; e.g. image_url = ''. However, it remains the case that validations are tested separately in isolation.
I think maybe you are confused about the validation code? I'm a noob, and this is probably not entirely accurate: the validates keyword doesn't test for presence, it starts a block that you use to specify your validations.
As is, your code will validate the :image_url according to your specifications if it exists. If you took out allow_blank: true, then a nonexistent or blank :image_url would fail the validations.
I'm new to Rails as well and am using the same book. My understanding is that in order to stop the validation returning two errors immediately against validation (i.e. one if the field is blank and one if it doesn't have a correct file extension) it must allow_blank for the file format.
The best way I can explain it is to suggest removing the allow_blank: true code and trying to submit the description form again.
You should then see that you get both the validation errors for the field being blank and the file format being wrong.
The allow_blank therefore tells the validation to only error on the file format once the field is no longer blank.
Confused me as well which is why I ended up here!