toast 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,15 +4,15 @@ class ToastController < ApplicationController
4
4
 
5
5
  begin
6
6
 
7
- @resource = Toast::Resource.build( params, request )
7
+ @resource = Toast::Resource.build( params, request )
8
+
9
+ render @resource.apply(request.method, request.body.read)
8
10
 
9
- render @resource.apply(request.method, request.body.read)
10
-
11
11
  rescue Toast::ResourceNotFound => e
12
12
  return head(:not_found)
13
13
 
14
14
  rescue Toast::PayloadInvalid => e
15
- return render :text => e.message, :status => :forbidden
15
+ return render :text => e.message, :status => :forbidden
16
16
 
17
17
  rescue Toast::PayloadFormatError => e
18
18
  return head(:bad_request)
@@ -8,6 +8,7 @@ Rails.application.routes.draw do
8
8
  match("#{model.toast_config.namespace}/#{resource_name}(/:id(/:subresource))" => 'toast#catch_all',
9
9
  :constraints => { :id => /\d+/ },
10
10
  :resource => resource_name,
11
+ :as => resource_name,
11
12
  :defaults => { :format => 'json' })
12
13
 
13
14
  match("#{model.toast_config.namespace}/#{resource_name}/:subresource" => 'toast#catch_all',
@@ -25,9 +25,12 @@ module Toast
25
25
 
26
26
  # add instance methods
27
27
  self.class_eval do
28
- # Return toast's standard uri for a record
29
- def uri
30
- "#{self.class.uri_base}/#{self.class.to_s.pluralize.underscore}/#{self.id}"
28
+ # Return the path segment of the URI of this record
29
+ def uri_fullpath
30
+ "/" +
31
+ (self.class.toast_config.namespace ? self.class.toast_config.namespace+"/" : "") +
32
+ self.class.to_s.pluralize.underscore + "/" +
33
+ self.id.to_s
31
34
  end
32
35
 
33
36
  # Returns a Hash with all exposed attributes
@@ -45,22 +48,13 @@ module Toast
45
48
  acc
46
49
  end
47
50
 
48
- # association URIs
49
- exposed_assoc =
50
- options[:in_collection] ? self.class.toast_config.in_collection.exposed_associations :
51
- self.class.toast_config.exposed_associations
52
-
53
- exposed_assoc.each do |assoc|
54
- out[assoc] = self.uri + "/" + assoc
55
- end
56
-
57
- out["uri"] = self.uri if options[:with_uri]
58
-
59
51
  out
60
52
  end
61
53
  end
62
54
  end
63
55
 
56
+ alias acts_as_resource resourceful_model
57
+
64
58
  # defaults for non resourceful-models
65
59
  def is_resourceful_model?
66
60
  false
@@ -24,12 +24,16 @@ module Toast
24
24
 
25
25
  if result.is_a? Array
26
26
  {
27
- :json => result.map{|r| r.exposed_attributes(:in_collection => true)},
27
+ :json => result.map{|r|
28
+ r.exposed_attributes(:in_collection => true).
29
+ merge( uri_fields(r, true) )
30
+ },
28
31
  :status => :ok
29
32
  }
30
33
  else
31
34
  {
32
- :json => result.exposed_attributes(:in_collection => true),
35
+ :json => result.exposed_attributes(:in_collection => true).
36
+ merge( uri_fields(result) ),
33
37
  :status => :ok
34
38
  }
35
39
  end
@@ -37,48 +41,11 @@ module Toast
37
41
  end
38
42
 
39
43
  def put payload
40
- # only for has_one/belongs_to assocs
41
- raise MethodNotAllowed if @is_collection
42
-
43
-
44
- begin
45
- payload = ActiveSupport::JSON.decode(payload)
46
- rescue
47
- raise PayloadFormatError
48
- end
49
-
50
-
51
- unless payload.is_a? Hash
52
- raise PayloadFormatError
53
- end
54
-
55
- # update see record
56
- if self.media_type != @associate_model.toast_config.media_type
57
- raise UnsupportedMediaType
58
- end
59
-
60
- # silently ignore all exposed readable, but not writable fields
61
- (@associate_model.toast_config.readables - @associate_model.toast_config.writables).each do |rof|
62
- payload.delete(rof)
63
- end
64
-
65
- record = @record.send(@assoc)
66
-
67
- # set the virtual attributes
68
- (payload.keys.to_set - record.attribute_names.to_set).each do |vattr|
69
- record.send("#{vattr}=", payload.delete(vattr))
70
- end
71
-
72
- # mass-update for the rest
73
- record.update_attributes payload
74
- {
75
- :json => record.exposed_attributes,
76
- :status => :ok,
77
- :location => record.uri
78
- }
44
+ raise MethodNotAllowed
79
45
  end
80
46
 
81
47
  def post payload
48
+ raise MethodNotAllowed unless @model.toast_config.writables.include? @assoc
82
49
 
83
50
  if self.media_type != @associate_model.toast_config.media_type
84
51
  raise UnsupportedMediaType
@@ -105,8 +72,8 @@ module Toast
105
72
  record = @record.send(@assoc).create! payload
106
73
 
107
74
  {
108
- :json => record.exposed_attributes,
109
- :location => record.uri,
75
+ :json => record.exposed_attributes.merge( uri_fields(record) ),
76
+ :location => self.base_uri + record.uri_fullpath,
110
77
  :status => :created
111
78
  }
112
79
 
@@ -7,12 +7,13 @@ module Toast
7
7
 
8
8
  def initialize model
9
9
  @model = model
10
- @readables = ["uri"]
10
+ @readables = []
11
11
  @writables = []
12
12
  @collections = []
13
13
  @singles = []
14
14
  @media_type = "application/json"
15
- @disallow_methods = []
15
+ @deletable = false
16
+ @postable = false
16
17
  @pass_params_to = []
17
18
  @in_collection = ConfigDSL::InCollection.new model, self
18
19
  end
@@ -28,43 +29,40 @@ module Toast
28
29
  end
29
30
 
30
31
  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
32
+ @readables.push *ConfigDSL.sanitize(arg,"readables")
36
33
  end
37
34
 
38
35
  # args: Array or :all, :except => Array
39
36
  def readables *arg
40
37
  return(@readables) if arg.empty?
41
- self.readables = arg
38
+ self.readables = arg
42
39
  end
43
40
 
44
41
  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
42
  @model.attr_accessible *arg
50
- @writables.push *ConfigDSL.sanitize(arg,"writables")
51
- #end
43
+ @writables.push *ConfigDSL.sanitize(arg,"writables")
52
44
  end
53
45
 
54
46
  # args: Array or :all, :except => Array
55
47
  def writables *arg
56
48
  return(@writables) if arg.empty?
57
- self.writables = arg
49
+ self.writables = arg
50
+ end
51
+
52
+ def deletable
53
+ @deletable = true
58
54
  end
59
55
 
60
-
61
- def disallow_methods= arg
62
- @disallow_methods.push *ConfigDSL.sanitize(arg,"disallow methods")
56
+ def deletable?
57
+ @deletable
63
58
  end
64
59
 
65
- def disallow_methods *arg
66
- return(@disallow_methods) if arg.empty?
67
- self.disallow_methods = arg
60
+ def postable
61
+ @postable = true
62
+ end
63
+
64
+ def postable?
65
+ @postable
68
66
  end
69
67
 
70
68
  def pass_params_to= arg
@@ -73,9 +71,9 @@ module Toast
73
71
 
74
72
  def pass_params_to *arg
75
73
  return(@pass_params_to) if arg.empty?
76
- self.pass_params_to = arg
74
+ self.pass_params_to = arg
77
75
  end
78
-
76
+
79
77
  def collections= collections=[]
80
78
  @collections = ConfigDSL.sanitize(collections, "collections")
81
79
  end
@@ -111,15 +109,14 @@ module Toast
111
109
 
112
110
  def initialize model, base_config
113
111
  @model = model
114
- @readables = base_config.readables # must assign a reference
115
- @writables = base_config.writables # must assign a reference
116
- @disallow_methods = []
112
+ @readables = base_config.readables # must assign a reference
113
+ @writables = base_config.writables # must assign a reference
117
114
  @media_type = "application/json"
118
115
  end
119
116
 
120
117
  def readables= readables
121
118
  @writables = [] # forget inherited writables
122
- @readables = ConfigDSL.sanitize(readables,"readables") << "uri"
119
+ @readables = ConfigDSL.sanitize(readables,"readables")
123
120
  end
124
121
 
125
122
  def readables *arg
@@ -146,16 +143,6 @@ module Toast
146
143
  assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
147
144
  (@readables + @writables).uniq.select{|f| assocs.include?(f)}
148
145
  end
149
-
150
- def disallow_methods= arg
151
- @disallow_methods.push *ConfigDSL.sanitize(arg,"disallow methods")
152
- end
153
-
154
- def disallow_methods *arg
155
- return(@disallow_methods) if arg.empty?
156
- self.disallow_methods = arg
157
- end
158
-
159
146
  end
160
147
 
161
148
 
@@ -24,6 +24,23 @@ module Toast
24
24
  # raised when DB is not setup yet. (rake db:schema:load)
25
25
  end
26
26
 
27
+ # Monkey patch the request class for Rails 3.0, Rack 1.2
28
+ # Backport from Rack 1.3
29
+ if Rack.release == "1.2"
30
+ class Rack::Request
31
+ def base_url
32
+ url = scheme + "://"
33
+ url << host
34
+
35
+ if scheme == "https" && port != 443 ||
36
+ scheme == "http" && port != 80
37
+ url << ":#{port}"
38
+ end
39
+
40
+ url
41
+ end
42
+ end
43
+ end
27
44
  end
28
45
  end
29
46
  end
@@ -31,12 +31,12 @@ module Toast
31
31
  unless payload.is_a? Hash
32
32
  raise PayloadFormatError
33
33
  end
34
-
35
- # silently ignore all exposed readable, but not writable fields
36
- (@model.toast_config.readables - @model.toast_config.writables).each do |rof|
34
+
35
+ # ignore all exposed readable, but not writable fields
36
+ (@model.toast_config.readables - @model.toast_config.writables + ["uri"]).each do |rof|
37
37
  payload.delete(rof)
38
38
  end
39
-
39
+
40
40
  # set the virtual attributes
41
41
  (payload.keys.to_set - @record.attribute_names.to_set).each do |vattr|
42
42
  @record.send("#{vattr}=", payload.delete(vattr))
@@ -45,9 +45,9 @@ module Toast
45
45
  # mass-update for the rest
46
46
  @record.update_attributes payload
47
47
  {
48
- :json => @record.exposed_attributes,
48
+ :json => @record.exposed_attributes.merge( uri_fields(@record) ),
49
49
  :status => :ok,
50
- :location => @record.uri
50
+ :location => self.base_uri + @record.uri_fullpath
51
51
  }
52
52
  end
53
53
 
@@ -58,9 +58,9 @@ module Toast
58
58
  :template => "resources/#{model.to_s.underscore}",
59
59
  :locals => { model.to_s.underscore.to_sym => @record } # full record, view should filter
60
60
  }
