Coder Social home page Coder Social logo

roar-jsonapi's Introduction

Roar JSON API

Resource-Oriented Architectures in Ruby.

Gitter Chat TRB Newsletter .github/workflows/tests.yml Gem Version

Roar JSON API provides support for JSON API, a specification for building APIs in JSON. It can render and parse singular and collection documents.

Resource

A minimal representation of a Resource can be defined as follows:

require 'roar/json/json_api'

class SongsRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :songs

  attributes do
    property :title
  end
end

Properties (or attributes) of the represented model are defined within an attributes block.

An id property will automatically defined when using Roar JSON API.

Relationships

To define relationships, use ::has_one or ::has_many with either an inline or a standalone representer (specified with the extend: or decorates: option).

class SongsRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :songs

  has_one :album do
    property :title
  end

  has_many :musicians, extend: MusicianRepresenter
end

Meta information

Meta information can be included into rendered singular and collection documents in two ways.

You can define meta information on your collection object and then let Roar compile it.

class SongsRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :songs

  meta toplevel: true do
    property :page
    property :total
  end
end

Your collection object must expose the respective methods.

collection.page  #=> 1
collection.total #=> 12

This will render the {"meta": {"page": 1, "total": 12}} hash into the JSON API document.

Alternatively, you can provide meta information as a hash when rendering. Any values also defined on your object will be overriden.

collection.to_json(meta: {page: params["page"], total: collection.size})

Both methods work for singular documents too.

class SongsRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :songs

  meta do
    property :label
    property :format
  end
end
song.to_json(meta: { label: 'EMI' })

If you need more functionality (and parsing), please let us know.

Usage

As JSON API per definition can represent singular models and collections you have two entry points.

SongsRepresenter.prepare(Song.find(1)).to_json
SongsRepresenter.prepare(Song.new).from_json("..")

Singular models can use the representer module directly.

SongsRepresenter.for_collection.prepare([Song.find(1), Song.find(2)]).to_json
SongsRepresenter.for_collection.prepare([Song.new, Song.new]).from_json("..")

Parsing currently works great with singular documents - for collections, we are still working out how to encode the application semantics. Feel free to help.

Support

Questions? Need help? Free 1st Level Support on irc.freenode.org#roar ! We also have a mailing list, yiha!

License

Roar is released under the MIT License.

roar-jsonapi's People

Contributors

acidtangodev avatar apotonick avatar fran-worley avatar leoarnold avatar myabc avatar nico-acidtango avatar olleolleolle avatar seuros 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

Watchers

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

roar-jsonapi's Issues

Update Operation with Roar JSONAPI representer forces contract to open up id

Cross-referencing trailblazer/trailblazer-operation#17, where I originially submitted this issue

Complete Description of Issue

When using trailblazer-operation, roar-jsonapi, and reform in conjunction with an Update call, the id field of a resource is forced to be editable.

That seems like a bad idea to me. I personally would prefer it to ignore the id part of the JSONAPI request.

Admittedly this is also a bit of an inconsistency with the JSONAPI spec. An update as a PATCH /resources/1 MUST include the id field. An inconsistency between the URL id and the given id in the document is not handled. Maybe there is an edge case where you would want to edit the ID. However whether you want to or not, it has to be provided as a parameter.

Steps to reproduce (Code sample)

class MyResource < Struct.new(:id, :name)
  def self.find_by(id:)
     DB[id.to_i]
  end
  def save
    DB[id.to_i] = self
  end
end
DB = {1 => MyResource.new(1, 'one')} # Simulating database

class MyContract < Reform::Form
  property :name
end

class MyRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :resources
  attributes do
    property :name
  end
end

class MyOperation < Trailblazer::Operation
  step Model(MyResource, :find_by)
  step Contract::Build(constant: MyContract)
  step Contract::Validate(representer: MyRepresenter)
  step Contract::Persist()
end

MyOperation.({id: 1}, 'document' => '{"data":{"type":"resources","attributes":{"name":"changed"},"id":"9999"}}')
# => NoMethodError: undefined method `id=' for #<MyContract>

# Adding `property :id` to MyContract fixes the NoMethodError,
# but allows editing the ID field which is usually a security vulnerability:
MyOperation.({id: 1}, 'document' => '{"data":{"type":"resources","attributes":{"name":"changed"},"id":"9999"}}')
# => <Result:true ...>
DB
# => {1 => #<struct name="one">, 9999 => #<struct name="changed">}

Expected behavior

As stated, in edge cases this might be the actually desired behavior. In most cases one would not want an ID to be editable. So an opt-in would be preferable to me.

System configuration

Roar version: roar-edcd8efa0181
Roar JSONAPI version: roar-jsonapi-3ca7fa8e0f5a

References represented inside of link block using JSONAPI implementation

From @matheusca on November 19, 2015 22:15

Hi guys,

I've trying create association links using Roar::JSON::JSONAPI but I can't reference represented method inside of the has_one block.

For instance:

class RequesterRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI

  type :requester

  has_one :residence_address do
    type :address

    link :self do
      # I need way to get requester id here.
      "http://example.com/requester/#{requester.id}/relationships/residence_address"
    end
  end

  property :id
  # ... other properties
end

How I described, I would need some way to get requester.id inside of has_one. Anyway to do it?

Copied from original issue: trailblazer/roar#168

Exclude/Include of relationships

From @wuarmin on September 20, 2016 17:59

At http://trailblazer.to/gems/representable/3.0/api.html#include-and-exclude there's a documenation about the top-level-options include and exclude. Today I tried to exclude a has_many relationship while using Roar::JSON::JSONAPI, but can't achieve it.

class ArticleDecorator < Roar::Decorator
  include Roar::JSON::JSONAPI
  type :articles
# a property exclude works
  property :id
  property :title
# a relationship exclude does not work
  has_one :author, class: Author, populator: ::Representable::FindOrInstantiate do 
    type :authors
    property :id
    property :email
  end
end
#===============================================
ArticleDecorator.new(article).to_json(exclude: [:title]) #works
ArticleDecorator.new(article).to_json(exclude: [:author]) # does not work

Is the feature limited to properties?

Copied from original issue: trailblazer/roar#199

'links' in 'relationships' only printing last item in collection

From @jtzero on July 20, 2016 0:15

  has_many :stores do
      type :stores

      property :id

      link :self do
        "http://localhost:3000/stores/#{represented.id}"
      end
    end

produces

"relationships"=>
   {"stores"=>
      {"data"=>[
          {"type"=>"stores", "id"=>"1"}, 
          {"type"=>"stores", "id"=>"2"}, 
          {"type"=>"stores", "id"=>"3"}
        ], 
        "links"=>{"self"=>"http://localhost:3000/stores/3"}
      }
   },
   "links"=>{"self"=>"://localhost:3000/retailers/9"}},
   "included"=>
     [
       {"type"=>"stores", "id"=>"1", "links"=>{"self"=>"http://localhost:3000/stores/1"}},
       {"type"=>"stores", "id"=>"2", "links"=>{"self"=>"http://localhost:3000/stores/2"}},
       {"type"=>"stores", "id"=>"3", "links"=>{"self"=>"http://localhost:3000/stores/3"}}
     ]
   }

the links are correct in the "included" but the one in "stores" is incorrect,and changing the self link in has_many changes all the ones in the 'included'. How do I change the one in stores?

Copied from original issue: trailblazer/roar#194

Should be able to return relationship links without including the related representer

Per the jsonapi spec for links http://jsonapi.org/format/1.1/#fetching

We should be able to provide relationship links without including data about the related records:

{
  "links": {
    "self": "http://example.com/articles/1"
  },
  "data": {
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "JSON API paints my bikeshed!"
    },
    "relationships": {
      "author": {
        "links": {
          "related": "http://example.com/articles/1/author"
        }
      }
    }
  }
}

If we do this (which is pretty ugly for such a common usecase):

class ArticleRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :articles

  has_one :author, class: Person do
    relationship do
      link(:related)     { "/articles/#{represented.id}/relationships/author" }
    end
  end
end

# output =>

{
  "data": {
    "relationships": {
      "author": {
        "data": {
          "id": "1",
          "type": "author"
        },
        "links": {
          "related": "/articles/#{represented.id}/relationships/author"
        }
      }
    },
    "id": "1",,
    "type": "articles"
  },
  "included": [
    {
      "id": "1",
      "type": "author"
    }
  ]
}

The outputted json includes an includes block with the type and id of the related record. This causes issues with some API clients as they assume that the entire record is included and don't bother to fetch it from the related link.

Maybe the answer is to introduce a relationships api?

class ArticleRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :articles
  
  relationships do
    link(:author) { "/articles/#{represented.id}/relationships/author" } # would just return the link
    included(:author, class: Person, decorator: Person::Representer)  # would include the record data
  end
end

The included method should accept a block and all options current has_one/ has_many support. We could add a collection: true option to keep the API for single and collections in one call.
We might not need the option at all as we could work it out based on whether we're dealing with an array of objects.

Collection representation

I have some issues using the JSONAPI output for collections. I'm generating the collection output with:

orgs = paginate(Libis::Ingester::Organization.all)
Representer::OrganizationRepresenter.for_collection.prepare(orgs).to_hash(pagination_hash(orgs))

The options generated by 'pagination_hash' looks like this:

    {
      user_options:
      {
        ...,
        links:
        {
          self: "http://localhost:9393/api/organizations?per_page=2&page=1",
          first: "http://localhost:9393/api/organizations?per_page=2&page=1",
          last: "http://localhost:9393/api/organizations?per_page=2&page=3",
          next: "http://localhost:9393/api/organizations?per_page=2&page=2"
        }
      },
      meta: { per_page:2, :total=>5, :page=>1, :max_page=>3, :next_page=>2, :prev_page=>nil}
    }

