Closed lgebhardt closed 8 years ago
:+1: for meta/ pagination.
:+1:
We have PR #202 and PR #199 which both get us farther along towards support for meta tags. I appreciate the work done on these PRs and I see some good ideas in them. However I'm not sure they go far enough to account for the various uses of the meta sections.
As I see it the different meta sections need to be controlled/generated at different levels.
Top Level: This meta is the domain of the request (which could include the resource type, the filters, and the paginator). For example the total_count
field discussed in #199 needs to take into account filters used in the request, at least in some use cases. It may also be desirable to provide the total record count for a resource type, though that may need to account for permissions. I think the meta fields would be defined on the controller, and from here class methods on the resources could also be called.
Resource level: This meta is the domain of the resource instance. I see registering meta fields in the resource by key name and a method on the resource instance to call.
Links: This meta is the domain of the association. I can see total_count
being useful here as well in a has_many association.
At each of these levels I'd like to allow actual meta entries to be fully configurable by allowing any meta key to relate to a function which will be called with a defined set of parameters/options.
Finally I see defining some standard methods on the resource and controller that could be used by default. An obvious candidate would be to use the core of the find method that puts together the relation and use it for returning the count.
I'd love to hear opinions on this. I'm sure I've missed some big things so feel free to suggest them or to poke holes in this proposal.
I'm currently handling meta information this way, which doesn't solve everything but works for my needs. I override the index
method on my base controller:
class Api::V2::ApiController < JSONAPI::ResourceController
# ...
def index
serializer = JSONAPI::ResourceSerializer.new(resource_klass,
include: @request.include,
fields: @request.fields,
base_url: base_url,
key_formatter: key_formatter,
route_formatter: route_formatter)
resource_records = resource_klass.find(resource_klass.verify_filters(@request.filters, context),
context: context,
sort_criteria: @request.sort_criteria,
paginator: @request.paginator)
meta = {
meta: {
page: {
number: @request.paginator.number,
size: @request.paginator.size,
total: page_total
}
}
}
render json: meta.merge(serializer.serialize_to_hash(resource_records))
rescue => e
handle_exceptions(e)
end
private
def page_total
(item_total.to_f / @request.paginator.size).ceil
end
def item_total
resource_klass.records(context: context).size
end
# ...
end
I'd like to see a more formal way to handle this within jsonapi-resources, though.
So it seems to me that #202 (sans the class-method support) would handle the resource meta and the controller and/or request should instead provide some kind of api (maybe they just need to respond_to meta
which return a hash?) for paginators, filters and sorters to append meta fields.
Not sure where and how to deal with links though, they aren't really represented by anything "public" at the moment, unless I've missed something.
In the mean time I think I'll steal @jamonholmgren idea of hijacking the index action to be able to get page count and result count.
I agree that #202 would be a good model for the resource meta. I'm struggling a bit with the request meta. It seems that the contents of meta could vary based on the action. For example we don't need a page
section for the show
action, but we may want the copyright info for everything.
For links we do have an association that gets created behind the scenes. Meta configuration could be stored there, and set as you declare either has_one or has_many. But that could certainly be cumbersome.
I'm a little late to this conversation but I think the request-level metadata could be resolved by a combination of overwriting ActsAsResourceController#base_response_meta to contain the meta information that you want returned on every request and by defining a Resource.find_meta
method (similar to Resource.find_count
) that returns a hash of information based on the filters and options based in. That way each Operation type could make a Resource.respond_to?(:#{operation_type}_meta)
method, such find_meta
for a FindOperation, or a show_meta
for a ShowOperation, etc, and add them to the options on the way to creating a ResourcesOperationResult object.
Or, in ruby psuedo-code
class SomeController < JSONAPI::Controller
# is returned in the meta key for every response
def base_response_meta
{
copyright: "blah blah blah",
authors: [
"Mark Twain",
"Ernest Hemmingway"
]
}
end
end
class SomeResource < JSONAPI::Resource
attributes :title, :body
def self.find_meta(filters, options)
{
query_params: {
page_num: options[:paginator].number,
page_size: options[:paginator].size
}
}
end
end
# in FindOperation
class FindOperation < Operation
# ...
def find_meta
@_find_meta ||= @resource_klass.find_meta(@resource_klass.verify_filters(@filters, @context),
context: @context,
sort_criteria: @sort_critera,
paginator: @paginator)
end
def apply
resource_records = @resource_klass.find #...
options = {}
if JSONAPI.configuration.top_level_links_include_pagination
options[:pagination_params] = pagination_params
end
if JSONAPI.configuration.top_level_meta_include_record_count
options[:record_count] = record_count
end
if @resource_klass.respond_to?(:find_meta)
options[:meta] = find_meta
end
return JSONAPI::ResourcesOperationResult.new(:ok, resource_records, options)
end
end
so now a request to http://example.com/somes
returns
{
data: [<collection of objects>],
meta: {
copyright: "blah blah blah",
authors: [
"Mark Twain",
"Ernest Hemmingway"
],
query_params: {
page_num: 1
page_size: 10
}
}
}
whereas a request to http://example.com/somes/1
returns
{
data: {< single object>},
meta: {
copyright: "blah blah blah",
authors: [
"Mark Twain",
"Ernest Hemmingway"
]
}
}
Would love to see support for this - would be super handy.
For anyone that comes across this requirement in the future, here's a solution (and then I'll close the issue).
class FooController < JSONAPI::ResourceController
def base_meta
super.merge(
bar: "baz"
)
end
end
Then all responses for all methods on that controller will include "bar": "baz"
in the top level "meta"
.
For future searchers that find this issue: if you need the result set in order to calculate something for the meta key, the way to do it is via a Processor
-> http://jsonapi-resources.com/v0.9/guide/operation_processors.html
JR should support
meta
members. JSON API now allowsmeta
in the top level, in a resource, and in a link object. Details of both custom meta information and standard JR meta information (if any) still needs to be defined.