jsonapi-rails
jsonapi-rails copied to clipboard
Setting custom classes
I don't have a solid mental model for what's happening when I'm trying to set a class -> serializable class mapping.
I've got two classes:
class Relationship < ApplicationRecord
# relationship stuff. baggage, if you will.
end
# then, nested in app/models/relationships:
class Relationships::Approve < Relationship
# approval related things, so all that logic is only here
end
I've got a SerializableRelationship
class that's working. I'd like to reuse that with these subclasses.
class Api::Relationships::ApprovesController < Api::ApiController
before_action :set_relationship
def update
if @relationship.update(status: "approved", action_user: current_user)
render jsonapi: @relationship
else
render jsonapi_errors: @relationship.errors
end
end
private
def set_relationship
@relationship = Relationships::Approve.find(params[:id])
end
end
How do I:
- set this option inside my controller action?
- set this option at the controller wide level? (aka this )
- application-wide level (in the initializer)?
I know that, once I get it, it'll probably be an "ohhhh" moment. I conceptually understand it's a big hash that makes certain keys to certain other classes that serialize those things. But I need to see an example, and get details, because I don't understand:
- what does the key need to be?
- what does the value need to be?
- are modifications made to the things I pass in?
I've been guessing things and not getting feedback I know how to understand from the errors.
I think this would make some great addition to the docs! I'll check back in when I figure it out, and maybe PR in some updates if you're ok with that.
Hi @ZempTime
- set this option inside my controller action?
render jsonapi: @relationship, class: { 'Relationship::Approve': SerializableRelationship }
- set this option at the controller wide level?
def jsonapi_class
super.merge(
'Relationship::Approve': SerializableRelationship
)
end
- application-wide level
Override jsonapi_class
in your ApplicationController
.
- what does the key need to be?
The key is the class name of the object you are passing to render jsonapi:
. Usually, it is an ActiveRecord
model class name.
- what does the value need to be?
The value is a Serializable class (usually a subclass of JSONAPI::Serializable::Resource
).
- are modifications made to the things I pass in?
Not sure I understand but I'd say no: the models are not modified, and the serializable classes are just instantiated.
I think this would make some great addition to the docs! I'll check back in when I figure it out, and maybe PR in some updates if you're ok with that.
I agree and that would be of great help! (:
Oh my gosh! So helpful! I'm not doing Ruby/Rails full time anymore, but next coding session I'll sit down and get a PR in before starting on my personal stuff.
One thing that I am struggling with is how to "duck-type" the rendering; for example, I have more than one class (specifically, one is a model, the other is a double) that both respond to the same API and I want to render with the same subclass of JSONAPI::Serializable::Resource
. I was hoping that specifying jsonapi_class
in a call to render jsonapi:
would let me say "use this renderer", but that doesn't appear to be the case. Is there a way I can request that jsonapi-rails
render an object with a given serializable, regardless of what it's underlying class is?
@bprotas Yes, you were almost there: the class
option of the render
method does that for you, and the default value for the class
option can be overridden by overriding the jsonapi_class
method. Note that when explicitly specifying a class
option, you will prevent the default mapping to kick in (User -> SerializableUser
), but you can alway do either
render jsonapi: users, class: jsonapi_class.merge(Article: MyCustomSerializableArticle)
or overriding jsonapi_class
as
def jsonapi_class
super.merge(Article: MyCustomSerializableArticle)
end
Thanks @beauby ; I appreciate the response. This isn't quite what I was looking for - it assumes that here my class is always Article
to be serialized by MyCustomSerializableArticle
, but in fact I want MyCustomSerializableArticle
to be used to render regardless of if the object is of class Article
or not (it might be a subclass of Article
, or a class of Rspec::Mocks::Double
if I'm writing a test.
I ended up with this line of code to always use the same serializer, regardless of the class of object I'm serializing:
render jsonapi: article, class: {article.class.name.to_sym => MyCustomSerializableArticle}
This feels like a bit of a hack; I would rather not have to use the hash:
render jsonapi: article, class: MyCustomSerializableArticle
I might still be misunderstanding how the library is supposed to work?
Thank you again for the quick response!
@bprotas I see. The json:api standard is based on the idea of serializing a graph of resources, so the general case is that one needs to map each resource type to a serializer. In your case, you could do
render jsonapi: article, class: Hash.new { |h, k| h[k] = MyCustomSerializableArticle }
i.e. use a hash with a default value, or even
render jsonapi: article, class: ->(_) { MyCustomSerializableArticle }
using a lambda.
ah interesting - that is much cleaner, thank you!
I've created a PR that allows customizing the default serializer mappings in the config file. https://github.com/jsonapi-rb/jsonapi-rails/pull/92
I think it makes sense for situations like this one. i.e. STI models normally will use the same serializer so would be good if you could manually specify that.