2

我试图通过急切加载一些 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 

我尝试将 default_scope :include => :roles 放在 User 类中,并将 :includes 放在 has_and_belongs_to_many 调用中。

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

4

2 回答 2

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 次(对帐户表的一次命中和对 account_user 表的另一次命中,您可能永远无法避免)。

于 2012-04-25T15:41:04.607 回答
1

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

看起来您正在执行单独的查询以查看用户是否属于管理员、经理或用户类别。

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

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

添加

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

安全问题

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

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

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

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

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

于 2012-04-24T04:27:25.383 回答