3400: ArvadosResourceList returns all items (paging as necessary) unless limit()...
[arvados.git] / apps / workbench / app / models / arvados_resource_list.rb
1 class ArvadosResourceList
2   include ArvadosApiClientHelper
3   include Enumerable
4
5   def initialize resource_class=nil
6     @resource_class = resource_class
7     @fetch_multiple_pages = true
8   end
9
10   def eager(bool=true)
11     @eager = bool
12     self
13   end
14
15   def limit(max_results)
16     @limit = max_results
17     self
18   end
19
20   def offset(skip)
21     @offset = skip
22     self
23   end
24
25   def order(orderby_spec)
26     @orderby_spec = orderby_spec
27     self
28   end
29
30   def select(columns=nil)
31     # If no column arguments were given, invoke Enumerable#select.
32     if columns.nil?
33       super()
34     else
35       @select ||= []
36       @select += columns
37       self
38     end
39   end
40
41   def filter _filters
42     @filters ||= []
43     @filters += _filters
44     self
45   end
46
47   def where(cond)
48     @cond = cond.dup
49     @cond.keys.each do |uuid_key|
50       if @cond[uuid_key] and (@cond[uuid_key].is_a? Array or
51                              @cond[uuid_key].is_a? ArvadosBase)
52         # Coerce cond[uuid_key] to an array of uuid strings.  This
53         # allows caller the convenience of passing an array of real
54         # objects and uuids in cond[uuid_key].
55         if !@cond[uuid_key].is_a? Array
56           @cond[uuid_key] = [@cond[uuid_key]]
57         end
58         @cond[uuid_key] = @cond[uuid_key].collect do |item|
59           if item.is_a? ArvadosBase
60             item.uuid
61           else
62             item
63           end
64         end
65       end
66     end
67     @cond.keys.select { |x| x.match /_kind$/ }.each do |kind_key|
68       if @cond[kind_key].is_a? Class
69         @cond = @cond.merge({ kind_key => 'arvados#' + arvados_api_client.class_kind(@cond[kind_key]) })
70       end
71     end
72     self
73   end
74
75   def fetch_multiple_pages(f)
76     @fetch_multiple_pages = f
77     self
78   end
79
80   def results
81     if !@results
82       @results = []
83       self.each_page do |r|
84         @results.concat r
85       end
86     end
87     @results
88   end
89
90   def results=(r)
91     @results = r
92     @items_available = r.items_available if r.respond_to? :items_available
93     @result_limit = r.limit if r.respond_to? :limit
94     @result_offset = r.offset if r.respond_to? :offset
95     @result_links = r.links if r.respond_to? :links
96     @results
97   end
98
99   def all
100     results
101     self
102   end
103
104   def to_ary
105     results
106   end
107
108   def each_page
109     api_params = {
110       _method: 'GET'
111     }
112     api_params[:where] = @cond if @cond
113     api_params[:eager] = '1' if @eager
114     api_params[:limit] = @limit if @limit
115     api_params[:select] = @select if @select
116     api_params[:order] = @orderby_spec if @orderby_spec
117     api_params[:filters] = @filters if @filters
118
119     item_count = 0
120
121     if @offset
122       offset = @offset
123     else
124       offset = 0
125     end
126
127     if @limit.is_a? Integer
128       items_to_get = @limit
129     else
130       items_to_get = 1000000000
131     end
132
133     begin
134       api_params[:offset] = offset
135
136       res = arvados_api_client.api @resource_class, '', api_params
137       items = arvados_api_client.unpack_api_response res
138
139       if items.nil? or items.size == 0
140         break
141       end
142
143       @items_available = items.items_available if items.respond_to? :items_available
144       @result_limit = items.limit
145       @result_offset = items.offset
146       @result_links = items.links if items.respond_to? :links
147
148       item_count += items.size
149
150       if items.respond_to? :items_available and
151           (@limit.nil? or (@limit.is_a? Integer and  @limit > items.items_available))
152         items_to_get = items.items_available
153       end
154
155       offset = items.offset + items.size
156
157       yield items
158
159     end while @fetch_multiple_pages and item_count < items_to_get
160     self
161   end
162
163   def each(&block)
164     if not @results.nil?
165       @results.each &block
166     else
167       self.each_page do |items|
168         items.each do |i|
169           block.call i
170         end
171       end
172     end
173     self
174   end
175
176   def last
177     results.last
178   end
179
180   def [](*x)
181     results.send('[]', *x)
182   end
183
184   def |(x)
185     if x.is_a? Hash
186       self.to_hash | x
187     else
188       results | x.to_ary
189     end
190   end
191
192   def to_hash
193     Hash[self.collect { |x| [x.uuid, x] }]
194   end
195
196   def empty?
197     self.first.nil?
198   end
199
200   def items_available
201     @items_available
202   end
203
204   def result_limit
205     @result_limit
206   end
207
208   def result_offset
209     @result_offset
210   end
211
212   def result_links
213     @result_links
214   end
215
216   # Return links provided with API response that point to the
217   # specified object, and have the specified link_class. If link_class
218   # is false or omitted, return all links pointing to the specified
219   # object.
220   def links_for item_or_uuid, link_class=false
221     return [] if !result_links
222     unless @links_for_uuid
223       @links_for_uuid = {}
224       result_links.each do |link|
225         if link.respond_to? :head_uuid
226           @links_for_uuid[link.head_uuid] ||= []
227           @links_for_uuid[link.head_uuid] << link
228         end
229       end
230     end
231     if item_or_uuid.respond_to? :uuid
232       uuid = item_or_uuid.uuid
233     else
234       uuid = item_or_uuid
235     end
236     (@links_for_uuid[uuid] || []).select do |link|
237       link_class == false or link.link_class == link_class
238     end
239   end
240
241   # Note: this arbitrarily chooses one of (possibly) multiple names.
242   def name_for item_or_uuid
243     links_for(item_or_uuid, 'name').first.andand.name
244   end
245
246 end