Jsonapi-resources: Keep ID as integer-based primary key but use UUID as a finder

Created on 14 Jun 2017  路  4Comments  路  Source: cerebris/jsonapi-resources

For database performance reasons, we are keeping ID (primary key). For our client-end apps, we use UUID for security reasons. The UUID is not a primary key but it is used as a finder to find the single record.

For example, both use cases are valid:

http://janedoe.com/v1/people/6 #=> valid
http://janedoe.com/v1/people/7228ba24-336e-4edb-948c-b0531135aceb #=> also valid

What is the best way to handle that -- the flexibility to take in either an ID (for internal use only, such as our internal CRM webapp) or UUID (for our public webapp)?

Most helpful comment

Here's my final code based on the sample from @senid231 (thank you!) that allows us to be able to interchangeably find resources using id or uuid. I integrated the above sample into BaseResource that can be extended into other resources (eg: PersonResource, ProviderResource) that would benefit from it.

module Api
  module V1
    class BaseResource < JSONAPI::Resource

      UUID_REGEX = /\A[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}\z/
      INT_REGEX = /\A\d+\z/

      filter :id, :verify => :verify_keys, apply: -> (records, values, options) do
        col = values.uuid? ? :uuid : :id # refer to String extension using .uuid?
        records.where(col => values)
      end

      def id
        @model.respond_to?(:uuid) ? @model.uuid : @model.id
      end

      def self.verify_key(key, context = nil)
        return if key.nil?
        regex = key.uuid? ? UUID_REGEX : INT_REGEX # refer to String extension using .uuid?
        return key if regex =~ key.to_s
        raise JSONAPI::Exceptions::InvalidFieldValue.new(:id, key)
      end

    end
  end
end
class String
  def uuid?
    regex = /\A[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}\z/
    return regex =~ self
  end
end

All 4 comments

@chadwtaylor you can do something like this:

module V1
  class PersonResource < JSONAPI::Resource
    UUID_REGEXP = /\A[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\z/f
    INT_REGEXP = /\A\d+\z/f

    def id
      context[:internal] ? @model.id : @model.public_uuid
    end

    filter :id, verify: :verify_keys, apply: ->(records, values, options) do
      col = options[:context][:internal] ? :id : :public_uuid
      records.where(col => values)
    end

  def self.verify_key(key, context = nil)
    return if key.nil?
    regexp = context[:internal] ? INT_REGEXP : UUID_REGEXP
    if regexp =~ key.to_s
      key
    else
      raise JSONAPI::Exceptions::InvalidFieldValue.new(:id, key) 
    end
  end

  def self.sortable_fields(context)
  fields = super
  # why somebody ever need to sort by uuid?)
  context[:internal] ? fields : (fields - [:id])
  end

end

@senid231 Many thanks! I will give this a spin and keep you posted. I do appreciate you taking the time to share the code here for folks like me.

Here's my final code based on the sample from @senid231 (thank you!) that allows us to be able to interchangeably find resources using id or uuid. I integrated the above sample into BaseResource that can be extended into other resources (eg: PersonResource, ProviderResource) that would benefit from it.

module Api
  module V1
    class BaseResource < JSONAPI::Resource

      UUID_REGEX = /\A[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}\z/
      INT_REGEX = /\A\d+\z/

      filter :id, :verify => :verify_keys, apply: -> (records, values, options) do
        col = values.uuid? ? :uuid : :id # refer to String extension using .uuid?
        records.where(col => values)
      end

      def id
        @model.respond_to?(:uuid) ? @model.uuid : @model.id
      end

      def self.verify_key(key, context = nil)
        return if key.nil?
        regex = key.uuid? ? UUID_REGEX : INT_REGEX # refer to String extension using .uuid?
        return key if regex =~ key.to_s
        raise JSONAPI::Exceptions::InvalidFieldValue.new(:id, key)
      end

    end
  end
end
class String
  def uuid?
    regex = /\A[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}\z/
    return regex =~ self
  end
end

Here's a slightly more compact regex literal using ruby's \h character class, ftw.

UUID_V4_REGEX = /^\h{8}-\h{4}-4\h{3}-[89AaBb]\h{3}-\h{12}$/
Was this page helpful?
0 / 5 - 0 ratings