Class: X::Page

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
x-objects/lib/x/objects/page.rb

Overview

One page of results from a paginated endpoint

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(items, meta: {}, problems: []) ⇒ Page

Initialize a new page

Examples:

Create a page

X::Page.new([user], meta: {"result_count" => 1})

Parameters:

  • items (Array<Resource>) —

    the resources on the page

  • meta (Hash) (defaults to: {}) —

    the pagination metadata, empty by default, as for a page whose response holds none

  • problems (Array<Problem>) (defaults to: []) —

    the problems the page's response reported

Raises:

  • (ArgumentError) —

    if the items are not an Array of resources, the metadata is not a Hash, or the problems are not an Array of problems



62
63
64
65
66
67
# File 'x-objects/lib/x/objects/page.rb', line 62

def initialize(items, meta: {}, problems: [])
  @items = resources!(items).dup.freeze
  @meta = Utils.deep_freeze(Hash.try_convert(meta) || raise(ArgumentError, "meta must be a Hash, not #{meta.inspect}"))
  @problems = problems!(problems).dup.freeze
  freeze
end

Instance Attribute Details

#items ⇒ Array<Resource> (readonly)

The resources on this page

Examples:

Get the resources on a page

page.items

Returns:



31
32
33
# File 'x-objects/lib/x/objects/page.rb', line 31

def items
  @items
end

#meta ⇒ Hash{String => Object} (readonly)

The pagination metadata returned with this page

Examples:

Get the metadata

page.meta # => {"result_count" => 100, "next_token" => "..."}

Returns:

  • (Hash{String => Object}) —

    the metadata



49
50
51
# File 'x-objects/lib/x/objects/page.rb', line 49

def meta
  @meta
end

#problems ⇒ Array<Problem> (readonly)

The problems the response of this page reported

They are every problem of the response, where each resource of the page reports only those about it, or about a resource it refers to.

Examples:

Collect every problem a cursor's pages reported

user.followers.each_page.flat_map(&:problems)

Returns:

  • (Array<Problem>) —

    the problems



42
43
44
# File 'x-objects/lib/x/objects/page.rb', line 42

def problems
  @problems
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?

Check whether another page is the same page

Its resources are compared as resources are, by class and identifier, so a page read again, or read back from Marshal, equals the page it was read from.

Examples:

Check whether a page was read before

seen.include?(page)

Parameters:

  • other (Object) —

    the other page

Returns:

  • (Boolean) —

    true if the other page is a Page of the same resources, in the same order, with the same meta and problems



140
# File 'x-objects/lib/x/objects/page.rb', line 140

def ==(other) = other.instance_of?(self.class) && state.eql?(other.__send__(:state))

#[](*args) ⇒ Resource, ...

The resource at an index, or the resources of a range, as Array#[] reads them

Examples:

Get the first resource on a page

page[0]

Parameters:

  • args (Array<Integer, Range>) —

    an index, a start and a length, or a range

Returns:

  • (Resource, Array<Resource>, nil) —

    the resource, or the resources, or nil for an index past the end



118
# File 'x-objects/lib/x/objects/page.rb', line 118

def [](*args) = items[*args] # steep:ignore DifferentMethodParameterKind, UnresolvedOverloading

#as_json ⇒ Hash{String => Object}

This page, as a JSON encoder reads it, in the shape of the response it came from

Its resources are the data, each given as its own as_json gives it, beside the meta of the page, which holds the token of the next, and, when the response reported any, its problems as the errors, so that what this returns is plain data, which ActiveSupport reads too, and which the from_response of the resource class builds into a page again. The objects the response included are not among it, so a reference of a resource built again from it is a stub.

Examples:

Serialize a page

page.as_json # => {"data" => [{"id" => "7505382"}], "meta" => {"next_token" => "abc"}}

Build a page again from what it serialized to

X::User.from_response(JSON.parse(page.to_json), client: client)

Returns:

  • (Hash{String => Object}) —

    the data, meta, and errors of the page, frozen



205
206
207
208
209
# File 'x-objects/lib/x/objects/page.rb', line 205

