Class: JsonapiCompliable::Resource

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/jsonapi_compliable/resource.rb

Overview

Resources hold configuration: How do you want to process incoming JSONAPI requests?

Let's say we start with an empty hash as our scope object:

render_jsonapi({})

Let's define the behavior of various parameters. Here we'll merge options into our hash when the user filters, sorts, and paginates. Then, we'll pass that hash off to an HTTP Client:

class PostResource < ApplicationResource
  type :posts
  use_adapter JsonapiCompliable::Adapters::Null

  # What do do when filter[active] parameter comes in
  allow_filter :active do |scope, value|
    scope.merge(active: value)
  end

  # What do do when sorting parameters come in
  sort do |scope, attribute, direction|
    scope.merge(order: { attribute => direction })
  end

  # What do do when pagination parameters come in
  page do |scope, current_page, per_page|
    scope.merge(page: current_page, per_page: per_page)
  end

  # Resolve the scope by passing the hash to an HTTP Client
  def resolve(scope)
    MyHttpClient.get(scope)
  end
end

This code can quickly become duplicative - we probably want to reuse this logic for other objects that use the same HTTP client.

That's why we also have Adapters. Adapters encapsulate common, reusable resource configuration. That's why we don't need to specify the above code when using ActiveRecord - the default logic is already in the adapter.

class PostResource < ApplicationResource
  type :posts
  use_adapter JsonapiCompliable::Adapters::ActiveRecord

  allow_filter :title
end

Of course, we can always override the Resource directly for one-off customizations:

class PostResource < ApplicationResource
  type :posts
  use_adapter JsonapiCompliable::Adapters::ActiveRecord

  allow_filter :title_prefix do |scope, value|
    scope.where(["title LIKE ?", "#{value}%"])
  end
end

Resources can also define Sideloads. Sideloads define the relationships between resources:

allow_sideload :comments, resource: CommentResource do
  # How to fetch the associated objects
  # This will be further chained down the line
  scope do |posts|
    Comment.where(post_id: posts.map(&:id))
  end

  # Now that we've resolved everything, how to assign the objects
  assign do |posts, comments|
    posts.each do |post|
      relevant_comments = comments.select { |c| c.post_id === post.id }
      post.comments = relevant_comments
    end
  end
end

Once again, we can DRY this up using an Adapter:

use_adapter JsonapiCompliable::Adapters::ActiveRecord

has_many :comments,
  scope: -> { Comment.all },
  resource: CommentResource,
  foreign_key: :post_id

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.configHash

This is where we store all information set via DSL. Useful for introspection. Gets dup'd when inherited.

Returns:

  • (Hash)

    the current configuration



413
414
415
# File 'lib/jsonapi_compliable/resource.rb', line 413

def config
  @config
end

Instance Attribute Details

#contextObject (readonly)

The current context *object* set by #with_context. If you are using Rails, this is a controller instance.

This method is equivalent to +JsonapiCompliable.context+

Returns:

  • the context object

See Also:



459
460
461
# File 'lib/jsonapi_compliable/resource.rb', line 459

def context
  @context
end

Class Method Details

.allow_filter(name, options = {}) ⇒ Object

Whitelist a filter

If a filter is not allowed, a Jsonapi::Errors::BadFilter error will be raised.

Examples:

Basic Filtering

allow_filter :title

# When using ActiveRecord, this code is equivalent
allow_filter :title do |scope, value|
  scope.where(title: value)
end

Custom Filtering

# All filters can be customized with a block
allow_filter :title_prefix do |scope, value|
  scope.where('title LIKE ?', "#{value}%")
end

Guarding Filters

# Only allow the current user to filter on a property
allow_filter :title, if: :admin?

def admin?
  current_user.role == 'admin'
end

Parameters:

  • name (Symbol)

    The name of the filter

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :if (Symbol)

    A method name on the current context - If the method returns false, BadFilter will be raised.

  • :aliases (Array<Symbol>)

    Allow the user to specify these aliases in the URL, then match to this filter. Mainly used for backwards-compatibility.

