11168: Always deep-sort before comparing in where_serialized.
[arvados.git] / services / api / app / models / arvados_model.rb
index 18d5647cc929e760a72ed48ed709a9d18b8da8a3..0419dadafba891995156b7f5c286f1955a9fbb8e 100644 (file)
@@ -1,5 +1,6 @@
 require 'has_uuid'
 require 'record_filters'
+require 'serializers'
 
 class ArvadosModel < ActiveRecord::Base
   self.abstract_class = true
@@ -32,7 +33,11 @@ class ArvadosModel < ActiveRecord::Base
   # Note: This only returns permission links. It does not account for
   # permissions obtained via user.is_admin or
   # user.uuid==object.owner_uuid.
-  has_many :permissions, :foreign_key => :head_uuid, :class_name => 'Link', :primary_key => :uuid, :conditions => "link_class = 'permission'"
+  has_many(:permissions,
+           foreign_key: :head_uuid,
+           class_name: 'Link',
+           primary_key: :uuid,
+           conditions: "link_class = 'permission'")
 
   class PermissionDeniedError < StandardError
     def http_status
@@ -239,7 +244,7 @@ class ArvadosModel < ActiveRecord::Base
   end
 
   def logged_attributes
-    attributes.except *Rails.configuration.unlogged_attributes
+    attributes.except(*Rails.configuration.unlogged_attributes)
   end
 
   def self.full_text_searchable_columns
@@ -252,12 +257,7 @@ class ArvadosModel < ActiveRecord::Base
     parts = full_text_searchable_columns.collect do |column|
       "coalesce(#{column},'')"
     end
-    # We prepend a space to the tsvector() argument here. Otherwise,
-    # it might start with a column that has its own (non-full-text)
-    # index, which causes Postgres to use the column index instead of
-    # the tsvector index, which causes full text queries to be just as
-    # slow as if we had no index at all.
-    "to_tsvector('english', ' ' || #{parts.join(" || ' ' || ")})"
+    "to_tsvector('english', #{parts.join(" || ' ' || ")})"
   end
 
   def self.apply_filters query, filters
@@ -455,6 +455,20 @@ class ArvadosModel < ActiveRecord::Base
     end
   end
 
+  def self.where_serialized(colname, value)
+    sorted = deep_sort_hash(value)
+    where("#{colname.to_s} IN (?)", [sorted.to_yaml, SafeJSON.dump(sorted)])
+  end
+
+  Serializer = {
+    Hash => HashSerializer,
+    Array => ArraySerializer,
+  }
+
+  def self.serialize(colname, type)
+    super(colname, Serializer[type])
+  end
+
   def ensure_serialized_attribute_type
     # Specifying a type in the "serialize" declaration causes rails to
     # raise an exception if a different data type is retrieved from
@@ -490,7 +504,7 @@ class ArvadosModel < ActiveRecord::Base
   end
 
   def foreign_key_attributes
-    attributes.keys.select { |a| a.match /_uuid$/ }
+    attributes.keys.select { |a| a.match(/_uuid$/) }
   end
 
   def skip_uuid_read_permission_check
@@ -505,7 +519,7 @@ class ArvadosModel < ActiveRecord::Base
     foreign_key_attributes.each do |attr|
       attr_value = send attr
       if attr_value.is_a? String and
-          attr_value.match /^[0-9a-f]{32,}(\+[@\w]+)*$/
+          attr_value.match(/^[0-9a-f]{32,}(\+[@\w]+)*$/)
         begin
           send "#{attr}=", Collection.normalize_uuid(attr_value)
         rescue
@@ -584,13 +598,12 @@ class ArvadosModel < ActiveRecord::Base
     unless uuid.is_a? String
       return nil
     end
-    resource_class = nil
 
     uuid.match HasUuid::UUID_REGEX do |re|
       return uuid_prefixes[re[1]] if uuid_prefixes[re[1]]
     end
 
-    if uuid.match /.+@.+/
+    if uuid.match(/.+@.+/)
       return Email
     end
 
@@ -603,7 +616,7 @@ class ArvadosModel < ActiveRecord::Base
     if self == ArvadosModel
       # If called directly as ArvadosModel.find_by_uuid rather than via subclass,
       # delegate to the appropriate subclass based on the given uuid.
-      self.resource_class_for_uuid(uuid).find_by_uuid(uuid)
+      self.resource_class_for_uuid(uuid).unscoped.find_by_uuid(uuid)
     else
       super
     end