add links to generic specimen and project #show
[arvados.git] / app / models / orvos_base.rb
index 34b723a3bc21530e5e0856e42201db1761c001c6..824c46665758ef2274809ae0ce8cc00262728cc3 100644 (file)
@@ -1,10 +1,42 @@
 class OrvosBase < ActiveRecord::Base
-  @@orvos_v1_base = Rails.configuration.orvos_v1_base
+  self.abstract_class = true
+  attr_accessor :attribute_sortkey
+
+  def self.uuid_infix_object_kind
+    @@uuid_infix_object_kind ||= {
+      '4zz18' => 'orvos#collection',
+      'tpzed' => 'orvos#user',
+      'ozdt8' => 'orvos#api_client',
+      '57u5n' => 'orvos#log',
+      'j58dm' => 'orvos#specimen',
+      'ldvyl' => 'orvos#project'
+    }
+  end
+
+  def initialize
+    super
+    @attribute_sortkey ||= {
+      'id' => nil,
+      'uuid' => '000',
+      'owner' => '001',
+      'created_at' => '002',
+      'modified_at' => '003',
+      'modified_by_user' => '004',
+      'modified_by_client' => '005',
+      'tail_kind' => '100',
+      'tail_uuid' => '100',
+      'head_kind' => '101',
+      'head_uuid' => '101',
+      'info' => 'zzz-000',
+      'updated_at' => 'zzz-999'
+    }
+  end
+
   def self.columns
     return @columns unless @columns.nil?
     @columns = []
-    return @columns if orvos_schema[self.to_s.to_sym].nil?
-    orvos_schema[self.to_s.to_sym].each do |coldef|
+    return @columns if $orvos_api_client.orvos_schema[self.to_s.to_sym].nil?
+    $orvos_api_client.orvos_schema[self.to_s.to_sym].each do |coldef|
       k = coldef[:name].to_sym
       if coldef[:type] == coldef[:type].downcase
         @columns << column(k, coldef[:type].to_sym)
@@ -21,19 +53,23 @@ class OrvosBase < ActiveRecord::Base
   def self.column(name, sql_type = nil, default = nil, null = true)
     ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
   end
-  def self.all
-    thelist = api('')
-    thelist[:items].collect { |x| new(x) }
-  end
   def self.find(uuid)
-    new(api('/' + uuid))
-  end
-  def self.where(cond)
-    all.select do |o|
-      0 == cond.select do |k,v|
-        o.send(k) != v
-      end.size
+    if uuid.class != String or uuid.length < 27 then
+      raise 'argument to find() must be a uuid string. Acceptable formats: warehouse locator or string with format xxxxx-xxxxx-xxxxxxxxxxxxxxx'
     end
+    new.private_reload(uuid)
+  end
+  def self.where(*args)
+    OrvosResourceList.new(self).where(*args)
+  end
+  def self.limit(*args)
+    OrvosResourceList.new(self).limit(*args)
+  end
+  def self.eager(*args)
+    OrvosResourceList.new(self).eager(*args)
+  end
+  def self.all(*args)
+    OrvosResourceList.new(self).all(*args)
   end
   def save
     obdata = {}
@@ -45,59 +81,130 @@ class OrvosBase < ActiveRecord::Base
     postdata = { self.class.to_s.underscore => obdata }
     if etag
       postdata['_method'] = 'PUT'
-      resp = self.class.api('/' + uuid, postdata)
+      resp = $orvos_api_client.api(self.class, '/' + uuid, postdata)
     else
-      resp = self.class.api('', postdata)
+      resp = $orvos_api_client.api(self.class, '', postdata)
     end
     return false if !resp[:etag] || !resp[:uuid]
+
+    # set read-only non-database attributes
     @etag = resp[:etag]
     @kind = resp[:kind]
-    self.uuid ||= resp[:uuid]
+
+    # these attrs can be modified by "save" -- we should update our copies
+    %w(uuid owner created_at
+       modified_at modified_by_user modified_by_client
+      ).each do |attr|
+      self.send(attr + '=', resp[attr.to_sym])
+    end
+
     self
   end
   def save!
     self.save or raise Exception.new("Save failed")
   end
