【问题标题】:Rails Eager Loading CanCan RolesRails 急切加载 CanCan 角色
【发布时间】:2012-04-24 03:57:32
【问题描述】:

我正在尝试通过急切加载一些 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 查询?

【问题讨论】:

  • 欢迎堆栈溢出。请记住为所有有用的答案投票,包括对他人问题的回答。请记住检查/批准您自己问题的最佳答案。

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


【解决方案1】:

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

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

【讨论】:

  • 这可能是在检查 admin elsif manager elsif user then...在这种情况下,Kyle 可能会调用 CanCan 的 user.has_role?(:admin) 来处理每次访问数据库的问题。如果您没有使用 CanCan 的资源化方面,那么您可以改为:roles = user.roles.pluck(:name)...if roles.include?('admin')...etc。一次调用数据库,只有一列数据。
【解决方案2】:

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

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

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

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

已添加

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

安全问题

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

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

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

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

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

【讨论】:

  • 感谢您的回答,我认为将角色存储在会话中是可行的方法。我正在使用设计,我需要创建会话控制器还是更简单的方法?我猜我只需要保存角色 ID?
  • 还有安全隐患吗?
  • 谢谢拉里....我仍然不确定将登录注销挂钩放在哪里。我只为设计创建了一个注册控制器,并且不想为这个小改动放入一个会话控制器。反正周围有吗?
  • 对不起,我不知道设计。问另一个 Stackoverflow 问题,例如“设计:如何在登录/注销时添加方法调用?”如果答案是另一个控制器文件,那并没有什么问题。 Rails 应用程序往往有很多文件....
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多