Yield Parameters:

  • scope

    The object being scoped

  • value

    The sanitized value from the URL



199
200
201
202
203
204
205
206
207
# File 'lib/jsonapi_compliable/resource.rb', line 199

def self.allow_filter(name, *args, &blk)
  opts = args.extract_options!
  aliases = [name, opts[:aliases]].flatten.compact
  config[:filters][name.to_sym] = {
    aliases: aliases,
    if: opts[:if],
    filter: blk
  }
end

.allow_sideloadObject



100
# File 'lib/jsonapi_compliable/resource.rb', line 100

def_delegator :sideloading, :allow_sideload

.allow_stat(symbol_or_hash) {|scope, attr| ... } ⇒ Object

Whitelist a statistic.

Statistics are requested like

GET /posts?stats[total]=count

And returned in meta:

{
  data: [...],
  meta: { stats: { total: { count: 100 } } }
}

Statistics take into account the current scope, *without pagination*.

Examples:

Total Count

allow_stat total: [:count]

Average Rating

allow_stat rating: [:average]

Custom Stat

allow_stat rating: [:average] do
  standard_deviation { |scope, attr| ... }
end

Parameters:

  • symbol_or_hash (Symbol, Hash)

    The attribute and metric

Yield Parameters:

  • scope

    The object being scoped

  • attr (Symbol)

    The name of the metric



238
239
240
241
242
# File 'lib/jsonapi_compliable/resource.rb', line 238

def self.allow_stat(symbol_or_hash, &blk)
  dsl = Stats::DSL.new(config[:adapter], symbol_or_hash)
  dsl.instance_eval(&blk) if blk
  config[:stats][dsl.name] = dsl
end

.belongs_toObject



109
# File 'lib/jsonapi_compliable/resource.rb', line 109

def_delegator :sideloading, :belongs_to

.default_filter(name) {|scope| ... } ⇒ Object

When you want a filter to always apply, on every request.

Default filters can be overridden if there is a corresponding allow_filter:

Examples:

Only Active Posts

default_filter :active do |scope|
  scope.where(active: true)
end

Overriding Default Filters

allow_filter :active

default_filter :active do |scope|
  scope.where(active: true)
end

# GET /posts?filter[active]=false
# Returns only active posts

Parameters:

  • name (Symbol)

    The default filter name

Yield Parameters:

  • scope

    The object being scoped

See Also:



266
267
268
269
270
# File 'lib/jsonapi_compliable/resource.rb', line 266

def self.default_filter(name, &blk)
  config[:default_filters][name.to_sym] = {
    filter: blk
  }
end

.default_page_number(val) ⇒ Object

Set an alternative default page number. Defaults to 1.

Parameters:

  • val (Integer)

    The new default



393
394
395
# File 'lib/jsonapi_compliable/resource.rb', line 393

def self.default_page_number(val)
  config[:default_page_number] = val
end

.default_page_size(val) ⇒ Object

Set an alternate default page size, when not specified in query parameters.

Examples:

# GET /employees will only render 10 employees
default_page_size 10

Parameters:

  • val (Integer)

    The new default page size.



404
405
406
# File 'lib/jsonapi_compliable/resource.rb', line 404

def self.default_page_size(val)
  config[:default_page_size] = val
end

.default_sort(val) ⇒ Object

Override default sort applied when not present in the query parameters.

Default: [{ id: :asc }]

Examples:

Order by created_at descending by default

# GET /employees will order by created_at descending
default_sort([{ created_at: :desc }])

Parameters:

  • val (Array<Hash>)

    Array of sorting criteria



365
366
367
# File 'lib/jsonapi_compliable/resource.rb', line 365

def self.default_sort(val)
  config[:default_sort] = val
end

.extra_field(name) {|scope, current_page, per_page| ... } ⇒ Object

Perform special logic when an extra field is requested. Often used to eager load data that will be used to compute the extra field.

This is not required if you have no custom logic.

Examples:

Eager load if extra field is required

