使用Rails active_model_serializer实现分页功能

20

我正在使用 active_model_serializer。现在我想序列化一个带有分页的对象,我应该在控制器中还是在序列化器中进行分页逻辑?

如果我选择在序列化器中进行分页,我需要将 page_number 和 per_page 传递给序列化器。我该如何做到这一点?我的理解是序列化器只接受模型对象。


真的不清楚序列化在这里起到了什么作用。你正在序列化什么?它与分页有什么关系?这两者是完全独立的,我无法想象它们之间有什么联系。 - user229044
@meagar 我正在尝试对相册进行序列化,其中我想对照片进行分页。 - Bruce Lin
你是想说你的结果是一个数组,而will_paginate无法工作? - Ruby Racer
@StavrosSouvatzis 对于混淆感到抱歉。我实际上正在使用Kaminari,分页效果很好。我的问题是在哪里放置分页逻辑 - 我应该将其放在控制器中还是序列化器中。 - Bruce Lin
只是为了澄清未来的读者,AMS现在在使用Kaminari或WP gem时会自动包含分页:https://github.com/rails-api/active_model_serializers/blob/master/docs/howto/add_pagination_links.md - rmcsharry
1
@rmcsharry FYI,链接失效了。我认为这个页面是等价的:https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/howto/add_pagination_links.md - RonLugge
3个回答

46

单一使用解决方案

常规序列化器只关注单个项目,而不是分页列表。最直接的添加分页的方法是在控制器中进行:

customers = Customer.page(params[:page])
respond_with customers, meta: {
  current_page: customers.current_page,
  next_page: customers.next_page,
  prev_page: customers.prev_page,
  total_pages: customers.total_pages,
  total_count: customers.total_count
}

可复用的解决方案

但是,如果您需要对多个对象进行分页逻辑,则这相当繁琐。查看active_model_serializers文档,您将会遇到一个ArraySerializer,用于序列化对象数组。我所做的是创建一个pagination_serializer.rb,使用ArraySerializer自动添加分页数组的元标记:

# my_app/app/serializers/pagination_serializer.rb
class PaginationSerializer < ActiveModel::Serializer::ArraySerializer
  def initialize(object, options={})
    meta_key = options[:meta_key] || :meta
    options[meta_key] ||= {}
    options[meta_key][:pagination] = {
      current_page: object.current_page,
      next_page: object.next_page,
      prev_page: object.prev_page,
      total_pages: object.total_pages,
      total_count: object.total_count
    }
    super(object, options)
  end
end

一旦您将PaginationSerializer添加到rails应用程序中,您只需要在控制器中需要分页元标记时调用它:

customers = Customer.page(params[:page])
respond_with customers, serializer: PaginationSerializer

注意:我编写了这个使用Kaminari作为分页器。不过,可以很容易地修改它以与任何分页宝石或自定义解决方案配合使用。


8
@MarkMurphy,我更新了我的答案,展示了如何使用ArraySerializer来简化代码并使其更加DRY。 - lightswitch05
2
我希望分页键位于顶层,所以我也覆盖了PagedSerializer < ActiveModel::ArraySerializer中的#as_json。def as_json(*args) @options[:hash] = hash = {} @options[:unique_values] = {} hash.merge!(@options[:pagination]) if @options.key?(:pagination) root = @options[:root] if root.present? hash.merge!(root => serializable_array) include_meta(hash) hash else serializable_array end end - aaronmgdr
1
@LiDong 这取决于你使用的 active_model_serializers 版本。0.10.0 及以上版本需要使用 ActiveModel::Serializer::ArraySerializer,而旧版本则需要使用 ActiveModel::ArraySerializer - lightswitch05
2
很棒的解决方案,但不再需要,请参见:https://github.com/rails-api/active_model_serializers/blob/master/docs/howto/add_pagination_links.md - rmcsharry
1
如何将分页链接添加到文档更改的URL:https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/howto/add_pagination_links.md - Zoran Majstorovic
显示剩余5条评论

6

2020更新:如果您使用json_api模式,则active_model_serializer现在支持此功能,但文档也会教您如何添加json模式。

文档在这里:https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/howto/add_pagination_links.md

