Reputation: 1319
I have a model that has fields of name:string
, default:boolean
. I want the true
value to be unique so that only one item in the database can be set to true at once. How do I set my update and new actions in my controller to set all the rest of the values of items to false
?
Let's say I have the following setup in my database
name:string | default:boolean |
Item1 | true |
Item2 | false |
Item3 | false |
If I change Item2
default value to true, I want it to loop through all items and set the rest of them to false
, so only one is true
at once, so it looks like this.
name:string | default:boolean |
Item1 | false |
Item2 | true |
Item3 | false |
Upvotes: 18
Views: 7955
Reputation: 753
If you're coming here in a more recent time and are using Rails 6, this should be covered on the database level as well as the model level:
db level:
add_index :items, :default, unique: true, where: '(default IS TRUE)'
model level:
class Item < ApplicationRecord
scope :default, -> { where(default: true) }
validates :default, uniqueness: { conditions: -> { default } }
end
Upvotes: 2
Reputation: 15268
More ActiveRecord, less raw SQL decision
after_commit :reset_default, if: :default?
private
def reset_default
self.class.where.not(id: id).where(default: true).update_all(default: false)
end
Upvotes: 1
Reputation: 2883
class Model < ApplicationRecord
before_save :ensure_single_default, if: :is_default?
private
def ensure_single_default
self.class.update_all(is_default: false)
end
end
You don't need to check the id because this callback happens before the truthy one is saved.
Upvotes: 1
Reputation: 582
if you want this to work for creating and updating (rails v4) make note of this tidbit from rails guides
after_save runs both on create and update, but always after the more specific callbacks after_create and after_update, no matter the order in which the macro calls were executed.
Upvotes: 1
Reputation: 7166
I think it's good to check if the one you save is true before you falsify others. Otherwise you falsify everyone when you save a record that isn't active.
def falsify_all_others
if self.default
self.class.where('id != ? and default', self.id).update_all("default = 'false'")
end
end
Upvotes: 5
Reputation: 161
Okay there is a few more things you will need.
Don't use the field name default, its usually reserved for the database. Saving a record with a default as false will set all records to false, this isnt what you want. check to see if we are setting this record to true and the falseify.
before_save :falsify_all_others
def falsify_all_others
if is_default
self.class.where('id != ?', self.id).where('is_default').update_all(:is_default => false)
end
end
Upvotes: 3
Reputation: 433
I also recommend falsifying all your records then making them true.
add_column :users, :name ,:boolean, default: false
Upvotes: 3
Reputation: 9851
This code is stolen from previous answer and slightly simplified:
def falsify_all_others
Item.where('id != ?', self.id).update_all("default = 'false'")
end
You can use this method in before_save callback in your model.
Actually, it is better to "falsify" only records which values are 'true', like this:
Item.where('id != ? and default', self.id).update_all("default = 'false'")
UPDATE: to keep code DRY, use self.class
instead of Item
:
self.class.where('id != ? and default', self.id).update_all("default = 'false'")
Upvotes: 22
Reputation: 2269
In your controller code you could do something like this.... please note you're probably taking Item2 as a param[...] so you can interchange that below
@items_to_be_falsified = Item.where('id != ?', Item2.id)
@items_to_be_falsified.each do |item|
item.default = false
item.save
end
Please note when you get this working, its good practice to move this into the model, make it into a function and call it like Item2.falsify_all_others
like below
def falsify_all_others
Item.where('id != ?', self.id).each do |item|
item.default = false
item.save
end
end
Enjoy!
Upvotes: 3