I have a standard many-to-many relationship between users and roles in my Rails app:
class User < ActiveRecord::Base
has_many :user_roles
has_many :ro
You can use Array's |= join method to add an element to the Array, unless it is already present. Just make sure you wrap the element in an Array.
role #=> #<Role id: 1, name: "1">
user.roles #=> []
user.roles |= [role] #=> [#<Role id: 1, name: "1">]
user.roles |= [role] #=> [#<Role id: 1, name: "1">]
Can also be used for adding multiple elements that may or may not already be present:
role1 #=> #<Role id: 1, name: "1">
role2 #=> #<Role id: 2, name: "2">
user.roles #=> [#<Role id: 1, name: "1">]
user.roles |= [role1, role2] #=> [#<Role id: 1, name: "1">, #<Role id: 2, name: "2">]
user.roles |= [role1, role2] #=> [#<Role id: 1, name: "1">, #<Role id: 2, name: "2">]
Found this technique on this StackOverflow answer.
I ran into this today and ended up using #replace, which "will perform a diff and delete/add only records that have changed".
Therefore, you need to pass the union of the existing roles (so they don't get deleted) and your new role(s):
new_roles = [role]
user.roles.replace(user.roles | new_roles)
It's important to note that both this answer and the accepted one are loading the associated roles
objects into memory in order to perform the Array diff (-
) and union (|
). This could lead to performance issues if you're dealing with a large number of associated records.
If that's a concern, you may want to look into options that check for existence via queries first, or use an INSERT ON DUPLICATE KEY UPDATE
(mysql) type query for inserting.