У меня есть стандартное отношение "многие ко многим" между пользователями и ролями в приложении 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 сделает это, но в основном просто "select different".
Есть ли способ сделать это декларативно? Если нет, возможно, используя расширение ассоциации?
Вот пример того, как поведение по умолчанию выходит из строя:
>> u = User.create User Create (0.6ms) INSERT INTO "users" ("name") VALUES(NULL) => #<User id: 3, name: nil> >> u.roles << Role.first 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">] >> u.roles << Role.first Role Load (0.4ms) SELECT * FROM "roles" LIMIT 1 UserRole Create (0.5ms) INSERT INTO "user_roles" ("role_id", "user_id") VALUES(1, 3) => [#<Role id: 1, name: "1">, #<Role id: 1, name: "1">]