# GET /employees?extra_fields[employees]=net_worth
extra_field(employees: [:net_worth]) do |scope|
  scope.includes(:assets)
end

Parameters:

  • name (Symbol)

    Name of the extra field

Yield Parameters:

  • scope

    The current object being scoped

  • current_page (Integer)

    The page parameter value

  • per_page (Integer)

    The page parameter value

See Also:



341
342
343
# File 'lib/jsonapi_compliable/resource.rb', line 341

def self.extra_field(name, &blk)
  config[:extra_fields][name] = blk
end

.has_and_belongs_to_manyObject



112
# File 'lib/jsonapi_compliable/resource.rb', line 112

def_delegator :sideloading, :has_and_belongs_to_many

.has_manyObject



103
# File 'lib/jsonapi_compliable/resource.rb', line 103

def_delegator :sideloading, :has_many

.has_oneObject



106
# File 'lib/jsonapi_compliable/resource.rb', line 106

def_delegator :sideloading, :has_one

.inherited(klass) ⇒ Object



126
127
128
# File 'lib/jsonapi_compliable/resource.rb', line 126

def self.inherited(klass)
  klass.config = Util::Hash.deep_dup(self.config)
end

.model(klass) ⇒ Object

The Model object associated with this class.

This model will be utilized on write requests.

Models need not be ActiveRecord ;)

Examples:

class PostResource < ApplicationResource
  # ... code ...
  model Post
end

Parameters:

  • klass (Class)

    The associated Model class



285
286
287
# File 'lib/jsonapi_compliable/resource.rb', line 285

def self.model(klass)
  config[:model] = klass
end

.paginate {|scope, current_page, per_page| ... } ⇒ Object

Define custom pagination logic

Examples:

Use will_paginate instead of Kaminari

# GET /employees?page[size]=10&page[number]=2
paginate do |scope, current_page, per_page|
  scope.paginate(page: current_page, per_page: per_page)
end

Yield Parameters:

  • scope

    The current object being scoped

  • current_page (Integer)

    The page parameter value

  • per_page (Integer)

    The page parameter value



319
320
321
# File 'lib/jsonapi_compliable/resource.rb', line 319

def self.paginate(&blk)
  config[:pagination] = blk
end

.polymorphic_belongs_toObject



115
# File 'lib/jsonapi_compliable/resource.rb', line 115

def_delegator :sideloading, :polymorphic_belongs_to

.polymorphic_has_manyObject

See Also:

  • Adapters::ActiveRecordSideloading#polymorphic_has_many


118
# File 'lib/jsonapi_compliable/resource.rb', line 118

def_delegator :sideloading, :polymorphic_has_many

.sideload_whitelist(whitelist) ⇒ Object

Set the sideload whitelist. You may want to omit sideloads for security or performance reasons.

Uses JSONAPI::IncludeDirective from jsonapi-rb}

Examples:

Whitelisting Relationships

# Given the following whitelist
class PostResource < ApplicationResource
  # ... code ...
  sideload_whitelist([:blog, { comments: :author }])
end

# A request to sideload 'tags'
#
# GET /posts?include=tags
#
# ...will silently fail.
#
# A request for comments and tags:
#
# GET /posts?include=tags,comments
#
# ...will only sideload comments

Parameters:

  • whitelist (Hash, Array, Symbol)

See Also:



161
162
163
# File 'lib/jsonapi_compliable/resource.rb', line 161

def self.sideload_whitelist(whitelist)
  config[:sideload_whitelist] = JSONAPI::IncludeDirective.new(whitelist).to_hash
end

.sideloadingObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



131
132
133
# File 'lib/jsonapi_compliable/resource.rb', line 131

def self.sideloading
  @sideloading ||= Sideload.new(:base, resource: self)
end

.sort {|scope, att, dir| ... } ⇒ Object

Define custom sorting logic

Examples:

Sort on alternate table

# GET /employees?sort=title
sort do |scope, att, dir|
  if att == :title
    scope.joins(:current_position).order("title #{dir}")
  else
    scope.order(att => dir)
  end
end

