Skip to content

Commit

Permalink
Merge branch 'new-paginated-resource'
Browse files Browse the repository at this point in the history
See ably/ably-js#28 where the new API is discussed for PaginatedResource.
  • Loading branch information
mattheworiordan committed Apr 13, 2015
2 parents a903481 + 1863ce7 commit 069edce
Show file tree
Hide file tree
Showing 15 changed files with 253 additions and 246 deletions.
47 changes: 29 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,13 @@ channel.publish('greeting', 'Hello World!')
### Querying the History

```ruby
channel.history do |messages|
messages # Ably::Models::PaginatedResource
messages.first # Ably::Models::Message
messages.length # number of messages in the retrieved history page
messages.next_page # Ably::Models::PaginatedResource
channel.history do |messages_page|
messages_page #=> #<Ably::Models::PaginatedResource ...>
messages_page.items.first # #<Ably::Models::Message ...>
messages_page.items.first.data # payload for the message
messages_page.items.length # number of messages in the current page of history
messages_page.next # retrieves the next page => #<Ably::Models::PaginatedResource ...>
messages_page.has_next? # false, there are more pages
end
```

Expand All @@ -117,11 +119,11 @@ end
### Querying the Presence History

```ruby
channel.presence.history do |presence_messages|
presence_messages.first.action # Any of :enter, :update or :leave
presence_messages.first.client_id
presence_messages.first.data
presence_messages.next_page # Ably::Models::PaginatedResource
channel.presence.history do |presence_page|
presence_page.items.first.action # Any of :enter, :update or :leave
presence_page.items.first.client_id # client ID of member
presence_page.items.first.data # optional data payload of member
presence_page.next # retrieves the next page => #<Ably::Models::PaginatedResource ...>
end
```

Expand All @@ -147,22 +149,30 @@ channel.publish('myEvent', 'Hello!') #=> true
### Querying the History

```ruby
mesage_history = channel.history #=> #<Ably::Models::PaginatedResource ...>
message_history.first # => #<Ably::Models::Message ...>
messages_page = channel.history #=> #<Ably::Models::PaginatedResource ...>
messages_page.items.first #=> #<Ably::Models::Message ...>
messages_page.items.first.data # payload for the message
messages_page.next # retrieves the next page => #<Ably::Models::PaginatedResource ...>
messages_page.has_next? # false, there are more pages
```

### Presence on a channel

```ruby
members = channel.presence.get # => #<Ably::Models::PaginatedResource ...>
members.first # => #<Ably::Models::PresenceMessage ...>
members_page = channel.presence.get # => #<Ably::Models::PaginatedResource ...>
members_page.items.first # first member present in this page => #<Ably::Models::PresenceMessage ...>
members_page.items.first.client_id # client ID of first member present
members_page.next # retrieves the next page => #<Ably::Models::PaginatedResource ...>
members_page.has_next? # false, there are more pages
```

### Querying the Presence History

```ruby
presence_history = channel.presence.history # => #<Ably::Models::PaginatedResource ...>
presence_history.first # => #<Ably::Models::PresenceMessage ...>
presence_page = channel.presence.history #=> #<Ably::Models::PaginatedResource ...>
presence_page.items.first #=> #<Ably::Models::PresenceMessage ...>
presence_page.items.first.client_id # client ID of first member
presence_page.next # retrieves the next page => #<Ably::Models::PaginatedResource ...>
```

### Generate Token and Token Request
Expand All @@ -186,8 +196,9 @@ client = Ably::Rest.new(token_id: token.id)
### Fetching your application's stats

```ruby
stats = client.stats #=> #<Ably::Models::PaginatedResource ...>
stats.first = #<Ably::Models::Stat ...>
stats_page = client.stats #=> #<Ably::Models::PaginatedResource ...>
stats_page.items.first = #<Ably::Models::Stat ...>
stats_page.next # retrieves the next page => #<Ably::Models::PaginatedResource ...>
```

### Fetching the Ably service time
Expand Down
75 changes: 31 additions & 44 deletions lib/ably/models/paginated_resource.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
module Ably::Models
# Wraps any Ably HTTP response that supports paging and automatically provides methods to iterate through
# the array of resources using {#first_page}, {#next_page}, {#first_page?} and {#last_page?}
# Wraps any Ably HTTP response that supports paging and provides methods to iterate through
# the pages using {#first}, {#next}, {#first?}, {#has_next?} and {#last?}
#
# All items in the HTTP response are available in the Array returned from {#items}
#
# Paging information is provided by Ably in the LINK HTTP headers
#
class PaginatedResource
include Enumerable
include Ably::Modules::AsyncWrapper if defined?(Ably::Realtime)

# The items contained within this {PaginatedResource}
# @return [Array]
attr_reader :items