def as_json(*)
  json = {"data" => map(&:as_json), "meta" => meta}
  json["errors"] = problems.map(&:to_h) unless problems.empty?
  json.freeze
end

#each {|Resource| ... } ⇒ Enumerator, Page

Iterate over the resources on this page

Examples:

Iterate over a page

page.each { |user| puts user.username }

Yields:

Returns:

  • (Enumerator, Page) —

    an enumerator without a block, otherwise self, as a cursor returns itself



76
77
78
79
80
81
# File 'x-objects/lib/x/objects/page.rb', line 76

def each(&block)
  return to_enum { size } unless block

  items.each(&block)
  self
end

#empty? ⇒ Boolean

Check whether this page holds no resources

Examples:

Stop at an empty page

break if page.empty?

Returns:

  • (Boolean) —

    true if the page holds none



109
# File 'x-objects/lib/x/objects/page.rb', line 109

def empty? = items.empty?

#encode_with(coder) ⇒ void

This method returns an undefined value.

Write the state Marshal writes as YAML, without the clients of the resources

YAML reads no marshal_dump, and would write every instance variable of each resource, its client and the credentials it holds among them, so a page says how it is written: each part of the state Marshal writes, under its name.

Examples:

Write a page as YAML

YAML.dump(user.followers.page(0))

Parameters:

  • coder (Psych::Coder) —

    the coder YAML writes the page with



316
# File 'x-objects/lib/x/objects/page.rb', line 316

def encode_with(coder) = YAML_KEYS.zip(marshal_dump) { |key, value| coder[key] = value }

#hash ⇒ Integer

The hash of the page, which equal pages share

Examples:

Count the distinct pages

pages.uniq.size

Returns:

  • (Integer) —

    the hash



149
# File 'x-objects/lib/x/objects/page.rb', line 149

def hash = [self.class, state].hash

#init_with(coder) ⇒ void

This method returns an undefined value.

Restore a page YAML read, frozen, as Marshal restores one

Examples:

Read a page written as YAML

YAML.unsafe_load(YAML.dump(page)).next_token

Parameters:

  • coder (Psych::Coder) —

    the coder YAML read the page with

Raises:



326
# File 'x-objects/lib/x/objects/page.rb', line 326

def init_with(coder) = marshal_load(coder.map.values_at(*YAML_KEYS))

#last(*args) ⇒ Resource, ...

The last resource on this page, or the last few

Examples:

Get the last resource on a page

page.last

Parameters:

  • args (Array<Integer>) —

    nothing for the last resource, or the number of resources to take from the end

Returns:

  • (Resource, Array<Resource>, nil) —

    the last resource, or the last resources, or nil for an empty page



127
# File 'x-objects/lib/x/objects/page.rb', line 127

def last(*args) = items.last(*args) # steep:ignore DifferentMethodParameterKind, UnresolvedOverloading

#marshal_dump ⇒ Array

The state Marshal writes

What is written is plain data, led by the number of its format, so that a page written by one release of 1.x is read by a later one: each resource, as its class, its attributes, and whether it is hydrated, without its client; the included objects the resources refer to, once for the resources that came from one response, as a resource writes those it refers to, so that the resources that resolved a reference to the same object still do; its metadata; and its problems.

Examples:

Cache a page

Rails.cache.write("followers", user.followers.page(0))

Returns:

  • (Array) —

    the number of the format, then the state of the page



280
281
282
283
# File 'x-objects/lib/x/objects/page.rb', line 280

def marshal_dump
  responses = group_by { |item| response_of(item) }
  [MARSHAL_FORMAT, resource_states(responses.keys), meta, problems, responses.map { |includes, members| includes.state_of(members) }]
end

#marshal_load(state) ⇒ void

This method returns an undefined value.

Restore a page Marshal read, frozen as the page that was written was

The resources that came from one response are built over one identity map again, so a reference they share resolves to the same object, as it did before the page was written. Each is hydrated if it was, and the query of its request asks for every field this release requests, as a resource Marshal reads is.

Examples:

Read a cached page

Marshal.load(Marshal.dump(page)).next_token

Parameters:

  • state (Array) —

    the state Marshal wrote