The generated hash looks like this:

{
    "data": [
        {
            "id": "58b7ccd7e852dd0775f51cae",
            "attributes": {
                "name": "Test Ingest Organization"
            },
            "type": "organization",
            "links": {
                "self": "http://localhost:9393/api/organizations/58b7ccd7e852dd0775f51cae",
                "all": "http://localhost:9393/api/organizations"
            },
            "meta": {
                "per_page": 2,
                "total": 5,
                "page": 1,
                "max_page": 3,
                "next_page": 2,
                "prev_page": null
            }
        },
        {
            "id": "58b7ccd7e852dd0775f51caf",
            "attributes": {
                "name": "Dummy test organization"
            },
            "type": "organization",
            "links": {
                "self": "http://localhost:9393/api/organizations/58b7ccd7e852dd0775f51caf",
                "all": "http://localhost:9393/api/organizations"
            },
            "meta": {
                "per_page": 2,
                "total": 5,
                "page": 1,
                "max_page": 3,
                "next_page": 2,
                "prev_page": null
            }
        }
    ],
    "meta": {
        "per_page": 2,
        "total": 5,
        "page": 1,
        "max_page": 3,
        "next_page": 2,
        "prev_page": null
    }
}

There are two issues with this:

  • the 'meta' section is present on each 'organization', but should only be present as a top level entry.
  • the links are missing on the top-level (the links on the individual 'organization' entries are generated by the Decorator itself and are correct).

The output I'm expecting is:

{
    "data": [
        {
            "id": "58b7ccd7e852dd0775f51cae",
            "attributes": {
                "name": "Test Ingest Organization"
            },
            "type": "organization",
            "links": {
                "self": "http://localhost:9393/api/organizations/58b7ccd7e852dd0775f51cae",
                "all": "http://localhost:9393/api/organizations"
            }
        },
        {
            "id": "58b7ccd7e852dd0775f51caf",
            "attributes": {
                "name": "Dummy test organization"
            },
            "type": "organization",
            "links": {
                "self": "http://localhost:9393/api/organizations/58b7ccd7e852dd0775f51caf",
                "all": "http://localhost:9393/api/organizations"
            }
        }
    ],
    "links": {
        "self": "http://localhost:9393/api/organizations?per_page=2&page=1",
        "first": "http://localhost:9393/api/organizations?per_page=2&page=1",
        "last": "http://localhost:9393/api/organizations?per_page=2&page=3",
        "next": "http://localhost:9393/api/organizations?per_page=2&page=2"
    },
    "meta": {
        "per_page": 2,
        "total": 5,
        "page": 1,
        "max_page": 3,
        "next_page": 2
    }
}

BTW: I'm using latest releases of grape, grape-roar, roar and roar-jsonapi on Ruby 2.3.1.

[JSON-API] Rendering an empty relationship

From @lasseebert on April 15, 2016 21:53

I would like to render empty (nil) relationships with JSONAPI like so:

{
  "relationships": {
    "some_relation": { "data": null }
  }
}

This is what the jsonapi specification says. See http://jsonapi.org/format/#document-resource-object-linkage

When I use render_nil: true in a has_one I get

