Reputation: 1650
So I have a Model on my Rails app that has a name field in it. In the database this field has a unique constraint set against it.
In the model level I am also doing a uniqueness: { case_sensitive: false }, on: :create
check.
My idea is that, after the ActiveRecord validations are done and if the uniqueness check failed then I want to increment a counter for the all ready existing record in the same table. So what I do right now is this:
after_validation :term_exists
private
def term_exists
if self.errors.added? :name, :taken
Term.where('LOWER(name) = LOWER(?)', self[:name]).update_all('popularity_counter = popularity_counter + 1')
end
end
This would be great but when I run my tests against it, the counter never gets incremented. I imagine that the reason for this is that everything in that transaction will be rolled back once it turns out the validations failed.
But what would be the rails way to solve this kind of problem then? It is strongly advised not to keep such logic in controllers.
Upvotes: 0
Views: 818
Reputation:
I would say that you need to create your custom before_validation method where you can check whether the name is unique and if not then update your counter and then fail validation.
In this case you can remove Rails uniqueness: { case_sensitive: false }, on: :create
because it clearly does not do what you want.
I think it would be the cleanest way that comes to my mind.
Upvotes: 1
Reputation: 15934
If you use MySQL, you could instead try INSERT ... ON DUPLICATE KEY UPDATE ...
(see the docs). For this to work you would have to add a unique index for the name
column though. You would not need any uniqueness validation in this scenario.
There is also a promising gem called upsert
(which is a commonly used acronym for "update instead of insert") but it does not yet support updating a different column (e.g. a counter) instead of the inserted values. You can watch this issue though.
Upvotes: 1