toast 0.3.5 → 0.4.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/README.md +9 -12
- data/lib/toast/association.rb +51 -12
- data/lib/toast/config_dsl.rb +57 -47
- data/lib/toast/record.rb +9 -2
- data/lib/toast/resource.rb +1 -3
- data/lib/toast/root_collection.rb +5 -4
- metadata +4 -5
- data/lib/toast/attribute.rb +0 -42
data/README.md
CHANGED
@@ -11,7 +11,7 @@ and what attributes and associations are to be exposed. That's it. No
|
|
11
11
|
controller boiler plate code for every model, no routing setup.
|
12
12
|
|
13
13
|
Toast is a Rails engine that runs one generic controller and a sets up
|
14
|
-
the routing
|
14
|
+
the routing according to the definition in the models, which is
|
15
15
|
denoted using a block oriented DSL.
|
16
16
|
|
17
17
|
REST is more than some pretty URIs, the use of the HTTP verbs and
|
@@ -23,7 +23,7 @@ which ever suits the task best. That's why TOAST stands for:
|
|
23
23
|
> **TOast Ain't reST**
|
24
24
|
|
25
25
|
*Be careful*: This version is experimental and probably not bullet
|
26
|
-
proof. As soon as the gem is
|
26
|
+
proof. As soon as the gem is loaded a controller with ready routing
|
27
27
|
is enabled serving the annotated model's data records for reading,
|
28
28
|
updating and deleting. There are no measures to prevent XSS and CSFR
|
29
29
|
attacks.
|
@@ -49,8 +49,9 @@ and let a corresponding model class have a *resourceful_model* annotation:
|
|
49
49
|
|
50
50
|
resourceful_model do
|
51
51
|
# attributes or association names
|
52
|
-
|
53
|
-
|
52
|
+
readables :coconuts, :apple
|
53
|
+
writables :name, :number
|
54
|
+
|
54
55
|
# class methods of Banana returning an Array of Banana records
|
55
56
|
collections :find_some, :all
|
56
57
|
end
|
@@ -121,17 +122,13 @@ More details and configuration options are documented in the manual... (_comming
|
|
121
122
|
Installation
|
122
123
|
============
|
123
124
|
|
124
|
-
|
125
|
-
gem install jeweler
|
126
|
-
cd toast
|
127
|
-
rake install
|
125
|
+
With bundler
|
128
126
|
|
129
|
-
|
127
|
+
gem "toast"
|
130
128
|
|
131
|
-
|
129
|
+
the latest Git:
|
132
130
|
|
133
|
-
|
134
|
-
Please use the git (edge) version.
|
131
|
+
gem "toast", :git => "https://github.com/robokopp/toast.git"
|
135
132
|
|
136
133
|
Test Suite
|
137
134
|
==========
|
data/lib/toast/association.rb
CHANGED
@@ -10,8 +10,9 @@ module Toast
|
|
10
10
|
|
11
11
|
@model = model
|
12
12
|
@record = model.find(id) rescue raise(ResourceNotFound)
|
13
|
-
@
|
13
|
+
@assoc = subresource_name
|
14
14
|
@format = format
|
15
|
+
@is_collection = [:has_many, :has_and_belongs_to_many].include? @model.reflect_on_association(@assoc.to_sym).macro
|
15
16
|
|
16
17
|
@associate_model = Resource.get_class_by_resource_name subresource_name
|
17
18
|
@associate_model.uri_base = @model.uri_base
|
@@ -19,7 +20,7 @@ module Toast
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def get
|
22
|
-
result = @record.send(@
|
23
|
+
result = @record.send(@assoc)
|
23
24
|
|
24
25
|
if result.is_a? Array
|
25
26
|
{
|
@@ -35,8 +36,38 @@ module Toast
|
|
35
36
|
|
36
37
|
end
|
37
38
|
|
38
|
-
def put
|
39
|
-
|
39
|
+
def put payload
|
40
|
+
# only for has_one/belongs_to assocs
|
41
|
+
raise MethodNotAllowed if @is_collection
|
42
|
+
|
43
|
+
# update see record
|
44
|
+
if self.media_type != @associate_model.toast_config.media_type
|
45
|
+
raise UnsupportedMediaType
|
46
|
+
end
|
47
|
+
|
48
|
+
unless payload.is_a? Hash
|
49
|
+
raise PayloadFormatError
|
50
|
+
end
|
51
|
+
|
52
|
+
# silently ignore all exposed readable, but not writable fields
|
53
|
+
(@associate_model.toast_config.readables - @associate_model.toast_config.writables).each do |rof|
|
54
|
+
payload.delete(rof)
|
55
|
+
end
|
56
|
+
|
57
|
+
record = @record.send(@assoc)
|
58
|
+
|
59
|
+
# set the virtual attributes
|
60
|
+
(payload.keys.to_set - record.attribute_names.to_set).each do |vattr|
|
61
|
+
record.send("#{vattr}=", payload.delete(vattr))
|
62
|
+
end
|
63
|
+
|
64
|
+
# mass-update for the rest
|
65
|
+
record.update_attributes payload
|
66
|
+
{
|
67
|
+
:json => record.exposed_attributes,
|
68
|
+
:status => :ok,
|
69
|
+
:location => record.uri
|
70
|
+
}
|
40
71
|
end
|
41
72
|
|
42
73
|
def post payload
|
@@ -45,21 +76,29 @@ module Toast
|
|
45
76
|
raise UnsupportedMediaType
|
46
77
|
end
|
47
78
|
|
48
|
-
|
49
|
-
|
79
|
+
|
80
|
+
# silently ignore all exposed readable, but not writable fields
|
81
|
+
(@associate_model.toast_config.readables - @associate_model.toast_config.writables).each do |rof|
|
82
|
+
payload.delete(rof)
|
50
83
|
end
|
51
84
|
|
52
85
|
unless payload.is_a? Hash
|
53
86
|
raise PayloadFormatError
|
54
87
|
end
|
55
88
|
|
56
|
-
|
89
|
+
begin
|
90
|
+
record = @record.send(@assoc).create! payload
|
57
91
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
92
|
+
{
|
93
|
+
:json => record.exposed_attributes,
|
94
|
+
:location => record.uri,
|
95
|
+
:status => :created
|
96
|
+
}
|
97
|
+
|
98
|
+
rescue ActiveRecord::RecordInvalid => e
|
99
|
+
# model validation failed
|
100
|
+
raise PayloadInvalid.new(e.message)
|
101
|
+
end
|
63
102
|
end
|
64
103
|
|
65
104
|
def delete
|
data/lib/toast/config_dsl.rb
CHANGED
@@ -4,12 +4,11 @@ module Toast
|
|
4
4
|
class Base
|
5
5
|
include Blockenspiel::DSL
|
6
6
|
dsl_attr_accessor :media_type, :has_many, :namespace
|
7
|
-
# attr_reader :exposed_attributes, :exposed_associations
|
8
7
|
|
9
8
|
def initialize model
|
10
9
|
@model = model
|
11
|
-
@
|
12
|
-
@
|
10
|
+
@readables = ["uri"]
|
11
|
+
@writables = []
|
13
12
|
@collections = []
|
14
13
|
@singles = []
|
15
14
|
@media_type = "application/json"
|
@@ -18,50 +17,63 @@ module Toast
|
|
18
17
|
@in_collection = ConfigDSL::InCollection.new model, self
|
19
18
|
end
|
20
19
|
|
21
|
-
def fields= *fields
|
22
|
-
@fields.push *ConfigDSL.sanitize(fields,"fields")
|
23
|
-
end
|
24
|
-
|
25
|
-
def fields *arg
|
26
|
-
return(@fields) if arg.empty?
|
27
|
-
self.fields = *arg
|
28
|
-
end
|
29
|
-
|
30
20
|
def exposed_attributes
|
31
21
|
assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
|
32
|
-
@
|
22
|
+
(@writables + @readables).uniq.select{|f| !assocs.include?(f)}
|
33
23
|
end
|
34
24
|
|
35
25
|
def exposed_associations
|
36
26
|
assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
|
37
|
-
@
|
27
|
+
(@writables + @readables).uniq.select{|f| assocs.include?(f)}
|
28
|
+
end
|
29
|
+
|
30
|
+
def readables= arg
|
31
|
+
#if arg.first == :all
|
32
|
+
# @readables.push @model.attribute_names - ConfigDSL.sanitize(args.last[:except], "readables")
|
33
|
+
#else
|
34
|
+
@readables.push *ConfigDSL.sanitize(arg,"readables")
|
35
|
+
#end
|
38
36
|
end
|
39
37
|
|
40
|
-
|
41
|
-
|
38
|
+
# args: Array or :all, :except => Array
|
39
|
+
def readables *arg
|
40
|
+
return(@readables) if arg.empty?
|
41
|
+
self.readables = arg
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
44
|
+
def writables= arg
|
45
|
+
#if arg.first == :all
|
46
|
+
# @writables.push @model.attribute_names - ConfigDSL.sanitize(args.last[:except], "writables")
|
47
|
+
#else
|
48
|
+
# white list writables (protect the rest from mass-assignment)
|
49
|
+
@model.attr_accessible *arg
|
50
|
+
@writables.push *ConfigDSL.sanitize(arg,"writables")
|
51
|
+
#end
|
47
52
|
end
|
53
|
+
|
54
|
+
# args: Array or :all, :except => Array
|
55
|
+
def writables *arg
|
56
|
+
return(@writables) if arg.empty?
|
57
|
+
self.writables = arg
|
58
|
+
end
|
59
|
+
|
48
60
|
|
49
|
-
def disallow_methods=
|
61
|
+
def disallow_methods= arg
|
50
62
|
@disallow_methods.push *ConfigDSL.sanitize(arg,"disallow methods")
|
51
63
|
end
|
52
64
|
|
53
65
|
def disallow_methods *arg
|
54
66
|
return(@disallow_methods) if arg.empty?
|
55
|
-
self.disallow_methods =
|
67
|
+
self.disallow_methods = arg
|
56
68
|
end
|
57
69
|
|
58
|
-
def pass_params_to=
|
70
|
+
def pass_params_to= arg
|
59
71
|
@pass_params_to.push *ConfigDSL.sanitize(arg,"pass_params_to")
|
60
72
|
end
|
61
73
|
|
62
74
|
def pass_params_to *arg
|
63
75
|
return(@pass_params_to) if arg.empty?
|
64
|
-
self.pass_params_to =
|
76
|
+
self.pass_params_to = arg
|
65
77
|
end
|
66
78
|
|
67
79
|
def collections= collections=[]
|
@@ -70,7 +82,7 @@ module Toast
|
|
70
82
|
|
71
83
|
def collections *arg
|
72
84
|
return(@collections) if arg.empty?
|
73
|
-
self.collections =
|
85
|
+
self.collections = arg
|
74
86
|
end
|
75
87
|
|
76
88
|
def singles= singles=[]
|
@@ -79,7 +91,7 @@ module Toast
|
|
79
91
|
|
80
92
|
def singles *arg
|
81
93
|
return(@singles) if arg.empty?
|
82
|
-
self.singles =
|
94
|
+
self.singles = arg
|
83
95
|
end
|
84
96
|
|
85
97
|
def in_collection &block
|
@@ -99,53 +111,51 @@ module Toast
|
|
99
111
|
|
100
112
|
def initialize model, base_config
|
101
113
|
@model = model
|
102
|
-
@
|
114
|
+
@readables = base_config.readables # must assign a reference
|
115
|
+
@writables = base_config.writables # must assign a reference
|
103
116
|
@disallow_methods = []
|
104
|
-
# @exposed_attributes = base_config.exposed_attributes
|
105
|
-
# @exposed_associations = base_config.exposed_associations
|
106
117
|
@media_type = "application/json"
|
107
118
|
end
|
108
119
|
|
109
|
-
def
|
110
|
-
@
|
120
|
+
def readables= readables
|
121
|
+
@writables = [] # forget inherited writables
|
122
|
+
@readables = ConfigDSL.sanitize(readables,"readables") << "uri"
|
123
|
+
end
|
111
124
|
|
112
|
-
|
113
|
-
|
125
|
+
def readables *arg
|
126
|
+
return(@readables) if arg.empty?
|
127
|
+
self.readables = arg
|
128
|
+
end
|
114
129
|
|
115
|
-
|
116
|
-
|
117
|
-
# @exposed_attributes << attr_or_assoc
|
118
|
-
# else
|
119
|
-
# @exposed_associations << attr_or_assoc
|
120
|
-
# end
|
121
|
-
# end
|
130
|
+
def writables *arg
|
131
|
+
self.writables = 42
|
122
132
|
end
|
123
133
|
|
124
|
-
def
|
125
|
-
|
126
|
-
|
134
|
+
def writables= arg
|
135
|
+
puts
|
136
|
+
puts "Toast Config Warning (#{model.class}): Defining \"writables\" in collection definition has no effect."
|
137
|
+
puts
|
127
138
|
end
|
128
139
|
|
129
140
|
def exposed_attributes
|
130
141
|
assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
|
131
|
-
@
|
142
|
+
(@readables + @writables).uniq.select{|f| !assocs.include?(f)}
|
132
143
|
end
|
133
144
|
|
134
145
|
def exposed_associations
|
135
146
|
assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
|
136
|
-
@
|
147
|
+
(@readables + @writables).uniq.select{|f| assocs.include?(f)}
|
137
148
|
end
|
138
149
|
|
139
|
-
def disallow_methods=
|
150
|
+
def disallow_methods= arg
|
140
151
|
@disallow_methods.push *ConfigDSL.sanitize(arg,"disallow methods")
|
141
152
|
end
|
142
153
|
|
143
154
|
def disallow_methods *arg
|
144
155
|
return(@disallow_methods) if arg.empty?
|
145
|
-
self.disallow_methods =
|
156
|
+
self.disallow_methods = arg
|
146
157
|
end
|
147
158
|
|
148
|
-
# attr_reader :exposed_attributes, :exposed_associations
|
149
159
|
end
|
150
160
|
|
151
161
|
|
data/lib/toast/record.rb
CHANGED
@@ -25,10 +25,17 @@ module Toast
|
|
25
25
|
raise PayloadFormatError
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
# silently ignore all exposed readable, but not writable fields
|
29
|
+
(@model.toast_config.readables - @model.toast_config.writables).each do |rof|
|
30
|
+
payload.delete(rof)
|
30
31
|
end
|
31
32
|
|
33
|
+
# set the virtual attributes
|
34
|
+
(payload.keys.to_set - @record.attribute_names.to_set).each do |vattr|
|
35
|
+
@record.send("#{vattr}=", payload.delete(vattr))
|
36
|
+
end
|
37
|
+
|
38
|
+
# mass-update for the rest
|
32
39
|
@record.update_attributes payload
|
33
40
|
{
|
34
41
|
:json => @record.exposed_attributes,
|
data/lib/toast/resource.rb
CHANGED
@@ -7,7 +7,7 @@ module Toast
|
|
7
7
|
class UnsupportedMediaType < Exception; end
|
8
8
|
|
9
9
|
# Represents a resource. There are following resource types as sub classes:
|
10
|
-
# Record, RootCollection,
|
10
|
+
# Record, RootCollection, Association, Single
|
11
11
|
class Resource
|
12
12
|
|
13
13
|
attr_accessor :media_type
|
@@ -39,8 +39,6 @@ module Toast
|
|
39
39
|
Toast::Record.new(model, id, format)
|
40
40
|
elsif model.toast_config.exposed_associations.include? subresource_name
|
41
41
|
Toast::Association.new(model, id, subresource_name, format)
|
42
|
-
elsif model.toast_config.exposed_attributes.include? subresource_name
|
43
|
-
Toast::Attribute.new(model, id, subresource_name, format)
|
44
42
|
else
|
45
43
|
raise ResourceNotFound
|
46
44
|
end
|
@@ -65,11 +65,11 @@ module Toast
|
|
65
65
|
raise PayloadFormatError
|
66
66
|
end
|
67
67
|
|
68
|
-
|
69
|
-
|
68
|
+
# silently ignore all exposed readable, but not writable fields
|
69
|
+
(@model.toast_config.readables - @model.toast_config.writables).each do |rof|
|
70
|
+
payload.delete(rof)
|
70
71
|
end
|
71
72
|
|
72
|
-
|
73
73
|
begin
|
74
74
|
record = @model.create! payload
|
75
75
|
|
@@ -78,8 +78,9 @@ module Toast
|
|
78
78
|
:location => record.uri,
|
79
79
|
:status => :created
|
80
80
|
}
|
81
|
-
|
81
|
+
|
82
82
|
rescue ActiveRecord::RecordInvalid => e
|
83
|
+
# model validation failed
|
83
84
|
raise PayloadInvalid.new(e.message)
|
84
85
|
end
|
85
86
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 4
|
8
|
+
- 0
|
9
|
+
version: 0.4.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Robert Annies
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-
|
17
|
+
date: 2012-03-13 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -48,7 +48,6 @@ files:
|
|
48
48
|
- lib/toast.rb
|
49
49
|
- lib/toast/active_record_extensions.rb
|
50
50
|
- lib/toast/association.rb
|
51
|
-
- lib/toast/attribute.rb
|
52
51
|
- lib/toast/config_dsl.rb
|
53
52
|
- lib/toast/engine.rb
|
54
53
|
- lib/toast/record.rb
|
data/lib/toast/attribute.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
module Toast
|
2
|
-
class Attribute < Resource
|
3
|
-
|
4
|
-
attr_reader :model
|
5
|
-
|
6
|
-
def initialize model, id, attribute_name, format
|
7
|
-
unless model.toast_config.exposed_attributes.include? attribute_name
|
8
|
-
raise ResourceNotFound
|
9
|
-
end
|
10
|
-
|
11
|
-
@model = model
|
12
|
-
@record = model.find(id) rescue raise(ResourceNotFound)
|
13
|
-
@attribute_name = attribute_name
|
14
|
-
@format = format
|
15
|
-
end
|
16
|
-
|
17
|
-
def get
|
18
|
-
{
|
19
|
-
:json => @record[@attribute_name],
|
20
|
-
:status => :ok
|
21
|
-
}
|
22
|
-
end
|
23
|
-
|
24
|
-
def put payload
|
25
|
-
|
26
|
-
@record.update_attribute(@attribute_name, payload)
|
27
|
-
{
|
28
|
-
:json => @record[@attribute_name],
|
29
|
-
:stauts => :ok,
|
30
|
-
:loaction => @record.uri
|
31
|
-
}
|
32
|
-
end
|
33
|
-
|
34
|
-
def post payload
|
35
|
-
raise MethodNotAllowed
|
36
|
-
end
|
37
|
-
|
38
|
-
def delete
|
39
|
-
raise MethodNotAllowed
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|