我在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?
这很方便,对于我来说它做了所有的工作。这样,我就不必记得检查重复项或使用自定义方法。框架里有我错过的东西吗?一开始我想到的是 has_many 的 :uniq 选项可以实现,但它基本上只是“select distinct”。
有没有一种声明性的方法可以做到这一点?如果没有,也许可以使用关联扩展?
这里是默认行为失败的示例:
>> 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">]
def <<(*items) super(items - proxy_target) end
- KingPongs/proxy_owner/proxy_association.owner/
。相关问题 - Turadg<<
只接受单个对象的情况下,要使用参数*items
?http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-3C-3C - Turadghas_many
而不是has_many :through
看起来很奇怪。然而,修复这个问题的 Rails 问题(https://github.com/rails/rails/issues/8573)被拒绝了,理由是“这是你的领域逻辑,所以检查它是你的责任。” - Turadg<<
可以接受多个参数 http://guides.rubyonrails.org/association_basics.html#methods-added-by-has-many-collection-object - lulalala