Module: APIHelper::Includable
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/api_helper/includable.rb
Overview
Includable
Inclusion lets your API returns not only the data of the primary resource, but also resources that have relation to it. Includable APIs will also support customising the resources included using the include
parameter.
This design made references to the rules of Inclusion of Related Resources in JSON API: jsonapi.org/format/#fetching-includes
For instance, articles can be requested with their comments along:
GET /articles?include=comments
The server will respond
[
{
"id": 1,
"title": "First Post",
"content": "...",
"comments": [
{
"id": 1,
"content": "..."
},
{
"id": 3,
"content": "..."
},
{
"id": 6,
"content": "..."
}
]
},
{
"id": 2,
"title": "Second Post",
"content": "...",
"comments": [
{
"id": 2,
"content": "..."
},
{
"id": 4,
"content": "..."
},
{
"id": 5,
"content": "..."
}
]
}
]
instead of just the ids of each comment
[
{
"id": 1,
"title": "First Post",
"content": "...",
"comments": [1, 3, 6]
},
{
"id": 2,
"title": "Second Post",
"content": "...",
"comments": [2, 4, 5]
}
]
Multiple related resources can be stated in a comma-separated list, like this:
GET /articles/12?include=author,comments
Usage
Include this Concern
in your Action Controller:
SamplesController < ApplicationController
include APIHelper::Includable
end
or in your Grape API class:
class SampleAPI < Grape::API
helpers APIHelper::Includable
end
Then setup inclusion with inclusion_for
in the controller:
def index
inclusion_for :post, default: true
# ...
end
or in the Grape method if you’re using it:
resources :posts do
get do
inclusion_for :post, default: true
# ...
end
end
This helper parses the include
and/or include[resource_name]
parameters and saves the results into @inclusion for further usage.
Includable
integrates with Fieldsettable
if used together, by:
-
Sliceing the included fields that dosen’t appears in the fieldset - since the included resoure(s) are actually fields under the primary resorce, fieldset will be in charged to determine the fields to show. Thus, fields will be totally ignored if they aren’t appeared in the fieldset, regardless if they are included or not.
So notice that inclusion_for
should be set after fieldset_for
if both are used!
After that inclusion_for … line, you can use the inclusion
helper method to get the inclusion data of each request, and do something like this in your controller:
@posts = Post.includes(inclusion(:post))
The inclusion
helper method will return data depending on the parameters passed in, as the following example:
inclusion # => { 'post' => ['author'] }
inclusion(:post) # => ['author']
inclusion(:post, :author) # => true
And don’t forget to set your API views or serializers with the help of inclusion
to provide dynamic included resources!
API View with RABL
If you’re using RABL as the API view, it can be setup like this:
object @post
# set the includable and default inclusion fields of the view
set_inclusion :post, default_includes: [:author]
# set the details for all includable fields
set_inclusion_field :post, :author, :author_id
set_inclusion_field :post, :comments, :comment_ids
# extends the partial to show included fields
extends('extensions/includable_childs', locals: { self_resource: :post })
– TODO: provide an example of includable_childs.rabl ++
Class Method Summary collapse
-
.include_param_desc(example: nil, default: nil) ⇒ Object
Returns the description of the ‘include’ URL parameter.
Instance Method Summary collapse
-
#inclusion(resource = nil, field = nil) ⇒ Object
Getter for the inclusion data.
-
#inclusion_field(resource = nil) ⇒ Object
Getter for the data of includable fields.
-
#inclusion_for(resource, default: false, permitted_includes: [], defaults_to_permitted_includes: false, default_includes: []) ⇒ Object
Gets the include parameters, organize them into a @inclusion hash.
-
#set_inclusion(resource, default_includes: [], permitted_includes: []) ⇒ Object
View Helper to set the inclusion.
-
#set_inclusion_field(resource, field, id_field, resource_name: nil, resources_url: nil) ⇒ Object
View Helper to set the inclusion details.
Class Method Details
.include_param_desc(example: nil, default: nil) ⇒ Object
Returns the description of the ‘include’ URL parameter
337 338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/api_helper/includable.rb', line 337 def self.include_param_desc(example: nil, default: nil) if default.present? desc = "Returning compound documents that include specific associated objects, defaults to '#{default}'." else desc = "Returning compound documents that include specific associated objects." end if example.present? "#{desc} Example value: '#{example}'" else desc end end |
Instance Method Details
#inclusion(resource = nil, field = nil) ⇒ Object
Getter for the inclusion data
This method will act as a traditional getter of the inclusion data and returns a hash containing fields for each resource if no parameter is provided.
inclusion # => { 'post' => ['author', 'comments'] }
If one parameter - a specific resourse name is passed in, it will return an array of relation names that should be included for that specific resourse.
inclusion(:post) # => ['author', 'comments']
And if one more parameter - a field name, is passed in, it will return a boolen, determining if that relation should be included in the response.
inclusion(:post, :author) # => true
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/api_helper/includable.rb', line 248 def inclusion(resource = nil, field = nil) # act as a traditional getter if no parameters specified if resource.blank? @inclusion ||= ActiveSupport::HashWithIndifferentAccess.new # returns the inclusion array if an specific resource is passed in elsif field.blank? inclusion[resource] || [] # determine if a field is inculded else field = field.to_s inclusion(resource).is_a?(Array) && inclusion(resource).include?(field) end end |
#inclusion_field(resource = nil) ⇒ Object
Getter for the data of includable fields
Params:
resource
-
Symbol
the resource name of inclusion data to retrieve
325 326 327 328 329 330 331 332 333 334 |
# File 'lib/api_helper/includable.rb', line 325 def inclusion_field(resource = nil) # act as a traditional getter if no parameters specified if resource.blank? @inclusion_field ||= ActiveSupport::HashWithIndifferentAccess.new # returns the inclusion array if an specific resource is passed in else inclusion_field[resource] || {} end end |
#inclusion_for(resource, default: false, permitted_includes: [], defaults_to_permitted_includes: false, default_includes: []) ⇒ Object
Gets the include parameters, organize them into a @inclusion hash
Params:
resource
-
Symbol
name of resource to receive the inclusion default
-
Boolean
should this resource take the parameter frominclude
while noresourse name is specified?
permitted_includes
-
Array
of Symbols list of includable fields, permitting all by default default_includes
-
Array
of Symbols list of fields to be included by default defaults_to_permitted_includes
-
Boolean
if set to true, @inclusion will be set to all permitted_includes when the current resource’s included fields isn’t specified
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/api_helper/includable.rb', line 187 def inclusion_for(resource, default: false, permitted_includes: [], defaults_to_permitted_includes: false, default_includes: []) @inclusion ||= ActiveSupport::HashWithIndifferentAccess.new @inclusion_specified ||= ActiveSupport::HashWithIndifferentAccess.new # put the fields in place if params[:include].is_a?(Hash) || defined?(Rails) && Rails.version.to_i >= 5 && params[:include].is_a?(ActionController::Parameters) # get the specific resource inclusion fields from the "include" hash @inclusion[resource] = params[:include][resource] @inclusion_specified[resource] = true if params[:include][resource].present? elsif default # or get the "include" string directly if this resource is th default one @inclusion[resource] = params[:include] @inclusion_specified[resource] = true if params[:include].present? end # splits the string into array if @inclusion[resource].present? @inclusion[resource] = @inclusion[resource].split(',').map(&:to_s) elsif !@inclusion_specified[resource] @inclusion[resource] = default_includes.map(&:to_s) end if permitted_includes.present? permitted_includes = permitted_includes.map(&:to_s) # filter out unpermitted includes by intersecting them @inclusion[resource] &= permitted_includes if @inclusion[resource].present? # set default inclusion to permitted_includes if needed @inclusion[resource] = permitted_includes if @inclusion[resource].blank? && defaults_to_permitted_includes && !@inclusion_specified[resource] end if @fieldset.is_a?(Hash) && @fieldset[resource].present? @inclusion[resource] &= @fieldset[resource] end end |
#set_inclusion(resource, default_includes: [], permitted_includes: []) ⇒ Object
View Helper to set the inclusion
This is useful while using an resource view shared by multiple controllers, this will ensure the @inclusion instance variable presents, and can also set the default included fields of a model for convenience, or the fields that are permitted to be included for security.
270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/api_helper/includable.rb', line 270 def set_inclusion(resource, default_includes: [], permitted_includes: []) @inclusion ||= ActiveSupport::HashWithIndifferentAccess.new @inclusion_field ||= ActiveSupport::HashWithIndifferentAccess.new @inclusion_specified ||= ActiveSupport::HashWithIndifferentAccess.new @inclusion[resource] = default_includes.map(&:to_s) if @inclusion[resource].blank? && !@inclusion_specified[resource] @inclusion[resource] &= permitted_includes.map(&:to_s) if permitted_includes.present? if @fieldset.is_a?(Hash) && @fieldset[resource].present? @inclusion[resource] &= @fieldset[resource] end end |
#set_inclusion_field(resource, field, id_field, resource_name: nil, resources_url: nil) ⇒ Object
View Helper to set the inclusion details
Params:
resource
-
Symbol
name of the resource to receive the inclusion field data field
-
Symbol
the field name of the relatiion that can be included id_field
-
Symbol
the field to use (normally suffixed with “_id”) if the object isn’t included resource_name
-
Symbol
the name of the child resource, can be used to determine which view template should be extended for rendering that child node and also can shown in the response metadata as well resources_url
-
String
the resources URL of the child resource, can be used to be shown in the metadata for the clients’ convenience to learn ablou the API
306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/api_helper/includable.rb', line 306 def set_inclusion_field(resource, field, id_field, resource_name: nil, resources_url: nil) @inclusion_field ||= ActiveSupport::HashWithIndifferentAccess.new @inclusion_field[resource] ||= ActiveSupport::HashWithIndifferentAccess.new @inclusion_field[resource][field] = { field: field, id_field: id_field, resource_name: resource_name, resources_url: resources_url } end |