undefined method `each' for nil:NilClass

It seems like a simple issue to fix inside the JSON::JSONAPI. Can anyone confirm that it's not just me doing something stupid? If so I'll be happy to fix it in a pull request :)

Copied from original issue: trailblazer/roar#189

How to set the type of included items?

Complete Description of Issue

I have a record with two 'has_one' relationships to the same type of object. I've found I can set the type using the 'type' option. However, the 'type' in the 'included' list is incorrect.

Steps to reproduce

Here is some code that reproduces my problem. I need the 'type' in the 'included' list to be 'item'. Currently it is 'type':'second-item'.

require "roar/json/json_api"
require "ostruct"

class BaseModel < OpenStruct
end

class Item < BaseModel
end

class User < BaseModel
  #belongs_to :item
  #belongs_to :second_item, class_name: "Item"
end

class ItemRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :item
end

class UserRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :user
  has_one :item, extend: ItemRepresenter
  has_one :second_item, extend: ItemRepresenter, type: :item
end

user = User.new(id: 1)
user.item = Item.new(id: 1)
user.second_item = Item.new(id: 2)
puts UserRepresenter.for_collection.prepare([user]).to_json

Expected behavior

{"data":[{"relationships":{"item":{"data":{"id":"1","type":"item"}},"second-item":{"data":{"id":"2","type":"item"}}},"id":"1","type":"user"}],"included":[{"id":"1","type":"item"},{"id":"2","type":"item"}]}

Actual behavior

{"data":[{"relationships":{"item":{"data":{"id":"1","type":"item"}},"second-item":{"data":{"id":"2","type":"item"}}},"id":"1","type":"user"}],"included":[{"id":"1","type":"item"},{"id":"2","type":"second-item"}]}

System configuration

roar version: 1.1.0
roar-jsonapi version: 0.0.3
representable version: 3.0.4

JSON-API support for :extends/:decorator with has_one

From @caseymct on January 8, 2016 4:16

as per glitter chat:

I have an Event model that has_one host, a User. the User model has it's own (quite large) decorator. It would be great to have

has_one :host, class: User, decorator: UserDecorator

I'd like to avoid doing

has_one :host, class: User, populator: ::Representable::FindOrInstantiate do
  # many properties
end

and duplicating this in every decorator that has a user.

Copied from original issue: trailblazer/roar#182

JSON API Serialize deep relationships

From @wuarmin on September 27, 2016 17:49

I know JSON API module is not finished yet, but maybe this can be accomplished with less effort. In my opinion the serialized compound doc, generated from following code, should contain the ressource object (type: organisations, id: 8) in the included-payload.

require 'roar/json'
require 'roar/decorator'
require 'roar/json/json_api'

class ArticleDecorator < Roar::Decorator
  include Roar::JSON::JSONAPI
  type :articles

  property :id
  property :title


  has_one :author, class: Authors do
    type :authors

    property :id
    property :email

    has_one :organisation, class: Organisations do
      type :organisations

      property :id
      property :name
    end
  end

end

organisation = Organisations.new(id: 8, name: 'Articles23')
author = Authors.new(id: 4711, email: '[email protected]', organisation: organisation)
article = Articles.new(id: 1, author: author, title: 'Programming')

puts ArticleDecorator.new(article).to_json

The result json contains the relationship, but the ressource-object is missing:

{
  "data": {
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "Programming"
    },
    "relationships": {
      "author": {
        "data": {
          "type": "authors",
          "id": "4711"
        }
      }
    }
  },
  "included": [
    {
      "type": "authors",
      "id": "4711",
      "attributes": {
        "email": "[email protected]"
      },
      "relationships": {
        "organisation": {
          "data": {
            "type": "organisations",
            "id": "8"
          }
        }
      }
    }
  ]
}

best regards

Copied from original issue: trailblazer/roar#200

Performance issue when decorator files are loaded multiple times

Complete Description of Issue

When files containing classes that inherit from Roar::Decorator and use the include Roar::JSON::JSONAPI.resource declaration are dynamically reloaded, there is a performance degradation. As the number of reloads goes up, the performance degradation seems to increase as well.

Steps to reproduce

Simulate dynamic reloading by running the Kernel::load method multiple with the same file. Again, that file should contain a class that inherits from Roar::Decorator and uses the include Roar::JSON::JSONAPI.resource declaration, like so:

class DocumentSingleResourceObjectDecorator < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :articles

  attributes do
    property :title
  end
end

(In our case, we use trailblazer and roar-jsonapi within Rails, and the performance slowdown initially showed up in our development environment. We were able to reproduce the problem without Rails being involved, both in this repo and with a minimal benchmark test case in #29.)

Expected behavior

The time to reload the file with the class in it should exhibit constant-time performance characteristics.

Translation: It should just load quickly and move on.

Actual behavior

Every time the same file is reloaded, there appears to be a significant performance degradation.

Translation: It slows down significantly while loading, causing further use of the class to be delayed.

System configuration

Roar version: 1.1.0

Roar-JSONAPI version: 0.0.3

Representer attributes with null values are excluded from resource document

Attributes with null values are excluded from resource document.

Per the JSONAPI spec:
If a request does not include all of the attributes for a resource, the server MUST interpret the missing attributes as if they were included with their current values. The server MUST NOT interpret missing attributes as null values.

By not including attributes with null values, API consumers will treat their values as unchanged, rather than NULL.

double included fields

I have model User which has many Roles and Role has many Permissions
The UserRepresenter

class UserRepresenter < BaseRepresenter
  type :users

  link :self, toplevel: true do
    "/users"
  end

  attributes do
    property :first_name
    property :last_name
  end

  link(:self) { "/users/#{represented.id}" }
  link(:all) { "/users" }

  has_many :roles, class: Role, decorator: RoleRepresenter do
    relationship do
      link(:related)  {"/users/#{represented.id}/roles"}
    end
  end
end

The RoleRepresenter

class RoleRepresenter < BaseRepresenter
  type :roles

  link :self, toplevel: true do
    "/roles"
  end

  attributes do
    property :name
  end

  link :self do
    "/roles/#{represented.id}"
  end

  has_many :permissions, decorator: PermissionRepresenter
end

When I call it, I receive included for user and included for roles. I think that in Json API there should be only one included field

{
  "data": {
    "relationships": {
      "roles": {
        "data": [
          {
            "id": "1",
            "type": "roles"
          }
        ],
        "links": {
          "related": "/users/2/roles"
        }
      }
    },
    "id": "2",
    "attributes": {
      "first-name": "Lady",
      "last-name": "Gaga",
    },
    "type": "users",
    "links": {
      "self": "/users/2",
      "all": "/users"
    }
  },
  "included": [
    {
      "relationships": {
        "permissions": {
          "data": [
            {
              "id": "4",
              "type": "permissions"
            },
            {
              "id": "15",
              "type": "permissions"
            }
          ]
        }
      },
      "included": [
        {
          "id": "4",
          "attributes": {
            "name": "View project",
          },
          "type": "permissions",
          "links": {
            "self": "/permissions/4"
          }
        },
        {
          "id": "15",
          "attributes": {
            "name": "View project user",
          },
          "type": "permissions",
          "links": {
            "self": "/permissions/15"
          }
        }
      ],
      "id": "1",
      "attributes": {
        "name": "Project User",
      },
      "type": "roles",
      "links": {
        "self": "/roles/1"
      }
    }
  ]
}

Collections `to_json(included: 'xxx')` doesn't output included records

When I call to_json() on a representer from collection without specifying included records I get the full compound document complete with the included records.

"{\"data\":[
  {\"relationships\":{
    \"feature-image\":{\"data\":{\"id\":\"1\",\"type\":\"images\"}},
    \"images\":{\"data\":[{\"id\":\"1\",\"type\":\"images\"}]},
    \"documents\":{\"data\":[]},
  },
  \"id\":\"1\",
  \"attributes\":{
    \"name\":\"Site Name 1\",
    \"reference\":\"760\",
    \"address\":{
      \"address_line_one\":\"Addr 1\",
      \"town_city\":\"Town\",
      \"county\":\"County\",
      \"postcode\":\"Some Postcode\",
      \"country\":\"England\"
    }
  },\"type\":\"sites\"},
  {\"relationships\":{
    \"feature-image\":{\"data\":{\"id\":\"2\",\"type\":\"images\"}},
    \"images\":{\"data\":[{\"id\":\"2\",\"type\":\"images\"}]},
    \"documents\":{\"data\":[]},
  },
  \"id\":\"2\",\"attributes\":{
    \"name\":\"Site Name 2\",
    \"reference\":\"114\",
    \"address\":{
      \"address_line_one\":\"Addr 1\",
      \"town_city\":\"Town\",
      \"county\":\"County\",
      \"postcode\":\"Some Postcode\",
      \"country\":\"England\"
    }
  },\"type\":\"sites\"}
],
\"included\":[
  {\"id\":\"1\",\"attributes\":{\"caption\":\"Exterior view of building\",\"file-url\":\"some_url\"},\"type\":\"images\",\"links\":{\"self\":\"http://images/1\"}},
  {\"id\":\"2\",\"attributes\":{\"caption\":\"Interior view of building\",\"file-url\":\"some_url\"},\"type\":\"images\",\"links\":{\"self\":\"http://images/2\"}}
]}"

When I pass in an included option, all relationship attributes are rendered, but nothing is included at all.

"{\"data\":[
  {\"relationships\":{
    \"feature-image\":{\"data\":{\"id\":\"1\",\"type\":\"images\"}},
    \"images\":{\"data\":[{\"id\":\"1\",\"type\":\"images\"}]},
    \"documents\":{\"data\":[]},
  },
  \"id\":\"1\",
  \"attributes\":{
    \"name\":\"Site Name 1\",
    \"reference\":\"760\",
    \"address\":{
      \"address_line_one\":\"Addr 1\",
      \"town_city\":\"Town\",
      \"county\":\"County\",
      \"postcode\":\"Some Postcode\",
      \"country\":\"England\"
    }
  },\"type\":\"sites\"},
  {\"relationships\":{
    \"feature-image\":{\"data\":{\"id\":\"2\",\"type\":\"images\"}},
    \"images\":{\"data\":[{\"id\":\"2\",\"type\":\"images\"}]},
    \"documents\":{\"data\":[]},
  },
  \"id\":\"2\",\"attributes\":{
    \"name\":\"Site Name 2\",
    \"reference\":\"114\",
    \"address\":{
      \"address_line_one\":\"Addr 1\",
      \"town_city\":\"Town\",
      \"county\":\"County\",
      \"postcode\":\"Some Postcode\",
      \"country\":\"England\"
    }
  },\"type\":\"sites\"}
]}"

When doing the same thing for a single representer I get the expected behaviour so I assume this issue is restricted to collections

Compound Documents not rendered per JSONAPI standard?

Complete Description of Issue

So this seems really basic to me so I'm not sure if I'm doing anything wrong, but it seems to me like compound documents are not serialized per jsonapi standards. I tried to reproduce a simplified version of the sample given at http://jsonapi.org/format/#document-compound-documents without links and reduced number of fields:

Steps to reproduce

class Article < Struct.new(:id, :title, :author, :comments)
end
class Person < Struct.new(:id, :name)
end
class Comment < Struct.new(:id, :body)
end

class PersonRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :people

  attributes do
    property :name
  end
end

class CommentRepresenter < Roar:: Decorator
  include Roar::JSON::JSONAPI.resource :comments

  attributes do
    property :body
  end
end

class ArticleRepresenter < Roar:: Decorator
  include Roar::JSON::JSONAPI.resource :articles

  attributes do
    property :title
  end
  has_one :author, extend: PersonRepresenter
  has_many :comments, extend: CommentRepresenter
end

article = Article.new(
  1,
  "JSON API paints my bikeshed!",
  Person.new(9, "Dan"),
  [Comment.new(5, "First!"), Comment.new(12, "I like XML better")]
)

ArticleRepresenter.new(article).to_hash

Expected behavior

Nested data attributes should be detailed in the included part. relationships should only contain id and type.

{"data"=>
  {"relationships"=>
    {"author"=>
      {"data"=>{"id"=>"9", "type"=>"people"}},
     "comments"=>
      {"data"=>
        [{"id"=>"5", "type"=>"comments"},
         {"id"=>"12", "type"=>"comments"}]}},
   "id"=>"1",
   "attributes"=>{"title"=>"JSON API paints my bikeshed!"},
   "type"=>"articles"},
 "included"=>
  [{"id"=>"9", "type"=>"people", "attributes"=>{"name"=>"Dan"}},
   {"id"=>"5", "type"=>"comments", "attributes"=>{"body"=>"First!"}},
   {"id"=>"12", "type"=>"comments", "attributes"=>{"body"=>"I like XML better"}}]}

Actual behavior

relationships contain attributes, included does not.

{"data"=>
  {"relationships"=>
    {"author"=>
      {"data"=>{"id"=>"9", "attributes"=>{"name"=>"Dan"}, "type"=>"people"}},
     "comments"=>
      {"data"=>
        [{"id"=>"5", "attributes"=>{"body"=>"First!"}, "type"=>"comments"},
         {"id"=>"12",
          "attributes"=>{"body"=>"I like XML better"},
          "type"=>"comments"}]}},
   "id"=>"1",
   "attributes"=>{"title"=>"JSON API paints my bikeshed!"},
   "type"=>"articles"},
 "included"=>
  [{"id"=>"9", "type"=>"author"},
   {"id"=>"5", "type"=>"comments"},
   {"id"=>"12", "type"=>"comments"}]}

(I also just noticed a type=>author instead of people there, don't know how that happened but it is the actual console output)

System configuration

Roar version: roar-edcd8efa0181
Roar JSONAPI version: roar-jsonapi-3ca7fa8e0f5a

[JSON-API] Recursive has_many :children (include tree)

From @felixbuenemann on February 18, 2016 1:6

It should be possible to express recursive relationships using JSONAPI has_one/has_many.

Without has_one/has_many I could just use a self-referential collection:

class TaxonRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI

  LimitDepth = ->(user_options: {}, **) {
    user_options[:depth] ? depth < user_options[:depth] : true
  }

  type :taxons
  property :id

  property :name
  # ...

  collection :children, extend: self, class: Taxon, if: LimitDepth
end
# Serialize up to depth 2
TaxonRepresenter.prepare(taxon).to_hash(user_options: { depth: 2 })

However has_one/has_many requires a block, so I can't just specify self as the decorator.

After looking at the source of Roar::JSON::JSONAPI I came up with this beautiful solution hack:

class TaxonRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI

  LimitDepth = ->(user_options: {}, **) {
    user_options[:depth] ? depth < user_options[:depth] : true
  }

  type :taxons
  property :id

  class RelationshipRepresenter < self; end

  property :name
  # ...

  nested :included do
    collection :children, decorator: TaxonRepresenter, class: Taxon, if: LimitDepth
  end

  nested :relationships do
    collection :children, decorator: RelationshipRepresenter, class: Taxon, if: LimitDepth
  end

end

Surely there should be an easier solution, given that trees are not exactly uncommon.

I'm using roar master and representable 3.0.0.

Copied from original issue: trailblazer/roar#185

[JSON-API] Relationship links

From @franzliedke on December 16, 2015 13:44

As can be seen in the first example on jsonapi.org, the "links" defined for a relationship are not to be mistaken with the links for a resource.

Thus, relationships defined like the following (taken from the Roar tests), should only add the links to either the relationship or the included resource itself:

has_one :author, class: Author, populator: ::Representable::FindOrInstantiate do
  type :authors

  property :id
  property :email
  link(:self) { "http://authors/#{represented.id}" }
end

Copied from original issue: trailblazer/roar#177

[JSON-API] Nested polymorphic representation

From @caseymct on January 22, 2016 2:22

Trying to transition from a standard representer format to json-api. I have a model, Notification, that has a polymorphic target - it can be one of many different types. How would I write this

class NotificationRepresenter < ApplicationRepresenter
  wrap_with :notifications

  timestamp :read_at
  timestamp :sent_at, writeable: false
  property :notification_type, writeable: false
  property :message_variables, writeable: false
  property :representable_target, as: :target, decorator: proc {
    representable_target.representer
  }
end

as a JSON-API compliant response? or can I? Somehow I need to set a has_one with a class that is determined by representable_target, then have the block return that target's data.

Copied from original issue: trailblazer/roar#183

JSONAPI: Relationship IDs ignore as: :id option

From @ottodog on July 14, 2016 18:56

I'm using the as: option with :id inside a has_many relationship:

Representer

class ParentRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI
  type :parents

  has_many :children do
  type :children

  property :alias_id, as: :id
  link :self do
    parent_child_url(represented.parent.id, represented.alias_id)
  end
end

Output of respond_with

{
  "data": {
    "relationships": {
      "documents": {
        "data": [
          {
            "type": "documents",
            "id": ""
          }
        ],
        "links": {
          "self": "http://127.0.0.1:3000/parent/57879b4a3bb752aaa6e24a35/child/0"
        }
      }
    }
  },
  "included": [
    {
      "type": "documents",
      "id": "0",
      "attributes": {},
      "links": {
        "self": "http://127.0.0.1:3000/parent/57879b4a3bb752aaa6e24a35/child/0"
      }
    }
  ]
}

The ID is properly 0 in the included array, but empty in relationships.

Note:
I'm using roar-rails with the master branch of roar.
I'm also doing something a bit weird with the child IDs in my API. For each parent, they count up from 0, and so are not unique amongst all children, but are accessed through both the child and parent ID, ie. parent/:parent_id/children/child_id. That is why the child ID needs to be aliased, as they are stored in the database using unique ids, but only this separate index will be exposed through API.

Copied from original issue: trailblazer/roar#193

JSON API dasherize property names

From @thhermansen on November 30, 2015 14:31

Hi,

I wasn't able to figure this one out myself. Given the following:

class User < Roar::Decorator
  include Roar::JSON::JSONAPI

  property :full_name
end

Is there a way to make the serialised version of a user to have property named full-name?

I can solve it by using #property's as-option, but I'd like to dasherize all property names automatically.

Copied from original issue: trailblazer/roar#173

Using JSONAPI

From @dwhelan on May 14, 2016 0:30

I am trying to use Roar::JSON::JSONAPI to accept and return JSONAPI payloads. I think I must be missing something because the json rendered in a get does not seem to conform JSONAPI v1.0.

require_relative 'spec_helper'

class AppleSauce
  attr_reader :id, :taste

  def initialize
    @id = '1'
    @taste = 'tart'
  end
end

class Decorator < Roar::Decorator
  include Roar::JSON::JSONAPI
  include Roar::Hypermedia
  include Grape::Roar::Representer

  type :apple_sauce
  property :id
  property :taste
end

module Representer
  include Roar::JSON::JSONAPI
  include Roar::Hypermedia
  include Grape::Roar::Representer

  type :apple_sauce
  property :id
  property :taste
end

describe 'roar' do
  let(:expected) { { data: { type: 'apple_sauce', id: '1', attributes: { taste: 'tart' } } } }

  it { expect(JSON.parse(Decorator.prepare(AppleSauce.new).to_json)).to eq expected }
  it { expect(JSON.parse(AppleSauce.new.extend(Representer).to_json)).to eq expected }
end
Failures:

  1) roar should eq {:data=>{:type=>"apple_sauce", :id=>"1", :attributes=>{:taste=>"tart"}}}
     Failure/Error: it { expect(JSON.parse(Decorator.prepare(AppleSauce.new).to_json)).to eq expected }

       expected: {:data=>{:type=>"apple_sauce", :id=>"1", :attributes=>{:taste=>"tart"}}}
            got: {"apple_sauce"=>{"id"=>"1", "taste"=>"tart"}}

       (compared using ==)

       Diff:
       @@ -1,2 +1,2 @@
       -:data => {:type=>"apple_sauce", :id=>"1", :attributes=>{:taste=>"tart"}},
       +"apple_sauce" => {"id"=>"1", "taste"=>"tart"},

     # ./spec/test_spec.rb:35:in `block (2 levels) in <top (required)>'

  2) roar should eq {:data=>{:type=>"apple_sauce", :id=>"1", :attributes=>{:taste=>"tart"}}}
     Failure/Error: it { expect(JSON.parse(AppleSauce.new.extend(Representer).to_json)).to eq expected }

       expected: {:data=>{:type=>"apple_sauce", :id=>"1", :attributes=>{:taste=>"tart"}}}
            got: {"apple_sauce"=>{"id"=>"1", "taste"=>"tart"}}

       (compared using ==)

       Diff:
       @@ -1,2 +1,2 @@
       -:data => {:type=>"apple_sauce", :id=>"1", :attributes=>{:taste=>"tart"}},
       +"apple_sauce" => {"id"=>"1", "taste"=>"tart"},

     # ./spec/test_spec.rb:36:in `block (2 levels) in <top (required)>'

Finished in 0.01212 seconds (files took 0.47538 seconds to load)
2 examples, 2 failures

Any suggestions would be most appreciated!

Copied from original issue: trailblazer/roar#191

Representers: `defaults` block is lost in nested declarations

Complete Description of Issue

Due to legacy design, we want to use the non-strict behavior for as: for property-names, allowing underscores.

Following the Documentation I implemented a representer with the default set to strict: false.

require "roar/json"
require "roar/json/json_api"

class RuleRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :rules

  defaults do |name, _|
    { as: JSONAPI::MemberName.call(name, strict: false) }
  end

  attributes do
    property :chain_id
    property :name
    property :event_type
  end
end

However, the output was hyphenated!

[{:id=>"1c9c618a-86ad-437f-b3dd-50dd5a3e290f", :attributes=>{:"chain-id"=>"072bb8ca-a7c7-4408-b5f1-831d7277f676", :"event-type"=>"transactions"}, :type=>"rules"}]

I was able to workaround by changing my Representer. I placed the defaults block inside of the attributes block.

require "roar/json"
require "roar/json/json_api"

class RuleRepresenter < Roar::Decorator
  include Roar::JSON::JSONAPI.resource :rules

  attributes do
    defaults do |name, _|
      { as: JSONAPI::MemberName.call(name, strict: false) }
    end

    property :chain_id
    property :name
    property :event_type
  end
