rack-autocrud 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +119 -0
  3. data/lib/rack/autocrud.rb +152 -0
  4. 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: []