Class: JsonApiServer::Include

Inherits:
Object
  • Object
show all
Defined in:
lib/json_api_server/include.rb

Overview

Description:

Handles include parameters per JSON API spec jsonapi.org/format/#fetching-includes.

An endpoint may support an include request parameter to allow the client to customize which related resources should be returned.

ie., GET /articles/1?include=comments,comment.author,tags HTTP/1.1

This class (1) whitelists include params, (2) maintains an array of permitted inclusions, and (3) generates a sub-query if eagerloading is configured for inclusions.

Usage:

An inclusion request looks like:

/topics?include=author,comment.author,comments

It is converted to an array of relationships:

['author', 'comment.author', 'comments']

Includes are whitelisted with a configuration that looks like:

{
  {'author': -> { includes(:author) }},
  {'comments': -> { includes(:comments) }},
  'comment.author'
}

In this example, author, comments, and comment.author are allowed includes. If an unsupported include is requested, a JsonApiServer::BadRequest exception is raised which renders a 400 error.

A proc/lambda can be specified to eagerload relationships. Be careful, to date, there is no way to apply limits to :includes.

Example:

permitted = {
  {'author': -> { includes(:author) }},
  {'comments': -> { includes(:comments) }},
  'comment.author'
}

# create instance
include = JsonApiServer::Include.new(request, Topic, permitted)

# merge into master query
recent_topics = Topic.recent.merge(include.query)

# use in serializers
class CommentSerializer < JsonApiServer::ResourceSerializer
  def relationships
    if relationship?('comment.author') # relationship? is a helper methods in serializers.
      #...
    end
  end
end

Note:

JsonApiServer::Builder class provides an easier way to use this class.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(request, model, permitted = []) ⇒ Include

Arguments:

  • request - ActionDispatch::Request

  • model (ActiveRecord::Base) - Model to append queries to.

  • permitted (Array) - Permitted inclusions. To eagerload the relationship, pass a proc:

Example:

Eagerloads author, comments, comments -> authors.

[
  {'author': -> { includes(:author) }},
  {'comments': -> { includes(:comments) }},
  {'comments.author': -> {includes(comments: :author) }},
  'publisher.addresses'
]


92
93
94
95
96
97
# File 'lib/json_api_server/include.rb', line 92

def initialize(request, model, permitted = [])
  @request = request
  @model = model
  @permitted = permitted.is_a?(Array) ? permitted : []
  @params = request.query_parameters
end

Instance Attribute Details

#modelObject (readonly)

ActiveRecord::Base model passed in constructor.



71
72
73
# File 'lib/json_api_server/include.rb', line 71

def model
  @model
end

#paramsObject (readonly)

Query parameters from #request.



68
69
70
# File 'lib/json_api_server/include.rb', line 68

def params
  @params
end

#permittedObject (readonly)

Include configs passed in constructor.



74
75
76
# File 'lib/json_api_server/include.rb', line 74

def permitted
  @permitted
end

#requestObject (readonly)

ActionDispatch::Request passed in constructor.



65
66
67
# File 'lib/json_api_server/include.rb', line 65

def request
  @request
end

Instance Method Details

#include_paramsObject

Array of include params from the request.

Examples
include=comments becomes ['comments']
include=comments.author,tags becomes ['comments.author', 'tags']


116
117
118
119
120
# File 'lib/json_api_server/include.rb', line 116

def include_params
  @include_params ||= begin
    params[:include].present? ? params[:include].split(',').map!(&:strip) : []
  end
end

#includesObject

Array of whitelisted include params. Raises JsonApiServer::BadRequest if any #include_params is not whitelisted.

Examples

include=comments becomes ['comments']
include=comments.author,tags becomes ['comments.author', 'tags']


106
107
108
# File 'lib/json_api_server/include.rb', line 106

def includes
  include_params.select { |i| config_for(i).present? }
end

#relationObject Also known as: query

Returns an ActiveRecord::Relation object (a query fragment). Returns nil if no eagerloading is configured.



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/json_api_server/include.rb', line 124

def relation
  @relation ||= begin
    additions = false
    # TODO: merge! has unexpected results.
    frag = include_params.reduce(model.all) do |result, inclusion|
      config = config_for(inclusion)
      query = config.respond_to?(:keys) ? config.values.first : nil
      unless query.nil?
        additions = true
        result = result.merge(query)
      end
      result
    end
    additions ? frag : nil
  end
end