You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.19 03:45:07