-  def initialize(h={})
-    @etag = h.delete :etag
-    @kind = h.delete :kind
-    super
+  def links(*args)
+    o = {}
+    o.merge!(args.pop) if args[-1].is_a? Hash
+    o[:link_class] ||= args.shift
+    o[:name] ||= args.shift
+    o[:head_kind] ||= args.shift
+    o[:tail_kind] = self.kind
+    o[:tail_uuid] = self.uuid
+    if all_links
+      return all_links.select do |m|
+        ok = true
+        o.each do |k,v|
+          if !v.nil?
+            test_v = m.send(k)
+            if (v.respond_to?(:uuid) ? v.uuid : v.to_s) != (test_v.respond_to?(:uuid) ? test_v.uuid : test_v.to_s)
+              ok = false
+            end
+          end
+        end
+        ok
+      end
+    end
+    @links = $orvos_api_client.api Link, '', { _method: 'GET', where: o, eager: true }
+    @links = $orvos_api_client.unpack_api_response(@links)
   end
-
-  def kind_uuid
-    self.kind + '#' + self.uuid
+  def all_links
+    return @all_links if @all_links
+    res = $orvos_api_client.api Link, '', {
+      _method: 'GET',
+      where: {
+        tail_kind: self.kind,
+        tail_uuid: self.uuid
+      },
+      eager: true
+    }
+    @all_links = $orvos_api_client.unpack_api_response(res)
   end
-
-  protected
-  def self.api(action, data=nil, o={})
-    dataargs = []
-    if !data.nil?
-      data.each do |k,v|
-        dataargs << '-d'
-        if v.is_a? String
-          dataargs << "#{k}=#{v}"
-        else
-          dataargs << "#{k}=#{JSON.generate v}"
+  def reload
+    private_reload(self.uuid)
+  end
+  def private_reload(uuid_or_hash)
+    raise "No such object" if !uuid_or_hash
+    if uuid_or_hash.is_a? Hash
+      hash = uuid_or_hash
+    else
+      hash = $orvos_api_client.api(self.class, '/' + uuid_or_hash)
+    end
+    hash.each do |k,v|
+      if self.respond_to?(k.to_s + '=')
+        self.send(k.to_s + '=', v)
+      else
+        # When OrvosApiClient#schema starts telling us what to expect
+        # in API responses (not just the server side database
+        # columns), this sort of awfulness can be avoided:
+        self.instance_variable_set('@' + k.to_s, v)
+        if !self.respond_to? k
+          singleton = class << self; self end
+          singleton.send :define_method, k, lambda { instance_variable_get('@' + k.to_s) }
         end
       end
     end
-    json = nil
-    IO.popen([ENV,
-              'curl',
-              '-sk',
-              *dataargs,
-              "#{@@orvos_v1_base}/#{o[:resource_path] || self.to_s.underscore.pluralize}#{action}"],
-             'r') do |io|
-      json = io.read
+    @all_links = nil
+    self
+  end
+  def dup
+    super.forget_uuid!
+  end
+
+  def attributes_for_display
+    self.attributes.reject { |k,v|
+      attribute_sortkey.has_key?(k) and !attribute_sortkey[k]
+    }.sort_by { |k,v|
+      attribute_sortkey[k] or k
+    }
+  end
+
+  def self.resource_class_for_uuid(uuid, attr_name=nil, object=nil)
+    if uuid.is_a? OrvosBase
+      return uuid.class
+    end
+    unless uuid.is_a? String
+      return nil
+    end
+    if uuid.match /^[0-9a-f]{32}(\+[^,]+)*(,[0-9a-f]{32}(\+[^,]+)*)*$/
+      return Collection
     end
-    resp = JSON.parse json, :symbolize_names => true
-    if resp[:errors]
-      raise "API errors:\n#{json[:errors].join "\n"}\n"
+    resource_class = nil
+    uuid.match /^[0-9a-z]{5}-([0-9a-z]{5})-[0-9a-z]{15}$/ do |re|
+      resource_class ||= $orvos_api_client.
+        kind_class(self.uuid_infix_object_kind[re[1]])
     end
-    resp
+    if object and attr_name and attr_name.match /_uuid$/
+      resource_class ||= $orvos_api_client.kind_class(object.attributes[attr_name.sub(/_uuid$/, '_kind')])
+    end
+    resource_class
   end
 
-  def self.orvos_schema
-    $orvos_schema ||= api '', nil, {resource_path: 'schema'}
+  protected
+
+  def forget_uuid!
+    self.uuid = nil
+    @etag = nil
+    self
   end
 end