Ruby复杂查询的优雅解决方案

3

在我的Rails 4项目中,我有以下的表格:enter image description here

在这个SO问题中,我搜索了一个SQL查询来获取实际项目状态ID = XX的项目。 这里的“实际”是指具有最大(created_at)的那个。

我得到了一个答案,这是我的查询:

select p.* from projects p
     left join projects_status_projects psp on (psp.project_id = p.id)
     where created_at = (select max(created_at) from projects_status_projects 
           where project_id = p.id)    
     and project_status_id = XX

我的模型已经定义。
class Project < ActiveRecord::Base
   has_many :projects_status_projects
   has_many :projects_statuses, :through => :projects_status_projects
end

class ProjectStatus < ActiveRecord::Base
   has_many :projects_status_projects
   has_many :projects, :through => :projects_status_projects
end

class ProjectsStatusType < ActiveRecord::Base
   belongs_to :project
   belongs_to :project_status
end

在我的项目模型中,我有以下方法。
def self.with_status(status)
   joins(:projects_status_projects)
       .where('projects_status_projects.created_at = (select max(created_at) from 
                    projects_status_projects where project_id = p.id)')
       .where('projects_status_projects.project_status_id = ?', status)
end

虽然查询是正确的,收到的结果也经过了很好的过滤,但我认为这种解决方案非常糟糕,并不优雅。

是否有任何使用范围(scope)可以获得相同结果的方法?

谢谢您的帮助。

3个回答

1

你对此有何看法?

scope :with_status, -> (status) { 
   ProjectsStatusType.where(:project_status_id, status).order(:created_at).last.project 
}

根据评论进行编辑:

正如sockmonk所说,作用域应该是可链接的。这里有一种更清晰的方法来解决这个问题,也可以修复如果没有找到项目的问题。

# ProjectStatusType model
scope :with_ordered_status, -> (status) { 
   where(:project_status_id, status).order(:created_at)
}

# Project model
def self.find_by_status(status)
  project_statuses = ProjectsStatusType.with_ordered_status(status)
  project_statuses.any? ? project_statuses.last.project : nil
end

你的方法真的很优雅,主要问题是如果请求没有返回结果(对于这种状态),会出现“undefined method 'project' for nil:NilClass”的错误。 - bmichotte

0

怎么样?

scope :with_status, ->(status = "default_status") {
    joins(:projects_status_projects).
    where('projects_status_projects.project_status_id = ?', status).
    order("projects_status_projects.created_at DESC").first
  }

0
scope :with_status, ->(status = "default_status") {
  joins(:projects_status_projects)
  .where('projects_status_projects.project_status_id = ?', status)
  .order("projects_status_projects.created_at DESC")    
}

当您调用它时,您需要在其末尾添加'.first'; 不能将.first包含在作用域本身中,因为这会使其无法链接。

心灵感应还是复制粘贴? - Jakub Kuchar
作用域需要返回一个Relation,以便它们可以链接在一起,所以如果在作用域中使用.first是不行的;.first要么返回一个单个的 ActiveRecord 对象,要么返回nil(如果没有找到任何对象)。 - sockmonk
也许我作为一个 Ruby/Rails 新手是这个问题的原因。但我不知道这个范围是否能够返回所有具有此状态的项目?我的意思是,如果我调用类似 @projects = Project.with_status(XX).first 的东西,我只会收到第一个状态为 XX 的项目吗? - bmichotte
调用 Project.with_status(XX) 会返回所有该状态下的项目; 调用 Project.with_status(XX).first 会仅返回该状态下创建时间最近的项目。 - sockmonk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接