61
- when "json"
61
+ when "json"
62
62
  {
63
- :json => @record.exposed_attributes,
63
+ :json => @record.exposed_attributes.merge( uri_fields(@record) ),
64
64
  :status => :ok
65
65
  }
66
66
  else
@@ -69,11 +69,14 @@ module Toast
69
69
  end
70
70
 
71
71
  def delete
72
+ raise MethodNotAllowed unless @model.toast_config.deletable?
73
+
72
74
  @record.destroy
73
75
  {
74
76
  :nothing => true,
75
77
  :status => :ok
76
78
  }
77
79
  end
80
+
78
81
  end
79
82
  end
@@ -10,40 +10,37 @@ module Toast
10
10
  # Record, RootCollection, Association, Single
11
11
  class Resource
12
12
 
13
- attr_accessor :media_type
13
+ attr_accessor :media_type, :base_uri
14
14
 
15
15
  def initialize
16
16
  raise 'ToastResource#new: use #build to create an instance'
17
17
  end
18
18
 
19
19
  def self.build params, request
20
-
21
20
  resource_name = params[:resource]
22
21
  id = params[:id]
23
22
  subresource_name = params[:subresource]
24
23
  format = params[:format]
25
24
 
26
25
  begin
27
-
26
+
28
27
  model = get_class_by_resource_name resource_name
