Rails渴望加载CanCan角色

时间:2012-04-24 03:57:33

标签: ruby-on-rails ruby-on-rails-3 ruby-on-rails-3.1 cancan eager-loading

我正试图通过加载一些sql调用来加速我的应用程序。我正在使用CanCan gem来处理我的管理员授权。我有一个包含三个不同角色的Roles表和一个多对多表roles_users。每次页面加载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 

我尝试过设置default_scope:include => :User类中的角色,还包括:has_and_belongs_to_many调用中包含的内容。

我在哪里可以急切加载Roles表以仅使用1个SQL查询?

2 个答案:

答案 0 :(得分:2)

看起来你可能有一些如下所示的代码。也就是说,它需要一个字符串或符号来比较角色记录中的某个值,而不是角色本身。所以寻找这样的东西:

def do_i_have_role(role_name_to_check)
  self.roles.detect do |role|
    role.name == role_name_to_check
  end
end

你可以在这里做一些急切的加载来修复它,但另一种方法来修改这个猫是重构您的查询。也就是说,首先查找角色对象,然后查看您的帐户是否具有该角色。

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

现在它只是对角色数据库的一次点击而不是3次(对帐户表的点击以及对帐户表的另一次点击,您可能永远无法避免)。

答案 1 :(得分:1)

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

看起来您正在进行单独的查询,以查看用户是否在管理员,经理或用户类别中。

您应该命令查询首先查询最可能的用户类型。例如,如果赔率是一个人很可能是一个用户,那么首先查询该用户。

更好的是,缓存用户在会话中的角色。这样你只会进行一次查询。

<强>加

您可以在登录/注销挂钩后使用设计来设置会话中的值。您可以使用Action Controller filters查找角色(来自会话)。 Session docs

安全问题

  • 在注销挂钩后使用设计来清除会话中的角色。
  • 确保您的测试涵盖会话的角色ID
  • 您无需担心人们会对自己的角色进行自我升级:会话会根据更改进行数字签名。请参阅会话文档。
  • 即时删除/更改用户:

根据您的安全级别,您可能需要涵盖用户被删除或降级的情况,并且您希望立即进行更改(而不是等到用户下次登录时)

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

  • 一种方法是在会话中存储角色检查时间戳以及role_id。然后使role_id无效(再次查找),如果超过30分钟,等等。
  • 或者将用户的role_id存储在memcache中,而不是会话存储中。然后,另一个进程(执行delete-user)可以从缓存中删除活动用户会话的role_id值。
  • 一种更简单,蛮力的方式:让管理员清除所有当前会话(需要所有当前用户再次登录)。这将解决不必从系统启动某人的不常见情况。
  • 正如许多系统所做的那样,在几个小时后超时所有会话。或者每天清理一次所有会话。如果这样做,最好实现并测试自动重新登录。包括重新登录后返回请求的页面。重新登录将阻止其凭据已更改的人员。

使用常量您可以在会话(或内存缓存)中存储role_ids。但是将它们与常数进行比较。例如user.role_id === ROLE_ADMIN您可以通过从db中查找role_ids来在运行时设置常量。确保在初始化系统中每个rails进程执行一次此操作。每次传入请求都不会一次。