Skip to content

Rails Engine for updating nested resources and performing bulk updates.

License

Notifications You must be signed in to change notification settings

graveflex/deep_unrest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CircleCI Test Coverage Code Climate Issue Count

Goals

  • Allow updates and deletions of deeply nested resources
  • Allow bulk updates and deletions
  • Perform authorization on all affected resources
  • Control which params each user is allowed to update for each resource
  • Provide clear errors to the client

Caveats

  • This approach is not RESTful

Installation

Add this line to your application's Gemfile:

gem 'deep_unrest'

And then execute:

$ bundle

Configuration

  1. Mount the endpoint:

    # config/routes.rb
    Rails.application.routes.draw do
      # ...
      mount DeepUnrest::Engine => '/deep_unrest'
    end
  2. Set the authentication concern and authorization strategy:

    # config/initailizers/deep_unrest.rb
    DeepUnrest.configure do |config|
      # will be included by the controller as a concern
      config.authentication_concern = DeviseTokenAuth::Concerns::SetUserByToken
    
      # will be called from the controller to identify the current user
      config.get_user = proc { current_user }
    
      # or if your app has multiple user types:
      # config.get_user = proc { current_admin || current_user }
    
      # stategy that will be used to authorize the current user for each resource
      config.authorization_strategy = DeepUnrest::Authorization::PunditStrategy
    end

Usage

For the following examples, consider this data model:

Example 1 - Simple Update:

Update attributes on a single Submission with id 123

Request:
// PATCH /deep_unrest/update
{
  redirect: '/api/submissions/123',
  data: [
    {
      path: 'submissions.123',
      attributes: {
        approved: true
      }
    }
  ]
}
200 Response:

The success action is to follow the redirect request param (/api/submissions/123 in the example above).

{
  id: 123,
  type: 'submissions',
  attributes: {
    approved: 'true'
  }
}
403 Response:

This error will occur when a user attempts to update a resource that is not within their policy scope.

[
  {
    source: { pointer: { 'submissions.123' } },
    title: "User with id '456' is not authorized to update Submission with id '123'"
  }
]
405 Response:

This error will occur when a user is allowed to update a resource, but not specified attributes of that resource.

[
  {
    source: { pointer: { 'submissions.123' } },
    title: "Attributes [:approved] of Submission not allowed to Applicant with id '789'"
  }
]
409 Response:

This error will occur when field-level validation fails on any resource updates.

[
  {
    source: { pointer: { 'submissions.123.name' } },
    title: 'Name is required',
    detail: 'is required',
  }
]

Example 2 - Simple Delete:

To delete a resource, pass the param destroy: true along with the path to that resource.

Request:
// PATCH /deep_unrest/update
{
  data: [
    {
      path: 'submissions.123',
      destroy: true,
    }
  ]
}
200 Response:

When no redirect path is specified, an empty object will be returned as the response.

{}

Example 3 - Simple Create:

When creating new resources, the client should assign a temporary ID to the new resource. The temporary ID should be surrounded in brackets ([]).

Create Request
// PATCH /deep_unrest/update
{
  redirect: '/submissions/[1]',
  data: [
    {
      path: 'submissions[1]',
      attributes: {
        name: 'testing'
      }
    }
  ]
}
200 Response:

When a temp id ([id]) is present in the redirect url, the temp id will be replaced with the id given to the new resource.

Using the example above, assuming that the Submission at path submissions[1] was given the id 123, the redirect request param of /submissions/[1] will be replaced with /submissions/123.

{
  id: 123,
  type: 'submissions',
  attributes: {
    name: 'testing'
  }
}
Create Errors:

All errors regarding the new resource will use the temp ID as the path to the error.

[
  {
    source: { pointer: { 'submissions[123].name' } },
    title: 'Name is invalid',
    detail: 'is invalid',
  }
]

Example 4 - Complex Nested Update:

This shows an example of a complex operation involving multiple resources. This example will perform the following operations:

  • Change the name column of Submission with id 123 to test
  • Change the value column of Answer with id 1 to yes
  • Create a new Answer with a value of no using temp ID [1]
  • Delete the Answer with id 2

These operations will be performed within a single ActiveRecord transaction.

Complex Nested Update Request
// PATCH /deep_unrest/update
{
  redirect: '/api/submissions/123',
  data: [
    {
      path: 'submissions.123',
      attributes: { name: 'test' }
    },
    {
      path: "submissions.123.questions.456.answers.1",
      attributes: { value: 'yes' }
    },
    {
      path: "submissions.123.questions.456.answers[1]",
      attributes: {
        value: 'no',
        questionId: 456,
        submissionId: 123,
        applicantId: 890
      }
    },
    {
      path: "submissions.123.questions.456.answers.2",
      destroy: true
    }
  ]
}

Example 5 - Bulk Updates

The following example will mark every Submission as approved.

When using an authorization strategy, the scope of the bulk update will be limited to the current user's allowed scope.

Bulk Update Request

// PATCH /deep_unrest/update
{
  redirect: '/api/submissions',
  data: [
    {
      path: 'submissions.*',
      attributes: {
        approved: true
      }
    }
  ]
}

Example 6 - Bulk Delete

The following example will delete every Submission.

When using an authorization strategy, the scope of the bulk delete will be limited to the current user's allowed scope.

Bulk Delete Request

// PATCH /deep_unrest/update
{
  redirect: '/api/submissions',
  data: [
    {
      path: 'submissions.*',
      destroy: true
    }
  ]
}

TODO

  • Allow the use of filters when performing bulk operations.
  • How should we handle nested bulk operations? i.e. submissions.*.questions.*.answers.*

Contributing

TDB

License

The gem is available as open source under the terms of the WTFPL.

About

Rails Engine for updating nested resources and performing bulk updates.

Resources

License

Stars

Watchers

Forks

Packages

No packages published