Rails API: as_json 包含查询数据库,尽管之前包含

Rails API: as_json with includes query the DB despite previous includes

我正在努力寻找呈现记录的最佳方式。到目前为止,我是按照以下方式完成的,但是,尽管在获取主对象时包含了包含,但在为包含的子记录调用 as_json 时,我得到了大量的数据库查询。我错过了什么?还有更好的方法来做我想做的事吗?

我不知道如何进行更好的呈现,因为我想决定在关联记录数组上序列化和使用自定义范围的属性和方法。

def show
  # The include below seems to be useless, the DB is queried again on render.
  @grandParent = GrandParent.includes(parents: { children: %i[grand_children friends] })
    .find_by_name(params[:name])

  return head :not_found unless @grandParent

  render json: grand_parent_as_json, status: :ok
end

private

def grand_parent_as_json
  json = @grandParent.as_json(
    only: %i[attr1 attr2],
    methods: %i[meth1 meth2]
  )
  # I don't see a better way to render it since I want to use a custom scope on parents
  json[:parents] = @grandParent.parents.ordered_by_birthdate(:desc).map do |parent|
    parent_as_json parent
  end
 
  json
end

# The include below seem to be the one responsible for querying the DB again.
def parent_as_json(parent)
  parent.as_json(
    only: %i[attr1 attr2],
    methods: %i[meth1 meth2],
    include: [
      children: {
        only: %i[attr1 attr2],
        include: [
          grand_children: { %i[attr1 attr2] }
        ]
      }
    ]
  )
end

很确定有更优雅的方法来解决这个问题,但问题确实是这里使用的范围:

@grandParent.parents.ordered_by_birthdate(:desc)

原因是范围保证 return 一个新的 ActiveRecord::Relation,当访问时会命中数据库。

这可能不是最佳答案,但它可以通过更改您的初始查询来工作,为 birthdate 字段包含一个 .order

@grandParent = GrandParent
  .includes(parents: { children: %I[grand_children friends] })
  .order("parents.birthdate DESC")
  .find_by_name(params[:name])

然后在映射父对象时删除 .ordered_by_birthdate,因为它们已经按您想要的顺序排列。这样做的缺点是不能使用在 Parent 上定义的范围 ordered_by_birthdate 。这可能没问题,具体取决于您如何看待控制器与模型的职责。

或者,上面的代码片段也可以是 GrandParent 范围的一部分,例如

class GrandParent
  scope :family_tree, -> { includes(parents: { children: %I[grand_children friends] }).order("parents.birthdate DESC") }
end

那么你可以这样做:

GrandParent.family_tree.find_by_name(params[:name])