Reputation: 1108
A table has following fields: badge_id, output_id, timely, removed, updated_at. For each badge_id, there can't have two valid records with the same output_id. But it doesn't mean that (badge_id, output_id) is a unique combination. Removed column indicates the current row has been removed or not. Basically delete or update operation triggers inserting a new row in the table with the latest change. So for example, we have a record like this:
badge_id| output_id| removed| timely | updated_at
1 | 1 | N | Y | 2013-11-26
To remove that record, we actually insert another row and now it reads like
badge_id| output_id| removed| timely | updated_at
1 | 1 | N | Y | 2013-11-26
1 | 1 | Y | Y | 2013-11-27
Because the latest record of (badge_id: 1, output_id: 1) has removed column set, it means that combination has been deleted. But I can't have two rows of same (badge_id: 1, output_id: 1), both have removed as "N" like:
badge_id| output_id| removed| timely | updated_at
1 | 1 | N | N | 2013-11-26
1 | 1 | N | Y | 2013-11-27
So every time to add a new output_id for a certain badge_id, I have to check for duplication. But usual validates uniqueness of (badge_id, output_id) from ActiveModel doesn't work here. How do I write a clean custom validation for this? Thanks.
UPDATE:
I think I might have missed some key points. A record can be added and then deleted and then added repeatedly. So a combination of (badge_id, output_id, removed) isn't unique either. When add a new record, we need to check for (badge_id, output_id), whether latest record has removed set as 'Y' or not.
So for possible answer like
validate_uniqueness_of :badge_id, scope: [:output_id],
conditions: -> { where(removed: "N") }
At the condition where clause, it should have order by updated_at desc and the first one has removed: 'N'. How do I fit that kind of condition into this one line code? Or there's a better way of doing this?
Upvotes: 0
Views: 871
Reputation: 13541
Hopefully I understand your use case properly.
Try validating the uniqueness of the removed
and scoping it to both the badge_id
and output_id
columns but only when the removed field is N:
class Model < ActiveRecord::Base
validates_uniqueness_of :removed,
scope: [:badge_id, :output_id],
conditions: -> { where.not(removed: 'Y') }
end
There's a chance it might just work.
Upvotes: 0
Reputation: 13354
You could do something like:
validates :unique_badge_and_output_ids
Then unique_badge_and_output_ids
could be:
def unique_badge_and_output_ids
unless Object.find_by_badge_id_and_output_id_and_removed(self.badge_id, self.output_id, self.removed).blank?
self.errors.add "record already exists" # obviously a better error here would be ideal
end
end
Upvotes: 2
Reputation: 1793
You can specify an SQL condition on validates_uniqueness_of:
It is also possible to limit the uniqueness constraint to a set of records matching certain conditions. In this example archived articles are not being taken into consideration when validating uniqueness of the title attribute:
class Article < ActiveRecord::Base validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') } end
So in your example:
class YourModel < AR::Base
validate_uniqueness_of :badge_id, scope: [:output_id],
conditions: -> { where(removed: "N") }
end
Upvotes: 0