rack-scaffold 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 360768fd0ef66689a6eb6c3fda2fe1d66088c5ec
4
- data.tar.gz: 61ec5dbd6a8b956694c6d0752ec4944961ca0598
3
+ metadata.gz: 7ef91f24e25308a80570de00566258b1f6dd8ba3
4
+ data.tar.gz: 044364fe5fca8ce8bfe21d4ca62f34201b5f2d3b
5
5
  SHA512:
6
- metadata.gz: 342900ad0c9c8e43f0911b0ae9d556fe357047136e2d0c0a7618e4004909efcc5d291823d65cae6469f72c399a96f3bbbbbb815497f43524735a33141404b344
7
- data.tar.gz: 87f6a502de098edfc7abc9cff2b28de7153b77629783bf296fa2bbdf8474a00337ec7250de0fb184addd2619a66b320f21da2eca83e94e309c83d5d665eb4401
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.3)
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.3)
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.0)
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 being actively developed for inclusion in the next release of [Helios](https://github.com/helios-framework/helios)
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
- ```Ruby
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 200
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
@@ -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.3"
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.3"
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.3
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-07-12 00:00:00.000000000 Z
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.3'
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.3'
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