Open lgebhardt opened 8 years ago
I have a use case that might warrant a slightly different approach to the public id.
My records are stored in a Microsoft SQL Server database. The ids are in UUID format, and the integer primary key you see in most other databases is a field called new_count
. For various reasons, new_count
cannot be the primary key. Before using JR, in order to create a friendly URL that I could also parse in a controller, I had:
class Project < ActiveRecord::Base
def to_param
"#{count}--#{name.to_s.parameterize}"
end
end
Project.find_by(new_count: 430).to_param
# => "430--improving-the-youth-summer-jobs-program"
In the controller, I would then parse the friendly URL and look it up using the count.
class ProjectsController < ActionController::Base
def show
count, _sep, name = params[:id].partition('--')
# using above example, count: 430, _sep: '--', name: 'improving-the-youth-summer-jobs-program'
@project = Project.find_by(new_count: count)
end
end
I might suggest a slightly different approach to this issue, which is, to implicitly use #to_param
as the primary key instead of #id
, and then to allow overriding the controller's find operation for a given action, in case the developer has to do anything to parse the URL or call a specific method.
In the end, I would like to write code that overrides the way records are looked up in the #show
action. I imagine it looking something like this.
class ProjectResource < JSONAPI::Resource
# This could also be done implicitly in every resource,
# as most resources would simply return the record's primary key.
def id
to_param
end
end
class ProjectsController < JSONAPI::ResourceController
find_operation :show do
count, _, name = params[:id].partition('--')
# Then either returning the ActiveRecord Relation directly
Project.find_by(count: new_count)
# OR returning a hash that will be used in `#find_by`
{ count: new_count }
end
# or
def show
find_operation do
# parse & find logic from above
end
super
end
end
Thoughts? If we come to an agreement on an approach, I'm happy to try to implement this.
Have you looked at overriding the resource's find_by_key
method instead of doing the logic in the controller?
@lgebhardt I'm interested in this and willing to do the changes necessary if you have some guidance on how you'd like to approach solving this
@lgebhardt could you elaborate on the problems caused by making the resource's #_primary_key
method name a field other than the actual primary key? I'm interested in helping support the public key feature but it would be nice to know the problem you ran into.
I'm also interested in the public_key
approach.
Right now I have done to following to make this work:
class NewsResource < JSONAPI::Resource
immutable
key_type :uuid
attributes :uuid, :title, :content
def id
uuid
end
def self.find_by_key(key, options = {})
context = options[:context]
records = records(options)
records = apply_includes(records, options)
model = records.find_by(uuid: key)
fail JSONAPI::Exceptions::RecordNotFound, key if model.nil?
resource_for_model(model).new(model, context)
end
end
@lgebhardt Is there an official way currently to handle the resource using a different attribute that the models primary key as the public key? I saw that https://github.com/cerebris/jsonapi-resources/pull/427 was closed but don't understand the point in the final comment https://github.com/cerebris/jsonapi-resources/pull/427#issuecomment-468480847
Based on the fact that support for other types of models has progressed
Resurrecting this thread. I too would like to be able define an attribute to be the key used for looking up a model. I don't like leaking the internal database id out into the API, and many of our resources have naturally unique identifiers that are not the database primary key.
An example might be a country model where the database primary key is an integer, but the natural key is a country code string. I would like to be able to do something like this:
class CountryResource < JSONAPI::Resource
public_key :code
key_type :string
attributes :code, :name
end
Maybe the primary key is 17
and the code is uk
, I would be able to do this:
GET /countries/uk
{
"id": "uk",
"type": "countries",
"attributes": {
"code": "uk",
"name": "United Kingdom"
}
}
Update - it does seem I can get what I want with the following:
class CountryResource < JSONAPI::Resource
key_type :string
attributes :code, :name
key_type :string
def id
@model.code
end
def self._primary_key
:code
end
end
I didn't test a ton but it seems to work with the include
param as well.
So really, the idea of a public_key
setting would be to DRY up the above.
Have the ability to have a public key, which is used by the resources with the underlying model using a different primary key. For example internally the models use integer primary keys and externally the resources use uuids.
Initially this project was trying to make the primary_key option perform both roles, which was problematic. I think it might be possible however to make this feature work with a
public_key
option on a Resource.This has been requested several times, and recently #427 brought this up again.