Ruby on rails Rails避免在has_many:through中重复的习惯用法
我在Rails应用程序中的用户和角色之间有一个标准的多对多关系:Ruby on rails Rails避免在has_many:through中重复的习惯用法,ruby-on-rails,activerecord,associations,idioms,Ruby On Rails,Activerecord,Associations,Idioms,我在Rails应用程序中的用户和角色之间有一个标准的多对多关系: class User < ActiveRecord::Base has_many :user_roles has_many :roles, :through => :user_roles end 它只是为我做的工作。这样,我就不必记得检查DUP或使用自定义方法。框架中是否有我遗漏的东西?我首先想到:uniq选项has_many可以做到这一点,但它基本上只是“selectdistinct” 有没有一种方法可以以
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, :through => :user_roles
end
它只是为我做的工作。这样,我就不必记得检查DUP或使用自定义方法。框架中是否有我遗漏的东西?我首先想到:uniq选项has_many可以做到这一点,但它基本上只是“selectdistinct”
有没有一种方法可以以声明的方式实现这一点?如果不是,可能使用关联扩展
以下是默认行为如何失败的示例:
>> 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">]
>>u=User.create
用户创建(0.6ms)插入“用户”(“名称”)值(空)
=> #
>>u.roles[#]
>>u.roles[#,#]也许可以创建验证规则
validates_uniqueness_of :user_roles
然后捕获验证异常并优雅地继续。但是,如果可能的话,这会让人感觉非常粗糙,非常不雅观。只要附加的角色是一个ActiveRecord对象,您所做的是:
user.roles << role
如果super不起作用,您可能需要设置别名\u方法\u链。我认为正确的验证规则在您的用户角色加入模型中:
validates_uniqueness_of :user_id, :scope => [:role_id]
您可以结合使用validates\u university\u of和overriding[:role\u id] 类用户 有多个:角色,:到=>:用户角色
def我认为您希望执行以下操作:
user.roles.find_or_create_by(role_id: role.id) # saves association to database
user.roles.find_or_initialize_by(role_id: role.id) # builds association to be saved later
我今天遇到了这个问题,最后使用了,它“将执行一个diff并仅删除/添加已更改的记录” 因此,您需要传递现有角色(因此它们不会被删除)和新角色的联合: 需要注意的是,这个答案和接受的答案都是将相关的
角色
对象加载到内存中,以便执行数组差异(-
)和并集(|
)。如果处理大量相关记录,这可能会导致性能问题
如果这是一个问题,您可能希望首先查看通过查询检查存在性的选项,或者在重复密钥更新时使用INSERT
(mysql)类型的查询进行插入。
使用数组的联接方法。
可以使用数组的联接方法将元素添加到数组中,除非该元素已经存在。只需确保将元素包装在数组中
role#=>#
user.roles#=>[]
user.roles |=[role]#=>[#]
user.roles |=[role]#=>[#]
也可用于添加可能存在或可能不存在的多个元素:
role1#=>#
角色2#=>#
user.roles#=>
user.roles |=[role1,role2]#=>
user.roles |=[role1,role2]#=>
在上找到此技术。这将在数据库中仅创建一个关联,即使多次调用也是如此
谢谢但这实际上并没有达到我想要的效果(这是一种类似集合的行为),我已经在最初的帖子中澄清了这一点。对不起,我想这是解决你问题的最好办法。如果你在创建界面时很小心,用户将不得不破解它以添加错误的角色,在这种情况下,验证异常是完全合适的响应。嘿,你疯了吗?用户不添加自己的角色:-)典型的用例是用户成为角色的一员,作为其他角色的副作用。例如,购买特定产品。其他产品也可能提供相同的角色,因此有机会在那里复制。我宁愿在一个地方进行重复检查,也不愿在任何随机的地方进行重复检查,以确保用户有一个角色。从这个意义上说,给用户一个他已经拥有的角色不是一个错误条件,它不是这样工作的。我将更新帖子以包含测试。对于后代,上述方法可以缩短并通用为:Rails 3.1的def,
s/proxy\u owner/proxy\u association.owner/
为什么在时使用参数*items
,我很奇怪,你不能将异常更改为ActiveRecord::RecordNotUnique
?我喜欢这个答案。不过要注意,回答得不错。我使用它时没有验证
的唯一性,在数据库中声明了唯一的索引,并且工作得非常出色。
class User
has_many :roles, :through => :user_roles do
def <<(new_item)
super( Array(new_item) - proxy_association.owner.roles )
end
end
end
validates_uniqueness_of :user_id, :scope => [:role_id]
validates_uniqueness_of :user_id, :scope => [:role_id]
class User
has_many :roles, :through => :user_roles do
def <<(*items)
super(items) rescue ActiveRecord::RecordInvalid
end
end
end
user.roles.find_or_create_by(role_id: role.id) # saves association to database
user.roles.find_or_initialize_by(role_id: role.id) # builds association to be saved later
new_roles = [role]
user.roles.replace(user.roles | new_roles)
user.roles=[Role.first]