Ruby on rails Rails正在加载CanCan角色

Ruby on rails Rails正在加载CanCan角色,ruby-on-rails,ruby-on-rails-3,ruby-on-rails-3.1,cancan,eager-loading,Ruby On Rails,Ruby On Rails 3,Ruby On Rails 3.1,Cancan,Eager Loading,我正试图通过急切地加载一些sql调用来加速我的应用程序。我正在使用CanCan gem来处理我的管理员授权。我有一个角色表,其中包含三个不同的角色和一个多对多的用户角色表。每次加载带有CanCan功能集的页面时,它都会执行三个单独的sql查询 Role Load (0.6ms) SELECT "roles".* FROM "roles" INNER JOIN "roles_users" ON "roles"."id" = "roles_users"."role_id" WHERE "

我正试图通过急切地加载一些sql调用来加速我的应用程序。我正在使用CanCan gem来处理我的管理员授权。我有一个角色表,其中包含三个不同的角色和一个多对多的用户角色表。每次加载带有CanCan功能集的页面时,它都会执行三个单独的sql查询

 Role Load (0.6ms)  SELECT "roles".* FROM "roles" INNER JOIN "roles_users" ON "roles"."id"    
= "roles_users"."role_id" WHERE "roles_users"."user_id" = 2 AND "roles"."name" = 'admin'  
  LIMIT 1
Role Load (0.4ms)  SELECT "roles".* FROM "roles" INNER JOIN "roles_users" ON "roles"."id" 
  = "roles_users"."role_id" WHERE "roles_users"."user_id" = 2 AND "roles"."name" =  
 'manager' LIMIT 1
 Role Load (0.3ms)  SELECT "roles".* FROM "roles" INNER JOIN "roles_users" ON "roles"."id" 
  = "roles_users"."role_id" WHERE "roles_users"."user_id" = 2 AND "roles"."name" = 'user' 
  LIMIT 1 
我已经尝试将默认的作用域:include=>:roles放在User类中,并将:include放在has_中,并且将\u beliens_放在许多调用中


在哪里可以加载角色表以仅使用1个SQL查询?

您可以通过显示用于查询cancan系统的代码(对cancan的api调用)来改进您的问题

看起来您正在进行单独的查询,以查看用户是否属于admin、manager或user类别

您应该对查询进行排序,以便首先查询最可能的用户类型。例如,如果一个人很可能是一个用户,那么首先查询该用户

更好的是,缓存会话中用户的角色。这样,您只需执行一次查询

已添加

您可以在登录/注销后使用Desive挂钩来设置会话中的值。您可以使用查找角色(从会话中)

安全问题

  • 使用Desive after logout钩子清除会话中的角色
  • 确保测试覆盖会话的角色id
  • 你不必担心人们会对他们的角色进行自我升级:会话是针对更改进行数字签名的。请参阅会话文档
  • 正在动态删除/更改的用户:
根据您的安全级别,您可能需要涵盖用户被删除或降级的情况,并且您希望立即进行更改(而不是等到用户下次登录)

这样做可能很棘手。一些技巧:

  • 一种方法是将角色检查时间戳与角色id一起存储在会话中。如果角色id超过30分钟,则使其无效(再次查找),以此类推
  • 或者将用户的角色id存储在memcache中,而不是会话存储中。然后,另一个进程(删除用户的进程)可以从活动用户会话的缓存中删除角色id值
  • 一种更简单的暴力方式:为管理员提供一种清除所有当前会话的方式(需要所有当前用户再次登录)。这将解决必须将某人从系统中引导出来的罕见情况
  • 和许多系统一样,在几个小时后使所有会话超时。或者每天清除一次所有会话。如果您这样做,最好实现并测试自动重新登录。包括重新登录后返回请求的页面。重新登录将停止凭据已更改的用户

使用常量您可以在会话(或memcache)中存储角色ID。但将它们与常数进行比较。例如
user.role\u id===role\u ADMIN
您可以在运行时通过从数据库中查找role\u id来设置常量。确保在初始化系统中每个rails进程执行一次此操作。不是每个传入请求一次。

看起来您可能有如下代码。也就是说,它接受一个字符串或符号,您需要将其与角色记录中的某个值进行比较,而不是角色本身。因此,请看下面的例子:

def do_i_have_role(role_name_to_check)
  self.roles.detect do |role|
    role.name == role_name_to_check
  end
end
您可以在这里进行一些快速加载来修复它,但另一种剥除这只猫皮肤的方法是重新构造查询。也就是说,首先查找role对象,然后查看您的帐户是否具有该角色

def do_i_have_role(role_name_to_check)
  role = Role.where(:name => role_name_to_check)
  self.roles.include? role
end

现在只需点击一次角色数据库,而不是3次(点击accounts表,再点击account\u user表,这是您可能无法避免的)。

谢谢您的回答,我认为在会话中存储角色将是一种方法。我正在使用Desive,我是否需要创建会话控制器,或者这是一种更简单的方法?我猜我只需要保存角色id?是否有任何安全隐患?谢谢Larry。。。。我仍然不确定在哪里放置登录注销挂钩。我只为Desive创建了一个注册控制器,不想为这个小小的更改添加会话控制器。这有什么关系吗?对不起,我不知道。问另一个Stackoverflow问题,比如“设计:如何在登录/注销时添加方法调用?”如果答案是另一个控制器文件,那么这并没有什么问题。Rails应用程序往往有许多文件…欢迎使用堆栈溢出。记住对所有有用的答案进行投票,包括对他人问题的投票。请记住检查/批准您自己问题的最佳答案。这可能是在检查管理员elsif manager elsif user then…时,在这种情况下,Kyle可能会调用CanCan的用户。每次访问数据库时,都有一个角色(:admin)。如果您没有使用CanCan的资源配置方面,那么您可以改为:roles=user.roles.pulk(:name)…If roles.include?('admin')…等等。一次对数据库的调用,仅通过连线使用一列数据。