Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/ruby-on-rails/54.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ruby on rails Rails避免在has_many:through中重复的习惯用法_Ruby On Rails_Activerecord_Associations_Idioms - Fatal编程技术网

Ruby on rails Rails避免在has_many:through中重复的习惯用法

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” 有没有一种方法可以以

我在Rails应用程序中的用户和角色之间有一个标准的多对多关系:

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]