Add an option to the resource class to specify the default fields. Would be used by the request parser if the fields are not specified. Would allow a pared down fields set as the default, while still allowing the full field set if requested.
For example:
class CommentResource < JSONAPI::Resource
attributes :body
has_one :post
has_one :author, class_name: 'Person'
has_many :tags
filters :body
default_fields [:body, :post, :author]
end
Would skip the tags relationship, unless specified in the request.
Comments?
Btw, is JR using SELECT *fieldnames ?
I definitely think something like this would be useful, specifically defaulted attributes. With regards to the defaulted associations I am more ambivalent.
Personally I have come across the situation where I would like to not serialize an expensive attribute.
class FooResource < JSONAPI::Resource
attribute :expensive_calculation
def expensive_calculation
...
end
end
I'm sure there are methods that can be overridden to accomplish this goal, or by creating a Meta association resource to house the expensive attributes, but it would nice to be first class.
Another syntax might be something like
attribute :expensive_calculation, defaulted: false
The final thing I will add, which will probably be seen as heresy, is that we could add an additional query param when fetching data. In addition to the fields param we could allow something like extraFields which would allow for specifying non defaulted attributes without having to specify every field. Honestly, I'm not sure this is worthwhile, but just thought I'd throw the idea out there.
It's a bit more verbose and outside of the resource, but you can already do this in controllers:
def show
params[:fields] = 'body,post,author' if params[:fields].nil?
super
end
That is a good point and definitely a fairly clean solution. I guess the real question is whether this functionality is common enough to warrant something baked into the library itself.
Having to overwrite a bunch of methods in the controller, probably some BaseController, isn't the end of the world.
I went ahead and implemented the above logic in the controller. It is relatively simple, but due to handling invalid params and included resources the logic is a bit more involved. It also requires parsing the request twice.
def inject_default_fields
# parse the request for validation. Will throw exception on error.
# Sadly this means that we will be reparsing the request twice, another time after the injection of the fields.
parser = JSONAPI::RequestParser.new(
params,
context: context,
key_formatter: key_formatter,
server_error_callbacks: (self.class.server_error_callbacks || [])
)
# traverse the include hash to find which resources will be fetched.
resource = parser.resource_klass
directives_hash = parser.include_directives.try(:include_directives) || {}
resources = [resource] + extract_included_resources(resource, directives_hash)
# grab current values of fields
fields = params.fetch(:fields, ActionController::Parameters.new({}))
# inject default values if no fields are specified.
resources.each do |r|
if !fields.include?(r._type)
default_fields = r.default_fields()
if !default_fields.nil?
fields[r._type] = default_fields.map(&:to_s).join(',')
end
end
end
end
# Helper method to above inject_default_fields
# Returns included resources.
def extract_included_resources(parent_resource, directives_hash)
related = directives_hash[:include_related]
return [] if related.nil? || related.empty?
related.flat_map { |association_name, child_directive_hash|
resource = parent_resource._relationships[association_name].resource_klass
[resource] + extract_included_resources(resource, child_directive_hash)
}.uniq
end
This works with 0.8 release. The other option would be to monkey patch the fields parsing method, but I felt that this was cleaner.
Most helpful comment
It's a bit more verbose and outside of the resource, but you can already do this in controllers: