rack-autocrud 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +22 -0
- data/README.md +119 -0
- data/lib/rack/autocrud.rb +152 -0
- metadata +99 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012, Tim Hentenaar
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
14
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
15
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
17
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
18
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
19
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
20
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
22
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
rack-autocrud
|
2
|
+
=============
|
3
|
+
|
4
|
+
Rack middleware that works with Sinatra and DataMapper to dynamically
|
5
|
+
create CRUD endpoints and routes based on models. It ain't perfect, but
|
6
|
+
it works.
|
7
|
+
|
8
|
+
These generated CRUD routes are assumed to return a Rack response.
|
9
|
+
|
10
|
+
It's important to note, that you models and endpoints must be in separate
|
11
|
+
modules (read: namespaces).
|
12
|
+
|
13
|
+
Input and Response data are formatted as JSON.
|
14
|
+
|
15
|
+
Licensing
|
16
|
+
=========
|
17
|
+
|
18
|
+
This software is licensed under the [Simplified BSD License](http://en.wikipedia.org/wiki/BSD_licenses#2-clause_license_.28.22Simplified_BSD_License.22_or_.22FreeBSD_License.22.29) as described in the LICENSE file.
|
19
|
+
|
20
|
+
Requirements
|
21
|
+
============
|
22
|
+
|
23
|
+
* sinatra
|
24
|
+
* datamapper
|
25
|
+
|
26
|
+
Installation
|
27
|
+
============
|
28
|
+
|
29
|
+
gem install rack-autocrud
|
30
|
+
|
31
|
+
Usage
|
32
|
+
=====
|
33
|
+
|
34
|
+
Just add something like this to your _config.ru_:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'rack/autocrud'
|
38
|
+
|
39
|
+
# Load your models
|
40
|
+
require 'models'
|
41
|
+
|
42
|
+
# Load your endpoints
|
43
|
+
require 'endpoints'
|
44
|
+
|
45
|
+
# Auto-magical CRUD
|
46
|
+
run Rack::AutoCRUD.new nil, :model_namespace => 'Models', :endpoint_namespace => 'Endpoints'
|
47
|
+
```
|
48
|
+
|
49
|
+
This would assume you only want CRUD-based routing. You can also _use_ this middleware:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
use Rack::AutoCRUD, :model_namespace => 'Models', :endpoint_namespace => 'Endpoints'
|
53
|
+
```
|
54
|
+
|
55
|
+
How Routing Works
|
56
|
+
=================
|
57
|
+
|
58
|
+
The routing is simple. You have a model *Models::Person*. You've added something like the above to your
|
59
|
+
_config.ru_. This middleware will dynamically create a _Sinatra::Base_ subclass called *Endpoints::Person*
|
60
|
+
(if it already exists, these routes are added to it) which will contain the following routes:
|
61
|
+
|
62
|
+
| Route | Action | HTTP Response Code |
|
63
|
+
| ----------- | -------------------------------| ------------------ |
|
64
|
+
| get / | List all _Person_ entries | 403 |
|
65
|
+
| post / | Create a new _Person_ | 201 / 402 |
|
66
|
+
| get /:id | Retrieve a _Person_ | 200 |
|
67
|
+
| put /:id | Update a _Person_ | 201 / 403 |
|
68
|
+
| delete /:id | Destroy a _Person_ | 204 |
|
69
|
+
|
70
|
+
The middleware will route based on the URI. Thus, _/person_ would correspond to *Endpoints::Person*'s _get /_ route.
|
71
|
+
|
72
|
+
Overriding Generated Routes
|
73
|
+
===========================
|
74
|
+
|
75
|
+
You can define your own CRUD routes, which will be called and return a response
|
76
|
+
before the autogenerated routes, as long as they're added after your endpoint is defined.
|
77
|
+
|
78
|
+
For example:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
require 'sinatra/base'
|
82
|
+
|
83
|
+
module Endpoints
|
84
|
+
class Person < Sinatra::Base
|
85
|
+
get '/'
|
86
|
+
Models::Person.all.to_json
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
In this case, if you're using _dm-serializer_,you'd get back every _Models::Person_ record in the database in
|
92
|
+
a JSON array. By default, the _get /_ route returns "Access Denied."
|
93
|
+
|
94
|
+
CRUD Processing Hooks
|
95
|
+
=====================
|
96
|
+
|
97
|
+
There are some basic processing hooks you can define in your endpoint:
|
98
|
+
|
99
|
+
| Hook | Description |
|
100
|
+
| ------------------------------ | ---------------------------------------------------------------- |
|
101
|
+
| pre_create(env,request,obj) | Called after the record is created, but before it's saved |
|
102
|
+
| post_create(env,request,obj) | Called after the record is saved, if it was saved successfully |
|
103
|
+
| pre_retrieve(env,request) | Called before the record is fetched |
|
104
|
+
| post_retrieve(env,request,obj) | Called after the record is fetched |
|
105
|
+
| pre_update(env,request) | Called before the record is updated |
|
106
|
+
| post_update(env,request) | Called after the record is updated, if it was saved successfully |
|
107
|
+
| pre_destroy(env,request) | Called before the record is destroyed |
|
108
|
+
| post_destroy(env,request,obj) | Called after the record is destroyed |
|
109
|
+
|
110
|
+
Parameters:
|
111
|
+
|
112
|
+
* *env* is the current Rack environment
|
113
|
+
* *request* is the current request object
|
114
|
+
* *obj* is the ORM object corresponding to the record in question
|
115
|
+
|
116
|
+
If any of these hooks returns anything other than _nil_, it is assumed to be a response object, which
|
117
|
+
is returned immediately, and no further processing is performed.
|
118
|
+
|
119
|
+
|
@@ -0,0 +1,152 @@
|
|
1
|
+
#
|
2
|
+
# Rack::AutoCRUD - AutoCRUD Middleware for Rack
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012 Tim Hentenaar. All Rights Reserved.
|
5
|
+
#
|
6
|
+
# Licensed under the Simplified BSD License.
|
7
|
+
# See the LICENSE file for details.
|
8
|
+
#
|
9
|
+
# This Rack middleware automatically generates Sinatra
|
10
|
+
# endpoints (descended from Sinatra::Base) to handle
|
11
|
+
# basic CRUD operations on defined models.
|
12
|
+
#
|
13
|
+
|
14
|
+
require 'sinatra/base'
|
15
|
+
require 'json'
|
16
|
+
|
17
|
+
module Rack
|
18
|
+
class AutoCRUD
|
19
|
+
def initialize(app,options={})
|
20
|
+
@app = app
|
21
|
+
@model_namespace = options[:model_namespace]
|
22
|
+
@endpoint_namespace = options[:endpoint_namespace]
|
23
|
+
@endpoint_mod = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
dup._call(env) # For thread safety...
|
28
|
+
end
|
29
|
+
|
30
|
+
def _call(env)
|
31
|
+
model_klass = nil
|
32
|
+
endpoint_klass = nil
|
33
|
+
verb,endpoint,*uri = env['REQUEST_URI'].split('/')
|
34
|
+
verb = env['REQUEST_METHOD'].downcase
|
35
|
+
|
36
|
+
# Enumerate through all defined classes, checking for the model / endpoint
|
37
|
+
ObjectSpace.each_object(Class) { |klass|
|
38
|
+
model_klass = klass if String(klass.name).downcase == String(@model_namespace + '::' + endpoint).downcase
|
39
|
+
endpoint_klass = klass if String(klass.name).downcase == String(@endpoint_namespace + '::' + endpoint).downcase
|
40
|
+
}
|
41
|
+
|
42
|
+
# Lazily locate the endpoint namespace module (if we haven't already)
|
43
|
+
if endpoint_klass.nil? && @endpoint_mod.nil?
|
44
|
+
ObjectSpace.each_object(Module) { |klass|
|
45
|
+
@endpoint_mod = klass if String(klass.name).downcase == @endpoint_namespace.downcase
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Now, if we've got something, do our magic.
|
50
|
+
if !model_klass.nil?
|
51
|
+
# If we don't have an endpoint class, make one
|
52
|
+
if endpoint_klass.nil?
|
53
|
+
endpoint_klass = Class.new(Sinatra::Base)
|
54
|
+
@endpoint_mod.const_set(endpoint.capitalize,endpoint_klass)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Patch in the routes
|
58
|
+
endpoint_klass.class_exec(model_klass,endpoint,env) { |model,endpoint,env|
|
59
|
+
get '/' do
|
60
|
+
halt [403, '{ "error": "Access Denied" }']
|
61
|
+
end
|
62
|
+
|
63
|
+
post '/' do
|
64
|
+
obj = model.new(JSON.parse(request.body.read))
|
65
|
+
|
66
|
+
# Call the pre-create hook
|
67
|
+
if self.respond_to?(:pre_create)
|
68
|
+
ret = pre_create(env,request,obj)
|
69
|
+
return ret unless ret.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
obj.save
|
73
|
+
halt [402, '{ "error": "Failed to save ' + endpoint + '" }'] unless obj.saved?
|
74
|
+
|
75
|
+
# Call the post-create hook
|
76
|
+
if self.respond_to?(:post_create)
|
77
|
+
ret = post_create(env,request,obj)
|
78
|
+
return ret unless ret.nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
[ 201, { 'id' => obj.id.to_i }.to_json ]
|
82
|
+
end
|
83
|
+
|
84
|
+
get '/:id' do
|
85
|
+
# Call the pre-retrieve hook
|
86
|
+
if self.respond_to?(:pre_retrieve)
|
87
|
+
ret = pre_retrieve(env,request)
|
88
|
+
return ret unless ret.nil?
|
89
|
+
end
|
90
|
+
|
91
|
+
obj = model.get(params[:id])
|
92
|
+
|
93
|
+
# Call the post-retrieve hook
|
94
|
+
if self.respond_to?(:post_retrieve)
|
95
|
+
ret = post_retrieve(env,request,obj)
|
96
|
+
return ret unless ret.nil?
|
97
|
+
end
|
98
|
+
|
99
|
+
obj.to_json
|
100
|
+
end
|
101
|
+
|
102
|
+
put '/:id' do
|
103
|
+
# Call the pre-update hook
|
104
|
+
if self.respond_to?(:pre_update)
|
105
|
+
ret = pre_update(env,request)
|
106
|
+
return ret unless ret.nil?
|
107
|
+
end
|
108
|
+
|
109
|
+
saved = model.update(JSON.parse(request.body.read))
|
110
|
+
halt [403, 'Access Denied'] unless saved
|
111
|
+
|
112
|
+
# Call the post-update hook
|
113
|
+
if self.respond_to?(:post_update)
|
114
|
+
ret = post_update(env,request)
|
115
|
+
return ret unless ret.nil?
|
116
|
+
end
|
117
|
+
|
118
|
+
[ 201, '{ "status": "ok" }' ]
|
119
|
+
end
|
120
|
+
|
121
|
+
delete '/:id' do
|
122
|
+
# Call the pre-destroy hook
|
123
|
+
if self.respond_to?(:pre_destroy)
|
124
|
+
ret = pre_destroy(env,request)
|
125
|
+
return ret unless ret.nil?
|
126
|
+
end
|
127
|
+
|
128
|
+
obj = model.get(params[:id])
|
129
|
+
obj.destroy if obj
|
130
|
+
|
131
|
+
# Call the post-destroy hook
|
132
|
+
if self.respond_to?(:post_destroy)
|
133
|
+
ret = post_destroy(env,request,obj)
|
134
|
+
return ret unless ret.nil?
|
135
|
+
end
|
136
|
+
|
137
|
+
[ 204 ]
|
138
|
+
end
|
139
|
+
}
|
140
|
+
|
141
|
+
# Now, call the endpoint class (assuming it will return a response)
|
142
|
+
env['PATH_INFO'] = '/' + uri.join('/')
|
143
|
+
return endpoint_klass.call(env)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Otherwise, pass the request down the chain...
|
147
|
+
@app.call(env)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# vi:set ts=2:
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-autocrud
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tim Hentenaar
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: sinatra
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: data_mapper
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: ! "\tRack middleware that works with Sinatra and DataMapper to dynamically\n\tcreate
|
63
|
+
CRUD endpoints and routes based on models. It ain't perfect, but\n\tit works.\n\n\tThese
|
64
|
+
generated CRUD routes are assumed to return a Rack response.\n\n\tIt's important
|
65
|
+
to note, that you models and endpoints must be in separate\n\tmodules (read: namespaces).\n\n\tInput
|
66
|
+
and Response data are formatted as JSON.\n\n\tSee the README for more info.\n"
|
67
|
+
email: tim.hentenaar@gmail.com
|
68
|
+
executables: []
|
69
|
+
extensions: []
|
70
|
+
extra_rdoc_files: []
|
71
|
+
files:
|
72
|
+
- lib/rack/autocrud.rb
|
73
|
+
- README.md
|
74
|
+
- LICENSE
|
75
|
+
homepage: https://github.com/thentenaar/rack-autocrud
|
76
|
+
licenses: []
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 1.8.24
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: Rack middleware that automagically handles basic CRUD operations
|
99
|
+
test_files: []
|