如果您正在使用json_apijson适配器,请按照以下方式实现所需的结果。检查您正在使用的适配器类型:ActiveModelSerializers.config.adapter

如果您使用JSON API适配器(即ActiveModelSerializers.config.adapter = :json_api

只要资源进行了分页并且您正在使用JsonApi适配器,则分页链接将自动包含在响应中。

如果您希望在响应中包含分页链接,请使用KaminariWillPaginate

#array
@posts = Kaminari.paginate_array([1, 2, 3]).page(3).per(1)
render json: @posts

#active_record
@posts = Post.page(3).per(1)
render json: @posts

#array
@posts = [1,2,3].paginate(page: 3, per_page: 1)
render json: @posts

#active_record
@posts = Post.page(3).per_page(1)
render json: @posts

ActiveModelSerializers.config.adapter = :json_api

ex:

{
  "data": [
    {
      "type": "articles",
      "id": "3",
      "attributes": {
        "title": "JSON API paints my bikeshed!",
        "body": "The shortest article. Ever.",
        "created": "2015-05-22T14:56:29.000Z",
        "updated": "2015-05-22T14:56:28.000Z"
      }
    }
  ],
  "links": {
    "self": "http://example.com/articles?page[number]=3&page[size]=1",
    "first": "http://example.com/articles?page[number]=1&page[size]=1",
    "prev": "http://example.com/articles?page[number]=2&page[size]=1",
    "next": "http://example.com/articles?page[number]=4&page[size]=1",
    "last": "http://example.com/articles?page[number]=13&page[size]=1"
  }
}

ActiveModelSerializers分页依赖于带有current_pagetotal_pagessize方法的已分页集合,这些方法被KaminariWillPaginate支持。

如果您正在使用JSON适配器(即ActiveModelSerializers.config.adapter = :json)

如果您没有使用JSON适配器,则不会自动包含分页链接,但可以使用meta键来实现。

将此方法添加到您的基本API控制器中。

def pagination_dict(collection)
  {
    current_page: collection.current_page,
    next_page: collection.next_page,
    prev_page: collection.prev_page, # use collection.previous_page when using will_paginate
    total_pages: collection.total_pages,
    total_count: collection.total_count
  }
end

然后,在你的渲染方法中使用它。

render json: posts, meta: pagination_dict(posts)

ex.

{
  "posts": [
    {
      "id": 2,
      "title": "JSON API paints my bikeshed!",
      "body": "The shortest article. Ever."
    }
  ],
  "meta": {
    "current_page": 3,
    "next_page": 4,
    "prev_page": 2,
    "total_pages": 10,
    "total_count": 10
  }
}

如果您有一个添加分页信息到meta标签的帮助方法,也可以实现相同的结果。例如,在您的操作中指定一个定制的序列化器。
render json: @posts, each_serializer: PostPreviewSerializer, meta: meta_attributes(@posts)

#expects pagination!
def meta_attributes(collection, extra_meta = {})
  {
    current_page: collection.current_page,
    next_page: collection.next_page,
    prev_page: collection.prev_page, # use collection.previous_page when using will_paginate
    total_pages: collection.total_pages,
    total_count: collection.total_count
  }.merge(extra_meta)
end

属性适配器

该适配器不允许我们使用 meta 键,这导致无法添加分页链接。


关键在于将 ActiveModelSerializers.config.adapter = :json_api 添加到位于 config/intializers/ams.rb 文件中的代码中。 - Gregory Ray

1

https://github.com/x1wins/tutorial-rails-rest-api/blob/master/lib/pagination.rb

# /lib/pagination.rb
class Pagination
  def self.build_json object, param_page = {}
    ob_name = object.name.downcase.pluralize
    json = Hash.new
    json[ob_name] = ActiveModelSerializers::SerializableResource.new(object.to_a, param_page: param_page)
    json[:pagination] = {
        current_page: object.current_page,
        next_page: object.next_page,
        prev_page: object.prev_page,
        total_pages: object.total_pages,
        total_count: object.total_count
    }
    return json
  end
end

如何使用
#app/controller/posts_controller.rb
#post#index
render json: Pagination.build_json(@posts)

完整源代码 https://github.com/x1wins/tutorial-rails-rest-api


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