end

Steps to reproduce

Create a representer which sets a defaults block like so:

defaults do |name, _|
  { as: JSONAPI::MemberName.call(name, strict: false) }
end

and has attributes

attributes do
  property :name
end

Expected behavior

The attributes should be allowed to pass the non-strict conversion and remain underscored.

Actual behavior

Attributes are hyphenated.

System configuration

Roar version: 1.1.0
Roar JSONAPI version: 0.0.3
Declarative version: 0.0.9

During research, I found many things I didn't understand. Please excuse my ignorance.

  • attributes -> Roar::JSON::JSONAPI::Declarative, sets the inherit: true property, presumably so you can have multiple attributes blocks and they merge together.
  • nested -> When a block is passed, the options[:_base] is set to Decorator.default_nested_class, which is just an base Representable::Decorator. At this point, self is actually still RuleRepresenter.
  • Several steps omitted...
  • Declarative::Definitions::Definition#add Called with attributes, options and the block. Here it extracts some properties, calls the _defaults pipeline, and then calls the nested builder with the _base, name, and block.
  • Declarative::Schema::DSL::NestedBuilder (proc) is called, wherein self is Declarative::Schema::DSL. No context anymore here, except what was provided in options. which is provided for that purpose. This proc begins with Class.new(options[:_base]), which extends the Representable::Decorator supplied above. Then a block is evaluated which adds the features from the options context, and finally the block is class_evald. The evaluation of feature with the Roar::JSON::JSONAPI::Defaults feature sets up @options with a fresh set, and the options[:_defaults] from the parent is not used here. There's no way to set the options. There's no way to access the @dynamic_options block of options[:_defaults] either. I can't determine if there's some means by which to merge without hacking too much further.

