Giter Site home page Giter Site logo

jsonapi-serializers's Introduction

JSONAPI::Serializers

Build Status Gem Version

JSONAPI::Serializers is a simple library for serializing Ruby objects and their relationships into the JSON:API format.

This library is up-to-date with the finalized v1 JSON API spec.

Features

  • Works with any Ruby web framework, including Rails, Sinatra, etc. This is a pure Ruby library.
  • Supports the readonly features of the JSON:API spec.
    • Full support for compound documents ("side-loading") and the include parameter.
  • Similar interface to ActiveModel::Serializers, should provide an easy migration path.
  • Intentionally unopinionated and simple, allows you to structure your app however you would like and then serialize the objects at the end. Easy to integrate with your existing authorization systems and service objects.

JSONAPI::Serializers was built as an intentionally simple serialization interface. It makes no assumptions about your database structure or routes and it does not provide controllers or any create/update interface to the objects. It is a library, not a framework. You will probably still need to do work to make your API fully compliant with the nuances of the JSON:API spec, for things like supporting /relationships routes and for supporting write actions like creating or updating objects. If you are looking for a more complete and opinionated framework, see the jsonapi-resources project.

Installation

Add this line to your application's Gemfile:

gem 'jsonapi-serializers'

Or install directly with gem install jsonapi-serializers.

Usage

Define a serializer

require 'jsonapi-serializers'

class PostSerializer
  include JSONAPI::Serializer

  attribute :title
  attribute :content
end

Serialize an object

JSONAPI::Serializer.serialize(post)

Returns a hash:

{
  "data": {
    "id": "1",
    "type": "posts",
    "attributes": {
      "title": "Hello World",
      "content": "Your first post"
    },
    "links": {
      "self": "/posts/1"
    }
  }
}

Serialize a collection

JSONAPI::Serializer.serialize(posts, is_collection: true)

Returns:

{
  "data": [
    {
      "id": "1",
      "type": "posts",
      "attributes": {
        "title": "Hello World",
        "content": "Your first post"
      },
      "links": {
        "self": "/posts/1"
      }
    },
    {
      "id": "2",
      "type": "posts",
      "attributes": {
        "title": "Hello World again",
        "content": "Your second post"
      },
      "links": {
        "self": "/posts/2"
      }
    }
  ]
}

You must always pass is_collection: true when serializing a collection, see Null handling.

Null handling

JSONAPI::Serializer.serialize(nil)

Returns:

{
  "data": null
}

And serializing an empty collection:

JSONAPI::Serializer.serialize([], is_collection: true)

Returns:

{
  "data": []
}

Note that the JSON:API spec distinguishes in how null/empty is handled for single objects vs. collections, so you must always provide is_collection: true when serializing multiple objects. If you attempt to serialize multiple objects without this flag (or a single object with it on) a JSONAPI::Serializer::AmbiguousCollectionError will be raised.

Multiple attributes

You could declare multiple attributes at once:

 attributes :title, :body, :contents

Custom attributes

By default the serializer looks for the same name of the attribute on the object it is given. You can customize this behavior by providing a block to attribute, has_one, or has_many:

  attribute :content do
    object.body
  end

  has_one :comment do
    Comment.where(post: object).take!
  end

  has_many :authors do
    Author.where(post: object)
  end

The block is evaluated within the serializer instance, so it has access to the object and context instance variables.

More customizations

Many other formatting and customizations are possible by overriding any of the following instance methods on your serializers.

# Override this to customize the JSON:API "id" for this object.
# Always return a string from this method to conform with the JSON:API spec.
def id
  object.slug.to_s
end
# Override this to customize the JSON:API "type" for this object.
# By default, the type is the object's class name lowercased, pluralized, and dasherized,
# per the spec naming recommendations: http://jsonapi.org/recommendations/#naming
# For example, 'MyApp::LongCommment' will become the 'long-comments' type.
def type
  'long-comments'
end
# Override this to customize how attribute names are formatted.
# By default, attribute names are dasherized per the spec naming recommendations:
# http://jsonapi.org/recommendations/#naming
def format_name(attribute_name)
  attribute_name.to_s.dasherize
end
# The opposite of format_name. Override this if you override format_name.
def unformat_name(attribute_name)
  attribute_name.to_s.underscore
end
# Override this to provide resource-object metadata.
# http://jsonapi.org/format/#document-structure-resource-objects
def meta
end
# Override this to set a base URL (http://example.com) for all links. No trailing slash.
def base_url
  @base_url
end
# Override this to provide a resource-object jsonapi object containing the version in use.
# http://jsonapi.org/format/#document-jsonapi-object
def jsonapi
end
def self_link
  "#{base_url}/#{type}/#{id}"
end
def relationship_self_link(attribute_name)
  "#{self_link}/relationships/#{format_name(attribute_name)}"
end
def relationship_related_link(attribute_name)
  "#{self_link}/#{format_name(attribute_name)}"
end

If you override self_link, relationship_self_link, or relationship_related_link to return nil, the link will be excluded from the serialized object.

Base URL

You can override the base_url instance method to set a URL to be used in all links.

class BaseSerializer
  include JSONAPI::Serializer

  def base_url
    'http://example.com'
  end
end

class PostSerializer < BaseSerializer
  attribute :title
  attribute :content

  has_one :author
  has_many :comments
end

JSONAPI::Serializer.serialize(post)

Returns:

{
  "data": {
    "id": "1",
    "type": "posts",
    "attributes": {
      "title": "Hello World",
      "content": "Your first post"
    },
    "links": {
      "self": "http://example.com/posts/1"
    },
    "relationships": {
      "author": {
        "links": {
          "self": "http://example.com/posts/1/relationships/author",
          "related": "http://example.com/posts/1/author"
        }
      },
      "comments": {
        "links": {
          "self": "http://example.com/posts/1/relationships/comments",
          "related": "http://example.com/posts/1/comments"
        },
      }
    }
  }
}

Alternatively, you can specify base_url as an argument to serialize which allows you to build the URL with different subdomains or other logic from the request:

JSONAPI::Serializer.serialize(post, base_url: 'http://example.com')

Note: if you override self_link in your serializer and leave out base_url, it will not be included.

Root metadata

You can pass a meta argument to specify top-level metadata:

JSONAPI::Serializer.serialize(post, meta: {copyright: 'Copyright 2015 Example Corp.'})

Root links

You can pass a links argument to specify top-level links:

JSONAPI::Serializer.serialize(post, links: {self: 'https://example.com/posts'})

Root errors

You can use serialize_errors method in order to specify top-level errors:

errors = [{ "title": "Invalid Attribute", "detail": "First name must contain at least three characters." }]
JSONAPI::Serializer.serialize_errors(errors)