# @param [Faraday::Response] http_response Initial HTTP response from an Ably request to a paged resource
# @param [String] base_url Base URL for request that generated the http_response so that subsequent paged requests can be made
# @param [Client] client {Ably::Client} used to make the request to Ably
Expand All @@ -25,18 +31,19 @@ def initialize(http_response, base_url, client, options = {}, &each_block)
@each_block = each_block
@make_async = options.fetch(:async_blocking_operations, false)

@body = http_response.body
@body = coerce_items_into(body, @coerce_into) if @coerce_into
@body = body.map { |item| yield item } if block_given?
@items = http_response.body
@items = coerce_items_into(items, @coerce_into) if @coerce_into
@items = items.map { |item| yield item } if block_given?
end

# Retrieve the first page of results.
# When used as part of the {Ably::Realtime} library, it will return a {Ably::Util::SafeDeferrable} object,
# and allows an optional success callback block to be provided.
#
# @return [PaginatedResource,Ably::Util::SafeDeferrable]
def first_page(&success_callback)
def first(&success_callback)
async_wrap_if_realtime(success_callback) do
return nil unless supports_pagination?
PaginatedResource.new(client.get(pagination_url('first')), base_url, client, pagination_options, &each_block)
end
end
Expand All @@ -46,78 +53,58 @@ def first_page(&success_callback)
# and allows an optional success callback block to be provided.
#
# @return [PaginatedResource,Ably::Util::SafeDeferrable]
def next_page(&success_callback)
def next(&success_callback)
async_wrap_if_realtime(success_callback) do
raise Ably::Exceptions::InvalidPageError, 'There are no more pages' if supports_pagination? && last_page?
return nil unless has_next?
PaginatedResource.new(client.get(pagination_url('next')), base_url, client, pagination_options, &each_block)
end
end

# True if this is the last page in the paged resource set
#
# @return [Boolean]
def last_page?
def last?
!supports_pagination? ||
pagination_header('next').nil?
end

# True if this is the first page in the paged resource set
#
# @return [Boolean]
def first_page?
def first?
!supports_pagination? ||
pagination_header('first') == pagination_header('current')
end

# True if there is a subsequent page in this paginated set available with {#next}
#
# @return [Boolean]
def has_next?
supports_pagination? && !last?
end

# True if the HTTP response supports paging with the expected LINK HTTP headers
#
# @return [Boolean]
def supports_pagination?
!pagination_headers.empty?
end

# Standard Array accessor method
def [](index)
body[index]
end

# Returns number of items within this page, not the total number of items in the entire paged resource set
def length
body.length
end
alias_method :count, :length
alias_method :size, :length

# Method to allow {PaginatedResource} to be {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
def each(&block)
return to_enum(:each) unless block_given?
body.each(&block)
end

# First item in this page
def first
body.first
end

# Last item in this page
def last
body.last
end

def inspect
<<-EOF.gsub(/^ /, '')
#<#{self.class.name}:#{self.object_id}
@base_url="#{base_url}",
@first_page?=#{!!first_page?},
@last_page?=#{!!first_page?},
@body=
#{body.map { |item| item.inspect }.join(",\n ") }
@first?=#{!!first?},
@last?=#{!!first?},
@has_next?=#{!!has_next?},
@items=
#{items.map { |item| item.inspect }.join(",\n ") }
>
EOF
end

private
attr_reader :body, :http_response, :base_url, :client, :coerce_into, :raw_body, :each_block, :make_async
attr_reader :http_response, :base_url, :client, :coerce_into, :raw_body, :each_block, :make_async

