tiagopog / jsonapi-utils Goto Github PK
View Code? Open in Web Editor NEWBuild JSON API-compliant APIs on Rails with no (or less) learning curve.
License: MIT License
Build JSON API-compliant APIs on Rails with no (or less) learning curve.
License: MIT License
When I'm including JSONAPI::Utils
in my base controller I get an error:
undefined local variable or method `context' for #<Api::V1::AuthController:0x007fa0f2ec1158>
It seems like the variable context
on line 13 in setup_request in JSONAPI::Utils:Request does not exist. I'm not sure where context
should come from. I thought is might be because i used ActionController::Metal, but the error shows when I use ActionController::Base.
Has anyone else experienced this before?
I'm using JU 0.4.6 with rails 4.2.3
The JSONAPI spec recommends using dasherized keys
http://jsonapi.org/recommendations/#naming
However, that breaks with jsonapi-utils.
jsonapi-resources 0.7
jsonapi-utils (~> 0.4.5)
With a request such as this:
{
"data": {
"id": "58",
"type": "doctor-family-members",
"links": {
"self": "http://localhost:3000/doctor-family-members/58"
},
"attributes": {
"doctor-id": 14,
"family-member-id": 5,
"min-visits": 2000,
"avg-extra-visits": 3000,
"doctor-name": "MATTHEW PARSONS"
},
"relationships": {
"doctor": {
"links": {
"self": "http://localhost:3000/doctor-family-members/58/relationships/doctor",
"related": "http://localhost:3000/doctor-family-members/58/doctor"
}
},
"family-member": {
"links": {
"self": "http://localhost:3000/doctor-family-members/58/relationships/family-member",
"related": "http://localhost:3000/doctor-family-members/58/family-member"
}
}
}
}
}
a param method of:
params.require(:data).require(:attributes).permit(:min_visits, :avg_extra_visits)
Shows min-visits
and avg-extra-visits
as not permitted.
It appears that trying to customize a filter doesn't work. Given a resource that looks like this:
class UserResource < JSONAPI::Resource
attributes :first_name, :last_name, :full_name
attribute :full_name
has_many :posts
filter :first_name, apply: ->(records, value, _options) {
# customize how the filter is applied - this never runs
records.where(first_name: 'Steve')
}
def full_name
"#{@model.first_name} #{@model.last_name}"
end
end
Assuming a stock controller and routes, the following request:
/users?filter[first_name]=Mark
results in the default filter being applied, not the customized lambda specified.
We have in our codebase something like this:
class AppointmentsController < BaseController
# GET /api/v2/appointments
def index
appointments = Appointment.all.includes(:professional, :service)
jsonapi_render json: appointments
end
end
We use includes
to eager load associations and avoid the N+1 query problem.
However, the record_count query becomes very slow since there is now a LEFT OUTER JOIN
for each eager loded association:
SELECT DISTINCT COUNT(DISTINCT appointments.id) FROM "appointments" LEFT OUTER JOIN "professionals" ON "professionals"."id" = "appointments"."employment_id" LEFT OUTER JOIN "services" ON "services"."id" = "appointments"."service_id"
I suppose a simple fix could be done by changing this line from this:
def count_records(records, options)
...
records.except(:group, :order).count("DISTINCT #{records.table.name}.id")
end
to this:
def count_records(records, options)
...
records.except(:group, :includes, :order).count("DISTINCT #{records.table.name}.id")
end
By the way, can you please backport this patch to the 0.4
series as well?
A simple example resource with filterable multi-word attribute:
class PageResource < JSONAPI::Resource
attribute :trashed_at
filter :trashed_at
end
And a contrived spec that attempts to use the attribute:
it 'returns an array containing the non-trashed page', :aggregate_failures do
get('/pages', { filter: { 'trashed-at' => nil })
expect(response).to be_ok
end
The response ends up including an error that indicates that the filter isn't being translated back to underscored before being passed to the model:
{
"errors": [
{
"title": "Internal Server Error",
"detail": "Internal Server Error",
"code": "500",
"status": "500",
"meta": {
"exception": "PG::UndefinedColumn: ERROR: column pages.trashed-at does not exist ..."
}
}
]
}
I have these configurations in place:
#:underscored_key, :camelized_key, :dasherized_key, or custom
config.json_key_format = :dasherized_key
#:underscored_route, :camelized_route, :dasherized_route, or custom
config.route_format = :dasherized_route
I am able to override the resource type in the response with
# Forcing a different resource
jsonapi_render json: User.all, options: { resource: V2::UserResource }
However on a patch request, i get
{
"errors": [
{
"title": "Invalid resource",
"detail": "notifications is not a valid resource.",
"code": "101",
"status": "400"
}
]
}
I want all my actions except for #create
to use JSON API.
The action create
has too many relationships and becomes cumbersome in the JSON API format. Is there a way to deactivate the validations just for one action?
One of my endpoints needs to just build a record and return the attributes, rather than persisting it. When using jsonapi_render
with the unsaved model, all I get on the frontend is { "data": null }
.
Is there a way to do this or is it by design?
To clarify:
# app/resources/user_resource.rb
class UserResource < JSONAPI::Resource
attributes :first_name, :last_name, :full_name, :email, :image_url, :is_admin
has_many :projects
def full_name
@model.name
end
end
# app/models/user_model.rb
...
# Convenient way of getting full name
def name
[first_name, last_name].join(' ')
end
The request was : http://localhost:3000/users?sort=fullName
jsonapi-resources version is now locked to 0.9.0. 0.9.2 has been recently released, but can't be used with jsonapi-utils.
Howdy,
first I'd like to thank you for your good work.
jsonapi-resources have a config to show both the record count and the page count. it would be nice to be able to support both, and the flexibility of choosing the key name:
# Metadata
# Output record count in top level meta for find operation
config.top_level_meta_include_record_count = true
config.top_level_meta_record_count_key = :record_count
config.top_level_meta_include_page_count = true
config.top_level_meta_page_count_key = :page_count
the line I found useful in the original gem is the following:
what I did in the meantime is override the module like so:
module JSONAPI
module Utils
module Response
module Formatters
private
def calculate_page_count record_count
(record_count / paginator.size.to_f).ceil
end
# Overriding method to also return page count in the meta response
def result_options records, options
{}.tap do |data|
if JSONAPI.configuration.default_paginator != :none &&
JSONAPI.configuration.top_level_links_include_pagination
data[:pagination_params] = pagination_params(records, options)
end
if JSONAPI.configuration.top_level_meta_include_record_count
data[:record_count] = count_records(records, options)
data[:page_count] = calculate_page_count(data[:record_count])
end
end
end
end
end
end
end
would be nice to have this baked in to the gem instead of having to do an override.
Thanks!
This is a question/confirmation issue and not a bug/code flaw issue.
When using JSONAPI::Utils, are the controller actions expected to explicitly be defined?
When mixing in JSONAPI::Utils, we also get the JSONAPI::ActsAsResourceController module and its behavior. Within that module, default actions are defined.
The first sentence in the "How does it work?" section of the README made me think more about it:
One of the main motivations behind JSONAPI::Utils is to keep things explicit in controllers so that developers can easily understand and maintain code.
With a primary motivation being explicit controller behavior, does that in turn mean that explicit controller actions are implied?
Context
While test driving some resources using JSONAPI::Utils + JSONAPI::Resources, I noticed that my immutable endpoints were working without defining any actions. At the time, I did not know why, and the specs were green so I let it be.
Recently, I encountered some trouble attempting to add a fully mutable resource, in particular with PATCH requests. After verifying that the API request body was correct, I narrowed the issue down to double parsing of the request (once in JSONAPI::Utils and a second time in the default action definition in JSONAPI::Resources).
The double parsing happened since it passed through a before filter than used the RequestParser instance and then entered the default update action which initialized a second RequestParser instance.
While looking closer at the specs for JSONAPI::Utils, I noticed that the controllers were always defining the actions. This led me back to the README, since I figured that I must have missed something.
To sum it up, I think that I have been doing it wrong (from the JSONAPI::Utils perspective) and I am looking for confirmation. ๐
Hi,
I would like to hide the relationship links from the response, while still displaying the included data of the related resources (using the include parameter).
Is that possible with the gem, or with some work-around?
Thank you!
Since https://github.com/cerebris/jsonapi-resources/releases/tag/v0.9.4 JR allows to filter records on nested relationship values while JU allows filtering only by where
condition.
Are there any reasons why you decided to rewrite filter/sort/pagination workflow?
I've played a little with monkey patching, and looks like using filters from JR is possible.
Here is my code, it's working within my application.
Note: it breaks core functionality for usage with non-AR objects, and can break something else, be careful if you want to use it.
module JSONAPI::Utils
module Response
module Formatters
def jsonapi_format(object, options = {})
operations = @request.operations
unless JSONAPI.configuration.resource_cache.nil?
operations.each {|op| op.options[:cache_serializer] = resource_serializer }
end
if object.respond_to?(:to_ary)
operations.each { |op| op.options[:context][:records] = object }
results = process_operations(operations)
else
results = JSONAPI::OperationResults.new
record = turn_into_resource(object, options)
results.add_result(JSONAPI::ResourceOperationResult.new(:ok, record))
end
@_response_document = create_response_document(results)
@_response_document.contents
end
end
end
end
module JSONAPI
class Resource
class << self
def records(options = {})
options.dig(:context, :records) || _model_class.all
end
def apply_included_resources_filters(records, options = {})
include_directives = options[:include_directives]
return records unless include_directives
related_directives = include_directives.include_directives.fetch(:include_related)
related_directives.reduce(records) do |memo, (relationship_name, config)|
relationship = _relationship(relationship_name)
next memo unless relationship
filtering_resource = relationship.resource_klass
# Don't try to merge where clauses when relation isn't already being joined to query.
next memo unless config[:include_in_join]
filters = config[:include_filters]
next memo unless filters
rel_records = filtering_resource.apply_filters(filtering_resource.records({}), filters, options).references(relationship_name)
memo.merge(rel_records)
end
end
end
end
end
Currently there's no support to render multiple invalid AR objects like this:
user = User.new
user.valid? #=> false
post = user.posts.new
post.valid? #=> false
jsonapi_utils json: [user, post], status: :unprocessable_entity
In our application resource controller:
7: if creator.save
=> 8: binding.pry
9: jsonapi_render json: creator.application, status: :created
10: else
Calling #jsonapi_serialize
to check what the issue is:
149: def turn_into_resource(record, options = {})
150: if options[:resource]
151: options[:resource].to_s.constantize.new(record, context)
152: else
=> 153: binding.pry
154: @request.resource_klass.new(record, context)
155: end
[1] pry(#<Api::V1::ApplicationsController>)> @request
=> nil
It's very common to see a pattern like this in a Rails controller (I'm using jsonapi-utils
methods in the code):
if model.save
jsonapi_render json: model
else
jsonapi_render json: model.errors # doesn't work
end
Seems like this gem should expose a helper for this type of failed POST
request, e.g. jsonapi_render_unprocessable model.errors
which would serve 422
response with a response body listing the reasons why the model
couldn't be written to the DB.
Not sure what JSONAPI spec says about this, however...
I need to render an Application
instance (AR::Base inherited) which has associated users
.
I want to side-load those users, i.e. have them serialized beneath an included
key, following this type of pattern:
{
"data": {
"type": "application",
"id": "f18ba709-4903-4024-856d-cb0a4539f498",
"links": {
"self": "https://sandbox.opentransact.com/v1/applications/f18ba709-4903-4024-856d-cb0a4539f498"
},
"attributes": {
"name": "Zipmark, Inc. Deposit Application"
},
"relationships": {
"users": {
"data": [
{ "type": "user", "id": "de4a83b6-ba4f-40db-89d2-a5c4078d9a19" }
]
}
},
"included": [{
"type": "user",
"id": "de4a83b6-ba4f-40db-89d2-a5c4078d9a19",
"links": {
"self": "https://sandbox.opentransact.com/v1/users/de4a83b6-ba4f-40db-89d2-a5c4078d9a19"
},
"attributes": {
"email": "[email protected]",
"password": "password",
"name": "James Dean"
}
}
}
I tried invoking it as jsonapi_render json: application, include: ['credentials']
which is how jsonapi-resources
does it.
Is this possible?
Failure/Error: expect(response_body).to eq({
expected: "{\"errors\":[{\"title\":\"Record not found\",\"detail\":\"The record identified by 06264908-21e5-4a1e-9d33-2eeb3eb0ba88 could not be found.\",\"id\":null,\"href\":null,\"code\":\"404\",\"source\":null,\"links\":null,\"status\":\"404\",\"meta\":null}]}"
got: "{\"errors\":[{\"title\":\"Record not found\",\"detail\":\"The record identified by (no identifier) could not be found.\",\"code\":\"404\",\"status\":\"404\"}]}"
This makes no sense to me... By the time you want to invoke jsonapi_render
the request object has been processed and you're formulating the response.
I assume you're doing it to set some context/variables that you share among your util methods, but is there another reason?
This is an example of why this puzzles and frustrates me.
At debugger in controller:
8: if creator.save
=> 9: binding.pry
10: jsonapi_render json: creator.session, status: :created
jsonapi_serialize
is fine:
[1] pry(#<Api::V1::SessionsController>)> jsonapi_serialize creator.session
{:data=>{"id"=>"9a5ad4ef-4e97-40a8-a884-daffd1c43db1", "type"=>"sessions", "links"=>{:self=>"http://example.org/api/v1/sessions/9a5ad4ef-4e97-40a8-a884-daffd1c43db1"}, "relationships"=>{"credential"=>{:links=>{:self=>"http://example.org/api/v1/sessions/9a5ad4ef-4e97-40a8-a884-daffd1c43db1/relationships/credential", :related=>"http://example.org/api/v1/sessions/9a5ad4ef-4e97-40a8-a884-daffd1c43db1/credential"}}}}}
jsonapi_render
is not:
[3] pry(#<Api::V1::SessionsController>)> jsonapi_render json: creator.session, status: :created
"{\"errors\":[{\"title\":\"Param not allowed\",\"detail\":\"email is not allowed.\",\"id\":null,\"href\":null,\"code\":\"105\",\"source\":null,\"links\":null,\"status\":\"400\",\"meta\":null},{\"title\":\"Param not allowed\",\"detail\":\"password is not allowed.\",\"id\":null,\"href\":null,\"code\":\"105\",\"source\":null,\"links\":null,\"status\":\"400\",\"meta\":null}]}"
Sorry for venting a bit here. I just don't understand why we need to have such an explicit tie between the request and the response. Depending on the application they may not share parameters, like in our case.
Howdy!
I'm using the following gems:
I recently started using this gem and have found it quite useful. Thanks. However I tried to use jsonapi_render_bad_request
and I got the following error:
# NameError:
# uninitialized constant JSONAPI::Utils::Exceptions::BadRequest
It looks like the original refactoring to split up the exceptions into separate files resulted in BadRequest getting skipped/forgotten.
It seems, and this is without knowing the current design philosophy, updating jsonapi_render_bad_request
to use JSONAPI::Exceptions::BadRequest
directly, overriding detail:
to match the original Utils BadRequest
implementation could be one solution.
Another would be to implement JSONAPI::Utils::Exceptions::BadRequest
.
Another would be to remove jsonapi_render_bad_request
.
I'm happy to submit a pull request for any of those proposed solutions.
I have two models that we'd like to be created in the same request.
Resource
module API
module V1
class ProjectToolboxTalkResource < JSONAPI::Resource
attributes :date
has_one :toolbox_talk
has_one :superintendent
has_one :project
has_many :attendees
has_many :project_toolbox_talk_users
end
end
end
POST Request
{
"data": {
"type": "project_toolbox_talks",
"attributes": { "date": "2017-12-11" },
"relationships": {
"project_toolbox_talk_users": {
"data": [
{
"type": "project_toolbox_talk_users",
"attributes": { "user_id": "559ff2c9-beb6-47cd-9757-66104617403b" }
}
]
},
"projects": {
"data": {
"type": "projects", "id": "d9b28ffd-6f30-4dd0-a227-720caa9b881e"
}
}
}
}
}
When I make the POST request I get the following error even though I have linked project with has_one :project
{
"errors": [
{
"code": "105",
"detail": "projects is not allowed.",
"status": "400",
"title": "Param not allowed"
}
]
}
What am I not understanding?
I have object with validation error. It correctly translated with I18n:
angle.errors.to_a
=> ["ะะฐะผะตัะฐ ะฝะต ะธะผะตะตั ัะพั
ัะฐะฝัะฝะฝัั
ะบะพะพัะดะธะฝะฐั"]
Translated as Camera doesn't have coordindates
in :en
locale.
But in request I got partly translated response:
{"errors"=>[{"title"=>"Camera ะฝะต ะธะผะตะตั ัะพั
ัะฐะฝัะฝะฝัั
ะบะพะพัะดะธะฝะฐั", "id"=>"camera", "code"=>"100", "source"=>{"pointer"=>"/data/relationships/camera"}, "status"=>"422"}]}
Where validation message is not fully translated: Camera
word not translated, but rest of message translated.
Hi @tiagopog,
Thanks for maintaining this gem. I've forked a copy and changed the gem to use version jsonapi-resources version 0.8.0 final, which was released today.
Separately, I've removed the dependency for piped_ruby, which (it appears) you added into beta2, and is why I haven't proposed my fork back into yours. I'm not sure why the dependency was added, and I can't find any discussion about it, but it doesn't seem to be necessary for this gem to work properly. It also seems like requiring piped_ruby forces someone into a design decision unrelated to the use of jsonapi.
I'm happy to propose a PR with the updated version, and/or rename this issue title, but I just was hoping for some clarification on the piped_ruby dependency and why it was added as a requirement.
Thanks!
This is a new issue spawned from discussion in #19.
Honestly, I've just started using this gem and don't know any details about its internals. So I'm not quite sure where to start in recommending how it should work. Would it be an option passed to jsonapi_render
?
From the spec, it seems that we should encourage only overriding the include
value if none is already provided in the query string.
@tiagopog Is there a way to use jsonapi-authorization
, which plugs into JR as a custom operation processor? Based on the JR controller logic here (https://github.com/cerebris/jsonapi-resources/blob/v0.9.0/lib/jsonapi/acts_as_resource_controller.rb#L77), there seems to be some logic around how these processors are invoked -- but jsonapi-utils
seems to be overriding this functionality, if I understand it correctly.
Do you have any recommendations for how best to integrate with something like jsonapi-authorization
? Would love to take your ideas and make them work.
Thanks!
According to the spec, code
s in the exceptions must be strings. Currently generated errors aren't valid against the JSON API Schema. See cerebris/jsonapi-resources#675. An example:
{
"errors": [
{
"title": "Bad Request",
"detail": "This request is not supported.",
"code": 400,
"status": "400"
}
]
}
When I add jsonapi_resources :foo
to routes.rb, given I have a FooResource
and a BarResource
where FooResource
has_one
Bar
, I get additional routes like api/v2/foo/:foo_id/relationships/bar
. I notice that the route created references a method called update_relationship
.
However, I could not find any examples of what an update would look like using this methodology. According to JSON API, updating a resource looks like this:
PATCH /articles/1/relationships/author HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": { "type": "people", "id": "12" }
}
(http://jsonapi.org/format/#crud-updating-to-one-relationships)
How is update_relationship
supposed to work with data coming in in this format? Is there some sort of jsonapi-utils helper for doing something like foo.update(bar)
in this case?
Hi there,
Firstly, thank you for this gem, it makes using JR much easier when dealing with API's more complex than the simple examples in the docs. I really appreciate it.
I'm getting this weird error when triggering a jsonapi_render_not_found
error. It seems the params hash gets lost somewhere. Anyway, here's the code (it's for a multi-tenant system, which is why I'm searching from the current_user.
module API
module V1
class AccountsController < AuthenticatedController
include JSONAPI::ActsAsResourceController
def show
jsonapi_render json: current_user.accounts.find(params[:id])
end
end
end
end
And here's the exception and stacktrace... I hope you can help...
NoMethodError in API::V1::AccountsController#show: undefined method `params' for nil:NilClass
jsonapi-utils (0.4.0) lib/jsonapi/utils.rb:46:in `jsonapi_render_not_found'
activesupport (4.2.6) lib/active_support/rescuable.rb:80:in `call'
activesupport (4.2.6) lib/active_support/rescuable.rb:80:in `rescue_with_handler'
actionpack (4.2.6) lib/action_controller/metal/rescue.rb:15:in `rescue_with_handler'
actionpack (4.2.6) lib/action_controller/metal/rescue.rb:32:in `rescue in process_action'
actionpack (4.2.6) lib/action_controller/metal/rescue.rb:29:in `process_action'
actionpack (4.2.6) lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
activesupport (4.2.6) lib/active_support/notifications.rb:164:in `block in instrument'
activesupport (4.2.6) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
activesupport (4.2.6) lib/active_support/notifications.rb:164:in `instrument'
actionpack (4.2.6) lib/action_controller/metal/instrumentation.rb:30:in `process_action'
actionpack (4.2.6) lib/action_controller/metal/params_wrapper.rb:250:in `process_action'
activerecord (4.2.6) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
actionpack (4.2.6) lib/abstract_controller/base.rb:137:in `process'
actionview (4.2.6) lib/action_view/rendering.rb:30:in `process'
actionpack (4.2.6) lib/action_controller/metal.rb:196:in `dispatch'
actionpack (4.2.6) lib/action_controller/metal/rack_delegation.rb:13:in `dispatch'
actionpack (4.2.6) lib/action_controller/metal.rb:237:in `block in action'
actionpack (4.2.6) lib/action_dispatch/routing/route_set.rb:74:in `dispatch'
actionpack (4.2.6) lib/action_dispatch/routing/route_set.rb:43:in `serve'
actionpack (4.2.6) lib/action_dispatch/journey/router.rb:43:in `block in serve'
actionpack (4.2.6) lib/action_dispatch/journey/router.rb:30:in `each'
actionpack (4.2.6) lib/action_dispatch/journey/router.rb:30:in `serve'
actionpack (4.2.6) lib/action_dispatch/routing/route_set.rb:817:in `call'
rack-pjax (0.8.0) lib/rack/pjax.rb:12:in `call'
actionpack (4.2.6) lib/action_dispatch/middleware/flash.rb:260:in `call'
warden (1.2.4) lib/warden/manager.rb:35:in `block in call'
warden (1.2.4) lib/warden/manager.rb:34:in `catch'
warden (1.2.4) lib/warden/manager.rb:34:in `call'
rack (1.6.4) lib/rack/etag.rb:24:in `call'
rack (1.6.4) lib/rack/conditionalget.rb:25:in `call'
rack (1.6.4) lib/rack/head.rb:13:in `call'
remotipart (1.2.1) lib/remotipart/middleware.rb:27:in `call'
actionpack (4.2.6) lib/action_dispatch/middleware/params_parser.rb:27:in `call'
actionpack (4.2.6) lib/action_dispatch/middleware/flash.rb:260:in `call'
rack (1.6.4) lib/rack/session/abstract/id.rb:225:in `context'
rack (1.6.4) lib/rack/session/abstract/id.rb:220:in `call'
actionpack (4.2.6) lib/action_dispatch/middleware/cookies.rb:560:in `call'
activerecord (4.2.6) lib/active_record/query_cache.rb:36:in `call'
activerecord (4.2.6) lib/active_record/connection_adapters/abstract/connection_pool.rb:653:in `call'
activerecord (4.2.6) lib/active_record/migration.rb:377:in `call'
actionpack (4.2.6) lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
activesupport (4.2.6) lib/active_support/callbacks.rb:88:in `__run_callbacks__'
activesupport (4.2.6) lib/active_support/callbacks.rb:778:in `_run_call_callbacks'
activesupport (4.2.6) lib/active_support/callbacks.rb:81:in `run_callbacks'
actionpack (4.2.6) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
actionpack (4.2.6) lib/action_dispatch/middleware/reloader.rb:73:in `call'
actionpack (4.2.6) lib/action_dispatch/middleware/remote_ip.rb:78:in `call'
actionpack (4.2.6) lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'
web-console (2.3.0) lib/web_console/middleware.rb:28:in `block in call'
web-console (2.3.0) lib/web_console/middleware.rb:18:in `catch'
web-console (2.3.0) lib/web_console/middleware.rb:18:in `call'
actionpack (4.2.6) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
railties (4.2.6) lib/rails/rack/logger.rb:38:in `call_app'
railties (4.2.6) lib/rails/rack/logger.rb:20:in `block in call'
activesupport (4.2.6) lib/active_support/tagged_logging.rb:68:in `block in tagged'
activesupport (4.2.6) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (4.2.6) lib/active_support/tagged_logging.rb:68:in `tagged'
railties (4.2.6) lib/rails/rack/logger.rb:20:in `call'
request_store (1.2.1) lib/request_store/middleware.rb:8:in `call'
actionpack (4.2.6) lib/action_dispatch/middleware/request_id.rb:21:in `call'
rack (1.6.4) lib/rack/methodoverride.rb:22:in `call'
rack (1.6.4) lib/rack/runtime.rb:18:in `call'
activesupport (4.2.6) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
rack (1.6.4) lib/rack/lock.rb:17:in `call'
actionpack (4.2.6) lib/action_dispatch/middleware/static.rb:120:in `call'
rack (1.6.4) lib/rack/sendfile.rb:113:in `call'
rack-cors (0.4.0) lib/rack/cors.rb:80:in `call'
railties (4.2.6) lib/rails/engine.rb:518:in `call'
railties (4.2.6) lib/rails/application.rb:165:in `call'
puma (2.15.3) lib/puma/commonlogger.rb:31:in `call'
puma (2.15.3) lib/puma/configuration.rb:79:in `call'
puma (2.15.3) lib/puma/server.rb:541:in `handle_request'
puma (2.15.3) lib/puma/server.rb:388:in `process_client'
puma (2.15.3) lib/puma/server.rb:270:in `block in run'
puma (2.15.3) lib/puma/thread_pool.rb:106:in `block in spawn_thread'
While poking around with this gem, I noticed the error formatter provides this sort of response:
{
"errors": [
{
"title": "Title can't be blank",
"id": "title",
"code": "100",
"status": "422"
}
]
}
I've seen the format prescribed like so in examples:
{
"errors": [
{
"status": "422",
"source": { "pointer": "/data/attributes/title" },
"title": "Invalid Attribute",
"detail": "Title can't be blank.",
"code": "100"
}
]
}
Also, I noticed that according to the spec, title
may not be used properly as this gem currently stands because it is displaying an error message instead of a generic summary that could be localized:
title
: a short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization.
Do you think that JSONAPI::Utils
should be updated to use the source.pointer
style of error reporting instead of using id
like it current is?
rails (4.2.8)
jsonapi-utils (0.4.9)
jsonapi-resources (~> 0.8.0)
I have a nested resource where the index
action has a code path that executes resource_params
which ends up producing:
NoMethodError: undefined method `keys` for nil:NilClass
Due to:
operation = @request.operations.find { |e| e.options[:data].keys & keys == keys }
https://github.com/tiagopog/jsonapi-utils/blob/v0.4.9/lib/jsonapi/utils/request.rb#L77
@request.operations
contains:
[#<JSONAPI::Operation:0x007fea7b3b2640
@operation_type=:find,
@resource_klass=Api::V2::ArticleResource,
@options={
:context=>{},
:filters=>{},
:include_directives=>nil,
:sort_criteria=>nil,
:paginator=>#<PagedPaginator:0x007fea7b3b2690 @number=1, @size=20>, :fields=>{}}>]
Which does not include a :data
key, hence the resulting exception.
One option is to update the offending piece of code to:
operation = @request.operations.find { |e| e.options.fetch(:data, {}).keys & keys == keys }
I'll look into submitting a PR. In the meantime I've rescued the exception.
Hi !!
I have one to one relation between model such as
class Plan < ActiveRecord::Base
has_one :subscription
end
class Subscription < ActiveRecord::Base
belongs_to :plan
end
So that I could define resources like,
class PlanResource < JSONAPI::Resource
immutable
attributes :uuid,
:amount,
:name,
:created_at,
:updated_at
has_one :subscription, class_name: 'Subscription'
end
class SubscriptionResource < JSONAPI::Resource
immutable
attributes :stripe_subscription_id,
:created_at,
:updated_at
has_one :plan, class_name: 'Plan', foreign_key: 'plan_id'
end
And then when I send the request to
http://localhost:3000/plans/1/relationships/subscription
.
I can see the errors below;
{
"errors": [
{
"title": "Internal Server Error",
"detail": "Internal Server Error",
"id": null,
"href": null,
"code": 500,
"source": null,
"links": null,
"status": "500",
"meta": {
"exception": "undefined method `subscription_id' for class `#<Class:0x007ff5e0e4ffb0>'",
"backtrace": [
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource.rb:804:in `method'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource.rb:804:in `block (2 levels) in _add_relationship'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:300:in `public_send'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:300:in `foreign_key_value'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:249:in `to_one_linkage'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:61:in `serialize_to_links_hash'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/jsonapi-resources-0.7.0/lib/jsonapi/response_document.rb:113:in `results_to_hash'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/jsonapi-resources-0.7.0/lib/jsonapi/response_document.rb:11:in `contents'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/jsonapi-resources-0.7.0/lib/jsonapi/acts_as_resource_controller.rb:155:in `render_results'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/jsonapi-resources-0.7.0/lib/jsonapi/acts_as_resource_controller.rb:64:in `process_request'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/jsonapi-resources-0.7.0/lib/jsonapi/acts_as_resource_controller.rb:21:in `show_relationship'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/actionpack-4.2.5.1/lib/action_controller/metal/implicit_render.rb:4:in `send_action'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/actionpack-4.2.5.1/lib/abstract_controller/base.rb:198:in `process_action'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/actionpack-4.2.5.1/lib/action_controller/metal/rendering.rb:10:in `process_action'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/actionpack-4.2.5.1/lib/abstract_controller/callbacks.rb:20:in `block in process_action'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/activesupport-4.2.5.1/lib/active_support/callbacks.rb:117:in `call'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/activesupport-4.2.5.1/lib/active_support/callbacks.rb:555:in `block (2 levels) in compile'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/activesupport-4.2.5.1/lib/active_support/callbacks.rb:505:in `call'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/activesupport-4.2.5.1/lib/active_support/callbacks.rb:92:in `__run_callbacks__'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/activesupport-4.2.5.1/lib/active_support/callbacks.rb:778:in `_run_process_action_callbacks'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/activesupport-4.2.5.1/lib/active_support/callbacks.rb:81:in `run_callbacks'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/actionpack-4.2.5.1/lib/abstract_controller/callbacks.rb:19:in `process_action'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/actionpack-4.2.5.1/lib/action_controller/metal/rescue.rb:29:in `process_action'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/actionpack-4.2.5.1/lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/activesupport-4.2.5.1/lib/active_support/notifications.rb:164:in `block in instrument'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/activesupport-4.2.5.1/lib/active_support/notifications/instrumenter.rb:20:in `instrument'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/activesupport-4.2.5.1/lib/active_support/notifications.rb:164:in `instrument'",
"/Users/toshikiinami/Desktop/billing/vendor/bundle/ruby/2.3.0/gems/actionpack-4.2.5.1/lib/action_controller/metal/instrumentation.rb:30:in `process_action'",
When I used to use JR the result is following:
{
"links": {
"self": "http://localhost:3000/plans/1/relationships/subscription",
"related": "http://localhost:3000/plans/1/subscription"
},
"data": null
}
Maybe the controller is not generated automatically? or I've been missing something?
**I removed api/v1
in the path for the sake of clarity within the code above. **
I noticed than this doesn't use the cache :
def index
jsonapi_render json: ProjectGroup.all
end
But this does :
def index
process_request
end
Is there a way to use the cache for both? Should I cache records myself? It can be useful to have this "built-in".
README says to use gem 'jsonapi-utils', '0.5.0.beta2'
but beta2 is not yet released to rubygems. Only beta1 is available.
Hello, it looks like custom paginators aren't supported as JSONAPI::Utils::Support::Pagination
looks explicitly for Page and Offset paginators. I'm using version 0.4.6.
Here are the links to the code:
https://github.com/b2beauty/jsonapi-utils/blob/master/lib/jsonapi/utils/support/pagination.rb#L47
and
https://github.com/b2beauty/jsonapi-utils/blob/master/lib/jsonapi/utils/support/pagination.rb#L38
What would be great would to have support for:
If this hasn't been fixed (perhaps I missed a configuration option), I can give this a shot.
Hi there,
Just had a hard time tracking down an issue with an update
I was doing on an a record.
I included JSONAPI::Utils
in my controller, and implemented it in my index
method.
The update
method was failing in jsonapi-resources
because an attribute was included in the request that wasn't supposed to be there, so it was failing. However as long as JSONAPI::Utils
was included, the error that came back was that the key was not included {"errors":[{"title":"A key is required","detail":"...
This was extremely confusing and sent me down a bunch of rabbit holes.
This is in reference to v0.6.0.
I use active_interaction for services in lots of my projects, and recently started using this gem to enforce JSONAPI spec more easily.
When I have an ActiveInteraction
instance - interaction
- and pass it into jsonapi_render_errors json: interaction
in my controller, I get TypeError: no implicit conversion of Symbol into Integer
because it tries to sanitize the errors. However, ActiveInteraction's errors follows the same interface as ActiveRecord's errors. The first line of JSONAPI::Utils::Response::Formatters#jsonapi_format_errors
would work for ActiveInteraction, except that it's explicitly checking if data
is an active_record_obj?
, which of course it isn't.
Would it be possible to make this not rely on whether it's an ActiveRecord object, but instead just check that it responds to the necessary methods?
Netflix recently released a blazing fast JSON:API serializer which seems to be a very suitable choice for jsonapi-utils
.
Although it will require a deep change in the JU's structure, the perfomance gains for serialization are quite impressive:
I still need to take some time to get through the fast_jsonapi
's code and then I may start planning the migration from the old serializer (jsonapi-resource
) to this new one.
For more information see the Netflix's article.
Affects jsonapi-utils 0.5.0.beta4
, jsonapi-resources 0.8.1
and Rails 5.0.0.1
.
Given a model SampleModel
:
class SampleModel < ApplicationRecord
# name: :string
end
And a resource SampleModelResource
:
class SampleModelResource < JSONAPI::Resource
attributes :name
end
With controllers:
class JsonApiController < JSONAPI::ResourceController
include JSONAPI::Utils
end
class SampleModelsController < JsonApiController
end
And routes:
Rails.application.routes.draw do
jsonapi_resources :sample_models
end
And a controller spec that performs a patch operation:
require 'rails_helper'
describe SampleModelsController, type: :controller do
before do
request.headers['Content-Type'] = "application/vnd.api+json"
end
describe 'PATCH #update' do
subject { patch :update, params: params, body: body.to_json }
let(:params) { { id: id } }
let(:body) { { data: { id: id, type: 'sample_models', attributes: { name: 'bar' } } }
let(:sample_model) { SampleModel.new(name: 'foo').tap { |m| m.save } }
let(:id) { sample_model.id }
it { expect(subject.status).to eq(200) }
end
end
Running PATCH raises a 400 Bad Request
, which manifests as:
{"errors":[{"title":"A key is required","detail":"The resource object does not contain a key.","code":"109","status":"400"}]}
I dug deeper, and what I found was that JSONAPI::Utils
adds a Rails callback that creates a JSONAPI::RequestParser
prior to the request being processed:
def setup_request
@request ||=
JSONAPI::RequestParser.new(
params.dup,
context: context,
key_formatter: key_formatter,
server_error_callbacks: (self.class.server_error_callbacks || [])
)
end
This ends up running some code in jsonapi-resources which parses the data parameters for the PATCH operation. In the process of parsing this data, it performs a delete
on the data hash.
def parse_single_replace_operation(data, keys, id_key_presence_check_required: true)
fail JSONAPI::Exceptions::MissingKey.new if data[:id].nil?
key = data[:id].to_s
if id_key_presence_check_required && !keys.include?(key)
fail JSONAPI::Exceptions::KeyNotIncludedInURL.new(key)
end
data.delete(:id) unless keys.include?(:id)
# ...
end
Now the JSONAPI::Utils
callback finishes without issue. However, when JSONAPI::RequestParser
is invoked again as Rails runs the actual action, the data
hash in params
no long has id
, which is required. It raises a MissingKey
error, and the request fails.
It seems like calling JSONAPI::RequestHelper
twice with the same parameters is not safe. Despite JSONAPI::Util
's effort to params.dup
prior to invoking it, the JSONAPI::RequestHelper
code still modifies the underlying data
hash in a pretty bad way.
Frankly, this seems to be more of an issue with jsonapi-resources
than jsonapi-utils
. But I thought I'd bring it to your attention, because as it stands, it means one cannot compose your package into a JSON API controller.
Are there any feasible workarounds? Or should an issue be opened with jsonapi-resources
?
In the jsonapi-resources version 0.7.1.beta2
there's a breaking change removes the JSONAPI::Request
class.
Just a heads up because you may want to lock the current version to 0.7.0
in that case.
First off, this gem is pretty awesome!
This is not an issue but I just wonder about the pagination.
I know we can define the default pagination, but could I set the pagination for each models/resources?
Should I use library kaminari or something?
I'd love to use this library in conjunction with the latest version of JSONAPI::Resources. There are a number of breaking changes from what I can see, particularly with the removal of JSONAPI::OperationResults
.
I'm new to this library and JSONAPI::Resources
so I don't think I have the context yet to get this up and running myself so I wanted to open a ticket to see if it's on anyone else's radar.
Looks like in current implementation (ver. 0.5.0) only first filter from the custom_filters
list added to the @_allowed_custom_filters
. In this method elsif
condition doesn't work properly.
module JSONAPI::Utils::Support::Filter
module Custom
...
def custom_filter(attr)
attr = attr.to_sym
@_allowed_filters[attr] = {}
if !@_allowed_custom_filters.is_a?(Array)
@_allowed_custom_filters = Array(attr)
elsif @_allowed_custom_filters.include?(attr)
@_allowed_custom_filters.push(attr)
end
end
end
end
I'm having some trouble while filtering. I need my resource to be filtered by its name using a LIKE query (autocomplete field). If I implement JSONAPI::Resources approach, using a lambda, it doesn't work, the lambda it's not called. JSONAPI::Utils provides a custom_filters method, which allows me to apply the filter in the controller, but I don't think that is the best solution. Am I missing something? I can't find any further documentation. Could it be possible to implement all custom filtering code in the resource instead of the controller, as JR proposes?
I've been using json-api resource and want to move on to this library.
However I just wonder if I could use uuid
instead of id
for resource sometimes.
In json-api resource, we can do something like:
class ContactResource < JSONAPI::Resource
attribute :id
attributes :name_first, :name_last, :email, :twitter
key_type :uuid
end
I wonder if I could do the same thing in jsonapi-utils.
I'm trying to use JSONAPI Utils for a non-RESTful endpoint users/invite
and would like to be able to use the resource_params
and relationship_params
helpers, however they're both returning {}
because @request.operations
is empty. Is there any guidance on how to approach this?
Hi,
In our project, we are using JU for our API, currently only for get calls.
We have had some difficulties since we do not have direct access to our models in the API project, meaning we only have resources and controllers, and we are retrieving the data manually in the controller actions, into a hash which we then pass to jsonapi_format and render.
Specifically with pagination, what happens is the following:
We have some nested resources, with the routes declared this way:
jsonapi_resources :accounts, :only => [:show] do
jsonapi_resources :bills, :only => [:index]
end
So for our bills index route, we end up with the expected request url of "http://localhost:3005/accounts/2/bills" for example,
but the pagination links get generated with the following url, which doesn't exist in our routing:
"http://localhost:3005/bills?page%5Bnumber%5D=2&page%5Bsize%5D=1" (example for the "next" link)
If i manually add the account part to the request url, it seems like the paging functionality works as expected and I see the next page in the response.
My question is if there is any support for this scenario of pagination of a nested resource, or do I have no alternative but to manually alter the links section of the response and concatenate the missing part of the URL, which seems far from an ideal solution to me.
Not sure if this is a JU or JR issue, but any help would be much appreciated!
Thanks!
Hi!
I've been struggling to pass attributes when I migrate JU from JR.
{
"errors": [
{
"title": "Param not allowed",
"detail": "stripe_plan_id is not allowed.",
"code": "105",
"status": "400"
}
]
}
I could turn this config.raise_if_parameters_not_allowed = true
into false
, however I still see some error within meta.
Could you suggest how I could work this around?
Maybe resource_params
checks stripe_plan_id
and raise an error somehow so that it can not reach the endpoint #create
.
#app/resources/api/v1/plan_resource.rb
class Api::V1::PlanResource < JSONAPI::Resource
immutable
attributes :uuid,
:stripe_plan_id,
:name,
:description,
:amount,
:currency,
:interval,
:is_active,
:activated_at,
:disabled_at,
:created_at,
:updated_at
has_one :subscription, class_name: 'Subscription'
end
#app/controllers/api/v1/plans_controller.rb
module Api
module V1
class PlansController < Api::V1::ApiController
before_action only: [:create] { check_type_of('plans') }
def index
jsonapi_render json: Plan.all
end
def show
jsonapi_render json: Plan.find(params[:id])
end
def create
form = PlanCreateForm.new plan_params
form.validate!
@result = Plan::Create.call(form.attributes)
if @result.errors.blank?
# render json: jsonapi_serialized_body(result: @result, resource_name: 'plan'), status: 201 # :created
jsonapi_render json: @result, status: 201 # :created
else
errors = jsonapi_errors(@result)
response = { errors: errors }
render json: response, status: 422 # :unprocessable_entity
end
end
private
def plan_params
@plan_params ||= params.require(:data).require(:attributes)
end
end
end
end
Maybe I'm not understanding how jsonapi-utils/jsonapi-resource works, but I have a OrderResource
and a OrderListResource
(which is a simplified version of OrderResource
). I am trying to render OrderListResource
, but have the type
value be order
. Currently, it's setting it to order_list
. Here's the render statement:
jsonapi_render json: Order.last(2), options: { resource: Api::V2::OrderListResource }
module Api
module V2
class OrderListResource < JSONAPI::Resource
attribute :status_name
end
end
end
Response
{
"data": [
{
"id": "1",
"type": "order_lists", # <-- wrong, should be "order"
"links": {
"self": "http://localhost:3001/api/v2/order_lists/1" # <-- wrong, should be order/1
},
"attributes": {
"status_name": "some_status"
}
},
{
"id": "2",
"type": "order_lists", # <-- wrong, should be "order"
"links": {
"self": "http://localhost:3001/api/v2/order_lists/2" # <-- wrong, should be order/2
},
"attributes": {
"status_name": "some_other_status"
}
}
]
}
EDIT:
I'm not sure if this is the proper solution, or if I'm using jsonapi-resource incorrectly, but I fixed it by adding the following to OrderListResource
:
def self.name
'Order'
end
def self._type
'order'
end
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.