If you are using Rails models (ActiveModel by default), you can pass in an object's errors:

JSONAPI::Serializer.serialize_errors(user.errors)

A more complete usage example (assumes ActiveModel):

class Api::V1::ReposController < Api::V1::BaseController
  def create
    post = Post.create(post_params)
    if post.errors
      render json: JSONAPI::Serializer.serialize_errors(post.errors)
    else
      render json: JSONAPI::Serializer.serialize(post)
    end
  end
end

Root 'jsonapi' object

You can pass a jsonapi argument to specify a top-level "jsonapi" key containing the version of JSON:API in use:

JSONAPI::Serializer.serialize(post, jsonapi: {version: '1.0'})

Explicit serializer discovery

By default, jsonapi-serializers assumes that the serializer class for Namespace::User is Namespace::UserSerializer. You can override this behavior on a per-object basis by implementing the jsonapi_serializer_class_name method.

class User
  def jsonapi_serializer_class_name
    'SomeOtherNamespace::CustomUserSerializer'
  end
end

Now, when a User object is serialized, it will use the SomeOtherNamespace::CustomUserSerializer.

Namespace serializers

Assume you have an API with multiple versions:

module Api
  module V1
    class PostSerializer
      include JSONAPI::Serializer
      attribute :title
    end
  end
  module V2
    class PostSerializer
      include JSONAPI::Serializer
      attribute :name
    end
  end
end

With the namespace option you can choose which serializer is used.

JSONAPI::Serializer.serialize(post, namespace: Api::V1)
JSONAPI::Serializer.serialize(post, namespace: Api::V2)

This option overrides the jsonapi_serializer_class_name method.

Sparse fieldsets

The JSON:API spec allows to return only specific fields from attributes and relationships.

For example, if you wanted to return only the title field and author relationship link for posts:

fields =
JSONAPI::Serializer.serialize(post, fields: {posts: [:title]})

Sparse fieldsets also affect relationship links. In this case, only the author relationship link would be included:

JSONAPI::Serializer.serialize(post, fields: {posts: [:title, :author]})

Sparse fieldsets operate on a per-type basis, so they affect all resources in the response including in compound documents. For example, this will affect both the posts type in the primary data and the users type in the compound data:

JSONAPI::Serializer.serialize(
  post,
  fields: {posts: ['title', 'author'], users: ['name']},
  include: 'author',
)

