避免在 has_many 中重复的 Rails 习惯用法:through [英] Rails idiom to avoid duplicates in has_many :through

查看:31
本文介绍了避免在 has_many 中重复的 Rails 习惯用法:through的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的 Rails 应用中的用户和角色之间存在标准的多对多关系:

class User :user_roles结尾

我想确保一个用户只能被分配一次任何角色.任何插入重复项的尝试都应忽略该请求,而不是抛出错误或导致验证失败.我真正想代表的是一个集合",插入一个已经存在于集合中的元素是没有效果的.{1,2,3} U {1} = {1,2,3},而不是 {1,1,2,3}.

我意识到我可以这样做:

user.roles <<角色除非 user.roles.include?(role)

或者通过创建一个包装方法(例如 add_to_roles(role)),但我希望有一些惯用的方式通过关联使其自动化,这样我就可以写:

user.roles <<role # 自动检查roles.include?

它只是为我工作.这样,我就不必记得检查重复项或使用自定义方法.框架中是否有我遗漏的东西?我最初认为 has_many 的 :uniq 选项可以做到这一点,但它基本上只是选择不同".

有没有办法以声明方式做到这一点?如果没有,也许通过使用关联扩展?

以下是默认行为如何失败的示例:

<预> >>u = User.create用户创建 (0.6ms) INSERT INTO "users" ("name") VALUES(NULL)=>#<用户ID:3,姓名:nil>>>u.roles<<角色优先角色负载 (0.5ms) SELECT * FROM "roles" LIMIT 1UserRole 创建 (0.5ms) INSERT INTO "user_roles" ("role_id", "user_id") VALUES(1, 3)角色负载 (0.4ms) SELECT "roles".* FROM "roles" INNER JOIN "user_roles" ON "roles".id = "user_roles".role_id WHERE (("user_roles".user_id = 3))=>[#<角色 ID:1,名称:1">]>>u.roles<<角色优先角色负载 (0.4ms) SELECT * FROM "roles" LIMIT 1UserRole 创建 (0.5ms) INSERT INTO "user_roles" ("role_id", "user_id") VALUES(1, 3)=>[#, #]

解决方案

只要被追加的角色是一个ActiveRecord对象,你在做什么:

user.roles <<角色

应该为 :has_many 关联自动去重.

对于has_many :through,尝试:

类用户has_many :roles, :through =>:user_roles 做def <<(new_item)超级(数组(新项目)-proxy_association.owner.roles)结尾结尾结尾

如果 super 不起作用,您可能需要设置一个 alias_method_chain.

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 :roles, :through => :user_roles
end

I want to make sure that a user can only be assigned any role once. Any attempt to insert a duplicate should ignore the request, not throw an error or cause validation failure. What I really want to represent is a "set", where inserting an element that already exists in the set has no effect. {1,2,3} U {1} = {1,2,3}, not {1,1,2,3}.

I realize that I can do it like this:

user.roles << role unless user.roles.include?(role)

or by creating a wrapper method (e.g. add_to_roles(role)), but I was hoping for some idiomatic way to make it automatic via the association, so that I can write:

user.roles << role  # automatically checks roles.include?

and it just does the work for me. This way, I don't have to remember to check for dups or to use the custom method. Is there something in the framework I'm missing? I first thought the :uniq option to has_many would do it, but it's basically just "select distinct."

Is there a way to do this declaratively? If not, maybe by using an association extension?

Here's an example of how the default behavior fails:

    >> 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">]

解决方案

As long as the appended role is an ActiveRecord object, what you are doing:

user.roles << role

Should de-duplicate automatically for :has_many associations.

For has_many :through, try:

class User
  has_many :roles, :through => :user_roles do
    def <<(new_item)
      super( Array(new_item) - proxy_association.owner.roles )
    end
  end
end

if super doesn't work, you may need to set up an alias_method_chain.

这篇关于避免在 has_many 中重复的 Rails 习惯用法:through的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