У меня стандартное отношение «многие ко многим» между пользователями и ролями в моем приложении Rails:
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, :through => :user_roles
end
Я хочу убедиться, что пользователю может быть назначена любая роль только один раз. Любая попытка вставить дубликат должна игнорировать запрос, не выдавать ошибку или вызывать ошибку проверки. Что я действительно хочу представить, так это «набор», в котором вставка элемента, уже существующего в наборе, не имеет никакого эффекта. {1,2,3} U {1} = {1,2,3}, а не {1,1,2,3}.
Я понимаю, что могу сделать это так:
user.roles << role unless user.roles.include?(role)
или путем создания метода-оболочки (например, add_to_roles(role)
), но я надеялся на какой-то идиоматический способ сделать его автоматическим с помощью ассоциации, чтобы я мог написать:
user.roles << role # automatically checks roles.include?
и это просто работает для меня. Таким образом, мне не нужно проверять наличие дуплекса или использовать собственный метод. Есть ли что-то в рамках, что я пропускаю? Сначала я подумал, что это сделает опция: uniq для has_many, но в основном это просто «выбрать отличное».
Есть ли способ сделать это декларативно? Если нет, может быть, с помощью расширения ассоциации?
Вот пример того, как поведение по умолчанию терпит неудачу:
>> <strong>u = User.create</strong>
User Create (0.6ms) INSERT INTO "users" ("name") VALUES(NULL)
=> #<User id: 3, name: nil>
>> <strong>u.roles << Role.first</strong>
Role Load (0.5ms) SELECT * FROM "roles" LIMIT 1
UserRole Create (0.5ms) INSERT INTO "user_roles" ("role_id", "user_id") VALUES(1, 3)
Role Load (0.4ms) SELECT "roles".* FROM "roles" INNER JOIN "user_roles" ON "roles".id = "user_roles".role_id WHERE (("user_roles".user_id = 3))
=> [#<Role id: 1, name: "1">]
>> <strong>u.roles << Role.first</strong>
Role Load (0.4ms) SELECT * FROM "roles" LIMIT 1
UserRole Create (0.5ms) INSERT INTO "user_roles" ("role_id", "user_id") VALUES(1, 3)
<strong><em>=> [#<Role id: 1, name: "1">, #<Role id: 1, name: "1">]</em></strong>