如何在Ruby on Rails中实现查询对象模式?
作者:互联网
我们为什么需要它,这种模式可以解决什么问题?
有时我们有非常复杂的查询,直接在业务逻辑中使用。例如,可以在不同的控制器和服务对象中多次使用以下查询:
def index
seasons = Season.joins(league: :country).where("countries.name = 'England'")
render json: seasons
end
这给我们带来了什么问题?
- 不可能与控制器分开测试。
- 存根/模拟数据库请求非常困难
- 它不是干的
- 不清楚,很难阅读和理解发生了什么。
那么我们能做些什么来解决这个问题呢?
- 我们可以尝试将此逻辑移动到模型级别。
- 我们可以尝试创建一个查询对象并将该逻辑移动到那里。
如果我们决定选择第一个选项并使用模型,有两种方法可以实现这一点:
类方法
# frozen_string_literal: true
class Season < ApplicationRecord
def self.by_league(league)
where(league: league)
end
def self.by_status(status)
where(status: status)
end
end
Season.by_league(league)
Season.by_status(status)
范围
class Season < ApplicationRecord
scope :by_league, -> (league) { where(league: league) }
scope :by_status, -> (status) { where(status: status) }
end
Season.by_league(league)
Season.by_status(status)
界面看起来不错,但我们仍然有一些问题。最重要的一个是违反了单一责任原则。模型变得太大太胖。我们一年可以获得100多种方法。因此,阅读、更改和维护将非常困难。拥有相同的接口会很好,但将该逻辑移动到单独的类中。
我们该怎么做呢?
创建查询对象模式就是为了解决此问题。
让我们从最简单的查询对象开始。
# app/units/queries/season.rb
module Queries
class Season
class << self
def by_league(league)
::Season.where(league: league)
end
def by_status(status)
::Season.where(status: status)
end
end
end
end
Queries::Season.by_league(league)
Queries::Season.by_status(status)
接口看起来不错,但有一个重要的问题 - 方法不可链接,我们每次都必须重复前缀。例如,如果你想写这样的东西,它是行不通的。::Season
Queries::Season.by_league(league).by_status(status)
那么,我们如何解决它呢?
Rails有一个有趣的方法。此方法允许您在模型上添加任何方法并将它们链接起来。例如,以下工作正常。