Module: JsonapiCompliable::Base

Extended by:
ActiveSupport::Concern
Defined in:
lib/jsonapi_compliable/base.rb

Overview

Provides main interface to jsonapi_compliable

This gets mixed in to a “context” class, such as a Rails controller.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.jsonapi(foo = 'bar', resource: nil, &blk) ⇒ void

This method returns an undefined value.

Define your JSONAPI configuration

Examples:

Inline Resource

# 'Quick and Dirty' solution that does not require a separate
# Resource object
class PostsController < ApplicationController
  jsonapi do
    type :posts
    use_adapter JsonapiCompliable::Adapters::ActiveRecord

    allow_filter :title
  end
end

Resource Class (preferred)

# Make code reusable by encapsulating it in a Resource class
class PostsController < ApplicationController
  jsonapi resource: PostResource
end

Parameters:

  • resource (Resource)

    the Resource class associated to this endpoint

See Also:



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/jsonapi_compliable/base.rb', line 45

def jsonapi(foo = 'bar', resource: nil, &blk)
  if resource
    self._jsonapi_compliable = resource
  else
    if !self._jsonapi_compliable
      self._jsonapi_compliable = Class.new(JsonapiCompliable::Resource)
    end
  end

  self._jsonapi_compliable.class_eval(&blk) if blk
end

.sideload_whitelist(hash) ⇒ 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 PostsController < ApplicationResource
  jsonapi resource: MyResource

  sideload_whitelist({
    index: [:blog],
    show: [:blog, { comments: :author }]
  })

  # ... code ...
end

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

Parameters:

  • whitelist (Hash, Array, Symbol)

See Also:



89
90
91
# File 'lib/jsonapi_compliable/base.rb', line 89

def sideload_whitelist(hash)
  self._sideload_whitelist = JSONAPI::IncludeDirective.new(hash).to_hash
end

Instance Method Details

#default_jsonapi_render_optionsHash

Define a hash that will be automatically merged into your render_jsonapi call

Examples:

# this
render_jsonapi(foo)
# is equivalent to this
render jsonapi: foo, default_jsonapi_render_options

Returns:

  • (Hash)

    the options hash you define

See Also:



286
287
288
289
# File 'lib/jsonapi_compliable/base.rb', line 286

def default_jsonapi_render_options
  {}.tap do |options|
  end
end

#deserialized_paramsDeserializer

Returns:

See Also:



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

def deserialized_params
  @deserialized_params ||= JsonapiCompliable::Deserializer.new(params, request.env)
end

#jsonapi_createUtil::ValidationResponse

Create the resource model and process all nested relationships via the serialized parameters. Any error, including validation errors, will roll back the transaction.

Examples:

Basic Rails

# Example Resource must have 'model'
#
# class PostResource < ApplicationResource
#   model Post
# end
def create
  post, success = jsonapi_create.to_a

  if success
    render_jsonapi(post, scope: false)
  else
    render_errors_for(post)
  end
end

Returns:

See Also:



189
190
191
192
193
194
195
196
# File 'lib/jsonapi_compliable/base.rb', line 189

def jsonapi_create
  _persist do
    jsonapi_resource.persist_with_relationships \
      deserialized_params.meta,
      deserialized_params.attributes,
      deserialized_params.relationships
  end
end

#jsonapi_destroyObject



229
230
231
232
233
# File 'lib/jsonapi_compliable/base.rb', line 229

def jsonapi_destroy
  _persist do
    jsonapi_resource.destroy(params[:id])
  end
end

#jsonapi_resourceResource

Returns an instance of the associated Resource

In other words, if you configured your controller as:

jsonapi resource: MyResource

This returns MyResource.new

Returns:

  • (Resource)

    the configured Resource for this controller



108
109
110
# File 'lib/jsonapi_compliable/base.rb', line 108

def jsonapi_resource
  @jsonapi_resource ||= self.class._jsonapi_compliable.new
end

#jsonapi_scope(scope, opts = {}) ⇒ Scope

Use when direct, low-level access to the scope is required.

Examples:

Show Action

# Scope#resolve returns an array, but we only want to render
# one object, not an array
scope = jsonapi_scope(Employee.where(id: params[:id]))
render_jsonapi(scope.resolve.first, scope: false)

Scope Chaining

# Chain onto scope after running through typical DSL
# Here, we'll add active: true to our hash if the user
# is filtering on something
scope = jsonapi_scope({})
scope.object.merge!(active: true) if scope.object[:filter]

Returns:

  • (Scope)

    the configured scope

See Also:



155
156
157
# File 'lib/jsonapi_compliable/base.rb', line 155

def jsonapi_scope(scope, opts = {})
  jsonapi_resource.build_scope(scope, query, opts)
end

#jsonapi_updateUtil::ValidationResponse

Update the resource model and process all nested relationships via the serialized parameters. Any error, including validation errors, will roll back the transaction.

Examples:

Basic Rails

# Example Resource must have 'model'
#
# class PostResource < ApplicationResource
#   model Post
# end
def update
  post, success = jsonapi_update.to_a

  if success
    render_jsonapi(post, scope: false)
  else
    render_errors_for(post)
  end
end

Returns:

See Also:



220
221
222
223
224
225
226
227
# File 'lib/jsonapi_compliable/base.rb', line 220

def jsonapi_update
  _persist do
    jsonapi_resource.persist_with_relationships \
      deserialized_params.meta,
      deserialized_params.attributes,
      deserialized_params.relationships
  end
end

#queryQuery

Instantiates the relevant Query object

Returns:

  • (Query)

    the Query object for this resource/params

See Also:



116
117
118
# File 'lib/jsonapi_compliable/base.rb', line 116

def query
  @query ||= Query.new(jsonapi_resource, params)
end

#query_hashHash

Returns the normalized query hash for only the current resource

Returns:

  • (Hash)

    the normalized query hash for only the current resource

See Also:



122
123
124
# File 'lib/jsonapi_compliable/base.rb', line 122

def query_hash
  @query_hash ||= query.to_hash[jsonapi_resource.type]
end

#render_jsonapi(scope, opts = {}) ⇒ Object

Similar to render :json or render :jsonapi

By default, this will “build” the scope via #jsonapi_scope. To avoid this, pass scope: false

This builds relevant options and sends them to JSONAPI::Serializable::SuccessRenderer#renderfrom jsonapi-rb

Examples:

Build Scope by Default

# Employee.all returns an ActiveRecord::Relation. No SQL is fired at this point.
# We further 'chain' onto this scope, applying pagination, sorting,
# filters, etc that the user has requested.
def index
  employees = Employee.all
  render_jsonapi(employees)
end

Avoid Building Scope by Default

# Maybe we already manually scoped, and don't want to fire the logic twice
# This code is equivalent to the above example
def index
  scope = jsonapi_scope(Employee.all)
  # ... do other things with the scope ...
  render_jsonapi(scope.resolve, scope: false)
end

Parameters:

  • scope (Scope, Object)

    the scope to build or render.

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

    the render options passed to jsonapi-rb

Options Hash (opts):

  • :scope (Boolean)

    Default: true. Should we call #jsonapi_scope on this object?

See Also:



266
267
268
269
270
271
272
273
# File 'lib/jsonapi_compliable/base.rb', line 266

def render_jsonapi(scope, opts = {})
  scope = jsonapi_scope(scope) unless opts[:scope] == false || scope.is_a?(JsonapiCompliable::Scope)
  opts  = default_jsonapi_render_options.merge(opts)
  opts  = Util::RenderOptions.generate(scope, query_hash, opts)
  opts[:expose][:context] = self
  opts[:include] = deserialized_params.include_directive if force_includes?
  perform_render_jsonapi(opts)
end

#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.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/jsonapi_compliable/base.rb', line 21

module ClassMethods
  # Define your JSONAPI configuration
  #
  # @example Inline Resource
  #   # 'Quick and Dirty' solution that does not require a separate
  #   # Resource object
  #   class PostsController < ApplicationController
  #     jsonapi do
  #       type :posts
  #       use_adapter JsonapiCompliable::Adapters::ActiveRecord
  #
  #       allow_filter :title
  #     end
  #   end
  #
  # @example Resource Class (preferred)
  #   # Make code reusable by encapsulating it in a Resource class
  #   class PostsController < ApplicationController
  #     jsonapi resource: PostResource
  #   end
  #
  # @see Resource
  # @param resource [Resource] the Resource class associated to this endpoint
  # @return [void]
  def jsonapi(foo = 'bar', resource: nil, &blk)
    if resource
      self._jsonapi_compliable = resource
    else
      if !self._jsonapi_compliable
        self._jsonapi_compliable = Class.new(JsonapiCompliable::Resource)
      end
    end

    self._jsonapi_compliable.class_eval(&blk) if blk
  end

  # Set the sideload whitelist. You may want to omit sideloads for
  # security or performance reasons.
  #
  # Uses JSONAPI::IncludeDirective from {{http://jsonapi-rb.org jsonapi-rb}}
  #
  # @example Whitelisting Relationships
  #   # Given the following whitelist
  #   class PostsController < ApplicationResource
  #     jsonapi resource: MyResource
  #
  #     sideload_whitelist({
  #       index: [:blog],
  #       show: [:blog, { comments: :author }]
  #     })
  #
  #     # ... code ...
  #   end
  #
  #   # A request to sideload 'tags'
  #   #
  #   # GET /posts/1?include=tags
  #   #
  #   # ...will silently fail.
  #   #
  #   # A request for comments and tags:
  #   #
  #   # GET /posts/1?include=tags,comments
  #   #
  #   # ...will only sideload comments
  #
  # @param [Hash, Array, Symbol] whitelist
  # @see Query#include_hash
  def sideload_whitelist(hash)
    self._sideload_whitelist = JSONAPI::IncludeDirective.new(hash).to_hash
  end
end

#wrap_contextObject

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.

Tracks the current context so we can refer to it within any random object. Helpful for easy-access to things like the current user.

Yield Returns:

  • Code to run within the current context



132
133
134
135
136
# File 'lib/jsonapi_compliable/base.rb', line 132

def wrap_context
  jsonapi_resource.with_context(self, action_name.to_sym) do
    yield
  end
end