29
-
30
- # base is complete URL until the resource name
31
- model.uri_base = request.url.match(/(.*)\/#{resource_name}(?:\..+|\/|\z)/)[1]
32
28
 
33
29
  # decide which sub type
34
30
  rsc = if id.nil? and model.toast_config.singles.include?(subresource_name)
35
- Toast::Single.new(model, subresource_name, params.clone)
31
+ Toast::Single.new(model, subresource_name, params.clone)
36
32
  elsif id.nil?
37
33
  Toast::RootCollection.new(model, subresource_name, params.clone)
38
34
  elsif subresource_name.nil?
39
35
  Toast::Record.new(model, id, format)
40
36
  elsif model.toast_config.exposed_associations.include? subresource_name
41
- Toast::Association.new(model, id, subresource_name, format)
37
+ Toast::Association.new(model, id, subresource_name, format)
42
38
  else
43
39
  raise ResourceNotFound
44
40
  end
45
-
41
+
46
42
  rsc.media_type = request.media_type
43
+ rsc.base_uri = request.base_url
47
44
 
48
45
  rsc
49
46
  rescue NameError
@@ -67,18 +64,31 @@ module Toast
67
64
  end
68
65
 
69
66
  def apply method, payload
70
-
71
- raise MethodNotAllowed if self.model.toast_config.disallow_methods.include?(method.downcase)
72
-
73
67
  case method
74
68
  when "PUT","POST"
75
69
  self.send(method.downcase, payload)
76
70
  when "DELETE","GET"
77
- self.send(method.downcase)
71
+ self.send(method.downcase)
78
72
  else
79
73
  raise MethodNotAllowed
80
- end
74
+ end
75
+ end
76
+
77
+ private
78
+ def uri_fields record, in_collection=false
79
+ out = {}
80
+
81
+ exposed_assoc =
82
+ in_collection ? record.class.toast_config.in_collection.exposed_associations :
83
+ record.class.toast_config.exposed_associations
84
+
85
+ exposed_assoc.each do |assoc|
86
+ out[assoc] = "#{self.base_uri}#{record.uri_fullpath}/#{assoc}"
87
+ end
88
+
89
+ out["uri"] = self.base_uri + record.uri_fullpath
90
+
91
+ out
81
92
  end
82
93
  end
83
94
  end
84
-
@@ -18,9 +18,6 @@ module Toast
18
18
  end
19
19
 
20
20
  def get
21
- if @model.toast_config.in_collection.disallow_methods.include? "get"
22
- raise MethodNotAllowed
23
- end
24
21
 
25
22
  records = if @model.toast_config.pass_params_to.include?(@collection)
26
23
  @model.send(@collection, @params)
@@ -36,7 +33,10 @@ module Toast
36
33
  }
