Class: X::Page

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
x-resources/lib/x/resources/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



66
67
68
69
70
71
# File 'x-resources/lib/x/resources/page.rb', line 66

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:



35
36
37
# File 'x-resources/lib/x/resources/page.rb', line 35

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



53
54
55
# File 'x-resources/lib/x/resources/page.rb', line 53

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



46
47
48
# File 'x-resources/lib/x/resources/page.rb', line 46

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



144
# File 'x-resources/lib/x/resources/page.rb', line 144

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



122
# File 'x-resources/lib/x/resources/page.rb', line 122

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



209
210
211
212
213
# File 'x-resources/lib/x/resources/page.rb', line 209

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



80
81
82
83
84
85
# File 'x-resources/lib/x/resources/page.rb', line 80

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



113
# File 'x-resources/lib/x/resources/page.rb', line 113

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



320
# File 'x-resources/lib/x/resources/page.rb', line 320

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



153
# File 'x-resources/lib/x/resources/page.rb', line 153

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:



330
# File 'x-resources/lib/x/resources/page.rb', line 330

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



131
# File 'x-resources/lib/x/resources/page.rb', line 131

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



284
285
286
287
# File 'x-resources/lib/x/resources/page.rb', line 284

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:



301
302
303
304
305
306
307
# File 'x-resources/lib/x/resources/page.rb', line 301

def marshal_load(state)
  format, resources, meta, problems, responses = state
  raise UnsupportedFormat, "#{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



164
165
166
167
# File 'x-resources/lib/x/resources/page.rb', line 164

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



179
180
181
182
# File 'x-resources/lib/x/resources/page.rb', line 179

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:



191
192
193
# File 'x-resources/lib/x/resources/page.rb', line 191

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



103
# File 'x-resources/lib/x/resources/page.rb', line 103

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:



93
# File 'x-resources/lib/x/resources/page.rb', line 93

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



229
# File 'x-resources/lib/x/resources/page.rb', line 229

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



238
# File 'x-resources/lib/x/resources/page.rb', line 238

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