rack-scaffold 0.0.3 → 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.
- checksums.yaml +4 -4
- data/Gemfile.lock +15 -3
- data/README.md +13 -2
- data/lib/rack/scaffold.rb +60 -13
- data/lib/rack/scaffold/adapters.rb +8 -0
- data/lib/rack/scaffold/adapters/active_record.rb +4 -0
- data/lib/rack/scaffold/adapters/core_data.rb +23 -13
- data/lib/rack/scaffold/adapters/sequel.rb +4 -2
- data/rack-scaffold.gemspec +3 -2
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ef91f24e25308a80570de00566258b1f6dd8ba3
|
4
|
+
data.tar.gz: 044364fe5fca8ce8bfe21d4ca62f34201b5f2d3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 57c614e8c131c3f8b0aef53b3a2f5b6d4d9ea991ae251e0c4d11623f68a85398920fa5a6e5c139c08dec42bf85dc328a85887305dceb61a9208da601c737e961
|
7
|
+
data.tar.gz: d2c028e14bd9dd1dc82cc1cdd5be3c216cbbcebef372c9f5c6c1fde50ba2b299bfb08e6ae97fe0db033b6a28722a9e5c311d8a4cb275f9012aafbf8cca6b54ee
|
data/Gemfile.lock
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rack-scaffold (0.0
|
4
|
+
rack-scaffold (0.1.0)
|
5
5
|
activesupport (>= 3.0)
|
6
6
|
rack (~> 1.4)
|
7
7
|
rack-contrib (~> 1.1)
|
8
|
-
sinatra (~> 1.
|
8
|
+
sinatra (~> 1.4)
|
9
|
+
sinatra-contrib (~> 1.4)
|
9
10
|
sinatra-param (~> 0.1)
|
10
11
|
|
11
12
|
GEM
|
@@ -18,6 +19,8 @@ GEM
|
|
18
19
|
thread_safe (~> 0.1)
|
19
20
|
tzinfo (~> 0.3.37)
|
20
21
|
atomic (1.1.10)
|
22
|
+
backports (3.3.3)
|
23
|
+
eventmachine (1.0.3)
|
21
24
|
i18n (0.6.4)
|
22
25
|
minitest (4.7.5)
|
23
26
|
multi_json (1.7.7)
|
@@ -26,14 +29,23 @@ GEM
|
|
26
29
|
rack (>= 0.9.1)
|
27
30
|
rack-protection (1.5.0)
|
28
31
|
rack
|
32
|
+
rack-test (0.6.2)
|
33
|
+
rack (>= 1.0)
|
29
34
|
rake (10.0.4)
|
30
35
|
sinatra (1.4.3)
|
31
36
|
rack (~> 1.4)
|
32
37
|
rack-protection (~> 1.4)
|
33
38
|
tilt (~> 1.3, >= 1.3.4)
|
39
|
+
sinatra-contrib (1.4.0)
|
40
|
+
backports (>= 2.0)
|
41
|
+
eventmachine
|
42
|
+
rack-protection
|
43
|
+
rack-test
|
44
|
+
sinatra (~> 1.4.2)
|
45
|
+
tilt (~> 1.3)
|
34
46
|
sinatra-param (0.1.3)
|
35
47
|
sinatra (~> 1.3)
|
36
|
-
thread_safe (0.1.
|
48
|
+
thread_safe (0.1.2)
|
37
49
|
atomic
|
38
50
|
tilt (1.4.1)
|
39
51
|
tzinfo (0.3.37)
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Rack::Scaffold
|
2
2
|
**Automatically generate RESTful CRUD services**
|
3
3
|
|
4
|
-
> This project generalizes the webservice auto-generation functionality of [Rack::CoreData](https://github.com/mattt/rack-core-data) with a plugin architecture that can adapt to any data model format. It is currently
|
4
|
+
> This project generalizes the webservice auto-generation functionality of [Rack::CoreData](https://github.com/mattt/rack-core-data) with a plugin architecture that can adapt to any data model format. It is currently used in the latest release of [Helios](https://github.com/helios-framework/helios)
|
5
5
|
|
6
6
|
### Supported Data Models
|
7
7
|
|
@@ -13,10 +13,11 @@
|
|
13
13
|
|
14
14
|
### Gemfile
|
15
15
|
|
16
|
-
```
|
16
|
+
```ruby
|
17
17
|
source :rubygems
|
18
18
|
|
19
19
|
gem 'rack-scaffold', require: 'rack/scaffold'
|
20
|
+
|
20
21
|
gem 'sequel'
|
21
22
|
gem 'core_data'
|
22
23
|
|
@@ -36,6 +37,16 @@ DB = Sequel.connect(ENV['DATABASE_URL'])
|
|
36
37
|
run Rack::Scaffold model: './Example.xcdatamodeld', only: [:create, :read]
|
37
38
|
```
|
38
39
|
|
40
|
+
## Available Actions
|
41
|
+
|
42
|
+
By default, `Rack::Scaffold` will enable all of the actions described below. Actions can be whitelisted or blacklisted by passing either the `only` or `except` options, respectively.
|
43
|
+
|
44
|
+
- `create` (`POST /resources`): Creates a new resource with the fields in a `www-form-urlencoded` or `application/json` encoded HTTP request body.
|
45
|
+
- `read` (`GET /resources` & `GET /resources/123`): Reads a collection of resources or an individual resource at the specified URI. Supports pagination by passing either `page` & `per_page` or `limit` & `offset` parameters.
|
46
|
+
- `update` (`PUT` OR `PATCH /resources/123`): Updates the specified resource with the fields in a `www-form-urlencoded` or `application/json` encoded HTTP request body.
|
47
|
+
- `delete` (`DELETE /resources/123`): Deletes the specified resource.
|
48
|
+
- `susbscribe` (`SUBSCRIBE` or `GET /resources` with `Accept: text/event-stream`): Subscribes to create, update, and delete actions performed, streaming corresponding JSON Patch diffs. You can read more about the Rocket technique for streaming REST resources at http://rocket.github.io.
|
49
|
+
|
39
50
|
## Examples
|
40
51
|
|
41
52
|
An example web API using a Core Data model can be found the `/example` directory.
|
data/lib/rack/scaffold.rb
CHANGED
@@ -2,12 +2,15 @@ require 'rack'
|
|
2
2
|
require 'rack/contrib'
|
3
3
|
require 'sinatra/base'
|
4
4
|
require 'sinatra/param'
|
5
|
+
require 'sinatra/multi_route'
|
5
6
|
|
6
7
|
require 'rack/scaffold/adapters'
|
7
8
|
|
9
|
+
require 'pathname'
|
10
|
+
|
8
11
|
module Rack
|
9
12
|
class Scaffold
|
10
|
-
ACTIONS = [:create, :read, :update, :destroy]
|
13
|
+
ACTIONS = [:subscribe, :create, :read, :update, :destroy]
|
11
14
|
|
12
15
|
def initialize(options = {})
|
13
16
|
raise ArgumentError, "Missing option: :model or :models" unless options[:model] or options[:models]
|
@@ -18,6 +21,7 @@ module Rack
|
|
18
21
|
|
19
22
|
@app = Class.new(Sinatra::Base) do
|
20
23
|
use Rack::PostBodyContentTypeParser
|
24
|
+
register Sinatra::MultiRoute
|
21
25
|
helpers Sinatra::Param
|
22
26
|
|
23
27
|
before do
|
@@ -34,6 +38,28 @@ module Rack
|
|
34
38
|
timestamp = most_recently_updated.send(update_timestamp_field) if most_recently_updated
|
35
39
|
timestamp
|
36
40
|
end
|
41
|
+
|
42
|
+
def notify!(record)
|
43
|
+
return unless @@connections
|
44
|
+
|
45
|
+
pathname = Pathname.new(request.path)
|
46
|
+
|
47
|
+
lines = []
|
48
|
+
lines << "event: patch"
|
49
|
+
|
50
|
+
op = case status
|
51
|
+
when 201 then :add
|
52
|
+
when 204 then :remove
|
53
|
+
else
|
54
|
+
:update
|
55
|
+
end
|
56
|
+
|
57
|
+
data = [{op: op, path: record.url, value: record}].to_json
|
58
|
+
|
59
|
+
@@connections[pathname.dirname].each do |out|
|
60
|
+
out << "event: patch\ndata: #{data}\n\n"
|
61
|
+
end
|
62
|
+
end
|
37
63
|
end
|
38
64
|
|
39
65
|
@actions = (options[:only] || ACTIONS) - (options[:except] || [])
|
@@ -43,10 +69,29 @@ module Rack
|
|
43
69
|
|
44
70
|
resources = Array(@adapter.resources(options[:model], options))
|
45
71
|
resources.each do |resource|
|
72
|
+
@app.instance_eval do
|
73
|
+
@@connections = Hash.new([])
|
74
|
+
|
75
|
+
route :get, :subscribe, "/#{resource.plural}/?" do
|
76
|
+
pass unless request.accept? 'text/event-stream'
|
77
|
+
|
78
|
+
content_type 'text/event-stream'
|
79
|
+
|
80
|
+
stream :keep_open do |out|
|
81
|
+
@@connections[request.path] << out
|
82
|
+
|
83
|
+
out.callback do
|
84
|
+
@@connections[request.path].delete(out)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end if @actions.include?(:subscribe)
|
89
|
+
|
46
90
|
@app.instance_eval do
|
47
91
|
post "/#{resource.plural}/?" do
|
48
92
|
if record = resource.create!(params)
|
49
93
|
status 201
|
94
|
+
notify!(record)
|
50
95
|
{"#{resource.singular}" => record}.to_json
|
51
96
|
else
|
52
97
|
status 406
|
@@ -88,13 +133,24 @@ module Rack
|
|
88
133
|
{"#{resource.singular}" => record}.to_json
|
89
134
|
end
|
90
135
|
|
136
|
+
resource.one_to_many_associations.each do |association|
|
137
|
+
get "/#{resource.plural}/:id/#{association}/?" do
|
138
|
+
record = resource[params[:id]] or halt 404
|
139
|
+
associations = record.send(association)
|
140
|
+
|
141
|
+
{
|
142
|
+
"#{association}" => associations
|
143
|
+
}.to_json
|
144
|
+
end
|
145
|
+
end
|
91
146
|
end if @actions.include?(:read)
|
92
147
|
|
93
148
|
@app.instance_eval do
|
94
|
-
put "/#{resource.plural}/:id/?" do
|
149
|
+
route :put, :patch, "/#{resource.plural}/:id/?" do
|
95
150
|
record = resource[params[:id]] or halt 404
|
96
151
|
if record.update!(params)
|
97
152
|
status 200
|
153
|
+
notify!(record)
|
98
154
|
{"#{resource.singular}" => record}.to_json
|
99
155
|
else
|
100
156
|
status 406
|
@@ -107,23 +163,14 @@ module Rack
|
|
107
163
|
delete "/#{resource.plural}/:id/?" do
|
108
164
|
record = resource[params[:id]] or halt 404
|
109
165
|
if record.destroy
|
110
|
-
status
|
166
|
+
status 204
|
167
|
+
notify!(record)
|
111
168
|
else
|
112
169
|
status 406
|
113
170
|
{errors: record.errors}.to_json
|
114
171
|
end
|
115
172
|
end
|
116
173
|
end if @actions.include?(:destroy)
|
117
|
-
|
118
|
-
# @app.instance_eval do
|
119
|
-
# entity.relationships.each do |relationship|
|
120
|
-
# next unless relationship.to_many?
|
121
|
-
|
122
|
-
# get "/#{resource.plural}/:id/#{relationship.name}/?" do
|
123
|
-
# {relationship.name => resource[params[:id]].send(relationship.name)}.to_json
|
124
|
-
# end
|
125
|
-
# end
|
126
|
-
# end
|
127
174
|
end
|
128
175
|
end
|
129
176
|
|
@@ -53,6 +53,10 @@ module Rack
|
|
53
53
|
raise NotImplementedError
|
54
54
|
end
|
55
55
|
|
56
|
+
def one_to_many_associations
|
57
|
+
raise NotImplementedError
|
58
|
+
end
|
59
|
+
|
56
60
|
def find(options = {})
|
57
61
|
raise NotImplementedError
|
58
62
|
end
|
@@ -76,6 +80,10 @@ module Rack
|
|
76
80
|
def update_timestamp_field
|
77
81
|
raise NotImplementedError
|
78
82
|
end
|
83
|
+
|
84
|
+
def method_missing(method, *args, &block)
|
85
|
+
@klass.send(method)
|
86
|
+
end
|
79
87
|
end
|
80
88
|
end
|
81
89
|
end
|
@@ -37,6 +37,10 @@ module Rack::Scaffold::Adapters
|
|
37
37
|
self.find(id)
|
38
38
|
end
|
39
39
|
|
40
|
+
def one_to_many_associations
|
41
|
+
@klass.reflect_on_all_associations(:has_many).collect(&:name)
|
42
|
+
end
|
43
|
+
|
40
44
|
def update_timestamp_field
|
41
45
|
self.attribute_names.include?("updated_at") ? "updated_at" : "updated_on"
|
42
46
|
end
|
@@ -12,7 +12,14 @@ module Rack::Scaffold::Adapters
|
|
12
12
|
|
13
13
|
def resources(xcdatamodel, options = {})
|
14
14
|
model = ::CoreData::DataModel.new(xcdatamodel)
|
15
|
-
model.entities.collect{|entity| new(entity, options)}
|
15
|
+
resources = model.entities.collect{|entity| resource = new(entity, options)}
|
16
|
+
model.entities.each do |entity|
|
17
|
+
resources.each do |resource|
|
18
|
+
resource.establish_associations!(entity)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
return resources
|
16
23
|
end
|
17
24
|
end
|
18
25
|
|
@@ -37,23 +44,12 @@ module Rack::Scaffold::Adapters
|
|
37
44
|
else
|
38
45
|
plugin :timestamps
|
39
46
|
end
|
40
|
-
# plugin :timestamps, create: :createdAt, update: :updatedAt
|
41
47
|
end
|
42
48
|
|
43
49
|
def url
|
44
50
|
"/#{self.class.table_name}/#{self[primary_key]}"
|
45
51
|
end
|
46
52
|
|
47
|
-
# entity.relationships.each do |relationship|
|
48
|
-
# options = {:class => Rack::Scaffold::Models.const_get(relationship.destination.capitalize)}
|
49
|
-
|
50
|
-
# if relationship.to_many?
|
51
|
-
# one_to_many relationship.name.to_sym, options
|
52
|
-
# else
|
53
|
-
# many_to_one relationship.name.to_sym, options
|
54
|
-
# end
|
55
|
-
# end
|
56
|
-
|
57
53
|
set_schema do
|
58
54
|
primary_key :id
|
59
55
|
|
@@ -120,8 +116,22 @@ module Rack::Scaffold::Adapters
|
|
120
116
|
end
|
121
117
|
end
|
122
118
|
|
123
|
-
|
124
119
|
super(CoreData.const_set(entity.name, klass))
|
125
120
|
end
|
121
|
+
|
122
|
+
def establish_associations!(entity)
|
123
|
+
klass.class_eval do
|
124
|
+
entity.relationships.each do |relationship|
|
125
|
+
options = {:class => CoreData.const_get(relationship.destination.capitalize)}
|
126
|
+
|
127
|
+
options = {}
|
128
|
+
if relationship.to_many?
|
129
|
+
one_to_many relationship.name.to_sym, options
|
130
|
+
else
|
131
|
+
many_to_one relationship.name.to_sym, options
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
126
136
|
end
|
127
137
|
end
|
@@ -18,7 +18,6 @@ module Rack::Scaffold::Adapters
|
|
18
18
|
def resources(model, options = {})
|
19
19
|
model
|
20
20
|
end
|
21
|
-
|
22
21
|
end
|
23
22
|
|
24
23
|
def singular
|
@@ -33,9 +32,12 @@ module Rack::Scaffold::Adapters
|
|
33
32
|
@klass.limit(limit, offset)
|
34
33
|
end
|
35
34
|
|
35
|
+
def one_to_many_associations
|
36
|
+
@klass.all_association_reflections.select{|association| association[:type] == :one_to_many}.collect{|association| association[:name]}
|
37
|
+
end
|
38
|
+
|
36
39
|
def timestamps?
|
37
40
|
defined?(::Sequel::Plugins::Timestamps) and @klass.plugins.include?(::Sequel::Plugins::Timestamps)
|
38
41
|
end
|
39
|
-
|
40
42
|
end
|
41
43
|
end
|
data/rack-scaffold.gemspec
CHANGED
@@ -7,14 +7,15 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.email = "m@mattt.me"
|
8
8
|
s.homepage = "http://mattt.me"
|
9
9
|
s.license = "MIT"
|
10
|
-
s.version = "0.0
|
10
|
+
s.version = "0.1.0"
|
11
11
|
s.platform = Gem::Platform::RUBY
|
12
12
|
s.summary = "Rack::Scaffold"
|
13
13
|
s.description = "Automatically generate RESTful CRUD services"
|
14
14
|
|
15
15
|
s.add_dependency "rack", "~> 1.4"
|
16
16
|
s.add_dependency "rack-contrib", "~> 1.1"
|
17
|
-
s.add_dependency "sinatra", "~> 1.
|
17
|
+
s.add_dependency "sinatra", "~> 1.4"
|
18
|
+
s.add_dependency "sinatra-contrib", "~> 1.4"
|
18
19
|
s.add_dependency "sinatra-param", "~> 0.1"
|
19
20
|
s.add_dependency "activesupport", ">= 3.0"
|
20
21
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-scaffold
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mattt Thompson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -44,14 +44,28 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - ~>
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1.
|
47
|
+
version: '1.4'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1.
|
54
|
+
version: '1.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sinatra-contrib
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.4'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.4'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: sinatra-param
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|