Sparse fieldsets support comma-separated strings (fields: {posts: 'title,author'}, arrays of strings (fields: {posts: ['title', 'author']}), single symbols (fields: {posts: :title}), and arrays of symbols (fields: {posts: [:title, :author]}).

Relationships

You can easily specify relationships with the has_one and has_many directives.

class BaseSerializer
  include JSONAPI::Serializer
end

class PostSerializer < BaseSerializer
  attribute :title
  attribute :content

  has_one :author
  has_many :comments
end

class UserSerializer < BaseSerializer
  attribute :name
end

class CommentSerializer < BaseSerializer
  attribute :content

  has_one :user
end

Note that when serializing a post, the author association will come from the author attribute on the Post instance, no matter what type it is (in this case it is a User). This will work just fine, because JSONAPI::Serializers automatically finds serializer classes by appending Serializer to the object's class name. This behavior can be customized.

Because the full class name is used when discovering serializers, JSONAPI::Serializers works with any custom namespaces you might have, like a Rails Engine or standard Ruby module namespace.

Compound documents and includes

To reduce the number of HTTP requests, servers MAY allow responses that include related resources along with the requested primary resources. Such responses are called "compound documents". JSON:API Compound Documents

JSONAPI::Serializers supports compound documents with a simple include parameter.

For example:

JSONAPI::Serializer.serialize(post, include: ['author', 'comments', 'comments.user'])

Returns:

{
  "data": {
    "id": "1",
    "type": "posts",
    "attributes": {
      "title": "Hello World",
      "content": "Your first post"
    },
    "links": {
      "self": "/posts/1"
    },
    "relationships": {
      "author": {
        "links": {
          "self": "/posts/1/relationships/author",
          "related": "/posts/1/author"
        },
        "data": {
          "type": "users",
          "id": "1"
        }
      },
      "comments": {
        "links": {
          "self": "/posts/1/relationships/comments",
          "related": "/posts/1/comments"
        },
        "data": [
          {
            "type": "comments",
            "id": "1"
          }
        ]
      }
    }
  },
  "included": [
    {
      "id": "1",
      "type": "users",
      "attributes": {
        "name": "Post Author"
      },
      "links": {
        "self": "/users/1"
      }
    },
    {
      "id": "1",
      "type": "comments",
      "attributes": {
        "content": "Have no fear, sers, your king is safe."
      },
      "links": {
        "self": "/comments/1"
      },
      "relationships": {
        "user": {
          "links": {
            "self": "/comments/1/relationships/user",
            "related": "/comments/1/user"
          },
          "data": {
            "type": "users",
            "id": "2"
          }
        },
        "post": {
          "links": {
            "self": "/comments/1/relationships/post",
            "related": "/comments/1/post"
          }
        }
      }
    },
    {
      "id": "2",
      "type": "users",
      "attributes": {
        "name": "Barristan Selmy"
      },
      "links": {
        "self": "/users/2"
      }
    }
  ]
}

Notice a few things:

  • The primary data relationships now include "linkage" information for each relationship that was included.

  • The related objects themselves are loaded in the top-level included member.

  • The related objects also include "linkage" data when a deeper relationship is also present in the compound document. This is a very powerful feature of the JSON:API spec, and allows you to deeply link complicated relationships all in the same document and in a single HTTP response. JSONAPI::Serializers automatically includes the correct linkage data for whatever include paths you specify. This conforms to this part of the spec:

    Note: Full linkage ensures that included resources are related to either the primary data (which could be resource objects or resource identifier objects) or to each other. JSON:API Compound Documents

Relationship path handling

The include param also accepts a string of relationship paths, ie. include: 'author,comments,comments.user' so you can pass an ?include query param directly through to the serialize method. Be aware that letting users pass arbitrary relationship paths might introduce security issues depending on your authorization setup, where a user could include a relationship they might not be authorized to see directly. Be aware of what you allow API users to include.

Control links and data in relationships

The JSON API spec allows relationships objects to contain links, data, or both.

By default, links are included in each relationship. You can remove links for a specific relationship by passing include_links: false to has_one or has_many. For example:

has_many :comments, include_links: false  # Default is include_links: true.

Notice that links are now excluded for the comments relationship:

   "relationships": {
     "author": {
       "links": {
         "self": "/posts/1/relationships/author",
         "related": "/posts/1/author"
       }
     },
     "comments": {}
   }

By default, data is excluded in each relationship. You can enable data for a specific relationship by passing include_data: true to has_one or has_many. For example:

has_one :author, include_data: true  # Default is include_data: false.

Notice that linkage data is now included for the author relationship:

   "relationships": {
     "author": {
       "links": {
         "self": "/posts/1/relationships/author",
         "related": "/posts/1/author"
       },
       "data": {
         "type": "users",
         "id": "1"
       }
     }

Instrumentation

Using ActiveSupport::Notifications you can subscribe to key notifications to better understand the performance of your serialization.

The following notifications can be subscribed to:

  • render.jsonapi_serializers.serialize_primary - time spent serializing a single object
  • render.jsonapi_serializers.find_recursive_relationships - time spent finding objects to serialize through relationships

This is an example of how you might subscribe to all events that come from jsonapi-serializers.

require 'active_support/notifications'

ActiveSupport::Notifications.subscribe(/^render\.jsonapi_serializers\..*/) do |*args|
  event = ActiveSupport::Notifications::Event.new(*args)

  puts event.name
  puts event.time
  puts event.end
  puts event.payload
end

Rails example

# app/serializers/base_serializer.rb
class BaseSerializer
  include JSONAPI::Serializer

  def self_link
    "/api/v1#{super}"
  end
end

# app/serializers/post_serializer.rb
class PostSerializer < BaseSerializer
  attribute :title
  attribute :content
end

# app/controllers/api/v1/base_controller.rb
class Api::V1::BaseController < ActionController::Base
  # Convenience methods for serializing models:
  def serialize_model(model, options = {})
    options[:is_collection] = false
    JSONAPI::Serializer.serialize(model, options)
  end

  def serialize_models(models, options = {})
    options[:is_collection] = true
    JSONAPI::Serializer.serialize(models, options)
  end
end

# app/controllers/api/v1/posts_controller.rb
class Api::V1::ReposController < Api::V1::BaseController
  def index
    posts = Post.all
    render json: serialize_models(posts)
  end

  def show
    post = Post.find(params[:id])
    render json: serialize_model(post)
  end
end

# config/initializers/jsonapi_mimetypes.rb
# Without this mimetype registration, controllers will not automatically parse JSON API params.

# Rails 4
module JSONAPI
  MIMETYPE = "application/vnd.api+json"
end
Mime::Type.register(JSONAPI::MIMETYPE, :api_json)
ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup(JSONAPI::MIMETYPE)] = lambda do |body|
  JSON.parse(body)
end

# Rails 5 Option 1: Add another synonym to the json mime type
json_mime_type_synonyms = %w[
  text/x-json
  application/jsonrequest
  application/vnd.api+json
]
Mime::Type.register('application/json', :json, json_mime_type_synonyms)

# Rails 5 Option 2: Add a separate mime type
Mime::Type.register('application/vnd.api+json', :api_json)
ActionDispatch::Request.parameter_parsers[:api_json] = -> (body) {
  JSON.parse(body)
}

Sinatra example

Here's an example using Sinatra and Sequel ORM instead of Rails and ActiveRecord. The important takeaways here are that:

  1. The :tactical_eager_loading plugin will greatly reduce the number of queries performed when sideloading associated records. You can add this plugin to a single model (as demonstrated here), or globally to all models. For more information, please see the Sequel documentation.
  2. The :skip_collection_check option must be set to true in order for JSONAPI::Serializer to be able to serialize a single Sequel::Model instance.
  3. You should call #all on your Sequel::Dataset instances before passing them to JSONAPI::Serializer to greatly reduce the number of queries performed.
require 'sequel'
require 'sinatra/base'
require 'json'
require 'jsonapi-serializers'

class Post < Sequel::Model
  plugin :tactical_eager_loading

  one_to_many :comments
end

class Comment < Sequel::Model
  many_to_one :post
end

class BaseSerializer
  include JSONAPI::Serializer

  def self_link
    "/api/v1#{super}"
  end
end

class PostSerializer < BaseSerializer
  attributes :title, :content

  has_many :comments
end

class CommentSerializer < BaseSerializer
  attributes :username, :content

  has_one :post
end

module Api
  class V1 < Sinatra::Base
    configure do
      mime_type :api_json, 'application/vnd.api+json'

      set :database, Sequel.connect
    end

    helpers do
      def parse_request_body
        return unless request.body.respond_to?(:size) &&
          request.body.size > 0

        halt 415 unless request.content_type &&
          request.content_type[/^[^;]+/] == mime_type(:api_json)

        request.body.rewind
        JSON.parse(request.body.read, symbolize_names: true)
      end

      # Convenience methods for serializing models:
      def serialize_model(model, options = {})
        options[:is_collection] = false
        options[:skip_collection_check] = true
        JSONAPI::Serializer.serialize(model, options)
      end

      def serialize_models(models, options = {})
        options[:is_collection] = true
        JSONAPI::Serializer.serialize(models, options)
      end
    end

    before do
      halt 406 unless request.preferred_type.entry == mime_type(:api_json)
      @data = parse_request_body
      content_type :api_json
    end

    get '/posts' do
      posts = Post.all
      serialize_models(posts).to_json
    end

    get '/posts/:id' do
      post = Post[params[:id].to_i]
      not_found if post.nil?
      serialize_model(post, include: 'comments').to_json
    end
  end
end

See also: Sinja, which extends Sinatra and leverages jsonapi-serializers to provide a JSON:API framework.

Changelog

See Releases.

Unfinished business

  • Support for pagination/sorting is unlikely to be supported because it would likely involve coupling to ActiveRecord, but please open an issue if you have ideas of how to support this generically.

Contributing

  1. Fork it ( https://github.com/fotinakis/jsonapi-serializers/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Throw a โ˜… on it! :)

jsonapi-serializers's People

Contributors

aars avatar azdaroth avatar bantic avatar cball avatar cheerfulstoic avatar djones avatar doughsay avatar dylandrop avatar elkelk avatar exelord avatar fotinakis avatar garrettlancaster avatar hspazio avatar hubert avatar izidorome avatar jcarlos avatar jondruse avatar koriroys avatar lucasm-ironin avatar mrcasals avatar mwpastore avatar nubbel avatar scharfie avatar timhaines avatar tompesman avatar yonga9121 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jsonapi-serializers's Issues

Allow passing in the Serializer class

Can we add an option for the class we want to use for serialization? Because right now the option for custom serializers depend on method for a given class. It would be nicer if we can pass it in as an option.

[question] Any tips on deserializing/parsing JSONAPI incoming payloads?

@fotinakis (and others of course): What do you use to parse and process incoming JSONAPI messages? Any tips/hints?

I tried this new gem today: https://github.com/beauby/jsonapi_parser which is simple but works well for simple parsing.

Of course the next step is to actually process the data, in my case through Sequel in a Sinatra app.
I'm wondering in how far this could (or maybe more importantly, should) be generic. Handling relationships, or even inserting new models/objects as new relationships all through one posted JSONAPI document/payload for example.

I'd love to hear your thoughts.

PS: I hope it's alright to contact you through github issues. I don't see you on gitter and I don't have (or want) a Twitter account ;)

Cheers

Specify which attributes to include

In some circumstances I don't want to include all attributes. For example, when getting a list of users I don't want to include auth_token, but when logging in, the auth_token is required. Is this possible?

specifying serializers for API::V2

I'm using Active Model Serializers for V1 of my app, while I'm wanting to implement jsonapi-serializers for V2. Having trouble getting things routed correctly since both gems want serializers defined in a 'serializers' directory. Is there a way around this or is it dependent on overriding serializer discovery, as mentioned in 'unfinished business'?

Accessing model object and overriding attributes

Hello,
Is it possible to either override the value of an attribute or define an attribute in the Serializer?
I'm also looking at ways of accessing the model object from the serializer class, for example ActiveModel::Serializer uses object, JSONAPI::Resource uses @model.

Thank you!

class PostSerializer
  include JSONAPI::Serializer

  attributes :title, :text, :word_count

  def word_count
    # ..calculate word count here
  end
end

Pagination alternatives

The README suggests that pagination won't be supported in the gem itself, but I'm looking for a way to add it in manually.

I successfully edited the links on the object record:

# app/serializers/project_serializer.rb
def links
    {
      :self => "/#{type}/#{id}",
      :next => object.next ? "/#{type}/#{object.next.id}" : nil,
      :prev => object.prev ? "/#{type}/#{object.prev.id}" : nil
    }
end

which resulted in:

# ... inside a single resource object
links": {
    "self": "/projects/BBAD49FC-9EF5-E411-BC1F-96147297305B",
    "next": "/projects/9B46559C-9DF5-E411-BC1F-96147297305B",
    "prev": null
},
# ...

I have custom pagination links ready to go, but haven't been able to figure out how to add them.

Is there a way to add a top-level links object (specified here) to the root of the response, so that it would look like this?

{
  data: [...],
  included: [],
  links: {}
}

Thanks for a great gem, by the way.

Serializing a new record returns empty hash

I'm testing my serializers, and found that the attributes for new records -- that is, records that are not persisted -- are serialized to an empty hash.

> p = Place.new(id: 12, name: "Boston", description: "Hey Beantown!")
 => #<Place id: 12, name: "Boston", description: "Hey Beantown!", geometry: nil, created_at: nil, updated_at: nil>
> p.attribute_names.map {|a| p.send(a) }
 => [12, "Boston", "Hey Beantown!", nil, nil, nil]
> JSONAPI::Serializer.serialize( p )
 => {"data"=>{"id"=>"12", "type"=>"places", "attributes"=>{}, "links"=>{"self"=>"/places/12"}}}

I don't consider this a bug, but it is getting in the way of my tests, and I think it's solvable. I'm happy to do a PR, but I could use a little advice on how to get started. I think this behavior is happening around here but I'd appreciate confirmation of that before diving in.

data object within relationships object

Hello Mike,

First, thank you for this gem. I tried several before yours and this has been by far the best implementation and the easiest to work with.

I'm not entirely sure if this is user error, or a bug. But I am unable to get the data object within the relationships object to appear (as per your example output pasted below). I have tried referencing child objects as a has_one :tutorial and has_many :tutorials, and have been sure to include them with include: ['tutorials']. I do get a relationships object, but it only contains links (no data object). The included object is coming back properly and contains the expected data.

I checked out the gem source, and have started walking through the code. There are a few places where it seems that the symbol :tutorial might be incorrectly compared to the string 'tutorial'?

https://github.com/fotinakis/jsonapi-serializers/blob/master/lib/jsonapi-serializers/serializer.rb#L104
(@_include_linkages = ['tutorials] but formatted_attribute_name = :tutorials)

Am I missing something obvious and this should in fact work?

Thanks very much

{
  "data": {
    "id": "1",
    "type": "posts",
    "attributes": {
      "title": "Hello World",
      "content": "Your first post"
    },
    "links": {
      "self": "/posts/1"
    },
    "relationships": {  <--- this is rendered properly
      "author": {
        "links": {
          "self": "/posts/1/relationships/author",
          "related": "/posts/1/author"
        },
        "data": {  <--- this data object is not rendered at all
          "type": "users",    
          "id": "1"
        }
      },
      "comments": {
        "links": {
          "self": "/posts/1/relationships/comments",
          "related": "/posts/1/comments"
        },
        "data": [
          {
            "type": "comments",
            "id": "1"
          }
        ]
      }
    }
  },
  "included": [ <--- this is rendered properly
    {
      "id": "1",
      "type": "users",
      "attributes": {
        "name": "Post Author"
      },
      "links": {
        "self": "/users/1"
      }
    },
    {
      "id": "1",
      "type": "comments",
      "attributes": {
        "content": "Have no fear, sers, your king is safe."
      },
      "links": {
        "self": "/comments/1"
      },
      "relationships": {
        "user": {
          "links": {
            "self": "/comments/1/relationships/user",
            "related": "/comments/1/user"
          },
          "data": {
            "type": "users",
            "id": "2"
          }
        },
        "post": {
          "links": {
            "self": "/comments/1/relationships/post",
            "related": "/comments/1/post"
          }
        }
      }
    },
    {
      "id": "2",
      "type": "users",
      "attributes": {
        "name": "Barristan Selmy"
      },
      "links": {
        "self": "/users/2"
      }
    }
  ]
}

`serialize_errors` is throwing undefined method

I'm trying to serialize errors in a Rails project using serialize_errors but it keeps throwing this error:
NoMethodError (undefined method `serialize_errors' for JSONAPI::Serializer:Module

Here's the controller action:

if @participant.update(deserialize_params(participant_params))
  render json: serialize_model(@participant), status: :created
else
  render json: JSONAPI::Serializer.serialize_errors(@participant.errors), status: :unprocessable_entity
end

The regular JSONAPI::Serializer.serialize works fine for me, but serializing errors using the documentation isn't working for me.

Rails v4.2.6
Ruby v2.3.0

Including jsonapi object

I've been digging around looking for a way to add the top-level "jsonapi object" (http://jsonapi.org/format/#document-jsonapi-object) per the spec, so that we can easily see what version of json:api a service is using (especially now that the version 1.1 spec is under discussion).

Is there a way to output this object with the current system, or are there plans to add it? I'd be happy to submit a pull request if this fits within the scope and intent of the project.

Thanks!

[HELP] Adding relationship and link without having the relation on the model

Hi, let's say I have an User model and then the User have many Invitations. My issue is that in my actual User object I don't have a invitations method. Is there anyway I can add both resources (user and invitations) to the same JSON response without having to create some kind of "UserPresenter" object containing the collection?

Thanks in advance and keep up the EXCELENT work!

Duplicate resource in response document.

I was writing a benchmark to compare AMS, jsonapi-rb and jsonapi-serializers, and came across the following bug:

When a primary resource can be reached as an included related resource, it will appear both in the primary and included sections of the response document, although the spec states that

A compound document MUST NOT include more than one resource object for each type and id pair.

Is there a way to set options on an included object serializer

There might be a better way to achieve this, but here's what I have in mind:

An article includes a user, but it should only include details of the user to a limited degree, unless the user is the current_user at the moment, at which point it should reveal a bunch more attributes.

It's possible to do this on the directly accessed serializer because context can get passed through options, so current_user can get passed along. However, when they're included, the context doesn't get passed through it looks like.

Does that make sense? Am I going about this the wrong way?

Included assuming default class name to build Serializers

Hello there!

I am trying to build a included JSON API schema within my Serializer, but I am got an error while try to find the serializer class.

I've got:

  uninitialized constant Some::Other::Namespace::RequesterSerializer
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/activesupport-4.2.4/lib/active_support/inflector/methods.rb:263:in `const_get'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/activesupport-4.2.4/lib/active_support/inflector/methods.rb:263:in `block in constantize'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/activesupport-4.2.4/lib/active_support/inflector/methods.rb:259:in `each'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/activesupport-4.2.4/lib/active_support/inflector/methods.rb:259:in `inject'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/activesupport-4.2.4/lib/active_support/inflector/methods.rb:259:in `constantize'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/activesupport-4.2.4/lib/active_support/core_ext/string/inflections.rb:66:in `constantize'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/jsonapi-serializers-0.3.0/lib/jsonapi-serializers/serializer.rb:210:in `find_serializer_class'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/jsonapi-serializers-0.3.0/lib/jsonapi-serializers/serializer.rb:214:in `find_serializer'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/jsonapi-serializers-0.3.0/lib/jsonapi-serializers/serializer.rb:351:in `block in find_recursive_relationships'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/jsonapi-serializers-0.3.0/lib/jsonapi-serializers/serializer.rb:347:in `each'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/jsonapi-serializers-0.3.0/lib/jsonapi-serializers/serializer.rb:347:in `find_recursive_relationships'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/jsonapi-serializers-0.3.0/lib/jsonapi-serializers/serializer.rb:290:in `block in serialize'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/jsonapi-serializers-0.3.0/lib/jsonapi-serializers/serializer.rb:287:in `each'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/jsonapi-serializers-0.3.0/lib/jsonapi-serializers/serializer.rb:287:in `serialize'
     # /Users/hlegius/.rvm/gems/ruby-2.2.1@awesome-project/gems/jsonapi-serializers-0.3.0/lib/jsonapi-serializers/serializer.rb:20:in `serialize'

My implementation:

# Requester Serializer
module My
  module Awesome
    module Responders
      class Requester
        include JSONAPI::Serializer

        has_one :mailing_address
        has_one :shipping_address

        attribute :foo
        attribute :bar
      end
    end
  end
end

# Address Serializer 
module My
  module Awesome
    module Responders
      class Address
        include JSONAPI::Serializer

        attribute :zip_code
      end
    end
  end
end

# Domain Entity
module Some
  module Other
    module Namespace
      class Requester
        # ...

        def mailing_address
          # ...
        end

        def shipping_address
          # ...
        end
      end
    end
  end
end

# Returns Some::Other::Namespace::Requester object
requester = Some::Other::Namespace::RequesterRepository.find(...) 

My::Awesome::Responders::Requester.serialize(requester) # runs pretty well, thanks!
My::Awesome::Responders::Requester.serialize(
  requester,
  include: ['mailing_address', 'shipping_address']
) # fail with the error above.

So, as far as I've debugged, it's when ActiveSupport .constantize that tries to build a Some::Other::Namespace::RequesterSerializer with some fixed convention created within serialize.rb file.

Documentation says that it's possible to customize this behavior, but I didn't figure out who to do this.

If you need further informations, I am totally willing to help to understand/solve this o/

Thanks for reading!

Add include id relation only option and smart related link behavior based on that

I am experimenting with this gem in situations with heavily nested structures. The include: option of the serializer handles the full include stack. In many cases I would want only to add the type and id in the relationships section, but do not need to include the objects itself (I think, but want to test the idea over here). A possible scenario would be

JSONAPI::Serializer.serialize [posts], is_collection: true, include: %w[
  comments
  comments.annotations
  comments.#tags
  comments.#user
  #author
]

Meaning I want to serialize posts, include the comments and the comments' annotations (sorry for the bad inspiration in finding good examples, it is about the idea) but only want to include the id references of the comments' tags and user and of the post's author.

The resulting data should look something like:

data: [
  type: 'posts'
  id: 'post-1'
  attributes: {}
  relationships:
    comments:
      data: [
        {type: 'comments', id: 'comment-1'}
      ]
      # no related link needed since included in the payload
    author:
      data: {type: 'people', id: 'person-1'}
      links: {related: "/people/1"}
,
  type: 'posts'
  id: 'post-2'
  attributes: {}
  relationships: {tl: 'dr'}
]
included: [
  type: 'comments'
  id: 'comment-1'
  attributes: {}
  relationships:
    annotations:
      data: [
        {type: 'annotations', id: 'annotation-1'}
      ]
    user:
      data: {type: 'users', id: 'user-1'}
      links: {related: "/users/1"}
    tags:
      data: [{type: 'tags', id: 'tag-1'}]
      links: {related: "/comments/1/tags"}
,
  type: 'annotations'
  id: 'annotation-1'
  attributes: {}
  relationships:
    author: {links: {related: "/annotations/1/author"}}
]

Would this make sense at all? And if yes would the hashtag prefix notation be the way to go in this? This would at first need the addition to specify the hashtag relations, and a non trivial id lookup relationship option:

class PostSerializer
  has_one :author, id_lookup: -> post { post.author_id }
  has_many :comments, id_lookup: -> post { post.comment_ids }
end

I am willing to help implementing this if the idea behind it makes sense. Since I am really new to JSONAPI I would like to know first if there is not some other point I am missing. This in my idea would dramatically increase performance by removing the need to fetch all the associated objects. It would also solve the case where the same record is included in a different context bug (other issue).

For people more comfortable with the full javascript object notation in stead of the coffeescript version, here the payload as javascript:

{
  data: [
    {
      type: 'posts',
      id: 'post-1',
      attributes: {},
      relationships: {
        comments: {
          data: [
            {
              type: 'comments',
              id: 'comment-1'
            }
          ]
        },
        author: {
          data: {
            type: 'people',
            id: 'person-1'
          },
          links: {
            related: "/people/1"
          }
        }
      }
    }, {
      type: 'posts',
      id: 'post-2',
      attributes: {},
      relationships: {
        tl: 'dr'
      }
    }
  ],
  included: [
    {
      type: 'comments',
      id: 'comment-1',
      attributes: {},
      relationships: {
        annotations: {
          data: [
            {
              type: 'annotations',
              id: 'annotation-1'
            }
          ]
        },
        user: {
          data: {
            type: 'users',
            id: 'user-1'
          },
          links: {
            related: "/users/1"
          }
        },
        tags: {
          data: [
            {
              type: 'tags',
              id: 'tag-1'
            }
          ],
          links: {
            related: "/comments/1/tags"
          }
        }
      }
    }, {
      type: 'annotations',
      id: 'annotation-1',
      attributes: {},
      relationships: {
        author: {
          links: {
            related: "/annotations/1/author"
          }
        }
      }
    }
  ]
}

Thanks for the effort!

Intermediate resources should be returned in the include

Hello,

The spec for compound documents ( http://jsonapi.org/format/#document-structure-compound-documents ) says "[..] intermediate resources in a multi-part path must be returned along with the leaf nodes. For example, a response to a request for comments.author should include comments as well as the author of each of those comments [...]".

The current implementation (

# Also, spec: A request for comments.author should not automatically also include
) returns only the final resource (e.g. only the author of ?include=comments.author instead of comments and comments.author.

Cheers,

Best practice for creating secondary relationship?

I'm doing something a little wonky, and I'd like an opinion as I dig into the code. On my base object, I have a has_one relationship to a location, which works great. I now want to have a second relationship, also to location, from that same object.

has_one :location works terrifically, and now I'd like to add something like:

has_one :location, key: :event_location_id or something like that.

I haven't found a way to do this yet, but I am messing about with the has_one block format which seems promising. I thought I'd ask here for opinions on the matter.

Thank you!

Separator in 2 words key is using dash automatically

Hi,, it's a simple question, I have this serializer

class UserSerializer < BaseSerializer
  attribute :contact_number, key: :contact_number
  attribute :msisdn
  attribute :email
  attribute :created_at
  attribute :updated_at
  attribute :name
end

but the result is

{
  "data": {
    "id": "55efb4b5eab7091716000000",
    "type": "users",
    "attributes": {
      "contact-number": "8977000715",
      "msisdn": "+628977000715",
      "email": null,
      "created-at": "2015-09-09T04:25:25.150Z",
      "updated-at": "2015-09-09T04:30:34.110Z",
      "name": ""
    },
    "links": {
      "self": "/users/55efb4b5eab7091716000000"
    }
  }
}

contact_number in attributes is changed to contact-number, and it made my test failed. Do you have a simple workaround for this issue?

Optimize lookups of relationships where ID is sufficient

Consolidating issues:

  • #23 Add include id relation only option and smart related link behavior based on that
  • #29 Lazily-evaluated relationships

We need a mechanism to reference post.author_id rather than author.id when only finding the ID for relationship data and links. Right now, we naively always reference the .id method which, in the ActiveRecord world, forces a load of that record (unless it has been preloaded via .includes(:author) when loading the parent).

A few options:

  • Automatically try the relationship's <relationship name>_id method and fallback to the object's id method.
  • Provide a way to hint at the relationship method name, like id_lookup: :author_id. This idea could be part of the above option.

Remove ActiveSupport?

Since most of us are probably using this particular gem specifically to avoid pulling half of Rails into our non-Rails applications, what would it take to remove the dependency on ActiveSupport? Looks like it's being used primarily for inflection.

Add "relationships" key

It seems like JSON API got updated and replaced the "links key by a "relationships" key:

  "data": [{
    "type": "posts",
    "id": "1",
    "attributes": {
      "title": "JSON API paints my bikeshed!"
    },
    "relationships": {
      "author": {
        "links": {
          "self": "http://example.com/posts/1/relationships/author",
          "related": "http://example.com/posts/1/author"
        },
        "linkage": { "type": "people", "id": "9" }
      },
      "comments": {
        "links": {
          "self": "http://example.com/posts/1/relationships/comments",
          "related": "http://example.com/posts/1/comments"
        },
        "linkage": [
          { "type": "comments", "id": "5" },
          { "type": "comments", "id": "12" }
        ]
      }
    },
    "links": {
      "self": "http://example.com/posts/1"
    }
  }

Support for excluding the `links` object

According to the spec, a resource object may have a links object, but it need not have one: http://jsonapi.org/format/#document-resource-objects

So it would be nice if there were a way to specify in a serializer whether an object ought to have a links object. (From poking through the code, it looks like this object is always present.) Or, since there's already support for overriding the self_link and relationship_related_link methods, maybe if all of the values in a links object are null, it could poof itself out of existence.

Thanks for this gem!

Error support

Currently, I don't think that errors are supported. For example, the JSON API provides for the following structure for error responses:

{
  "errors": [
    {
      "source": { "pointer": "/data/attributes/first-name" },
      "title": "Invalid Attribute",
      "detail": "First name must contain at least three characters."
    },
    {
      "source": { "pointer": "/data/attributes/first-name" },
      "title": "Invalid Attribute",
      "detail": "First name must contain an emoji."
    }
  ]
}

From: JSON API examples

The current serialize methods don't support cases where responses are failures. Are there plans to support how errors are handled by jsonapi-serializers?

Using a serializer to restructure Hashes doesn't work because of `is_collection`

I have an application where the data is passed around as plain Hashes. When it comes time to serve the data with JSON API, they need to be restructured. I would like to use JSONAPI::Serializers to do this. First I figure out what serializer to use by calling a function get_serializer on the hash. Then I call JSONAPI::Serializer.serialize and pass the serializer explicitly. It complains with JSONAPI::Serializer::AmbiguousCollectionError, presumably because Hashes are collections and I did not pass is_collection: true. I tried to get around this by passing it is_collection: false but it fails in the same way. My current workaround is to wrap my Hashes in proxy objects, but this is ugly. It seems like all that is needed is to have the code take me at my word on is_collection: false ...

# `data` is a Hash
serializer = get_serializer(data)
JSONAPI::Serializers.serialize(data, serializer: serializer)  #=> AmbiguousCollectionError
JSONAPI::Serializers.serialize(data, serializer: serializer, is_collection: false)  #=> AmbiguousCollectionError

Support for serializing relationships

Currently, "links" is serialized within the "data" key when serializing relationship objects. However, the JSON API requires "links" to be top-level as follows:

{
  "links": {
    "self": "/articles/1/relationships/author",
    "related": "/articles/1/author"
  },
  "data": {
    "type": "people",
    "id": "12"
  }
}

Source

Default options in serializer

Is there a way to set default options for a serializer? e.g. set :skip_collection_check => true in my base serializer class?

Serializing Polymorphic Relationship

Hi, in the model I have field which is polymorphic such as this

belongs_to :agent, class_name: "User", polymorphic: true

the problem is, the serializer does not recognize the agent as a relationship with the user model. So there is no data found in the json.

Sorry, forgot to include the agent

serialize_errors support claims for activelmodel are wrong

From https://github.com/fotinakis/jsonapi-serializers/blob/master/lib/jsonapi-serializers/serializer.rb#L340-L356

The problem is, the lib assumes that the errors hash is an activemodel hash if it responds to #to_hash and #full_messages. First there's nowhere in the lint spec from activel model regarding these methods, so it's a false assumption. Second, many objects implement #to_hash without arguments, and this gem assumes one hash argument with support for :full_messages key, which is also not part of the spec.

These assumptions make this gem fail for the sequel ORM, which is ActiveModel-compliant, has an errors object, but this doesn't support #to_hash with arguments.

I think the lib should add suport explicitly for active record error hashes and fallback to something else.

Relationship data for include missing

I think this might be related to #47. When including posts the relationship data for posts is included, but for posts.users the relationship data for posts is not included. posts,posts.users works

The line direct_children_includes = includes.reject { |key| key.include?('.') } in serializer.rb:267 seems to cause this. Changing it to direct_children_includes = includes.reject { |key| key.split('.')[0] } fixes this. I couldn't tell if this is intended or not, because there is a test that checks for it.

Include type and id for relationship, without full resource include

In my response, I want to include relationship information in the main resource, but I don't want to include the whole resource in the "included" section, mostly because the other resource has no attributes, so it's redundant.

I.e., I want to generate a response that looks like the following:

{
    "data": {
        "id": "5",
        "type": "things",
        "attributes": {
            "name": "A Thing",
        },
        "links": {
            "self": "/things/5"
        },
        "relationships": {
            "otherThing": {
                "data": {
                    "type": "otherThings",
                    "id": "123"
                }
            }
        }
    }
}

When I serialize without any :include, then I get:

    "relationships": {
        "otherThing": {
        }
    }

When I serialize with include: 'otherThing', then I get:

    "relationships": {
        "otherThing": {
            "type": "otherThings",   <--- I want this information
            "id": "123"              <--- and also this
        }
    }
"included": [    <--- But I don't need any of this
    {
        "type": "otherThings",
        "id": "123",
        "attributes": {},
        "links": {
            "self": "/otherThings/123"
        }
    }
]

Is there a way to get this middleground?

Exclude the `attributes` member when there are none

The JSON API spec says that resource objects MAY (not MUST) have an attributes member: reference.

I have a serializer class with no attributes specified, and the generated hash has "attributes" => {},. Is there a way / could there be an option added, to totally exclude attributes, rather than have it present, but empty?

Missing relationship data for included relationship

Other bug: when serializing a Post, and including comments, comments.post, comments.post.author, the author relationship of the primary post does not get populated, though comments.post refers to the primary post.

More generally, I believe there is an undefined behavior when a related resource can be reached via multiple relationship paths.

top level errors

per #63, and the json-api spec,

The members data and errors MUST NOT coexist in the same document.

So I'm opening this issue to discuss a way forward.

@fotinakis I read that you don't want to bind this library to ActiveRecord, which I applaud. It is likely the most common case to be using AR though, so some kind of support, while leaving other options open seems good to me.

A first stab:

config/initializers/jsonapi_initializer.rb

JSONAPI.setup do |config|
  config.error_adapter = :active_record
end

option 1:

JSONAPI::ErrorSerializer.serialize(errors)

option 2:

JSONAPI::Serializer.serialize_errors(errors)

option 3:

JSONAPI::Serializer.serialize(obj) # detect errors

Of the three, I lean towards option 1, letting the user pass in the errors, and letting the configured error_adapter munge the errors into a form acceptable to JSON API.

A user might use it like this:
some_controller.rb

def create
  book = Book.create(underscore(book_params))
  if book.errors
    render json: JSONAPI::ErrorSerializer.serialize(book.errors), status: 422
  else
    render json: JSONAPI::Serializer.serialize(book)
  end
end

def book_params
  params.require(:data).require(:attributes).permit(:title, :author_id, :page_count)
end

Of course, you might be in the unenviable position of migrating an app to a new api, and so you should be able to override the configured error serializer when called. e.g.

  JSONAPI::ErrorSerializer.serialize(book.errors, error_adapter: :some_custom_adapter)

I'm not sure where some_custom_adapter.rb would live though.

Serialization for creation of new resources (POST)

I'm a little confused about how to properly use this library to serialize payloads when creating new resources for a jsonapi compliant service.

1.) This library seems to assume that you need an id. If id is nil, the library includes an empty string for the id in the payload. The id field is not required when POSTing a new resource. Is there a way I can get the id to not be included in the payload?

2.) It seems to always include the top level "self" link in the payload. Although my server will just ignore this, it seems silly to include it when creating a new resource. I can disable relationship links by using the "include_links" option, but is there a way to remove serialization of all links?

In general, how should one use this library to generate payloads for POSTing; has this been thought through any?

Plain serializers for non-standard data.

Hey,

I am using jsonapi-serializers to serializers some Elasticsearch results. The resulting object is a bit weird and non-standard and I am in a situation where I would want to have a serializer which wraps other serializers as attributes. Unfortunately, if I do this right now, I am stuck with "id", "type" keys which I don't want and all my attributes under an "attributes" key.

I was wondering if you could consider having a kind of plain serializer class, which simply serializes the data "as-is" without adding those unwanted keys (in my case).

If you have another idea, please tell me! If mine makes sense, I could be willing to try to implement it.

Thanks!

Registering mimetype documentation update

The docs state to register the mime type "application/vnd.api+json" with the Mime::Type module for params to be parsed correctly, but unfortunately this did not work for me.

I had to unregister the json mime type and then re-register it:

Mime::Type.unregister(:json)
Mime::Type.register(JSONAPI::MIMETYPE, :json)

I'm using Rails 5.0.0.1. Has anyone else encountered this issue? Should the docs reflect this solution?

Issue with collection detection

We're currently using Sequel for our data layer, and it happens that a Sequel::Model has an each method on individual models. This causes a JSONAPI::Serializer::AmbiguousCollectionError, because the test checks for that method.

Would it be beneficial to switch that check to use something like this (line 255 of serializer.rb):

if objects.kind_of?(Array)

Additional options to :has_one/:has_many

Hi. I want to pass additional options for has_one relations. In https://github.com/fotinakis/jsonapi-serializers/blob/master/lib/jsonapi-serializers/attributes.rb#L58 when make debug I see parent_name hash in options, but in https://github.com/fotinakis/jsonapi-serializers/blob/master/lib/jsonapi-serializers/serializer.rb#L174 attr_data[:options] is empty. Maybe I something misunderstood =(

My sample code:
Controller

serialize_models(categories, include: %w(image posts))

Serialisers

class API::V1::CategorySerializer < API::V1::BaseSerializer
  has_one :image, parent_name: 'category' 
  has_many :posts
end
class API::V1::PostSerializer < API::V1::BaseSerializer
  has_one :image, parent_name: 'post'
end

Support for including the `data` attribute in relationships

Hi!

I opened this issue in the official repo: json-api/json-api#758, open it so that you get the context of this issue. One of the suggested solutions is using the meta keyword on a relationship to flag whether it's a to_one or a to_many realtionship, but it's weird to me. The other suggestion is to include the data keyword for all relationships in the included objects.

Is there any way to specify that I want to add the data attribute on those relationships?

Thanks!

Time for a v1.0.0

Note to self, it's time to release a 1.0 release, since this is definitely being used in production in many places.

Need to identify if there are any blockers to this happening, or if we should just do it.

Remove automatic collection serialization

This feature also breaks usage for the sequel ORM. Consider this check:

# Duck-typing check for a collection being passed without is_collection true.
# We always must be told if serializing a collection because the JSON:API spec distinguishes
# how to serialize null single resources vs. empty collections.
if !options[:skip_collection_check] && objects.respond_to?(:each)
raise JSONAPI::Serializer::AmbiguousCollectionError.new(
'Must provide `is_collection: true` to `serialize` when serializing collections.')
end

Sequel::Model objects respond to #each although they're not collections. This is a needless check IMO. If I'm setting is_collection: true, I'm setting explicitly that it is a collection, and the same otherwise.

Access to include_data at customizing-block

Hello,

At first: I like this gem very much!
Furthermore I would find it very useful, if I had access to the options-hash, or at least to the include-array.

I have a serializer with a has_many-relationship. The serializer should call the corresponding getter-method only if the related type is set at include-options. If the include option is not set, the time-consuming method should not get called at all. For now I used the context-option and passed the same information two times, but the information at include-array would be adequate.

What do you think about it?

Best regards

Lazily-evaluated relationships

I'm performance tuning some of my endpoints and I've noticed that JSONAPI::Serializers is triggering database calls on relationships even when those relationships are not given in the included option (i.e. included is nil). The results of these database calls are not being included in the output. Is this intentional, and if so, is there a way to tell JSONAPI::Serializers to lazily-evaluate relationships? If not intentional, is it possible there is some some class instantiation logic that is inspecting or otherwise poking the relationships that is causing the upstream Sequel::Model to fire off queries?

require 'sequel'
require 'jsonapi-serializers'
require 'logger'
require 'pp'

DB = Sequel.sqlite nil, 
  :loggers => [Logger.new($stdout)]

DB.create_table :foos do
  primary_key :id
  foreign_key :bar_id, :bars
  String :name
end

DB.create_table :bars do
  primary_key :id
end

DB.run "INSERT INTO bars (id) VALUES (1)"
DB.run "INSERT INTO foos (id, bar_id, name) VALUES (1, 1, 'myfoo')"

class Foo < Sequel::Model
  many_to_one :bar
end

class Bar < Sequel::Model
  one_to_many :foos
end

class FooSerializer
  include JSONAPI::Serializer

  attribute :name

  has_one :bar
end

class BarSerializer
  include JSONAPI::Serializer

  has_many :foos
end

pp JSONAPI::Serializer.serialize(Foo.first, {
  :is_collection => false,
  :skip_collection_check => true
})

And the output:

$ bundle show jsonapi-serializers
/home/mwp/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/jsonapi-serializers-0.3.0
$ bundle show sequel
/home/mwp/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/sequel-4.25.0
$ ruby --version
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-linux]
$ ruby main.rb 
I, [2015-10-13T15:01:17.021499 #24657]  INFO -- : (0.000207s) PRAGMA foreign_keys = 1
I, [2015-10-13T15:01:17.021745 #24657]  INFO -- : (0.000031s) PRAGMA case_sensitive_like = 1
I, [2015-10-13T15:01:17.022125 #24657]  INFO -- : (0.000270s) CREATE TABLE `foos` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `bar_id` integer REFERENCES `bars`, `name` varchar(255))
I, [2015-10-13T15:01:17.022445 #24657]  INFO -- : (0.000118s) CREATE TABLE `bars` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT)
I, [2015-10-13T15:01:17.022600 #24657]  INFO -- : (0.000053s) INSERT INTO bars (id) VALUES (1)
I, [2015-10-13T15:01:17.022759 #24657]  INFO -- : (0.000053s) INSERT INTO foos (id, bar_id, name) VALUES (1, 1, 'myfoo')
I, [2015-10-13T15:01:17.023645 #24657]  INFO -- : (0.000307s) PRAGMA table_info('foos')
I, [2015-10-13T15:01:17.024828 #24657]  INFO -- : (0.000089s) PRAGMA table_info('bars')
I, [2015-10-13T15:01:17.025724 #24657]  INFO -- : (0.000076s) SELECT sqlite_version()
I, [2015-10-13T15:01:17.026072 #24657]  INFO -- : (0.000168s) SELECT * FROM `foos` LIMIT 1
I, [2015-10-13T15:01:17.026730 #24657]  INFO -- : (0.000078s) SELECT * FROM `bars` WHERE `id` = 1
{"data"=>
  {"id"=>"1",
   "type"=>"foos",
   "attributes"=>{"name"=>"myfoo"},
   "links"=>{"self"=>"/foos/1"},
   "relationships"=>
    {"bar"=>
      {"links"=>
        {"self"=>"/foos/1/relationships/bar", "related"=>"/foos/1/bar"}}}}}

The last INFO line (SELECT * FROM barsWHEREid = 1) is what I'm trying to eliminate.

Full Linkage Violation

In Serializer::find_recursive_relatioships, a tree is built for includes which offers the possibility of not including intermediate resources. Quoting from a comment:

    # We only include parent values if the sential value _include is set. This satifies the
    # spec note: A request for comments.author should not automatically also include comments
    # in the response. This can happen if the client already has the comments locally, and now
    # wants to fetch the associated authors without fetching the comments again.
    # http://jsonapi.org/format/#fetching-includes

I checked the spec and I think this part is gone-- now "full linkage" is required, meaning that excluding intermediate resources is not allowed. As far as I can tell the public API does not offer a way to trigger the exclusion functionality, but having this in the codebase is nonetheless confusing since it's not up to date with the spec.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.