I have an User model which has an array of roles.
From my schema.db:
create_table "users", force: true do |t|
t.string "roles", array: true
My model looks like this:
class User < ActiveRecord::Base
ROLES = %w(superadmin sysadmin secretary)
validate :allowed_roles
after_initialize :initialize_roles, if: :new_record?
private
def allowed_roles
roles.each do |role|
errors.add(:roles, :invalid) unless ROLES.include?(role)
end
end
def initialize_roles
write_attribute(:roles, []) if read_attribute(:roles).blank?
end
Problem is when I try to add another role from console like user.roles << "new_role" then user.save! says true and asking user.roles gives me my wanted output. But when I ask User.find(user_id).roles then I get the previous state without "new_role" in it.
For ex.
user.roles
=> ["superadmin"]
user.roles << "secretary"
=> ["superadmin", "secretary"]
user.save!
=> true
user.roles
=> ["superadmin", "secretary"]
User.find(<user_id>).roles
=> ["superadmin"]
When replacing the whole array, it works as I want:
user.roles
=> ["superadmin"]
user.roles = ["superadmin", "secretary"]
user.save!
=> true
user.roles
=> ["superadmin", "secretary"]
User.find(<user_id>).roles
=> ["superadmin", "secretary"]
I'm using rails 4 and postgresql, roles are for cancancan gem.
Changing other fields like user.name for ex works like expected. I made quite a lot of digging in google, but no help.
Active Record tracks which columns have changed and only saves these to the database. This change tracking works by hooking onto the setter methods - mutating an object inplace isn't detected. For example
user.roles << "superuser"
wouldn't be detected as a change.
There are 2 ways around this. One is never to change any Active Record object attribute in place. In your case this would mean the slight clumsier
user.roles += ["superuser"]
If you can't/won't do this then you must tell Active Record what you have done, for example
user.roles.gsub!(...)
user.roles_will_change!
lets Active Record know that the roles attribute has changed and needs to be updated.
It would be nicer if Active Record dealt better with this - when change tracking came in array columns weren't supported (mysql had the lion's share of the attention at the time)
Yet another approach would be to mark such columns as always needing saving (much like what happens with serialised attributes) but you'd need to monkey patch activerecord for that.