I have a User model and a Role model, and wanted to automatically add the :user role to each user whenever roles are assigned. The roles were set in the controller as follows:
if @user.save @user.role_ids = params[:user][:role_ids] ... end
… so I wanted to automatically add the :user role when @user.role_ids was set. Initially I tried adding a ‘before_filter’ to the User model:
class User < ActiveRecord::Base has_and_belongs_to_many :roles before_filter :add_default_roles ... def add_default_roles self[:role_ids] << Role.by_name(:user).id end ... end
Obviously this didn’t work - setting the habtm field in this case happens after the model is saved (as you can see in the 2 lines from the controller above).
Next I tried to over-ride the roles setter in the model :
class User < ActiveRecord::Base has_and_belongs_to_many :roles ... def role_ids=(_role_ids) _role_ids << Role.by_name(:user).id self.role_ids = _role_ids end ... end
The obvious (in hindsight) problem with this is that it is recursive - setting self.role_ids from inside the setter calls itself again, and results in a stack level too deep error. This recursive call can be fixed by changing the line
self.role_ids = _role_ids
self[:role_ids] = _role_ids
This fixes the recursion problem, but still doesn’t add the default role properly, due to some complexities with the way habtm is implemented.
After a lot of digging around I came up with a solution that works using alias_method_chain:
class User < ActiveRecord::Base has_and_belongs_to_many :roles ... # Make sure all users have at least the :user role. def role_ids_with_add_user_role=(_role_ids) _role_ids << Role.by_name(:user).id self[:role_ids] = _role_ids self.role_ids_without_add_user_role = _role_ids end alias_method_chain :role_ids=, :add_user_role ... end
After some testing this seems to do exactly what we need - when @user.role_ids is set, the role_ids_with_add_user_role function is called, and adds the :user role to the list of role ids before saving.