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 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