37
34
  when "json"
38
35
  {
39
- :json => records.map{|r| r.exposed_attributes(:in_collection => true)},
36
+ :json => records.map{|r|
37
+ r.exposed_attributes(:in_collection => true).
38
+ merge( uri_fields(r, true) )
39
+ },
40
40
  :status => :ok
41
41
  }
42
42
  else
@@ -49,10 +49,8 @@ module Toast
49
49
  end
50
50
 
51
51
  def post payload
52
- if @model.toast_config.in_collection.disallow_methods.include? "post"
53
- raise MethodNotAllowed
54
- end
55
-
52
+ raise MethodNotAllowed unless @model.toast_config.postable?
53
+
56
54
  if @collection != "all"
57
55
  raise MethodNotAllowed
58
56
  end
@@ -71,7 +69,7 @@ module Toast
71
69
  end
72
70
 
73
71
  # silently ignore all exposed readable, but not writable fields
74
- (@model.toast_config.readables - @model.toast_config.writables).each do |rof|
72
+ (@model.toast_config.readables - @model.toast_config.writables + ["uri"]).each do |rof|
75
73
  payload.delete(rof)
76
74
  end
77
75
 
@@ -79,8 +77,8 @@ module Toast
79
77
  record = @model.create! payload
80
78
 
81
79
  {
82
- :json => record.exposed_attributes,
83
- :location => record.uri,
80
+ :json => record.exposed_attributes.merge( uri_fields(record) ),
81
+ :location => self.base_uri + record.uri_fullpath,
84
82
  :status => :created
85
83
  }