Yield Parameters:

  • scope

    The current object being scoped

  • att (Symbol)

    The requested sort attribute

  • dir (Symbol)

    The requested sort direction (:asc/:desc)



304
305
306
# File 'lib/jsonapi_compliable/resource.rb', line 304

def self.sort(&blk)
  config[:sorting] = blk
end

.type(value = nil) ⇒ Object

The JSONAPI Type. For instance if you queried:

GET /employees?fields=title

And/Or got back in the response

{ id: '1', type: 'positions' }

The type would be :positions

This should match the type set in your serializer.

Examples:

class PostResource < ApplicationResource
  type :posts
end

Parameters:

  • value (Array<Hash>) (defaults to: nil)

    Array of sorting criteria



387
388
389
# File 'lib/jsonapi_compliable/resource.rb', line 387

def self.type(value = nil)
  config[:type] = value
end

.use_adapter(klass) ⇒ Object

Configure the adapter you want to use.

Examples:

ActiveRecord Adapter

require 'jsonapi_compliable/adapters/active_record'
use_adapter JsonapiCompliable::Adapters::ActiveRecord

Parameters:

  • klass (Class)

    The adapter class



352
353
354
# File 'lib/jsonapi_compliable/resource.rb', line 352

def self.use_adapter(klass)
  config[:adapter] = klass.new
end

Instance Method Details

#adapterObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

See Also:



715
716
717
# File 'lib/jsonapi_compliable/resource.rb', line 715

def adapter
  self.class.config[:adapter]
end

#allowed_sideloads(namespace = nil) ⇒ Hash

An Include Directive Hash of all possible sideloads for the current context namespace, taking into account the sideload whitelist.

In other words, say we have this resource:

class PostResource < ApplicationResource
  sideload_whitelist({
    index: :comments,
    show: { comments: :author }
  })
end

Expected behavior:

allowed_sideloads(:index) # => { comments: {} }
allowed_sideloads(:show) # => { comments: { author: {} }

instance.with_context({}, :index) do
  instance.allowed_sideloads # => { comments: {} }
end

Parameters:

  • namespace (Symbol) (defaults to: nil)

    Can be :index/:show/etc - The current context namespace will be used by default.

Returns:

  • (Hash)

    the scrubbed include directive

See Also:



598
599
600
601
602
603
604
605
606
607
# File 'lib/jsonapi_compliable/resource.rb', line 598

def allowed_sideloads(namespace = nil)
  return {} unless sideloading

  namespace ||= context_namespace
  sideloads = sideloading.to_hash[:base]
  if !sideload_whitelist.empty? && namespace
    sideloads = Util::IncludeParams.scrub(sideloads, sideload_whitelist[namespace])
  end
  sideloads
end

#association_namesArray<Symbol>

All possible sideload names, including nested names

{ comments: { author: {} } }

Becomes

[:comments, :author]

Returns:

  • (Array<Symbol>)

    the list of association names

See Also:



563
564
565
566
567
568
569
570
571
# File 'lib/jsonapi_compliable/resource.rb', line 563

def association_names
  @association_names ||= begin
    if sideloading
      Util::Hash.keys(sideloading.to_hash[:base])
    else
      []
    end
  end
end

#build_scope(base, query, opts = {}) ⇒ Scope

Build a scope using this Resource configuration

Essentially “api private”, but can be useful for testing.

Parameters:

  • base

    The base scope we are going to chain

  • query

    The relevant Query object

  • opts (defaults to: {})

    Opts passed to Scope.new

Returns:

  • (Scope)

    a configured Scope instance

See Also:



484
485
486
# File 'lib/jsonapi_compliable/resource.rb', line 484

def build_scope(base, query, opts = {})
  Scope.new(base, self, query, opts)
end

#context_namespaceSymbol

The current context *namespace* set by #with_context. If you are using Rails, this is the controller method name (e.g. :index)

This method is equivalent to +JsonapiCompliable.context+

Returns:

  • (Symbol)

    the context namespace

See Also:



470
471
472
# File 'lib/jsonapi_compliable/resource.rb', line 470