def coerce_items_into(items, type_string)
items.map do |item|
Expand Down
2 changes: 1 addition & 1 deletion lib/ably/realtime/channel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def presence
# @param (see Ably::Rest::Channel#history)
# @option options (see Ably::Rest::Channel#history)
#
# @yield [Ably::Models::PaginatedResource<Ably::Models::Message>] An Array of {Ably::Models::Message} objects that supports paging (#next_page, #first_page)
# @yield [Ably::Models::PaginatedResource<Ably::Models::Message>] First {Ably::Models::PaginatedResource page} of {Ably::Models::Message} objects accessible with {Ably::Models::PaginatedResource#items #items}.
#
# @return [Ably::Util::SafeDeferrable]
def history(options = {}, &callback)
Expand Down
2 changes: 1 addition & 1 deletion lib/ably/realtime/presence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def unsubscribe(*actions, &callback)
# @param (see Ably::Rest::Presence#history)
# @option options (see Ably::Rest::Presence#history)
#
# @yield [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] An Array of {Ably::Models::PresenceMessage} objects that supports paging (#next_page, #first_page)
# @yield [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResource page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResource#items #items}.
#
# @return [Ably::Util::SafeDeferrable]
#
Expand Down
2 changes: 1 addition & 1 deletion lib/ably/rest/channel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def publish(name, data)
# @option options [Integer] :limit Maximum number of messages to retrieve up to 10,000
# @option options [Symbol] :by `:message`, `:bundle` or `:hour`. Defaults to `:message`
#
# @return [Ably::Models::PaginatedResource<Ably::Models::Message>] An Array of {Ably::Models::Message} objects that supports paging (#next_page, #first_page)
# @return [Ably::Models::PaginatedResource<Ably::Models::Message>] First {Ably::Models::PaginatedResource page} of {Ably::Models::Message} objects accessible with {Ably::Models::PaginatedResource#items #items}.
def history(options = {})
url = "#{base_path}/messages"
options = options.dup
Expand Down
4 changes: 2 additions & 2 deletions lib/ably/rest/presence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def initialize(client, channel)
# @option options [Symbol] :direction `:forwards` or `:backwards`
# @option options [Integer] :limit Maximum number of members to retrieve up to 10,000
#
# @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] An Array of {Ably::Models::PresenceMessage} objects that supports paging (#next_page, #first_page)
# @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResource page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResource#items #items}.
#
def get(options = {})
options = options.dup
Expand All @@ -55,7 +55,7 @@ def get(options = {})
# @option options [Symbol] :direction `:forwards` or `:backwards`
# @option options [Integer] :limit Maximum number of presence messages to retrieve up to 10,000
#
# @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] An Array of {Ably::Models::PresenceMessage} objects that supports paging (#next_page, #first_page)
# @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResource page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResource#items #items}.
#
def history(options = {})
url = "#{base_path}/history"
Expand Down
40 changes: 20 additions & 20 deletions spec/acceptance/realtime/channel_history_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
channel.publish('event', payload) do |message|
history = channel.history
expect(history).to be_a(Ably::Util::SafeDeferrable)
history.callback do |messages|
expect(messages.count).to eql(1)
expect(messages).to be_a(Ably::Models::PaginatedResource)
history.callback do |page|
expect(page.items.count).to eql(1)
expect(page).to be_a(Ably::Models::PaginatedResource)
stop_reactor
end
end
Expand All @@ -32,9 +32,9 @@
context 'with a single client publishing and receiving' do
it 'retrieves real-time history' do
channel.publish('event', payload) do |message|
channel.history do |history|
expect(history.length).to eql(1)
expect(history[0].data).to eql(payload)
channel.history do |page|
expect(page.items.length).to eql(1)
expect(page.items[0].data).to eql(payload)
stop_reactor
end
end
Expand All @@ -45,12 +45,12 @@
it 'retrieves real-time history on both channels' do
channel.publish('event', payload) do |message|
channel2.publish('event', payload) do |message|
channel.history do |history|
expect(history.length).to eql(2)
expect(history.map(&:data).uniq).to eql([payload])
channel.history do |page|
expect(page.items.length).to eql(2)
expect(page.items.map(&:data).uniq).to eql([payload])

channel2.history do |history_2|
expect(history_2.length).to eql(2)
channel2.history do |page_2|
expect(page_2.items.length).to eql(2)
stop_reactor
end
end
Expand All @@ -65,18 +65,18 @@
let(:limit) { 15 }

def ensure_message_history_direction_and_paging_is_correct(direction)
channel.history(direction: direction, limit: limit) do |history|
expect(history.length).to eql(limit)
channel.history(direction: direction, limit: limit) do |history_page|
expect(history_page.items.length).to eql(limit)
limit.times do |index|
expect(history[index].data).to eql("history#{index}")
expect(history_page.items[index].data).to eql("history#{index}")
end

history.next_page do |history|
expect(history.length).to eql(limit)
history_page.next do |next_page|
expect(next_page.items.length).to eql(limit)
limit.times do |index|
expect(history[index].data).to eql("history#{index + limit}")
expect(next_page.items[index].data).to eql("history#{index + limit}")
end
expect(history.last_page?).to eql(true)
expect(next_page).to be_last

stop_reactor
end
Expand Down Expand Up @@ -141,8 +141,8 @@ def ensure_message_history_direction_and_paging_is_correct(direction)
channel.subscribe('event') do |message|
messages << message
if messages.count == batches * messages_per_batch
channel.history do |history|
expect(history.map(&:id).sort).to eql(messages.map(&:id).sort)
channel.history do |page|
expect(page.items.map(&:id).sort).to eql(messages.map(&:id).sort)
stop_reactor
end
end
Expand Down
4 changes: 2 additions & 2 deletions spec/acceptance/realtime/message_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@
context 'when retrieved over REST' do
it 'matches the sender connection#id' do
channel.publish('event', payload) do
channel.history do |messages|
expect(messages.first.connection_id).to eql(client.connection.id)
channel.history do |page|
expect(page.items.first.connection_id).to eql(client.connection.id)
stop_reactor
end
end
Expand Down
Loading

0 comments on commit 069edce

Please sign in to comment.