Raises:



297
298
299
300
301
302
303
# File 'x-objects/lib/x/objects/page.rb', line 297

def marshal_load(state)
  format, resources, meta, problems, responses = state
  raise UnsupportedMarshalFormat, "#{self.class} reads format #{MARSHAL_FORMAT} of Marshal, not #{format.inspect}" unless MARSHAL_FORMAT.eql?(format)

  responses = responses.map { |data, about, query| Includes.new(data, problems: about, query:) }
  initialize(resources.map { |klass, attrs, hydrated, response| read(klass, attrs, hydrated, responses.fetch(response)) }, meta:, problems:)
end

#next_token ⇒ String?

The token used to fetch the next page

An empty token names no page, so a page whose meta holds one is the last, as a page whose meta holds none is, rather than one whose next page is fetched with an empty token the API refuses.

Examples:

Get the next token

page.next_token

Returns:

  • (String, nil) —

    the token or nil if this is the last page



160
161
162
163
# File 'x-objects/lib/x/objects/page.rb', line 160

def next_token
  token = meta["next_token"]
  token unless token.eql?("")
end

#previous_token ⇒ String?

The token used to fetch the page before this one

Most endpoints that page, such as the followers of a user, the members of a list, and the events of a direct message conversation, name the page before each page after the first. An empty token names no page, as an empty next_token does.

Examples:

Fetch the page before this one

client.get("users/7505382/followers?pagination_token=#{page.previous_token}", object_class: X::User)

Returns:

  • (String, nil) —

    the token or nil if this is the first page, or the endpoint names none



175
176
177
178
# File 'x-objects/lib/x/objects/page.rb', line 175

def previous_token
  token = meta["previous_token"]
  token unless token.eql?("")
end

#result_count ⇒ Integer?

The number of results reported by the API

Examples:

Get the result count

page.result_count

Returns:

  • (Integer, nil) —

    the result count

Raises:



187
188
189
# File 'x-objects/lib/x/objects/page.rb', line 187

def result_count
  Utils.read("#{self.class}#result_count", meta["result_count"]) { |value| Shape.integer(value) }
end

#size ⇒ Integer Also known as: length

The number of resources on this page

Examples:

Count the resources on a page

page.size # => 100

Returns:

  • (Integer) —

    the number of resources



99
# File 'x-objects/lib/x/objects/page.rb', line 99

def size = items.size

#to_a ⇒ Array<Resource> Also known as: entries

The resources on this page, frozen, as #items returns them

Examples:

Get the resources on a page as an Array

page.to_a

Returns:



89
# File 'x-objects/lib/x/objects/page.rb', line 89

def to_a = items

#to_h {|resource| ... } ⇒ Hash

This page as a Hash in the shape of its response, or of a pair for each resource

Without a block it is #as_json, as the to_h of a resource is its attributes, rather than the to_h of Enumerable, which raises TypeError for resources that are not pairs. With a block it is the to_h of Enumerable, which builds a Hash of the pair the block returns for each resource.

Examples:

Get the page in the shape of its response

page.to_h # => {"data" => [{"id" => "7505382"}], "meta" => {"next_token" => "abc"}}

Index the users of a page by username

page.to_h { |user| [user.username, user] }

Yield Parameters:

Yield Returns:

  • (Array(Object, Object)) —

    the key and value of the resource

Returns:

  • (Hash) —

    the data, meta, and errors of the page, frozen, or the pairs the block returns



225
# File 'x-objects/lib/x/objects/page.rb', line 225

def to_h(&block) = block ? super() : as_json

#to_json(state = nil) ⇒ String

This page as a JSON object in the shape of the response it came from

Examples:

Serialize a page

page.to_json # => "{\"data\":[{\"id\":\"7505382\"}],\"meta\":{}}"

Parameters:

  • state (JSON::State, nil) (defaults to: nil) —

    the state a JSON encoder passes, which the attributes are given

Returns:

  • (String) —

    the data, meta, and errors of the page as a JSON object



234
# File 'x-objects/lib/x/objects/page.rb', line 234

def to_json(state = nil) = as_json.to_json(state)