Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting custom classes #68

Open
ZempTime opened this issue Nov 11, 2017 · 8 comments
Open

Setting custom classes #68

ZempTime opened this issue Nov 11, 2017 · 8 comments

Comments

@ZempTime
Copy link

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.

@beauby
Copy link
Member

beauby commented Nov 16, 2017

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! (:

@ZempTime
Copy link
Author

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.

@bprotas
Copy link

bprotas commented Dec 6, 2017

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?

@beauby
Copy link
Member

beauby commented Dec 6, 2017

@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

@bprotas
Copy link

bprotas commented Dec 6, 2017

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!

@beauby
Copy link
Member

beauby commented Dec 7, 2017

@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.

@bprotas
Copy link

bprotas commented Dec 7, 2017

ah interesting - that is much cleaner, thank you!

@JoeWoodward
Copy link

I've created a PR that allows customizing the default serializer mappings in the config file. #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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants