Giter Site home page Giter Site logo

jsonapi-rb / jsonapi-rails Goto Github PK

View Code? Open in Web Editor NEW
312.0 8.0 62.0 153 KB

Rails gem for fast jsonapi-compliant APIs.

Home Page: http://jsonapi-rb.org

License: MIT License

Ruby 90.75% HTML 6.90% JavaScript 1.43% CSS 0.92%
json api jsonapi rails serialization deserialization ruby json-api

jsonapi-rails's Introduction

jsonapi-rb

Ruby gem for building and consuming JSON API documents.

Status

Gem Version Gitter chat

Resources

Code

jsonapi-rb is simply a bundle of:

For framework integrations, see:

Installation

# In Gemfile
gem 'jsonapi-rb'

then

$ bundle

or manually via

$ gem install jsonapi-rb

Usage and documentation

See jsonapi-rb.org/guides.

License

jsonapi-rb is released under the MIT License.

jsonapi-rails's People

Contributors

alsemyonov avatar beauby avatar cassidycodes avatar cristianstu avatar dawidof avatar ezekg avatar malcolmbaig avatar nbibler avatar olleolleolle avatar petergoldstein avatar pjmartorell avatar remear avatar smaximov avatar tagliala 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

jsonapi-rails's Issues

The error pointers are not set unless the attribute is present in the payload

When an attribute is set as required in the model but not sent across in the request payload, the error gets included in the error JSON:API error response but the source pointer doesn't get set with it.

I'm currently not using any custom Deserializers and returning the errors as below;

render jsonapi_errors: @user.errors, status: :unprocessable_entity

Any help on how to resolve this will be appreciated. Great work on this Gem by the way.

Add global configuration options.

  • Opt-out of renderer registration (for instance when using deserialization only).
  • Opt-in for top level jsonapi member.
  • Global custom serializable resource class inferrer.
  • ...?

Next release date?

Hi,
Great work on this gem. I am waiting for the next release which has the hook for 'meta'. Any idea when the next release would be? And if there can any help in getting the next release out, I am happy to help.
thanks!

Raise on unsupported include fields

according to the json api spec, endpoints must respond with a 400 if unsupported includes fields are requested. I can handle undefined activerecord associations on my end, but i'd like it if jsonapi also raised something if there are included fields that arent defined in my serializers.

Thanks in advance for your time!

'source' is missing when model errors are serialized

Hi,

When my model is invalid I render the response (from controller) like this:

render jsonapi_errors: activity.errors, status: :unprocessable_entity

It serializes the model error as following:

    {
        "errors":
        [
            {
                "title": "Invalid occurred_at",
                "detail": "Occurred at Future time is not allowed",
                "source":
                {
                }
            },
            {
                "title": "Invalid code",
                "detail": "Code This status is in conflict with the previous one",
                "source":
                {
                }
            }
        ],
        "jsonapi":
        {
            "version": "1.0"
        }
    }

It has two issues: 1) 'source' value is missing, 2) The detail of the message shouldn't contain the source attribute. For example instead of 'Occurred at Future time is not allowed' it should be 'Future time is not allowed'.

Thanks

jsonapi-rails/jsonapi-rb incompatibility

jsonapi-rails 0.2.1 is not version compatible with jsonapi-rb 0.3.0, while
jsonapi-rails 0.1.2 is. I don't think this is intended. It causes very strange bundler version resolution.

Difficulty adding error codes to SerializableActiveModelError

I work on an app where one of our API clients would like to customize an error message shown to the user. The server is using jsonapi-rails (version 0.3.1), with the default error serializer, SerializableActiveModelErrors.

The Rails app in this case runs errors.add(:base, ...) and the error value could be one of many types.

There are title, detail, and source attributes on errors, but there's no natural way for the client to override a specific error message. The client could check title === "An error title value" and customize that, but the server may change title over time, since it's defined by the JSON API spec to be a human-readable summary.

What we'd like is to use code, which JSON API supports:

An error object MAY have the following members:

[...]

  • code: an application-specific error code, expressed as a string value.

This would let us detect, say, code === "foo", and even if title changes the client would continue with its custom error message.

