2

Before anything, I have read all the answers of Why doesn't Ruby support i++ or i—? and understood why. Please note that this is not just another discussion topic about whether to have it or not.

What I'm really after is a more elegant solution for the situation that made me wonder and research about ++/-- in Ruby. I've looked up loops, each, each_with_index and things alike but I couldn't find a better solution for this specific situation.

Less talk, more code:

# Does the first request to Zendesk API, fetching *first page* of results
all_tickets = zd_client.tickets.incremental_export(1384974614)

# Initialises counter variable (please don't kill me for this, still learning! :D )
counter = 1

# Loops result pages
loop do

  # Loops each ticket on the paged result
  all_tickets.all do |ticket, page_number|
    # For debug purposes only, I want to see an incremental by each ticket
    p "#{counter} P#{page_number} #{ticket.id} - #{ticket.created_at} | #{ticket.subject}"
    counter += 1
  end

  # Fetches next page, if any
  all_tickets.next unless all_tickets.last_page?

  # Breaks outer loop if last_page?
  break if all_tickets.last_page?

end

For now, I need counter for debug purposes only - it's not a big deal at all - but my curiosity typed this question itself: is there a better (more beautiful, more elegant) solution for this? Having a whole line just for counter += 1 seems pretty dull. Just as an example, having "#{counter++}" when printing the string would be much simpler (for readability sake, at least).

I can't simply use .each's index because it's a nested loop, and it would reset at each page (outer loop).

Any thoughts?

BTW: This question has nothing to do with Zendesk API whatsoever. I've just used it to better illustrate my situation.

Community
  • 1
  • 1
mathielo
  • 6,725
  • 7
  • 50
  • 63

4 Answers4

1

To me, counter += 1 is a fine way to express incrementing the counter.

You can start your counter at 0 and then get the effect you wanted by writing:

p "#{counter += 1} ..."

But I generally wouldn't recommend this because people do not expect side effects like changing a variable to happen inside string interpolation.

If you are looking for something more elegant, you should make an Enumerator that returns integers one at a time, each time you call next on the enumerator.

nums = Enumerator.new do |y|
  c = 0
  y << (c += 1) while true
end

nums.next  # => 1
nums.next  # => 2
nums.next  # => 3

Instead of using Enumerator.new in the code above, you could just write:

nums = 1.upto(Float::INFINITY)
David Grayson
  • 84,103
  • 24
  • 152
  • 189
0
all_tickets.each_with_index(1) do |ticket, i|

I'm not sure where page_number is coming from...

See Ruby Docs.

B Seven
  • 44,484
  • 66
  • 240
  • 385
  • `all_tickets.next` fetches the next page of results, automatically assigning it to `all_tickets`, therefore the need to loop over it again. – mathielo May 26 '15 at 21:37
  • Sorry @B Seven, I've mistaken `.each` with `.all` while testing/debuging and writing the question. The actual method that loops through the tickets is `.all`. I've updated the question. – mathielo May 26 '15 at 21:44
  • I think the issue is not that Ruby needs `++` or `--`. The clunkiness of `counter = 1` and `counter += 1` comes from the fact that the design is not clean. This code has two responsibilities: getting the tickets and printing them. I would use one loop to get all the tickets, and other to print them. – B Seven May 26 '15 at 21:45
  • Considering that each result page comes with 1k tickets resulting in somewhat ~7mb of raw text data **per page**, I'm not sure it would be wise to sum that all up in memory to loop it all at once later. In my case, I'll have less than 20 pages, but still.. – mathielo May 26 '15 at 21:48
  • But I agree with _the design is not clean_. That's actually what I'm trying to improve, just couldn't figure out _how_ yet. – mathielo May 26 '15 at 21:50
  • 1
    Maybe you can do `p "#{page_number * TICKETS_PER_PAGE + i} P#{page_number} ...` Then you can use `each_with_index`. – B Seven May 26 '15 at 22:00
  • Mmhh interesting! That way would eliminate the need of `counter` as well! I like it. I'll try using `each_with_index` instead of `all` and post a feedback later. – mathielo May 26 '15 at 22:24
0

As mentioned by B Seven each_with_index will work, but you can keep the page_number, as long all_tickets is a container of tuples as it must be to be working right now.

all_tickets.each_with_index do |ticket, page_number, i|
   #stuff
end

Where i is the index. If you have more than ticket and page_number inside each element of all_tickets you continue putting them, just remember that the index is the extra one and shall stay in the end.

Pedro Ivan
  • 322
  • 1
  • 14
  • Sorry @Pedro Ivan, I've mistaken `.each` with `.all` while testing/debuging and writing the question. The actual method that loops through the tickets is `.all`. I've updated the question. – mathielo May 26 '15 at 21:44
  • But still, the `each_with_index`'s index would get reset each time the outer loop _loops_. – mathielo May 26 '15 at 21:45
  • I cannot test it right now, but try adding an all.each_with_index since .all returns a collection. – Pedro Ivan May 26 '15 at 21:51
  • `[...] /gems/zendesk_api-1.7.4/lib/zendesk_api/collection.rb:317:in '_all': must pass a block (ArgumentError)`. No can do. – mathielo May 26 '15 at 21:54
  • needs a block... had you tryied this? all_tickets.all.each_with_index do |ticket, page_number, i| – Pedro Ivan May 26 '15 at 21:56
  • Yes, I didn't remove the block. I'll try a few other options with `each_with_index` and post back. – mathielo May 26 '15 at 22:29
  • I can't understand the all_tickets.next after you had iterated all of them (unless they keep coming, I really don't know zendesk). If you don't need the count inside the each, get the size of all_tickets outside, stills as one line, but in execution will only be done one time at each loop. – Pedro Ivan May 27 '15 at 01:23
0

Could be I oversimplified your example but you could calculate a counter from your inner and outer range like this.

all_tickets = *(1..10)
inner_limit = all_tickets.size
outer_limit = 5000
1.upto(outer_limit) do |outer_counter|
  all_tickets.each_with_index do |ticket, inner_counter|
    p [(outer_counter*inner_limit)+inner_counter, outer_counter, inner_counter, ticket]
  end
  # some conditional to break out, in your case the last_page? method
  break if outer_counter > 3
end
peter
  • 41,770
  • 5
  • 64
  • 108