@@ -6,6 +6,8 @@ module ZendeskAPI
66 # Represents a collection of resources. Lazily loaded, resources aren't
77 # actually fetched until explicitly needed (e.g. #each, {#fetch}).
88 class Collection
9+ DEFAULT_PAGE_SIZE = 100
10+
911 include ZendeskAPI ::Sideloading
1012
1113 # Options passed in that are automatically converted from an array to a comma-separated list.
@@ -186,17 +188,8 @@ def fetch!(reload = false)
186188 elsif association && association . options . parent && association . options . parent . new_record?
187189 return ( @resources = [ ] )
188190 end
189- path_query_link = ( @query || path )
190-
191- @response = get_response ( path_query_link )
192-
193- if path_query_link == "search/export"
194- handle_cursor_response ( @response . body )
195- else
196- handle_response ( @response . body )
197- end
198191
199- @resources
192+ get_resources ( @query || path )
200193 end
201194
202195 def fetch ( *args )
@@ -252,10 +245,12 @@ def replace(collection)
252245 # * If there is a next_page url cached, it executes a fetch on that url and returns the results.
253246 # * Otherwise, returns an empty array.
254247 def next
255- if @options [ "page" ]
248+ if @options [ "page" ] && ! cbp_request?
256249 clear_cache
257- @options [ "page" ] += 1
250+ @options [ "page" ] = @options [ "page" ] . to_i + 1
258251 elsif ( @query = @next_page )
252+ # Send _only_ url param "?after=token" to get the next page
253+ @options . page &.delete ( "before" )
259254 fetch ( true )
260255 else
261256 clear_cache
@@ -268,10 +263,12 @@ def next
268263 # * If there is a prev_page url cached, it executes a fetch on that url and returns the results.
269264 # * Otherwise, returns an empty array.
270265 def prev
271- if @options [ "page" ] && @options [ "page" ] > 1
266+ if ! cbp_request? && @options [ "page" ] . to_i > 1
272267 clear_cache
273268 @options [ "page" ] -= 1
274269 elsif ( @query = @prev_page )
270+ # Send _only_ url param "?before=token" to get the prev page
271+ @options . page &.delete ( "after" )
275272 fetch ( true )
276273 else
277274 clear_cache
@@ -327,21 +324,17 @@ def to_param
327324 end
328325
329326 def more_results? ( response )
330- response [ "meta" ] . present? && response [ "results" ] . present?
327+ Helpers . present? ( response [ "meta" ] ) && response [ "meta" ] [ "has_more" ]
331328 end
332329 alias_method :has_more_results? , :more_results? # For backward compatibility with 1.33.0 and 1.34.0
333330
334- def get_response_body ( link )
335- @client . connection . send ( "get" , link ) . body
336- end
337-
338331 def get_next_page_data ( original_response_body )
339332 link = original_response_body [ "links" ] [ "next" ]
340-
333+ result_key = @resource_class . model_key || "results"
341334 while link
342- response = get_response_body ( link )
335+ response = @client . connection . send ( "get" , link ) . body
343336
344- original_response_body [ "results" ] = original_response_body [ "results" ] + response [ "results" ]
337+ original_response_body [ result_key ] = original_response_body [ result_key ] + response [ result_key ]
345338
346339 link = response [ "meta" ] [ "has_more" ] ? response [ "links" ] [ "next" ] : nil
347340 end
@@ -351,17 +344,69 @@ def get_next_page_data(original_response_body)
351344
352345 private
353346
347+ def cbp_response? ( body )
348+ !!( body [ "meta" ] && body [ "links" ] )
349+ end
350+
351+ def cbp_request?
352+ @options [ "page" ] . is_a? ( Hash )
353+ end
354+
355+ def intentional_obp_request?
356+ Helpers . present? ( @options [ "page" ] ) && !cbp_request?
357+ end
358+
359+ def get_resources ( path_query_link )
360+ if intentional_obp_request?
361+ warn "Offset Based Pagination will be deprecated soon"
362+ elsif @next_page . nil?
363+ @options_per_page_was = @options . delete ( "per_page" )
364+ # Default to CBP by using the page param as a map
365+ @options . page = { size : ( @options_per_page_was || DEFAULT_PAGE_SIZE ) }
366+ end
367+
368+ begin
369+ # Try CBP first, unless the user explicitly asked for OBP
370+ @response = get_response ( path_query_link )
371+ rescue ZendeskAPI ::Error ::NetworkError => e
372+ raise e if intentional_obp_request?
373+ # Fallback to OBP if CBP didn't work, using per_page param
374+ @options . per_page = @options_per_page_was
375+ @options . page = nil
376+ @response = get_response ( path_query_link )
377+ end
378+
379+ # Keep pre-existing behaviour for search/export
380+ if path_query_link == "search/export"
381+ handle_search_export_response ( @response . body )
382+ else
383+ handle_response ( @response . body )
384+ end
385+ end
386+
354387 def set_page_and_count ( body )
355388 @count = ( body [ "count" ] || @resources . size ) . to_i
356- @next_page , @prev_page = body [ "next_page" ] , body [ "previous_page" ]
389+ @next_page , @prev_page = page_links ( body )
357390
358- if @next_page =~ /page=(\d +)/
391+ if cbp_response? ( body )
392+ # We'll delete the one we don't need in #next or #prev
393+ @options [ "page" ] [ "after" ] = body [ "meta" ] [ "after_cursor" ]
394+ @options [ "page" ] [ "before" ] = body [ "meta" ] [ "before_cursor" ]
395+ elsif @next_page =~ /page=(\d +)/
359396 @options [ "page" ] = $1. to_i - 1
360397 elsif @prev_page =~ /page=(\d +)/
361398 @options [ "page" ] = $1. to_i + 1
362399 end
363400 end
364401
402+ def page_links ( body )
403+ if body [ "meta" ] && body [ "links" ]
404+ [ body [ "links" ] [ "next" ] , body [ "links" ] [ "prev" ] ]
405+ else
406+ [ body [ "next_page" ] , body [ "previous_page" ] ]
407+ end
408+ end
409+
365410 def _all ( start_page = @options [ "page" ] , bang = false , &block )
366411 raise ( ArgumentError , "must pass a block" ) unless block
367412
@@ -370,13 +415,7 @@ def _all(start_page = @options["page"], bang = false, &block)
370415
371416 while ( bang ? fetch! : fetch )
372417 each do |resource |
373- arguments = [ resource , @options [ "page" ] || 1 ]
374-
375- if block . arity >= 0
376- arguments = arguments . take ( block . arity )
377- end
378-
379- block . call ( *arguments )
418+ block . call ( resource , @options [ "page" ] || 1 )
380419 end
381420
382421 last_page? ? break : self . next
@@ -422,7 +461,7 @@ def set_association_from_options
422461
423462 def get_response ( path )
424463 @error = nil
425- @response = @ client. connection . send ( @verb || "get" , path ) do |req |
464+ @client . connection . send ( @verb || "get" , path ) do |req |
426465 opts = @options . delete_if { |_ , v | v . nil? }
427466
428467 req . params . merge! ( :include => @includes . join ( "," ) ) if @includes . any?
@@ -435,43 +474,39 @@ def get_response(path)
435474 end
436475 end
437476
438- def handle_cursor_response ( response_body )
439- unless response_body . is_a? ( Hash )
440- raise ZendeskAPI ::Error ::NetworkError , @response . env
441- end
477+ def handle_search_export_response ( response_body )
478+ assert_valid_response_body ( response_body )
442479
480+ # Note this doesn't happen in #handle_response
443481 response_body = get_next_page_data ( response_body ) if more_results? ( response_body )
444482
445483 body = response_body . dup
446484 results = body . delete ( @resource_class . model_key ) || body . delete ( "results" )
447485
448- unless results
449- raise ZendeskAPI ::Error ::ClientError , "Expected #{ @resource_class . model_key } or 'results' in response keys: #{ body . keys . inspect } "
450- end
486+ assert_results ( results , body )
451487
452488 @resources = results . map do |res |
453489 wrap_resource ( res )
454490 end
455491 end
456492
493+ # For both CBP and OBP
457494 def handle_response ( response_body )
458- unless response_body . is_a? ( Hash )
459- raise ZendeskAPI ::Error ::NetworkError , @response . env
460- end
495+ assert_valid_response_body ( response_body )
461496
462497 body = response_body . dup
463498 results = body . delete ( @resource_class . model_key ) || body . delete ( "results" )
464499
465- unless results
466- raise ZendeskAPI ::Error ::ClientError , "Expected #{ @resource_class . model_key } or 'results' in response keys: #{ body . keys . inspect } "
467- end
500+ assert_results ( results , body )
468501
469502 @resources = results . map do |res |
470503 wrap_resource ( res )
471504 end
472505
473506 set_page_and_count ( body )
474507 set_includes ( @resources , @includes , body )
508+
509+ @resources
475510 end
476511
477512 # Simplified Associations#wrap_resource
@@ -514,5 +549,16 @@ def collection_method(name, *args, &block)
514549 def resource_methods
515550 @resource_methods ||= @resource_class . singleton_methods ( false ) . map ( &:to_sym )
516551 end
552+
553+ def assert_valid_response_body ( response_body )
554+ unless response_body . is_a? ( Hash )
555+ raise ZendeskAPI ::Error ::NetworkError , @response . env
556+ end
557+ end
558+
559+ def assert_results ( results , body )
560+ return if results
561+ raise ZendeskAPI ::Error ::ClientError , "Expected #{ @resource_class . model_key } or 'results' in response keys: #{ body . keys . inspect } "
562+ end
517563 end
518564end
0 commit comments