In our case, we plan on detecting errors.add(:base, :foo), seeing that :foo is a symbol, and treating that as an error code. (If the Rails app ever used a string, as with errors.add(:base, "There was an error"), that would not result in a code.)

This seems to be fairly tricky to accomplish.

Right now, the default error class of SerializableActiveModelErrors is configurable, but SerializableActiveModelErrors is private. If we were to ignore that it's private and, say, subclass SerializableActiveModelErrors, it looks like we'd need to reimplement code related to the exposures object:

def initialize(exposures)
  @errors = exposures[:object]
  @reverse_mapping = exposures[:_jsonapi_pointers] || {}

  freeze
end

Another issue is that SerializableActiveModelError (singular), which is also private, doesn't have access to the original errors object (or one of the single errors), and so it isn't possible to retrieve the :foo corresponding to errors.add(:base, :foo).

As far as I can tell, we'd have to more or less implement our own error serialization to pull code values out of ActiveModel errors.

Here's a very monkey-patch-y implementation of a serializer in the way I'm describing.

class CodedSerializableActiveModelErrors < ::JSONAPI::Rails::SerializableActiveModelErrors
  def as_jsonapi
    # Example: { email: [{ error: :blank }] }
    @errors.details.flat_map do |key, attribute_errors|
      attribute_errors.map do |error:|
        # full_message(:email, generate_message(:email, :blank))
        full_message = @errors.full_message(key, @errors.generate_message(key, error))

        error_attributes = ::JSONAPI::Rails::SerializableActiveModelError.new(field: key, message: full_message, pointer: @reverse_mapping[key]).as_jsonapi

        if error_attributes.key?(:code)
          raise "Code already provided by the library. Refusing to override."
        end

        # Override the error_attributes this way so that we inherit all SerializableActiveModelError
        # attributes, relationships, etc.
        if error
          error_attributes[:code] = error.to_s
        end

        error_attributes
      end
    end
  end
end

render jsonapi_errors source

I use ActiveInteraction gem with Rails API and jsonapi-rails gem for creating my JSON API. My problem is when I render errors using jsonapi_errors, the source of the error response is empty. How should I fill this field? This is the response:

{
    "errors": [
        {
            "title": "Invalid credit",
            "detail": "Credit must be greater than or equal to 2000000",
            "source": {}
        }
    ],
    "jsonapi": {
        "version": "1.0"
    }
}

Using `post` as deserializable resource key loses data

As per a discussion on Gitter.

It would appear that using post as the deserializable_resource key results in an empty hash at that key.

The data looks good at the Rack level but at some point between Rack and the Controller action the key is stripped of any nested data.

Rails 5.0.2

Prevent bogus serializer creation/cleanup after failure

