Rails:如何根据current_user类型过滤AccountStatement关联记录?
可行实现方案
方法1:动态关联 + 线程变量传递当前用户
先在全局控制器中把当前用户存入线程变量,确保模型能安全访问:
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base before_action :set_current_user after_action :clear_current_user private def set_current_user Thread.current[:current_user] = current_user end def clear_current_user Thread.current[:current_user] = nil end end
然后修改Client和Account的关联定义,用lambda动态过滤结果:
# app/models/client.rb class Client < ApplicationRecord has_many :accounts has_many :account_statements, lambda { current_user = Thread.current[:current_user] if current_user.is_a?(Client) && current_user.id == id where(auto_generated: false) end } end # app/models/account.rb class Account < ApplicationRecord belongs_to :client has_many :account_statements, lambda { current_user = Thread.current[:current_user] if current_user.is_a?(Client) && current_user.id == client_id where(auto_generated: false) end } end
这样调用client.account_statements或account.account_statements时,会自动根据当前用户身份过滤:登录用户是对应Client时,只返回非自动生成的账单;Admin用户则返回全部数据。
方法2:封装通用作用域,显式传参调用
如果不想依赖线程上下文,可以在AccountStatement模型中定义带参数的作用域,在业务逻辑中手动传入当前用户:
# app/models/account_statement.rb class AccountStatement < ApplicationRecord belongs_to :client belongs_to :account scope :visible_to, ->(user) { if user.is_a?(Client) where(auto_generated: false, client_id: user.id) else all end } end
在控制器中调用时:
# Client账单列表页 def index @client = Client.find(params[:id]) @account_statements = @client.account_statements.visible_to(current_user) end # Account账单详情页 def show @account = Account.find(params[:id]) @account_statements = @account.account_statements.visible_to(current_user) end
这种方式更显式,适合对模型上下文依赖有严格要求的场景。
方法3:覆盖关联默认方法,保持调用方式不变
如果希望外部调用account_statements的代码完全不用修改,可以重命名原始关联,再自定义同名方法做过滤:
# app/models/client.rb class Client < ApplicationRecord has_many :accounts has_many :raw_account_statements, class_name: 'AccountStatement' def account_statements current_user = Thread.current[:current_user] if current_user.is_a?(Client) && current_user.id == id raw_account_statements.where(auto_generated: false) else raw_account_statements end end end # app/models/account.rb class Account < ApplicationRecord belongs_to :client has_many :raw_account_statements, class_name: 'AccountStatement' def account_statements current_user = Thread.current[:current_user] if current_user.is_a?(Client) && current_user.id == client_id raw_account_statements.where(auto_generated: false) else raw_account_statements end end end
外部调用时依然用client.account_statements,完全感知不到内部的过滤逻辑。
优化:用Concern复用过滤逻辑
为避免Client和Account模型代码重复,可以把过滤逻辑封装到Concern中:
# app/models/concerns/account_statement_visibility.rb module AccountStatementVisibility extend ActiveSupport::Concern included do has_many :raw_account_statements, class_name: 'AccountStatement' end def account_statements current_user = Thread.current[:current_user] target_id = respond_to?(:client_id) ? client_id : id if current_user.is_a?(Client) && current_user.id == target_id raw_account_statements.where(auto_generated: false) else raw_account_statements end end end
然后在模型中引入即可:
class Client < ApplicationRecord include AccountStatementVisibility # 其他代码 end class Account < ApplicationRecord include AccountStatementVisibility # 其他代码 end
内容的提问来源于stack exchange,提问作者SomeSchmo
相关产品推荐
相关产品推荐

