Return API response objects as Models (with save() etc) instead of Hashes
[arvados.git] / sdk / ruby / lib / arvados.rb
index 51a1d403a60763562bd6c0a06c05384f2c37291f..0b004635082968e5f6784b2c65d5cf08f89739c4 100644 (file)
@@ -5,6 +5,7 @@ require 'json'
 
 ActiveSupport::Inflector.inflections do |inflect|
   inflect.irregular 'specimen', 'specimens'
+  inflect.irregular 'human', 'humans'
 end
 
 module Kernel
@@ -19,6 +20,9 @@ end
 
 class Arvados
 
+  class TransactionFailedError < StandardError
+  end
+
   @@debuglevel = 0
   class << self
     attr_accessor :debuglevel
@@ -38,9 +42,8 @@ class Arvados
       ENV['ARVADOS_API_TOKEN'] or
       raise "#{$0}: no :api_token or ENV[ARVADOS_API_TOKEN] provided."
 
-    @suppress_ssl_warnings = opts[:suppress_ssl_warnings] || false
-
-    if @suppress_ssl_warnings
+    if (opts[:api_host] ? opts[:suppress_ssl_warnings] :
+        ENV['ARVADOS_API_HOST_INSECURE'])
       suppress_warnings do
         OpenSSL::SSL.const_set 'VERIFY_PEER', OpenSSL::SSL::VERIFY_NONE
       end
@@ -50,6 +53,7 @@ class Arvados
     # resource. After this, self.job will return Arvados::Job;
     # self.job.new() and self.job.find() will do what you want.
     _arvados = self
+    namespace_class = Arvados.const_set "A#{self.object_id}", Class.new
     self.arvados_api.schemas.each do |classname, schema|
       next if classname.match /List$/
       klass = Class.new(Arvados::Model) do
@@ -69,11 +73,10 @@ class Arvados
         arvados_api.
         send(classname.underscore.split('/').last.pluralize.to_sym).
         discovered_methods.
-        collect(&:name).
-        each do |method_name|
+        each do |method|
         class << klass; self; end.class_eval do
-          define_method method_name do |*params|
-            self.api_exec(method_name.to_sym, *params)
+          define_method method.name do |*params|
+            self.api_exec(method.name.to_sym, *params)
           end
         end
       end
@@ -86,9 +89,10 @@ class Arvados
         @api_model_sym = classname.underscore.split('/').last.to_sym
       end
 
-      # This might produce confusing results when using multiple
-      # Arvados instances.
-      Arvados.const_set classname, klass
+      # Create the new class in namespace_class so it doesn't
+      # interfere with classes created by other Arvados objects. The
+      # result looks like Arvados::A26949680::Job.
+      namespace_class.const_set classname, klass
 
       self.class.class_eval do
         define_method classname.underscore do
@@ -151,7 +155,18 @@ class Arvados
         execute(:api_method => arvados_api.send(api_models_sym).send(method),
                 :authenticated => false,
                 :parameters => parameters)
-      JSON.parse result.body, :symbolize_names => true
+      resp = JSON.parse result.body, :symbolize_names => true
+      if resp[:errors]
+        raise Arvados::TransactionFailedError.new(resp[:errors])
+      elsif resp[:uuid] and resp[:etag]
+        self.new(resp)
+      elsif resp[:items].is_a? Array
+        resp.merge(items: resp[:items].collect do |i|
+                     self.new(i)
+                   end)
+      else
+        resp
+      end
     end
 
     def []=(x,y)
@@ -175,7 +190,7 @@ class Arvados
         :uuid => @attributes[:uuid],
         self.class.api_model_sym => @attributes_to_update.to_json
       }
-      unless j.is_a? Hash and j[:uuid]
+      unless j.respond_to? :[] and j[:uuid]
         debuglog "Failed to save #{self.to_s}: #{j[:errors] rescue nil}", 0
         nil
       else