module JsonapiCompliable::Base
Provides main interface to jsonapi_compliable
This gets mixed in to a “context” class, such as a Rails
controller.
Attributes
Public Class Methods
# File lib/jsonapi_compliable/base.rb, line 13 def self.inherited(klass) super klass._jsonapi_compliable = Class.new(_jsonapi_compliable) klass._sideload_whitelist = _sideload_whitelist.dup if _sideload_whitelist end
Public Instance Methods
Define a hash that will be automatically merged into your render_jsonapi
call
@example
# this render_jsonapi(foo) # is equivalent to this render jsonapi: foo, default_jsonapi_render_options
@see render_jsonapi
@return [Hash] the options hash you define
# File lib/jsonapi_compliable/base.rb, line 328 def default_jsonapi_render_options {}.tap do |options| end end
@see Deserializer#initialize @return [Deserializer]
# File lib/jsonapi_compliable/base.rb, line 188 def deserialized_params @deserialized_params ||= JsonapiCompliable::Deserializer.new(params, request.env) end
Override if you'd like the “context” to be something other than an instance of this class. In Rails
, context is the controller instance.
@example Overriding
# within your controller def jsonapi_context current_user end # within a resource default_filter :by_user do |scope, context| scope.where(user_id: context.id) end
@return the context object you'd like
# File lib/jsonapi_compliable/base.rb, line 161 def jsonapi_context self end
Create the resource model and process all nested relationships via the serialized parameters. Any error, including validation errors, will roll back the transaction.
@example 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
@see Resource.model
@see resource @see deserialized_params
@return [Util::ValidationResponse]
# File lib/jsonapi_compliable/base.rb, line 216 def jsonapi_create _persist do jsonapi_resource.persist_with_relationships \ deserialized_params.meta, deserialized_params.attributes, deserialized_params.relationships end end
Delete the model Any error, including validation errors, will roll back the transaction.
Note: before_commit
hooks still run unless excluded
@return [Util::ValidationResponse]
# File lib/jsonapi_compliable/base.rb, line 262 def jsonapi_destroy jsonapi_resource.transaction do model = jsonapi_resource.destroy(params[:id]) validator = ::JsonapiCompliable::Util::ValidationResponse.new \ model, deserialized_params validator.validate! jsonapi_resource.before_commit(model, :destroy) validator end end
Returns an instance of the associated Resource
In other words, if you configured your controller as:
jsonapi resource: MyResource
This returns MyResource.new
@return [Resource] the configured Resource
for this controller
# File lib/jsonapi_compliable/base.rb, line 108 def jsonapi_resource @jsonapi_resource ||= begin resource = self.class._jsonapi_compliable if resource.is_a?(Hash) resource[action_name.to_sym].new else resource.new end end end
Use when direct, low-level access to the scope is required.
@example 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)
@example 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]
@see Resource#build_scope
@return [Scope] the configured scope
# File lib/jsonapi_compliable/base.rb, line 182 def jsonapi_scope(scope, opts = {}) jsonapi_resource.build_scope(scope, query, opts) end
Update the resource model and process all nested relationships via the serialized parameters. Any error, including validation errors, will roll back the transaction.
@example 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
@see jsonapi_create
@return [Util::ValidationResponse]
# File lib/jsonapi_compliable/base.rb, line 247 def jsonapi_update _persist do jsonapi_resource.persist_with_relationships \ deserialized_params.meta, deserialized_params.attributes, deserialized_params.relationships end end
@see Query#to_hash
@return [Hash] the normalized query hash for only the current resource
# File lib/jsonapi_compliable/base.rb, line 129 def query_hash @query_hash ||= query.to_hash[jsonapi_resource.type] end
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#render+from {jsonapi-rb.org jsonapi-rb}
@example 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
@example 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
@param scope [Scope, Object] the scope to build or render. @param [Hash] opts the render options passed to {jsonapi-rb.org jsonapi-rb} @option opts [Boolean] :scope Default: true. Should we call jsonapi_scope
on this object? @see jsonapi_scope
# File lib/jsonapi_compliable/base.rb, line 304 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 if opts[:include].empty? && force_includes? opts[:include] = deserialized_params.include_directive end perform_render_jsonapi(opts) end
@api private
# File lib/jsonapi_compliable/base.rb, line 95 def sideload_whitelist self.class._sideload_whitelist || {} end
Tracks the current context so we can refer to it within any random object. Helpful for easy-access to things like the current user.
@api private @yieldreturn Code to run within the current context
# File lib/jsonapi_compliable/base.rb, line 139 def wrap_context jsonapi_resource.with_context(jsonapi_context, action_name.to_sym) do yield end end
Private Instance Methods
# File lib/jsonapi_compliable/base.rb, line 335 def _persist jsonapi_resource.transaction do ::JsonapiCompliable::Util::Hooks.record do model = yield validator = ::JsonapiCompliable::Util::ValidationResponse.new \ model, deserialized_params validator.validate! validator end end end
# File lib/jsonapi_compliable/base.rb, line 347 def force_includes? not (deserialized_params.data.nil? || deserialized_params.data.empty?) end
# File lib/jsonapi_compliable/base.rb, line 351 def perform_render_jsonapi(opts) # TODO(beauby): Reuse renderer. JSONAPI::Serializable::Renderer.new .render(opts.delete(:jsonapi), opts).to_json end