def context_namespace
  JsonapiCompliable.context[:namespace]
end

#create(create_params) ⇒ Object

Create the relevant model. You must configure a model (see .model) to create. If you override, you must return the created instance.

Examples:

Send e-mail on creation

def create(attributes)
  instance = model.create(attributes)
  UserMailer.welcome_email(instance).deliver_later
  instance
end

Parameters:

  • create_params (Hash)

    The relevant attributes, including id and foreign keys

Returns:

  • (Object)

    an instance of the just-created model

See Also:



503
504
505
# File 'lib/jsonapi_compliable/resource.rb', line 503

def create(create_params)
  adapter.create(model, create_params)
end

#default_filtersObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

See Also:



703
704
705
# File 'lib/jsonapi_compliable/resource.rb', line 703

def default_filters
  self.class.config[:default_filters]
end

#default_page_numberObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



648
649
650
# File 'lib/jsonapi_compliable/resource.rb', line 648

def default_page_number
  self.class.config[:default_page_number] || 1
end

#default_page_sizeObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

See Also:



654
655
656
# File 'lib/jsonapi_compliable/resource.rb', line 654

def default_page_size
  self.class.config[:default_page_size] || 20
end

#default_sortObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

See Also:



642
643
644
# File 'lib/jsonapi_compliable/resource.rb', line 642

def default_sort
  self.class.config[:default_sort] || [{ id: :asc }]
end

#destroy(id) ⇒ Object

Destroy the relevant model. You must configure a model (see .model) to destroy. If you override, you must return the destroyed instance.

Examples:

Send e-mail on destroy

def destroy(attributes)
  instance = model_class.find(id)
  instance.destroy
  UserMailer.goodbye_email(instance).deliver_later
  instance
end

Parameters:

  • id (String)

    The id of the relevant Model

Returns:

  • (Object)

    an instance of the just-created model

See Also:



542
543
544
# File 'lib/jsonapi_compliable/resource.rb', line 542

def destroy(id)
  adapter.destroy(model, id)
end

#extra_fieldsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

See Also:



691
692
693
# File 'lib/jsonapi_compliable/resource.rb', line 691

def extra_fields
  self.class.config[:extra_fields]
end

#filtersObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

See Also:



667
668
669
# File 'lib/jsonapi_compliable/resource.rb', line 667

def filters
  self.class.config[:filters]
end

#modelObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

See Also:



709
710
711
# File 'lib/jsonapi_compliable/resource.rb', line 709

def model
  self.class.config[:model]
end

#paginationObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

See Also:



685
686
687
# File 'lib/jsonapi_compliable/resource.rb', line 685

def pagination
  self.class.config[:pagination]
end

#persist_with_relationships(meta, attributes, relationships) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



547
548
549
550
551
# File 'lib/jsonapi_compliable/resource.rb', line 547

def persist_with_relationships(meta, attributes, relationships)
  persistence = JsonapiCompliable::Util::Persistence \
    .new(self, meta, attributes, relationships)
  persistence.run
end

#resolve(scope) ⇒ Array

How do you want to resolve the scope?

For ActiveRecord, when we want to actually fire SQL, it's #to_a.

This method must return an array of resolved model objects.

By default, delegates to the adapter. You likely want to alter your adapter rather than override this directly.

Examples:

Custom API Call

