Rails中使用Rolify gem添加角色过期时间字段的实现问题
Rolify 实现有时效性角色方案
1. 调整迁移配置
你需要先给users_roles中间表加上主键,否则ActiveRecord无法正常操作单条关联记录。如果还没执行迁移,直接修改原有迁移代码:
class RolifyCreateRoles < ActiveRecord::Migration[6.1] def change create_table(:roles) do |t| t.string :name t.references :resource, polymorphic: true t.timestamps end # 去掉原有的:id => false配置,默认生成自增主键 create_table(:users_roles) do |t| t.references :user t.references :role t.datetime :expired_at end add_index(:roles, [:name, :resource_type, :resource_id]) add_index(:users_roles, [:user_id, :role_id]) end end
如果已经执行过原有迁移,单独新建一个迁移补充主键和expired_at字段即可。
2. 创建中间表模型
新建app/models/users_role.rb,定义有效角色的过滤逻辑:
class UsersRole < ApplicationRecord belongs_to :user belongs_to :role # 过滤有效角色:永久角色(expired_at为null)+ 未到过期时间的角色 scope :valid, -> { where("expired_at IS NULL OR expired_at > ?", Time.current) } end
3. 重写User模型的Rolify方法
在app/models/user.rb中,引入Rolify后重写add_role和has_role?方法:
class User < ApplicationRecord rolify # 重写add_role,支持传入过期时间参数 def add_role(role_name, resource = nil, expired_at = nil) # 先调用Rolify原有逻辑创建角色/关联 super(role_name, resource) # 更新关联记录的过期时间 role = Role.find_by(name: role_name.to_s, resource: resource) users_role = users_roles.find_or_initialize_by(role_id: role.id) users_role.update(expired_at: expired_at) end # 重写has_role?,自动过滤过期角色 def has_role?(role_name, resource = nil) # 匹配对应名称和资源的角色 role_scope = Role.where(name: role_name.to_s) role_scope = role_scope.for_resource(resource) if resource.present? role_ids = role_scope.pluck(:id) # 检查是否存在有效关联 users_roles.valid.where(role_id: role_ids).exists? end # 可选扩展:返回用户所有有效角色 def valid_roles Role.joins(:users_roles).where(users_roles: { id: UsersRole.valid.where(user_id: id).ids }) end end
4. 功能测试
按照你预期的调用方式验证即可,也可以用travel_to模拟时间流逝快速测试:
room = Chat::Room.find(1) sarah = User.find(1) david = User.find(2) # 当日操作 sarah.add_role :muted, room, 7.days.from_now david.add_role :muted, room sarah.has_role? :muted, room # 返回true david.has_role? :muted, room # 返回true # 模拟7天后的场景 travel_to 8.days.from_now do sarah.has_role? :muted, room # 返回false david.has_role? :muted, room # 返回true end
注意:如果你的应用开启了时区配置,请统一使用
Time.current获取时间,避免用Time.now导致时区偏差。
内容的提问来源于stack exchange,提问作者KuroBayashi
相关产品推荐
相关产品推荐