86
84
 
@@ -6,47 +6,43 @@ module Toast
6
6
 
7
7
  # The single resource name must be a class method of the model and
8
8
  # must return nil or an instance.
9
-
9
+
10
10
  # GET is the only allowed verb. To make changes the URI with ID has
11
11
  # to be used.
12
12
  class Single < Resource
13
-
13
+
14
14
  attr_reader :model
15
-
15
+
16
16
  def initialize model, subresource_name, params
17
-
17
+
18
18
  unless model.toast_config.singles.include? subresource_name
19
19
  raise ResourceNotFound
20
20
  end
21
21
 
22
22
  @model = model
23
- @single_finder = subresource_name
24
23
  @params = params
25
24
  @format = params[:format]
26
- end
27
25
 
28
- def get
29
- if @model.toast_config.in_collection.disallow_methods.include? "get"
30
- raise MethodNotAllowed
31
- end
32
-
33
- record = if @model.toast_config.pass_params_to.include?(@single_finder)
34
- @model.send(@single_finder, @params)
26
+ @record = if @model.toast_config.pass_params_to.include?(subresource_name)
27
+ @model.send(subresource_name, @params)
35
28
  else
36
- @model.send(@single_finder)
29
+ @model.send(subresource_name)
37
30
  end
38
31
 
39
- raise ResourceNotFound if record.nil?
40
-
32
+ raise ResourceNotFound if @record.nil?
33
+
34
+ end
35
+
36
+ def get
41
37
  case @format
42
38
  when "html"
43
39
  {
44
40
  :template => "resources/#{model.to_s.underscore}",
45
- :locals => { model.to_s.pluralize.underscore.to_sym => record }
41
+ :locals => { model.to_s.pluralize.underscore.to_sym => @record }
46
42
  }
47
- when "json"
43
+ when "json"
48
44
  {
49
- :json => record.exposed_attributes,
45
+ :json => @record.exposed_attributes.merge( uri_fields(@record) ),
50
46
  :status => :ok
51
47
  }
52
48
  else
@@ -54,6 +50,7 @@ module Toast
54
50
  end
55
51
  end
56
52
 
53
+
57
54
  def put
58
55
  raise MethodNotAllowed
59
56
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 4
8
- - 1
9
- version: 0.4.1
7
+ - 5
8
+ - 0
9
+ version: 0.5.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-03-13 00:00:00 +01:00
17
+ date: 2012-03-20 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency