rack-autocrud
Rack middleware that works with Sinatra to dynamically create CRUD endpoints and routes based on models. It ain't perfect, but it works.
These generated CRUD routes are assumed to return a Rack response.
It's important to note, that you models and endpoints must be in separate modules (read: namespaces).
Input and Response data are formatted as JSON.
NOTE: While this gem is designed to work with DataMapper, it may be
usable with Sequel as well (via Sequel::Plugins::JsonSerializer
)
since none of the code in rack-autocrud
is DataMapper-dependent.
Licensing
This software is licensed under the Simplified BSD License as described in the LICENSE file.
Requirements
- sinatra
- json
For DataMapper: * dm-serializer
Installation
gem install rack-autocrud
Usage
Just add something like this to your config.ru:
require 'rack/autocrud'
# Load your models
require 'models'
# Load your endpoints
require 'endpoints'
# Auto-magical CRUD
run Rack::AutoCRUD.new nil, :model_namespace => 'Models', :endpoint_namespace => 'Endpoints'
This would assume you only want CRUD-based routing. You can also use this middleware:
use Rack::AutoCRUD, :model_namespace => 'Models', :endpoint_namespace => 'Endpoints'
Auto-Inclusion of Other Modules
This middleware also takes an option :includes which can be used to automatically include other modules (e.g. helpers) when creating and/or patching endpoints.
For example:
use Rack::AutoCRUD, :model_namespace => 'Models', :endpoint_namespace => 'Endpoints', :includes => [ Your::HelperModule ]
Setting Sinatra Options
You can set Sinatra options for your auto-generated endpoints by passing the :sinatra_opts
option to this middleware.
For example:
use Rack::AutoCRUD, :model_namespace => 'Models', :endpoint_namespace => 'Endpoints', :sinatra_opts => { :sessions => true }
Which can be extremely useful if you're handling sessions at a higher level than the auto-generated endpoints.
How Routing Works
The routing is simple. You have a model Models::Person. You've added something like the above to your config.ru. This middleware will dynamically create a Sinatra::Base subclass called Endpoints::Person (if it already exists, these routes are added to it) which will contain the following routes:
Route | Action | HTTP Response Code |
---|---|---|
get / | List all Person entries | 200 / 403 |
post / | Create a new Person | 201 / 402 |
get /:id | Retrieve a Person | 200 |
put /:id | Update a Person | 201 / 402 |
delete /:id | Destroy a Person | 204 |
get /count | Get a count of Person entries | 200 / 403 |
The middleware will route based on the URI. Thus, /person would correspond to Endpoints::Person's get / route. The /count route returns the total number of Person entries in the database, if COLLECTABLE is set.
Overriding Generated Routes
You can define your own CRUD routes, which will be called and return a response before the autogenerated routes, as long as they're added after your endpoint is defined.
For example:
require 'sinatra/base'
module Endpoints
class Person < Sinatra::Base
get '/'
Models::Person.all.to_json
end
end
end
In this case, if you're using dm-serializer,you'd get back every Models::Person record in the database in a JSON array. By default, the get / route returns "Access Denied."
CRUD Processing Hooks
There are some basic processing hooks you can define in your endpoint:
Hook | Description |
---|---|
pre_create(model,request,params) | Called before the record is created |
post_create(model,request,obj) | Called after the record is saved, if it was saved successfully |
pre_retrieve(model,request,params) | Called before the record is fetched |
post_retrieve(model,request,obj) | Called after the record is fetched |
pre_update(model,request,params) | Called before the record is updated |
post_update(model,request,params) | Called after the record is updated, if it was saved successfully |
pre_destroy(model,request,params) | Called before the record is destroyed |
post_destroy(model,request,obj) | Called after the record is destroyed |
pre_collect(model,request,params) | Called before the record is collected |
post_collect(model,request,collection) | Called after the record is collected |
Parameters:
- model is the model class for the endpoint
- request is the current request object
- obj is the ORM object corresponding to the record in question
- collection is the collection returned by the ORM
If any of these hooks returns anything other than nil, it is assumed to be a response object, which is returned immediately, and no further processing is performed.
Collections
All models in the namespace are not collectable by default. To enable collections, you need to set the COLLECTABLE constant in the model:
module Models
class Person
include DataMapper::Resource
property :id, Serial
property :name, String
# Enable collection
COLLECTABLE = 1
end
end
You can set this constant to 0 to disable it. If you want to enable collections on all models in your namespace, simply specify the constant there:
If you want to not expose all models by default, simply define the constant as part of the Models module:
module Models
# Enable collections on all models by default
COLLECTABLE = 1
end
Selective Exposure
All models in the namespace passed to Rack::AutoCRUD are exposed by default.
You can selectively hide models by defining a constant called EXPOSE in your model definition. A value of 0 will cause Rack::AutoCRUD to not create and endpoint for that particular model.
Example:
module Models
class Person
include DataMapper::Resource
property :id, Serial
property :name, String
# Don't expose this model via AutoCRUD
EXPOSE = 0
end
end
If you want to not expose all models by default, simply define the constant as part of the Models module:
module Models
# Hide all models by default
EXPOSE = 0
end
module Models
class ExposeMe
include DataMapper::Resource
property :id, Serial
# Expose this model
EXPOSE = 1
end
end
Helper Functions
This middleware also adds a helper function to the endpoints, set_request_body, to allow you to replace the request body from the aforementioned hooks, namely pre_create and pre_update.
This function is defined as:
def set_request_body(new_body,content_type='text/json')
where new_body is expected to be a string.