Skip to content

Conversation

@MPursche
Copy link
Contributor

Hello,

in this pull request I implemented a new feature to allow the correct rendering of related resources pages.
I use jsonapi-utils to create JSONAPI resources that are not backed by a database model but computed on the fly. For the index and show routes the output of jsonapi_render is as expected. But if a custom get_related_resources route is implemented the wrong links were added to the document. This caused my client implementation to load the wrong data for the other pages.

Example usage:

# GET /users/:user_id/posts def get_related_resources if params[:source] == "users" && params[:relationship] == "posts" # Example for custom method to fetch related resources @user = User.find(params[:user_id]) @posts = @user.posts # this method is not an ActiveRecord relation but returns a plain Array jsonapi_render json: @posts, options: { source_resource: UserResource.new(@user, context), relationship_type: :posts } else # handle other requests with default method process_request end end

If the source_resource: and relationship_type: options are set the response is rendered as a related resource and the links for pages are adapted accordingly.

Page links before the change for related resource requests:

"links":{ "first":"http://localhost:3000/posts?page%5Bnumber%5D=1\u0026page%5Bsize%5D=25", "last":"http://localhost:3000/posts?page%5Bnumber%5D=1\u0026page%5Bsize%5D=25" }

After:

"links":{ "first":"http://localhost:3000/users/1/posts?page%5Bnumber%5D=1\u0026page%5Bsize%5D=25", "last":"http://localhost:3000/users/1/posts?page%5Bnumber%5D=1\u0026page%5Bsize%5D=25" }
Copy link
Owner

@tiagopog tiagopog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome catch again, @MPursche! I made a few comments so we can discuss a bit more about this feature before it goes into master.


patch :update_with_error_on_base, to: 'posts#update_with_error_on_base'

get :get_related_resources, to: 'posts#get_related_resources'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're dealing with related resources, what about to define the route like this:

TestApp.routes.draw do jsonapi_resources :users do jsonapi_links :profile jsonapi_related_resource :posts # this will draw the route you need on the tests  end jsonapi_resources :posts #... end
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried change the routes this way but it didn't work for me. I don't know what I did wrong.
But yes this would be a better configuration for the tests.

records = build_collection(object, options)
results.add_result(JSONAPI::ResourcesOperationResult.new(:ok, records, result_options(object, options)))

if options[:source_resource].present? && options[:relationship_type].present?
Copy link
Owner

@tiagopog tiagopog Jul 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about to check for this:

if options[:source_resource].present? && (params[:relationship].present? || options[:relationship_type].present?) # ...

This way we would not force the use of relationship_type which is easily inferred from the @request object or via params[:relationship].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea. I wasn't aware the params object is available within this method.

end
else
record = turn_into_resource(object, options)
record = turn_into_resource(object, options)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if this feature also applies to a single related resource :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this is necessary. In jsonapi-resources the special handling is only used for has_many relationships, has_one relationships are handled and rendered the same as regular show requests, i.e., the links in the response is to the show page of an object and not the relationships link.
This makes sense as only for the has_many relationship an issue with paging would arise.

Copy link
Owner

@tiagopog tiagopog Jul 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, got your point, it makes sense.

I was thinking on the "links"->"self" member for a single resource when I made the suggestion above, but it may not be necessary since it seems to be working properly (I'll double check it later).


if options[:source_resource].present? && options[:relationship_type].present?
results.add_result(JSONAPI::RelatedResourcesOperationResult.new(:ok,
options[:source_resource],
Copy link
Owner

@tiagopog tiagopog Jul 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it already happens on formatters, you could try inferring some stuff from the params found in the @request object rather than making both :source_resource and :relationship_type mandatory in order to achieve such a feature.

I was also thinking on changing a little bit the way the optionals are passed to the helper method to something like this:

# Usage 1: # - Source resource is inferred: @request.source_klass #=> UserResource # - Relationship is inferred: params[:relationship].to_sym || @request.resource_klass._type #=> :posts jsonapi_render json: @posts, options: { source: @user } # Usage 2: # - Source resource is defined via optionals # - Relationship is inferred: params[:relationship].to_sym || @request.resource_klass._type #=> :posts jsonapi_render json: @posts, options: { source: WeirdNamespace::UserResource.new(@user, context) } # Usage 3: # - Source resource is inferred: @request.source_klass #=> UserResource # - Relationship is defined via optionals jsonapi_render json: @posts, options: { source: @user, relationship: :posts } # Usage 4: # - Source resource is defined via optionals # - Relationship is defined via optionals jsonapi_render json: @posts, options: { source: WeirdNamespace::UserResource.new(@user, context), relationship: :posts }
@tiagopog
Copy link
Owner

@MPursche, are you willing to go ahead with this PR or should I take over from now on?

@MPursche
Copy link
Contributor Author

Hello,

sorry for the long delay. I totally forgot about this pull request. I merged with the current master branch and added the discussed changes.

The jsonapi_render method now supports the following additional parameter:

# Usage 1: # - Source resource is inferred: @request.source_klass #=> UserResource # - Relationship is inferred: params[:relationship].to_sym || @request.resource_klass._type #=> :posts jsonapi_render json: @posts, options: { source: @user } # Usage 2: # - Source resource is defined via optionals # - Relationship is inferred: params[:relationship].to_sym || @request.resource_klass._type #=> :posts jsonapi_render json: @posts, options: { source: WeirdNamespace::UserResource.new(@user, context) } # Usage 3: # - Source resource is inferred: @request.source_klass #=> UserResource # - Relationship is defined via optionals jsonapi_render json: @posts, options: { source: @user, relationship: :posts } # Usage 4: # - Source resource is defined via optionals # - Relationship is defined via optionals jsonapi_render json: @posts, options: { source: WeirdNamespace::UserResource.new(@user, context), relationship: :posts }

Greetings Marcel

@tiagopog
Copy link
Owner

Thanks for this awesome feature, @MPursche :-)

@tiagopog tiagopog merged commit 3820417 into tiagopog:master Jul 30, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

2 participants