Jsonapi-resources: Default fields resource option

Created on 29 Sep 2016  路  5Comments  路  Source: cerebris/jsonapi-resources

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?

Enhancement

Most helpful comment

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

All 5 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.

Was this page helpful?
0 / 5 - 0 ratings