# Let's build a hash and pass it off to an HTTP client
class PostResource < ApplicationResource
  type :posts
  use_adapter JsonapiCompliable::Adapters::Null

  sort do |scope, attribute, direction|
    scope.merge!(order: { attribute => direction }
  end

  page do |scope, current_page, per_page|
    scope.merge!(page: current_page, per_page: per_page)
  end

  def resolve(scope)
    MyHttpClient.get(scope)
  end
end

Parameters:

  • scope

    The scope object we've built up

Returns:

  • (Array)

    array of resolved model objects

See Also:



751
752
753
# File 'lib/jsonapi_compliable/resource.rb', line 751

def resolve(scope)
  adapter.resolve(scope)
end

#sideloadObject

See Also:



123
# File 'lib/jsonapi_compliable/resource.rb', line 123

def_delegator :sideloading, :sideload

#sideload_whitelistObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

See Also:



697
698
699
# File 'lib/jsonapi_compliable/resource.rb', line 697

def sideload_whitelist
  self.class.config[:sideload_whitelist]
end

#sideloadingObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Interface to the sideloads for this Resource



636
637
638
# File 'lib/jsonapi_compliable/resource.rb', line 636

def sideloading
  self.class.sideloading
end

#sortingObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

See Also:



673
674
675
# File 'lib/jsonapi_compliable/resource.rb', line 673

def sorting
  self.class.config[:sorting]
end

#stat(attribute, calculation) ⇒ Proc

The relevant proc for the given attribute and calculation.

Raises JsonapiCompliable::Errors::StatNotFound if not corresponding stat has been configured.

Examples:

Custom Stats

# Given this configuration
allow_stat :rating do
  average { |scope, attr| ... }
end

# We'd call the method like
resource.stat(:rating, :average)
# Which would return the custom proc

Parameters:

  • attribute (String, Symbol)

    The attribute we're calculating.

  • calculation (String, Symbol)

    The calculation to run

Returns:

  • (Proc)

    the corresponding callable

Raises:

See Also:



628
629
630
631
632
# File 'lib/jsonapi_compliable/resource.rb', line 628

def stat(attribute, calculation)
  stats_dsl = stats[attribute] || stats[attribute.to_sym]
  raise Errors::StatNotFound.new(attribute, calculation) unless stats_dsl
  stats_dsl.calculation(calculation)
end

#statsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

See Also:



679
680
681
# File 'lib/jsonapi_compliable/resource.rb', line 679

def stats
  self.class.config[:stats]
end

#transactionObject

How to run write requests within a transaction.

Should roll back the transaction, but avoid bubbling up the error, if JsonapiCompliable::Errors::ValidationError is raised within the block.

By default, delegates to the adapter. You likely want to alter your adapter rather than override this directly.

Examples:

resource.transaction do
  # ... save calls ...
end

Returns:

  • the result of yield

See Also:



771
772
773
774
775
776
777
778
779
780
# File 'lib/jsonapi_compliable/resource.rb', line 771

def transaction
  response = nil
  begin
    adapter.transaction(model) do
      response = yield
    end
  rescue Errors::ValidationError
  end
  response
end

#typeObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns :undefined_jsonapi_type when not configured.

See Also:



661
662
663
# File 'lib/jsonapi_compliable/resource.rb', line 661

def type
  self.class.config[:type] || :undefined_jsonapi_type
end

#update(update_params) ⇒ Object

Update the relevant model. You must configure a model (see .model) to update. If you override, you must return the updated instance.

Examples:

Send e-mail on update

def update(attributes)
  instance = model.update_attributes(attributes)
  UserMailer.profile_updated_email(instance).deliver_later
  instance
end

Parameters:

  • update_params (Hash)

    The relevant attributes, including id and foreign keys

Returns:

  • (Object)

    an instance of the just-created model

See Also:



522
523
524
# File 'lib/jsonapi_compliable/resource.rb', line 522

def update(update_params)
  adapter.update(model, update_params)
end

#with_context(object, namespace = nil) ⇒ Object

Run code within a given context. Useful for running code within, say, a Rails controller context

When using Rails, controller actions are wrapped this way.

Examples:

Sinatra

get '/api/posts' do
  resource.with_context self, :index do
    scope = jsonapi_scope(Tweet.all)
    render_jsonapi(scope.resolve, scope: false)
  end
end

Parameters:

  • object

    The context (Rails controller or equivalent)

  • namespace (defaults to: nil)

    One of index/show/etc

See Also:



446
447
448
449
450
# File 'lib/jsonapi_compliable/resource.rb', line 446

def with_context(object, namespace = nil)
  JsonapiCompliable.with_context(object, namespace) do
    yield
  end
end