3400: Do not fetch API results just for the sake of looking up resource_class.
[arvados.git] / apps / workbench / app / models / arvados_resource_list.rb
1 class ArvadosResourceList
2   include ArvadosApiClientHelper
3   include Enumerable
4
5   attr_reader :resource_class
6
7   def initialize resource_class=nil
8     @resource_class = resource_class
9     @fetch_multiple_pages = true
10     @arvados_api_token = Thread.current[:arvados_api_token]
11     @reader_tokens = Thread.current[:reader_tokens]
12   end
13
14   def eager(bool=true)
15     @eager = bool
16     self
17   end
18
19   def limit(max_results)
20     if not max_results.nil? and not max_results.is_a? Integer
21       raise ArgumentError("argument to limit() must be an Integer or nil")
22     end
23     @limit = max_results
24     self
25   end
26
27   def offset(skip)
28     @offset = skip
29     self
30   end
31
32   def order(orderby_spec)
33     @orderby_spec = orderby_spec
34     self
35   end
36
37   def select(columns=nil)
38     # If no column arguments were given, invoke Enumerable#select.
39     if columns.nil?
40       super()
41     else
42       @select ||= []
43       @select += columns
44       self
45     end
46   end
47
48   def filter _filters
49     @filters ||= []
50     @filters += _filters
51     self
52   end
53
54   def where(cond)
55     @cond = cond.dup
56     @cond.keys.each do |uuid_key|
57       if @cond[uuid_key] and (@cond[uuid_key].is_a? Array or
58                              @cond[uuid_key].is_a? ArvadosBase)
59         # Coerce cond[uuid_key] to an array of uuid strings.  This
60         # allows caller the convenience of passing an array of real
61         # objects and uuids in cond[uuid_key].
62         if !@cond[uuid_key].is_a? Array
63           @cond[uuid_key] = [@cond[uuid_key]]
64         end
65         @cond[uuid_key] = @cond[uuid_key].collect do |item|
66           if item.is_a? ArvadosBase
67             item.uuid
68           else
69             item
70           end
71         end
72       end
73     end
74     @cond.keys.select { |x| x.match /_kind$/ }.each do |kind_key|
75       if @cond[kind_key].is_a? Class
76         @cond = @cond.merge({ kind_key => 'arvados#' + arvados_api_client.class_kind(@cond[kind_key]) })
77       end
78     end
79     self
80   end
81
82   def fetch_multiple_pages(f)
83     @fetch_multiple_pages = f
84     self
85   end
86
87   def results
88     if !@results
89       @results = []
90       self.each_page do |r|
91         @results.concat r
92       end
93     end
94     @results
95   end
96
97   def results=(r)
98     @results = r
99     @items_available = r.items_available if r.respond_to? :items_available
100     @result_limit = r.limit if r.respond_to? :limit
101     @result_offset = r.offset if r.respond_to? :offset
102     @results
103   end
104
105   def to_ary
106     results
107   end
108
109   def each(&block)
110     if not @results.nil?
111       @results.each &block
112     else
113       self.each_page do |items|
114         items.each do |i|
115           block.call i
116         end
117       end
118     end
119     self
120   end
121
122   def first
123     results.first
124   end
125
126   def last
127     results.last
128   end
129
130   def [](*x)
131     results.send('[]', *x)
132   end
133
134   def |(x)
135     if x.is_a? Hash
136       self.to_hash | x
137     else
138       results | x.to_ary
139     end
140   end
141
142   def to_hash
143     Hash[self.collect { |x| [x.uuid, x] }]
144   end
145
146   def empty?
147     self.first.nil?
148   end
149
150   def items_available
151     @items_available
152   end
153
154   def result_limit
155     @result_limit
156   end
157
158   def result_offset
159     @result_offset
160   end
161
162   # Obsolete method retained during api transition.
163   def links_for item_or_uuid, link_class=false
164     []
165   end
166
167   protected
168
169   def each_page
170     api_params = {
171       _method: 'GET'
172     }
173     api_params[:where] = @cond if @cond
174     api_params[:eager] = '1' if @eager
175     api_params[:select] = @select if @select
176     api_params[:order] = @orderby_spec if @orderby_spec
177     api_params[:filters] = @filters if @filters
178
179
180     item_count = 0
181     offset = @offset || 0
182     @result_limit = nil
183     @result_offset = nil
184
185     begin
186       api_params[:offset] = offset
187       api_params[:limit] = (@limit - item_count) if @limit
188
189       res = arvados_api_client.api(@resource_class, '', api_params,
190                                    arvados_api_token: @arvados_api_token,
191                                    reader_tokens: @reader_tokens)
192       items = arvados_api_client.unpack_api_response res
193
194       break if items.nil? or not items.any?
195
196       @items_available = items.items_available if items.respond_to?(:items_available)
197       @result_limit = items.limit if (@fetch_multiple_pages == false) and items.respond_to?(:limit)
198       @result_offset = items.offset if (@fetch_multiple_pages == false) and items.respond_to?(:offset)
199
200       item_count += items.size
201       if items.respond_to?(:offset)
202         offset = items.offset + items.size
203       else
204         offset = item_count
205       end
206
207       yield items
208
209       break if @limit and item_count >= @limit
210       break if items.respond_to? :items_available and offset >= items.items_available
211     end while @fetch_multiple_pages
212     self
213   end
214
215 end