cache metadata
[arvados.git] / app / models / orvos_base.rb
1 class OrvosBase < ActiveRecord::Base
2   self.abstract_class = true
3
4   @@orvos_v1_base = Rails.configuration.orvos_v1_base
5   def self.columns
6     return @columns unless @columns.nil?
7     @columns = []
8     return @columns if orvos_schema[self.to_s.to_sym].nil?
9     orvos_schema[self.to_s.to_sym].each do |coldef|
10       k = coldef[:name].to_sym
11       if coldef[:type] == coldef[:type].downcase
12         @columns << column(k, coldef[:type].to_sym)
13       else
14         @columns << column(k, :text)
15         serialize k, coldef[:type].constantize
16       end
17       attr_accessible k
18     end
19     attr_reader :etag
20     attr_reader :kind
21     @columns
22   end
23   def self.column(name, sql_type = nil, default = nil, null = true)
24     ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
25   end
26   def self.all
27     unpack_api_response(api(''))
28   end
29   def self.find(uuid)
30     new(api('/' + uuid))
31   end
32   def self.where(cond)
33     all.select do |o|
34       0 == cond.select do |k,v|
35         o.send(k) != v
36       end.size
37     end
38   end
39   def save
40     obdata = {}
41     self.class.columns.each do |col|
42       obdata[col.name.to_sym] = self.send(col.name.to_sym)
43     end
44     obdata.delete :id
45     obdata.delete :uuid
46     postdata = { self.class.to_s.underscore => obdata }
47     if etag
48       postdata['_method'] = 'PUT'
49       resp = self.class.api('/' + uuid, postdata)
50     else
51       resp = self.class.api('', postdata)
52     end
53     return false if !resp[:etag] || !resp[:uuid]
54     @etag = resp[:etag]
55     @kind = resp[:kind]
56     self.uuid ||= resp[:uuid]
57     self
58   end
59   def save!
60     self.save or raise Exception.new("Save failed")
61   end
62   def initialize(h={})
63     @etag = h.delete :etag
64     @kind = h.delete :kind
65     super
66   end
67   def metadata(*args)
68     o = {}
69     o.merge!(args.pop) if args[-1].is_a? Hash
70     o[:metadata_class] ||= args.shift
71     o[:name] ||= args.shift
72     o[:head_kind] ||= args.shift
73     o[:tail_kind] = self.kind
74     o[:tail] = self.uuid
75     if all_metadata
76       return all_metadata.select do |m|
77         ok = true
78         o.each do |k,v|
79           if !v.nil?
80             test_v = m.send(k)
81             if (v.respond_to?(:uuid) ? v.uuid : v.to_s) != (test_v.respond_to?(:uuid) ? test_v.uuid : test_v.to_s)
82               ok = false
83             end
84           end
85         end
86         ok
87       end
88     end
89     @metadata = self.class.api '', { _method: 'GET', where: o, eager: true }, { resource_path: 'metadata' }
90     @metadata = self.class.unpack_api_response(@metadata)
91   end
92   def all_metadata
93     return @all_metadata if @all_metadata
94     res = self.class.api '', {
95       _method: 'GET',
96       where: {
97         tail_kind: self.kind,
98         tail: self.uuid
99       },
100       eager: true
101     }, {
102       resource_path: 'metadata'
103     }
104     @all_metadata = self.class.unpack_api_response(res)
105   end
106   def reload
107     raise "No such object" if !uuid
108     api('/' + uuid).each do |k,v|
109       self.instance_variable_set('@' + k.to_s, v)
110     end
111     @all_metadata = nil
112   end
113
114   protected
115   def self.api(action, data=nil, o={})
116     dataargs = []
117     if !data.nil?
118       data.each do |k,v|
119         dataargs << '-d'
120         if v.is_a? String or v.nil?
121           dataargs << "#{k}=#{v}"
122         elsif v == true or v == false
123           dataargs << "#{k}=#{v ? 1 : 0}"
124         else
125           dataargs << "#{k}=#{JSON.generate v}"
126         end
127       end
128     end
129     json = nil
130     IO.popen([ENV,
131               'curl',
132               '-sk',
133               *dataargs,
134               "#{@@orvos_v1_base}/#{o[:resource_path] || self.to_s.underscore.pluralize}#{action}"],
135              'r') do |io|
136       json = io.read
137     end
138     resp = JSON.parse json, :symbolize_names => true
139     if resp[:errors]
140       raise "API errors:\n#{resp[:errors].join "\n"}\n"
141     end
142     resp
143   end
144
145   def self.orvos_schema
146     $orvos_schema ||= api '', nil, {resource_path: 'schema'}
147   end
148
149   def self.kind_class(kind)
150     kind.match(/^orvos\#(.+?)(_list|List)?$/)[1].pluralize.classify.constantize rescue nil
151   end
152
153   def self.unpack_api_response(j, kind=nil)
154     if j.is_a? Hash and j[:items].is_a? Array and j[:kind].match(/(_list|List)$/)
155       j[:items].collect { |x| unpack_api_response x, j[:kind] }
156     elsif j.is_a? Hash and (kind || j[:kind])
157       oclass = self.kind_class(kind || j[:kind])
158       if oclass
159         j.keys.each do |k|
160           childkind = j["#{k.to_s}_kind".to_sym]
161           if childkind
162             j[k] = self.unpack_api_response(j[k], childkind)
163           end
164         end
165         oclass.new(j)
166       else
167         j
168       end
169     else
170       j
171     end
172   end
173 end