【问题标题】:Rails 4 render json with multiple objects and includesRails 4 使用多个对象渲染 json 并包含
【发布时间】:2018-09-26 00:50:18
【问题描述】:

我有一个 Rails 4 API。当用户在视图中搜索船只时,会执行此方法以获取与搜索过滤器匹配的所有船只,并使用渲染 ActiveModel 和 :include 和 :only 以 json 格式返回船只模型数组,如下所示:

render :json => @boats, :include => { :mainPhoto => {:only => [:name, :mime]},
                                      :year => {:only => :name},
                                      # other includes...}

这很好用。

但是,除此信息外,在视图中,我想显示船的总数,例如“显示 80 艘船中的 1 - 20 艘”,因为有分页功能。所以,关键是我需要提供 80 艘船。我想避免发送两个执行几乎相同逻辑的请求,因此我的想法是只运行一次 searchBoats 方法,并在结果中提供船只列表和变量 numTotalBoats 中的船只总数。我了解 numTotalBoats 不是船模型属性。所以,我认为它应该在渲染结果中放入一个自变量。 比如:

render :json => {boats: @boats with all the includes, numTotalBoats: @NumTotalBoats}

我尝试了数千种组合,但是或者我遇到了语法错误,或者它们都没有返回预期的结果,比如

{boats: [boat1 with all the includes, boat2 with all the includes, ... boatN with all the includes], numTotalBoats: N}

【问题讨论】:

  • 你试过这样吗? render :json => {boats: @boats, numTotalBoats: @boats.size}
  • 您的意思是您正在使用active_model_serializers gem? AMS 可以用作 PORO,这意味着您可以预先获取可序列化对象,然后将其传递给render 方法。你可以从 AMS 文档 How to serialize a Plain-Old Ruby Object (PORO)Using AMS Outside Of A Controller 中找到这个想法
  • 我不这么认为。我没有安装gem。只是 ror 提供的默认活动模型。
  • 什么是numTotalBoats:?是模型还是方法?
  • 这不是一个模型,它只是一个变量,我需要传递所有船只的数量。

标签: ruby-on-rails json


【解决方案1】:

不添加任何宝石:

def index
  boats = Boat.includes(:year)

  render json: {
    boats: boats.as_json(include: { year: { only: :name } }),
    numTotalBoats: boats.count
  }
end

不过,我认为您应该使用独立的序列化程序:

注意:根据您是否使用分页 gem,您可能需要将下面的 .count 调用更改为 .total_count(用于 Kaminari)或其他可以读取计数的内容正确来自分页集合。

我建议使用ActiveModel Serializers,这就是您的案例的实现方式。

首先将 gem 添加到 Gemfile:

gem 'active_model_serializers', '~-> 0.10'

覆盖config/initializers/active_model_serializer.rb中的适配器:

ActiveModelSerializers.config.adapter = :json

为您的模型定义序列化程序,

# app/serializers/boat_serializer.rb
class BoatSerializer < ActiveModel::Serializer
  attributes :name

  has_one :year
end

# app/serializers/year_serializer.rb
class YearSerializer < ActiveModel::Serializer
  attributes :name
end

最后,在你的控制器中:

boats = Boat.includes(:year)

render json: boats, meta: boats.count, meta_key: "numTotalBoats"

你将实现:

{
  "boats": [
    {
      "name": "Boaty McBoatFace",
      "year": {
        "name": "2018"
      }
    },
    {
      "name": "Titanic",
      "year": {
        "name": "1911"
      }
    }
  ],
  "numTotalBoats": 2
}

在每个索引控制器中添加该计数有点乏味,因此我通常最终定义自己的适配器或集合序列化程序以便自动处理(使用 Rails 5,而不是 4 测试)。

# lib/active_model_serializers/adapter/json_extended.rb
module ActiveModelSerializers
  module Adapter
    class JsonExtended < Json
      def meta
        if serializer.object.is_a?(ActiveRecord::Relation)
          { total_count: serializer.object.count }
        end.to_h.merge(instance_options.fetch(:meta, {})).presence
      end
    end
  end
end

# config/initializers/active_model_serializer.rb
ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::JsonExtended

# make sure to add lib to eager load paths
# config/application.rb
config.eager_load_paths << Rails.root.join("lib")

现在您的索引操作可以如下所示

def index
  boats = Boat.includes(:year)

  render json: boats
end

然后输出:

{
  "boats": [
    {
      "name": "Boaty McBoatFace",
      "year": {
        "name": "2018"
      }
    },
    {
      "name": "Titanic",
      "year": {
        "name": "1911"
      }
    }
  ],
  "meta": {
    "total_count": 2
  }
}

我认为为不同的端点解析此计数会更容易一些,并且您会在使用集合响应时自动获取它,因此您的控制器会更简单一些。

【讨论】:

  • 谢谢!看起来不错。关于你的最后一句话。你的意思是保持搜索船的逻辑和计数分开在两个不同的端点,即使需要运行的查询是相同的,并且一个答案是船集合,另一个答案是计数?
  • 使用一个端点。我的意思是保持计数键独立于模型名称,因此如果您有更多端点,您可以简单地引用count 而不是numTotalBoatsnumTotalYears 等。关于扩展适配器,如果你有 3 个索引操作,您需要在所有操作中显式提供这些元键,这是很多重复,应该干掉。
  • @Rober 我在开头添加了一个不需要任何宝石的答案。
  • 谢谢。我来检查一下。我试过了,但我认为包含两个以上的内容会造成奇怪的事情。但是,让我检查一下并回复您!
  • 你的答案是对的!这就是我一直在寻找的答案。这是我尝试过的方法,但由于某种原因,我的语法错误并且没有正确解析。现在它可以工作了。
猜你喜欢
  • 2017-04-20
  • 2019-02-06
  • 1970-01-01
  • 1970-01-01
  • 2014-07-10
  • 1970-01-01
  • 2013-07-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多