Rails:如何自动注入params与current_user至Helper类
问题背景
刚接触Ruby on Rails(RoR),此前更熟悉Symfony框架。希望在RoR中实现类似Symfony的依赖注入效果:在控制器中通过Helper类根据查询参数和current_user查询数据时,无需手动传入params和current_user,实现自动注入。
现有代码如下:
控制器代码
# app/controllers/api_controller.rb class ApiController < ApplicationController def list @list_helper = ListHelper.new(Product, params, current_user) render :json => { "items": @list_helper.items, "page_count": @list_helper.page_count, "current_page": @list_helper.current_page, } end end
全局辅助方法
# app/helpers/application_helper.rb module ApplicationHelper def current_user @current_user ||= User.find_by_id(session[:user_id]) if !!session[:user_id] end end
ListHelper类
# app/helpers/list_helper.rb module ListHelper class ListHelper attr_reader :page_count, :current_page def initialize(active_record, params, current_user) # <-- 是否可自动注入后两个参数? @active_record = active_record @current_user = current_user @params = params # ...执行相关逻辑 end def items # ...查询操作 end end end
基础控制器
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base include ApplicationHelper include ListHelper end
尝试过服务模式,但难以传递active_record对象,希望找到简单可行的自动注入方案。
解决方案
方案1:封装控制器工厂方法(最简单)
在ApplicationController中添加一个工厂方法,自动传入params和current_user,控制器调用时只需传入active_record类型:
修改ApplicationController:
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base include ApplicationHelper include ListHelper # 封装ListHelper的创建逻辑 def build_list_helper(active_record) ListHelper::ListHelper.new(active_record, params, current_user) end end
然后修改ApiController的list方法:
# app/controllers/api_controller.rb class ApiController < ApplicationController def list @list_helper = build_list_helper(Product) render :json => { "items": @list_helper.items, "page_count": @list_helper.page_count, "current_page": @list_helper.current_page, } end end
这个方案无需修改ListHelper的初始化逻辑,仅通过控制器层封装消除重复传参,符合Rails"约定大于配置"的风格,实现成本极低。
方案2:通过控制器上下文注入(扩展性更好)
如果未来可能需要从控制器获取更多参数(如request、session等),可以让ListHelper接收控制器实例作为上下文,直接从上下文提取所需数据:
修改ListHelper的初始化方法:
# app/helpers/list_helper.rb module ListHelper class ListHelper attr_reader :page_count, :current_page def initialize(active_record, controller_context) @active_record = active_record @current_user = controller_context.current_user @params = controller_context.params # ...执行相关逻辑 end def items # ...查询操作 end end end
然后修改ApiController的调用方式:
# app/controllers/api_controller.rb class ApiController < ApplicationController def list @list_helper = ListHelper::ListHelper.new(Product, self) # 传入控制器实例self render :json => { "items": @list_helper.items, "page_count": @list_helper.page_count, "current_page": @list_helper.current_page, } end end
这种方式把控制器作为上下文容器,后续需要新增依赖时,只需在ListHelper中从上下文提取即可,无需修改初始化参数列表,扩展性更强。
额外建议:调整类的存放目录
在Rails中,app/helpers目录通常用于视图辅助方法,像ListHelper这种处理业务查询逻辑的类,更适合放在app/services或app/queries目录下,这样更符合Rails的目录结构规范。比如可以创建app/queries/list_query.rb来存放这个类,语义上也更清晰。
内容的提问来源于stack exchange,提问作者romain-p

