如何按嵌套属性对嵌套属性排序?Users与Profiles关联排序咨询
解决按关联User的last_name排序Profile的问题
嘿,这个问题我之前也碰到过!首先得说,千万别在视图或者helper里做排序——不是说不能做,而是数据量稍大时性能会拉胯,而且不符合Rails的MVC设计思路。咱们应该从数据库查询层面解决,这才是最优解。
核心思路:通过关联表查询实现排序
先假设你的模型关联是这样的(如果你的关联关系不同,对应调整即可):
# user.rb class User < ApplicationRecord has_one :profile end # profile.rb class Profile < ApplicationRecord belongs_to :user end
要按User的last_name排序Profile,本质是要把Profile表和User表关联查询,然后基于User表的字段完成排序。
方法1:直接在控制器中编写查询逻辑
在Profiles控制器的index方法里,用joins关联User表,再通过order指定排序字段:
# app/controllers/profiles_controller.rb def index # 按last_name升序排序,改成DESC就是降序 @profiles = Profile.joins(:user).order('users.last_name ASC') end
之后在视图里直接循环@profiles就行,数据已经是排好序的状态了。
方法2:封装成模型Scope(推荐,复用性更强)
如果这个排序逻辑会在多个地方用到,把它封装成Profile模型的scope会更优雅:
# app/models/profile.rb class Profile < ApplicationRecord belongs_to :user # 封装按用户last_name排序的scope,默认升序,可指定降序 scope :ordered_by_user_last_name, -> (direction = :asc) { joins(:user).order("users.last_name #{direction.to_s.upcase}") } end
然后控制器里就可以这样简洁调用:
# 默认升序 @profiles = Profile.ordered_by_user_last_name # 指定降序 @profiles = Profile.ordered_by_user_last_name(:desc)
为什么不推荐视图/Helper处理?
如果在视图里用@profiles.sort_by { |p| p.user.last_name },本质是把所有Profile加载到内存后再用Ruby排序,数据量小的时候看不出问题,但数据上千甚至上万时,数据库层面的排序(order)性能会比内存排序好太多。而且Helper的职责应该是处理视图格式化逻辑,数据排序这种业务逻辑放在模型/控制器更合理。
进阶:动态切换排序方向(可选)
如果需要前端可以切换升序/降序,可以通过接收参数实现,同时用Arel避免SQL注入:
# app/controllers/profiles_controller.rb def index direction = params[:direction] || 'asc' # 限制只能是asc或desc,防止恶意参数 direction = 'asc' unless %w[asc desc].include?(direction.downcase) # 用Arel构建安全的排序条件 user_table = User.arel_table @profiles = Profile.joins(:user).order(user_table[:last_name].send(direction.downcase.to_sym)) end
内容的提问来源于stack exchange,提问作者Tom Connolly
相关产品推荐
相关产品推荐