Currently, if the serializable resource generator fails (e.g. you generate a serializer for a model which doesn't exist), an empty file is created. IMO, the generator should either validate the existence of the model before creating the file or delete the file if the task fails.

New release soon?

Sorry for raising this as a new issue (wish Github had a discussion board or something).

I was wondering, with the Rails cache fix in the json-caching branch. If we could expect a new release? I try my best to avoid referencing Github branches directly whenever I can.

This is also a callout for a new release for the json-render gem (which I guess would need to be included regardless).

Setting custom classes

I don't have a solid mental model for what's happening when I'm trying to set a class -> serializable class mapping.

I've got two classes:

class Relationship < ApplicationRecord
  # relationship stuff. baggage, if you will.
end

# then, nested in app/models/relationships:
class Relationships::Approve < Relationship
  # approval related things, so all that logic is only here
end

I've got a SerializableRelationship class that's working. I'd like to reuse that with these subclasses.

class Api::Relationships::ApprovesController < Api::ApiController
  before_action :set_relationship

  def update
    if @relationship.update(status: "approved", action_user: current_user)
      render jsonapi: @relationship
    else
      render jsonapi_errors: @relationship.errors
    end
  end

  private
    def set_relationship
      @relationship = Relationships::Approve.find(params[:id])
    end
end

How do I:

  • set this option inside my controller action?
  • set this option at the controller wide level? (aka this )
  • application-wide level (in the initializer)?

I know that, once I get it, it'll probably be an "ohhhh" moment. I conceptually understand it's a big hash that makes certain keys to certain other classes that serialize those things. But I need to see an example, and get details, because I don't understand:

  • what does the key need to be?
  • what does the value need to be?
  • are modifications made to the things I pass in?

I've been guessing things and not getting feedback I know how to understand from the errors.

I think this would make some great addition to the docs! I'll check back in when I figure it out, and maybe PR in some updates if you're ok with that.

Next Release?

I have noticed master has diverged quite a lot from 0.3.1. Are you close to being able to release the new merges and refactors?

Render error code

I want to render error code property, as JSON API specification describes, with jsonapi_errors method. How could I set the code property in error object?

"included" key missing from response when object is same type

Working through implementing includes I have discovered what appears to be a bug. All my other code like this works as expected and the only difference I can find is that the association is the same class as the requested object.

Controller
users = User.includes(:managers, :direct_reports).page(params[:page]).per(params[:per_page])
render jsonapi: users,
             include: ['managers', 'direct_reports'],
             links: pagination_links(users, User)
Serializer
class SerializableUser < JSONAPI::Serializable::Resource
  type 'users'
  
  belongs_to :role
  belongs_to :parent
  has_many :managers, class_name: 'User'
  has_many :direct_reports, class_name: 'User'
end

The response looks nearly correct, except the includes are missing at the end. I have provided a sample below.

{
  "data": [
    {
      "id": "1",
      "type": "users",
      "attributes": {
        "name": "Meghan Wilkinson",
        "email": "[email protected]",
        "threshold": null
      },
      "relationships": {
        "direct_reports": {
          "links": {
            "self": "http://localhost:3000/v1/users/1/relationships/direct_reports",
            "related": "http://localhost:3000/v1/users"
          },
          "data": []
        },
        "managers": {
          "links": {
            "self": "http://localhost:3000/v1/users/1/relationships/managers",
            "related": "http://localhost:3000/v1/users"
          },
          "data": []
        }
      }
    },
    {
      "id": "2",
      "type": "users",
      "attributes": {
        "name": "Dora Nitzsche",
        "email": "[email protected]",
        "threshold": null
      },
      "relationships": {
        "parent": {
          "meta": {
            "included": false
          }
        },
        "role": {
          "meta": {
            "included": false
          }
        },
        "direct_reports": {
          "links": {
            "self": "http://localhost:3000/v1/users/2/relationships/direct_reports",
            "related": "http://localhost:3000/v1/users"
          },
          "data": [
            {
              "type": "users",
              "id": "5"
            }
          ]
        },
        "managers": {
          "links": {
            "self": "http://localhost:3000/v1/users/2/relationships/managers",
            "related": "http://localhost:3000/v1/users"
          },
          "data": [
            {
              "type": "users",
              "id": "4"
            }
          ]
        }
      }
    }
  ],
  "links": {
    "self": "http://localhost/v1/users?page=1&per_page=50",
    "first": "http://localhost/v1/users?page=1&per_page=50",
    "prev": null,
    "next": null,
    "last": "http://localhost/v1/users?page=1&per_page=50"
  },
  "jsonapi": {
    "version": "1.0"
  }
}

no json/jsonb support?

For render method I can use something like this (example from official doc):

           fields: { users: [:name, :email], posts: [:title, :content] }

But I can't use this, right?

           fields: { users: [:name, :email], posts: [:title, :content, data: [:additional_info, :some_key]] }

Why? I want that filtering for json/jsonb columns ;(

Deserialization does not work in Rails controller specs.

Issue moved from jsonapi-parser

I'm trying to use the jsonapi-rails gem to deserialize the incoming params on an HTTP request, but it's not working as expected.

I tried 2 approaches:

Firstly I followed the instructions from the guides and tried using the default JSONAPI::Deserializable::Resource.call(json_hash) like this:

class AuthorsController < ApplicationController
  def create
    respond_with Author.create(author_params), class: AuthorSerializer
  end

  private

  def author_params
    JSONAPI::Deserializable::Resource.call(params)  
  end

but since the JSONAPI::Parser::Resource is expecting a hash an error is raised.

The second approach is using the deserializable_resource on the controller level:

class AuthorsController < ApplicationController
  deserializable_resource :author, only: :create
  
  def create
    respond_with Author.create(author_params), class: AuthorSerializer
  end

  private

  def author_params
    params.require(:author).permit(:name)
  end

When I try it like this I get ActionController::ParameterMissing: param is missing or the value is empty: author when inspecting the author_params. If I inspect the params they are looking like this:

ActionController::Parameters {"data"=>{"type"=>"authors", "attributes"=>{"name"=>"Author name"}}, "controller"=>"api/v1/authors", "action"=>"create"} permitted: false>

which means that the desiralization on the params didn't occur.

Any guidance would be helpful. I'm using ruby 2.3.1p112 and Rails 5.0.1

Add documentation for using a cache

Hi there,
I do find the default values for jsonapi_cache and default initializers, but is it possible to add some documentation? I have a very static json and it would be awesome to have it cached somewhere.
Thanks in advance!

jsonapi:serializable declares `has_one` instead of `belongs_to`

When defining a serializable resource with the provided generator, jsonapi-rails declares has_one relationships in the serializer despite the resource specifying a belongs_to association.

ruby -v 2.5.0
rails -v 5.1.4

model:

class Attempt < ApplicationRecord
  belongs_to :user, optional: true
  belongs_to :consultant, class_name: 'User', optional: true
  belongs_to :version

  validates :price, presence: true

  monetize :price_cents
end

generator command:

cpp-api resource-crud % rails g jsonapi:serializable Attempt
      create  app/serializable/serializable_attempt.rb
cpp-api resource-crud %

output:

class SerializableAttempt < JSONAPI::Serializable::Resource
  type 'attempts'
  attribute :price_cents
  attribute :price_currency
  attribute :state
  attribute :results
  attribute :created_at
  attribute :updated_at
  has_one :user
  has_one :consultant
  has_one :version
end

Documentation (was: controller.params[:_jsonapi] is nil)

Using the skeleton setup below, I am getting a NoMethodError in https://github.com/jsonapi-rb/jsonapi-rails/blob/master/lib/jsonapi/rails/controller.rb#L35 since params[:_jsonapi] seems to be nil. Am I missing something?

Test repository: https://github.com/floriandejonckheere/jsonapi-rails-bug

class UsersController < ApplicationController
  deserializable_resource :user, :only => %i[show]

  # GET /users/:id
  def show
    @user = User.find params[:id]

    render :jsonapi => @user
  end
end

class SerializableUser < JSONAPI::Serializable::Resource
  type 'users'

  attribute :name
end

Specify class when including relationships

In my controller I have

module API
  module V1
    class ProjectsController < API::V1::ApplicationController

      # GET /projects
      def index
        render jsonapi: Project.all,
               class: { Project: API::V1::SerializableProject },
               include: params[:include]
      end

In my Serializer I have

module API
  module V1
    class SerializableProject < JSONAPI::Serializable::Resource
      type 'projects'

      attributes :name, :job_number

      has_many :companies
    end
  end
end

When I pass the include params with GET /projects?include=companies, I get undefined method 'new' for nil:NilClass. I'm assuming because it can't find API::V1::SerializableCompany

How am I supposed to specify the class for all includes? This was a simplified example, but some of my Models have multiple relationships.

undefined method `new' for nil:NilClass

class Api::V1::CitiesController < ApplicationController
  def index
    render jsonapi: City.limit(10).all,
      jsonapi_class: 'Api::V1::SerializableCity'
  end
end

# cat app/serializers/api/v1/serializable_city.rb
class Api::V1::SerializableCity < JSONAPI::Serializable::Resource
  type 'cities'
  attributes :code, :name_ru, :name_en
  # has_many :airports
end
Full Trace:
jsonapi-serializable (0.3.0) lib/jsonapi/serializable/renderer.rb:89:in `_build'
jsonapi-serializable (0.3.0) lib/jsonapi/serializable/renderer.rb:80:in `block in build_resources'
jsonapi-serializable (0.3.0) lib/jsonapi/serializable/renderer.rb:80:in `map'
jsonapi-serializable (0.3.0) lib/jsonapi/serializable/renderer.rb:80:in `build_resources'
jsonapi-serializable (0.3.0) lib/jsonapi/serializable/renderer.rb:44:in `render'
jsonapi-rails (0.3.1) lib/jsonapi/rails/renderer.rb:16:in `render'
jsonapi-rails (0.3.1) lib/jsonapi/rails/railtie.rb:58:in `block (4 levels) in register_renderers'
activesupport (5.1.4) lib/active_support/notifications.rb:168:in `instrument'
jsonapi-rails (0.3.1) lib/jsonapi/rails/railtie.rb:55:in `block (3 levels) in register_renderers'
actionpack (5.1.4) lib/action_controller/metal/renderers.rb:149:in `block in _render_to_body_with_renderer'
/usr/lib/ruby/2.3.0/set.rb:306:in `each_key'
/usr/lib/ruby/2.3.0/set.rb:306:in `each'
actionpack (5.1.4) lib/action_controller/metal/renderers.rb:145:in `_render_to_body_with_renderer'
actionpack (5.1.4) lib/action_controller/metal/renderers.rb:141:in `render_to_body'
actionpack (5.1.4) lib/abstract_controller/rendering.rb:24:in `render'
actionpack (5.1.4) lib/action_controller/metal/rendering.rb:36:in `render'
actionpack (5.1.4) lib/action_controller/metal/instrumentation.rb:44:in `block (2 levels) in render'
activesupport (5.1.4) lib/active_support/core_ext/benchmark.rb:12:in `block in ms'
/usr/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'
activesupport (5.1.4) lib/active_support/core_ext/benchmark.rb:12:in `ms'
actionpack (5.1.4) lib/action_controller/metal/instrumentation.rb:44:in `block in render'
actionpack (5.1.4) lib/action_controller/metal/instrumentation.rb:87:in `cleanup_view_runtime'
activerecord (5.1.4) lib/active_record/railties/controller_runtime.rb:29:in `cleanup_view_runtime'
actionpack (5.1.4) lib/action_controller/metal/instrumentation.rb:43:in `render'
app/controllers/api/v1/cities_controller.rb:4:in `index'
actionpack (5.1.4) lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'
actionpack (5.1.4) lib/abstract_controller/base.rb:186:in `process_action'
actionpack (5.1.4) lib/action_controller/metal/rendering.rb:30:in `process_action'
actionpack (5.1.4) lib/abstract_controller/callbacks.rb:20:in `block in process_action'
activesupport (5.1.4) lib/active_support/callbacks.rb:131:in `run_callbacks'
actionpack (5.1.4) lib/abstract_controller/callbacks.rb:19:in `process_action'
actionpack (5.1.4) lib/action_controller/metal/rescue.rb:20:in `process_action'
actionpack (5.1.4) lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
activesupport (5.1.4) lib/active_support/notifications.rb:166:in `block in instrument'
activesupport (5.1.4) lib/active_support/notifications/instrumenter.rb:21:in `instrument'
activesupport (5.1.4) lib/active_support/notifications.rb:166:in `instrument'
actionpack (5.1.4) lib/action_controller/metal/instrumentation.rb:30:in `process_action'
actionpack (5.1.4) lib/action_controller/metal/params_wrapper.rb:252:in `process_action'
activerecord (5.1.4) lib/active_record/railties/controller_runtime.rb:22:in `process_action'
actionpack (5.1.4) lib/abstract_controller/base.rb:124:in `process'
actionview (5.1.4) lib/action_view/rendering.rb:30:in `process'
actionpack (5.1.4) lib/action_controller/metal.rb:189:in `dispatch'
actionpack (5.1.4) lib/action_controller/metal.rb:253:in `dispatch'
actionpack (5.1.4) lib/action_dispatch/routing/route_set.rb:49:in `dispatch'
actionpack (5.1.4) lib/action_dispatch/routing/route_set.rb:31:in `serve'
actionpack (5.1.4) lib/action_dispatch/journey/router.rb:50:in `block in serve'
actionpack (5.1.4) lib/action_dispatch/journey/router.rb:33:in `each'
actionpack (5.1.4) lib/action_dispatch/journey/router.rb:33:in `serve'
actionpack (5.1.4) lib/action_dispatch/routing/route_set.rb:834:in `call'
rack (2.0.3) lib/rack/etag.rb:25:in `call'
rack (2.0.3) lib/rack/conditional_get.rb:25:in `call'
rack (2.0.3) lib/rack/head.rb:12:in `call'
rack (2.0.3) lib/rack/session/abstract/id.rb:232:in `context'
rack (2.0.3) lib/rack/session/abstract/id.rb:226:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/cookies.rb:613:in `call'
activerecord (5.1.4) lib/active_record/migration.rb:556:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/callbacks.rb:26:in `block in call'
activesupport (5.1.4) lib/active_support/callbacks.rb:97:in `run_callbacks'
actionpack (5.1.4) lib/action_dispatch/middleware/callbacks.rb:24:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/debug_exceptions.rb:59:in `call'
web-console (3.5.1) lib/web_console/middleware.rb:135:in `call_app'
web-console (3.5.1) lib/web_console/middleware.rb:28:in `block in call'
web-console (3.5.1) lib/web_console/middleware.rb:18:in `catch'
web-console (3.5.1) lib/web_console/middleware.rb:18:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
railties (5.1.4) lib/rails/rack/logger.rb:36:in `call_app'
railties (5.1.4) lib/rails/rack/logger.rb:24:in `block in call'
activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `block in tagged'
activesupport (5.1.4) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `tagged'
railties (5.1.4) lib/rails/rack/logger.rb:24:in `call'
sprockets-rails (3.2.1) lib/sprockets/rails/quiet_assets.rb:13:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/request_id.rb:25:in `call'
rack (2.0.3) lib/rack/method_override.rb:22:in `call'
rack (2.0.3) lib/rack/runtime.rb:22:in `call'
activesupport (5.1.4) lib/active_support/cache/strategy/local_cache_middleware.rb:27:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/static.rb:125:in `call'
rack (2.0.3) lib/rack/sendfile.rb:111:in `call'
rack-cors (1.0.2) lib/rack/cors.rb:97:in `call'
railties (5.1.4) lib/rails/engine.rb:522:in `call'
puma (3.11.0) lib/puma/configuration.rb:225:in `call'
puma (3.11.0) lib/puma/server.rb:624:in `handle_request'
puma (3.11.0) lib/puma/server.rb:438:in `process_client'
puma (3.11.0) lib/puma/server.rb:302:in `block in run'
puma (3.11.0) lib/puma/thread_pool.rb:120:in `block in spawn_thread'

Expose deserialization reverse mapping in controllers

When a payload is deserialized, a set of json pointers for the deserialized fields is generated, which can then be used when serializing errors.

Example

# Incoming payload
{
  data: {
    id: "1",
    type: "users",
    attributes: {
      name: "Lucas",
      email: "[email protected]"
    },
    relationships: {
      posts: {
        data: [{ type: "posts", id: "2" }, { type: "posts", id: "3" }]
      }
    }
  }
}

# Deserialized params (via `deserializable_resource :user`)
{
  id: "1",
  type: "users",
  name: "Lucas",
  email: "[email protected]",
  post_ids: ["2", "3"],
  post_types: ["posts", "posts"]
}

# Reverse mapping / json pointers
{
  id: "/data/id",
  type: "/data/type",
  name: "/data/attributes/name",
  email: "/data/attributes/email",
  post_ids: "/data/relationships/posts/data",
  post_types: "/data/relationships/posts/data"
}

Possible interface

  • An ActionController instance method, possibly named jsonapi_pointers
  • Other suggestions?

#<NoMethodError: undefined method `new' for nil:NilClass> during register_renderers?

Ruby: 2.4.2
Rails: 5.1.4

I get the title error with the following stracktrace when trying to call render jsonapi: user

{
                "id": 0,
                "trace": "jsonapi-serializable (0.3.0) lib/jsonapi/serializable/renderer.rb:88:in `_build'"
            },
            {
                "id": 1,
                "trace": "jsonapi-serializable (0.3.0) lib/jsonapi/serializable/renderer.rb:82:in `build_resources'"
            },
            {
                "id": 2,
                "trace": "jsonapi-serializable (0.3.0) lib/jsonapi/serializable/renderer.rb:44:in `render'"
            },
            {
                "id": 3,
                "trace": "jsonapi-rails (0.3.1) lib/jsonapi/rails/renderer.rb:16:in `render'"
            },
            {
                "id": 4,
                "trace": "jsonapi-rails (0.3.1) lib/jsonapi/rails/railtie.rb:58:in `block (4 levels) in register_renderers'"
            },
            {
                "id": 5,
                "trace": "activesupport (5.1.4) lib/active_support/notifications.rb:168:in `instrument'"
            },
            {
                "id": 6,
                "trace": "jsonapi-rails (0.3.1) lib/jsonapi/rails/railtie.rb:55:in `block (3 levels) in register_renderers'"
            }

My UserController looks like this:

module Api::V1
  class UsersController < Api::V1::ApiController

    #POST /register
    def register
      user = User.create(user_params)
      if user.save
        render jsonapi: user
      else
        render jsonapi_errors: user.errors
      end
    end
  end
end

And my user_serializer.rb

class SerializableUser < JSONAPI::Serializable::Resource
  type 'users'

  attributes :email, :first_name, :last_name
end

I have also tried namespacing my SerializableUser with module Api::V1 but no luck. Any ideas what I'm doing wrong? I can see in the logs that the user is being created, and if I attempt to register the same user, the jsonapi_errors call works as expected:

{
    "errors": [
        {
            "title": "Invalid email",
            "detail": "Email has already been taken",
            "source": {}
        }
    ],
    "jsonapi": {
        "version": "1.0"
    }
}

Pagination

Offer a controller-level interface for automatically adding pagination links.

Possible interface

An ActionController instance method jsonapi_pagination(resources) that gets automatically called at render time with the rendered collection. The method would return a Hash or nil.

Example

class UsersController < ...
  def jsonapi_pagination
    {
      first: ...,
      last: ...,
      ...
    }
  end

  def index
    # ...
    render jsonapi: users
  end
end

jsonapi-rails breaks the shortcut form of ActionController#render

jsonapi-rails breaks the shortcut form of render which dispatches on the single argument's value (render :action_name, render 'template/name', render '/file/name').

I have stumbled upon this issue when tried out the 'ActiveRecord Errors' branch.

This can be reproduced on the current HEADs of both master (dc373eb) and ar-validation-errors (498c2b5).

Steps to reproduce

  1. (Optional) Create new gemset to ensure clean environment:
rvm use 2.3.3@jsonapi --create
  1. Create and configure new Rails 4.2 application:
gem install rails:4.2 bundler
rails new jsonapi-test --skip-bundle --skip-sprockets --skip-spring --skip-javascript --skip-turbolinks --skip-test-unit
cd jsonapi-test
cat <<RUBY > Gemfile
source 'https://rubygems.org'
gem 'rails', '4.2'
gem 'sqlite3'
gem 'jsonapi-rails', github: 'jsonapi-rb/jsonapi-rails', ref: 'dc373eb'
RUBY
bundle install
  1. Setup sample resource
bin/rails generate model Post title:text
echo '2.times { |i| Post.create!(title: "Post ##{i + 1}") }' > db/seeds.rb
bin/rake db:create db:migrate db:seed
bin/rails generate jsonapi:serializable Post
  1. Setup sample actions
cat <<RUBY > config/routes.rb
Rails.application.routes.draw do
  get '/api', to: 'application#api'
  get '/html', to: 'application#html'
end
RUBY
cat <<RUBY > app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # sample action to make sure jsonapi-rails works
  def api
    render jsonapi: Post.all
  end

  # this action fails
  def html
    render :welcome
  end
end
RUBY
  1. Start Rails server and hit '/api' to make sure jsonapi-rails works.

  2. Hit '/html'

Expected behavior

Rails fails with an ActionView::MissingTemplate error.

Actual behavior

Rails fails with the following error:

NoMethodError (undefined method `merge' for :welcome:Symbol)
  app/controllers/application_controller.rb:9:in `html'

[... backtrace ...]

Workarounds

Some debugging reveals that the error comes from this line (introduced in 20a6756) where #merge is being called on the :welcome Symbol.

I'm not very familiar with the Rails internals, but it seems like the signature of JSONAPI::Rails::ActionController#render (which takes a single hash argument) is not compatible with the ActionPack's render (both AbstractController::Rendering and ActionController::Renderer can take variable number of arguments).

I see several workarounds:

  1. Change the signature of JSONAPI::Rails::ActionController#render and have it normalize its arguments somehow. But this will likely duplicate the work ActionPack does under the hood to process the shortcut syntax of render.

  2. Drop JSONAPI::Rails::ActionController#render and override AbstractController::Rendering#_normalize_options instead. I don't like it because _normalize_options is kinda undocumented and I have not seen a project that actually uses it.

  3. Move providing :_reverse_mapping inside the renderer proc, since the proc is evaluated in the controller context and has access to request. Another benefit is that it will modify options passed to the JSONAPI renderers only.

I have implemented the second and the third options and they both work on the sample application.

Parse standard JSON API request instead of requiring _jsonapi key

Started looking into using this project in my Rails app but right out of the gate I started getting

NoMethodError (undefined method `to_unsafe_hash' for nil:NilClass)

errors with even the simplest test request made to my create action.

Tracked it down to this source code:

          before_action(options) do |controller| 
            # TODO(lucas): Fail with helpful error message if _jsonapi not 
            #   present. 
            hash = controller.params[:_jsonapi].to_unsafe_hash 

Indeed failing with a helpful error message would be better than a Ruby exception, but why is it requiring a _jsonapi key in the params in the first place?

It seems rather non-standard (I don't see mention of a _jsonapi key in the http://jsonapi.org/ spec) and surprising and I don't see anything about it in the Readme nor on http://jsonapi-rb.org/guides/getting_started/rails.html guide either nor http://jsonapi-rb.org/guides/deserialization/deserializing.html.

Why does it expect the payload to be wrapped with this extra key? And how are such payloads expected to be generated? Maybe I'm just missing something but it would be nice if the getting started code was a little more complete and included an example of producing a payload in addition to consuming it (not that I plan on using this library to produce the payload—planning to use an Angular service to make the request—but it would at least serve as an example). The example shows a create action but doesn't show how you would make a request that it can understand.

What I would expect instead

I would expect that a JSON payload formatted according to the JSON API spec would be parsed without error, so that I could use this in the backend with any frontend framework that speaks JSON API.

Example of a valid payload for creating a resource (from http://jsonapi.org/format/#crud-creating):

POST /photos HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "photos",
    "attributes": {
      "title": "Ember Hamster",
      "src": "http://example.com/images/productivity.png"
    },
    "relationships": {
      "photographer": {
        "data": { "type": "people", "id": "9" }
      }
    }
  }
}

My test example

class TestRecordsController < ApplicationController
  deserializable_resource :test_record, only: [:create]

  def create
    test_record = TestRecord.new(test_record_params)
    ... # doesn't even get this far
  end

  def test_record_params
    params.require(:test_record).permit(
      :counter
    )
  end
end

Using versions:

    jsonapi-rails (0.3.1)
      jsonapi-parser (~> 0.1.0)
      jsonapi-rb (~> 0.5.0)
    rails (5.1.4)  

uninitialized constant ActiveSupport::InheritableOptions

be ruby deserialization_benchmark.rb
/home/lprestonsegoiii/.gem/ruby/2.4.1/gems/jsonapi-rails-0.3.1/lib/jsonapi/rails/configuration.rb:6:in `module:Rails': uninitialized constant ActiveSupport::InheritableOptions (NameError)

it gets stuck on require of jsonapi/rails

require 'benchmark/ips'
require 'awesome_print'
require 'active_support'
require 'pry-byebug'
require 'kalibera'
require 'benchmark-memory'
require 'rake'
require 'jsonapi/rails'
$ be gem list | grep rails
coffee-rails (4.2.2)
jsonapi-rails (0.3.1)
rails (5.1.4)
rails-dom-testing (2.0.3)
rails-html-sanitizer (1.0.3)
rspec-rails (3.6.1)
sass-rails (5.0.6)
sprockets-rails (3.2.1)

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.