Now, if you were to change Roar::JSON::JSONAPI::Declarative to pass the private option _defaults, then those configurations would be available inside of Declarative::Definitions::Definition#add. However, getting it merged inside of Declarative::Schema::DSL::NestedBuilder is beyond me at this time.

JSON-API representer does not work with Roar::Client

From @ekosz on January 6, 2015 15:42

Given the code

module SongsRepresenter
  include Roar::JSON::JSONAPI
  type :songs

  property :id
  property :name
end

class Song < OpenStruct
  include Roar::JSON::JSONAPI
  include SongRepresenter
  include Roar::Client
end

When I run

Song.new.get(uri: 'http://example.com/songs/1', as: 'application/json')

I get the error

NoMethodError: undefined method `deserialize' for #<Song>
    from /roar-1.0.0/lib/roar/http_verbs.rb:65:in `handle_response'
    from /roar-1.0.0/lib/roar/http_verbs.rb:40:in `get'

I then tried replacing Roar::JSON::JSONAPI with Roar::JSON in the client (as it does have #deserialize there) but I then got this error instead:

TypeError: no implicit conversion of String into Integer
    from /roar-1.0.0/lib/roar/json/json_api.rb:45:in `[]'
    from /roar-1.0.0/lib/roar/json/json_api.rb:45:in `from_hash'
    from /roar-1.0.0/lib/roar/json/json_api.rb:126:in `from_hash'
    from /representable-2.1.3/lib/representable/json.rb:30:in `from_json'
    from /roar-1.0.0/lib/roar/json.rb:21:in `from_json'
    from /roar-1.0.0/lib/roar/json.rb:30:in `deserialize'
    from /roar-1.0.0/lib/roar/http_verbs.rb:65:in `handle_response'
    from /roar-1.0.0/lib/roar/http_verbs.rb:40:in `get'

As an aside, I also noticed the current README is out of date in a few locations regarding JSON-API. For example the README refers to the JSON-API module as Roar::JSON::JsonApi but in the source its Roar::JSON::JSONAPI.

Copied from original issue: trailblazer/roar#119

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.