toast 0.2.0 → 0.3.4

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 CHANGED
@@ -126,6 +126,13 @@ Installation
126
126
  cd toast
127
127
  rake install
128
128
 
129
+ or with bundler (Gemfile)
130
+
131
+ gem "toast", :git => "https://github.com/robokopp/toast.git"
132
+
133
+ Note: There is a Gem on RubyGems.org too, but I will not keep it up-to-date for now.
134
+ Please use the git (edge) version.
135
+
129
136
  Test Suite
130
137
  ==========
131
138
 
@@ -3,14 +3,16 @@ class ToastController < ApplicationController
3
3
  def catch_all
4
4
 
5
5
  begin
6
- resource = Toast::Resource.build( params, request )
7
- render resource.apply(request.method, request.body.read)
8
6
 
7
+ @resource = Toast::Resource.build( params, request )
8
+
9
+ render @resource.apply(request.method, request.body.read)
10
+
9
11
  rescue Toast::ResourceNotFound => e
10
12
  return head(:not_found)
11
13
 
12
14
  rescue Toast::PayloadInvalid => e
13
- return head(:unprocessable_entity)
15
+ return render :text => e.message, :status => :forbidden
14
16
 
15
17
  rescue Toast::PayloadFormatError => e
16
18
  return head(:bad_request)
data/config/routes.rb CHANGED
@@ -5,8 +5,14 @@ Rails.application.routes.draw do
5
5
 
6
6
  resource_name = model.to_s.pluralize.underscore
7
7
 
8
- match "#{model.toast_config.namespace}/#{resource_name}(/:id(/:subresource))" => 'toast#catch_all', :constraints => { :id => /\d+/ }, :resource => resource_name
9
- match "#{model.toast_config.namespace}/#{resource_name}/:subresource" => 'toast#catch_all', :resource => resource_name
8
+ match("#{model.toast_config.namespace}/#{resource_name}(/:id(/:subresource))" => 'toast#catch_all',
9
+ :constraints => { :id => /\d+/ },
10
+ :resource => resource_name,
11
+ :defaults => { :format => 'json' })
12
+
13
+ match("#{model.toast_config.namespace}/#{resource_name}/:subresource" => 'toast#catch_all',
14
+ :resource => resource_name,
15
+ :defaults => { :format => 'json' })
10
16
  end
11
17
 
12
18
  match ":resource(/:id(/:subresource))" => 'toast#not_found', :constraints => { :id => /\d+/ }
@@ -41,7 +41,7 @@ module Toast
41
41
  self.class.toast_config.exposed_attributes
42
42
 
43
43
  out = exposed_attr.inject({}) do |acc, attr|
44
- acc[attr] = self[attr]
44
+ acc[attr] = self.send attr
45
45
  acc
46
46
  end
47
47
 
@@ -1,9 +1,9 @@
1
1
  module Toast
2
- class AssociateCollection < Resource
2
+ class Association < Resource
3
3
 
4
4
  attr_reader :model
5
5
 
6
- def initialize model, id, subresource_name
6
+ def initialize model, id, subresource_name, format
7
7
  unless model.toast_config.exposed_associations.include? subresource_name
8
8
  raise ResourceNotFound
9
9
  end
@@ -11,6 +11,7 @@ module Toast
11
11
  @model = model
12
12
  @record = model.find(id) rescue raise(ResourceNotFound)
13
13
  @collection = subresource_name
14
+ @format = format
14
15
 
15
16
  @associate_model = Resource.get_class_by_resource_name subresource_name
16
17
  @associate_model.uri_base = @model.uri_base
@@ -18,11 +19,20 @@ module Toast
18
19
  end
19
20
 
20
21
  def get
21
- records = @record.send(@collection)
22
- {
23
- :json => records.map{|r| r.exposed_attributes(:in_collection => true)},
24
- :status => :ok
25
- }
22
+ result = @record.send(@collection)
23
+
24
+ if result.is_a? Array
25
+ {
26
+ :json => result.map{|r| r.exposed_attributes(:in_collection => true)},
27
+ :status => :ok
28
+ }
29
+ else
30
+ {
31
+ :json => result.exposed_attributes(:in_collection => true),
32
+ :status => :ok
33
+ }
34
+ end
35
+
26
36
  end
27
37
 
28
38
  def put
@@ -31,19 +41,18 @@ module Toast
31
41
 
32
42
  def post payload
33
43
 
34
-
35
44
  if self.media_type != @associate_model.toast_config.media_type
36
45
  raise UnsupportedMediaType
37
46
  end
38
47
 
39
- if payload.keys.to_set != @associate_model.toast_config.exposed_attributes.to_set
48
+ if payload.keys.to_set != (@associate_model.toast_config.exposed_attributes.to_set - @associate_model.toast_config.auto_fields.to_set)
40
49
  raise PayloadInvalid
41
50
  end
42
51
 
43
52
  unless payload.is_a? Hash
44
53
  raise PayloadFormatError
45
54
  end
46
- #debugger
55
+
47
56
  record = @record.send(@collection).create payload
48
57
 
49
58
  {
@@ -3,7 +3,7 @@ module Toast
3
3
 
4
4
  attr_reader :model
5
5
 
6
- def initialize model, id, attribute_name
6
+ def initialize model, id, attribute_name, format
7
7
  unless model.toast_config.exposed_attributes.include? attribute_name
8
8
  raise ResourceNotFound
9
9
  end
@@ -11,6 +11,7 @@ module Toast
11
11
  @model = model
12
12
  @record = model.find(id) rescue raise(ResourceNotFound)
13
13
  @attribute_name = attribute_name
14
+ @format = format
14
15
  end
15
16
 
16
17
  def get
@@ -21,6 +22,7 @@ module Toast
21
22
  end
22
23
 
23
24
  def put payload
25
+
24
26
  @record.update_attribute(@attribute_name, payload)
25
27
  {
26
28
  :json => @record[@attribute_name],
@@ -4,27 +4,22 @@ 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
7
8
 
8
9
  def initialize model
9
10
  @model = model
10
11
  @fields = []
12
+ @auto_fields = []
11
13
  @collections = []
14
+ @singles = []
12
15
  @media_type = "application/json"
13
- @exposed_attributes = []
14
- @exposed_associations = []
15
16
  @disallow_methods = []
17
+ @pass_params_to = []
16
18
  @in_collection = ConfigDSL::InCollection.new model, self
17
19
  end
18
20
 
19
21
  def fields= *fields
20
22
  @fields.push *ConfigDSL.sanitize(fields,"fields")
21
- @fields.each do |attr_or_assoc|
22
- if @model.new.attributes.keys.include? attr_or_assoc
23
- @exposed_attributes << attr_or_assoc
24
- else
25
- @exposed_associations << attr_or_assoc
26
- end
27
- end
28
23
  end
29
24
 
30
25
  def fields *arg
@@ -32,6 +27,25 @@ module Toast
32
27
  self.fields = *arg
33
28
  end
34
29
 
30
+ def exposed_attributes
31
+ assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
32
+ @fields.select{|f| !assocs.include?(f)}
33
+ end
34
+
35
+ def exposed_associations
36
+ assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
37
+ @fields.select{|f| assocs.include?(f)}
38
+ end
39
+
40
+ def auto_fields= *arg
41
+ @auto_fields.push *ConfigDSL.sanitize(arg,"auto fields")
42
+ end
43
+
44
+ def auto_fields *arg
45
+ return(@auto_fields) if arg.empty?
46
+ self.auto_fields = *arg
47
+ end
48
+
35
49
  def disallow_methods= *arg
36
50
  @disallow_methods.push *ConfigDSL.sanitize(arg,"disallow methods")
37
51
  end
@@ -41,8 +55,15 @@ module Toast
41
55
  self.disallow_methods = *arg
42
56
  end
43
57
 
44
- attr_reader :exposed_attributes, :exposed_associations
58
+ def pass_params_to= *arg
59
+ @pass_params_to.push *ConfigDSL.sanitize(arg,"pass_params_to")
60
+ end
45
61
 
62
+ def pass_params_to *arg
63
+ return(@pass_params_to) if arg.empty?
64
+ self.pass_params_to = *arg
65
+ end
66
+
46
67
  def collections= collections=[]
47
68
  @collections = ConfigDSL.sanitize(collections, "collections")
48
69
  end
@@ -52,6 +73,15 @@ module Toast
52
73
  self.collections = *arg
53
74
  end
54
75
 
76
+ def singles= singles=[]
77
+ @singles = ConfigDSL.sanitize(singles, "singles")
78
+ end
79
+
80
+ def singles *arg
81
+ return(@singles) if arg.empty?
82
+ self.singles = *arg
83
+ end
84
+
55
85
  def in_collection &block
56
86
  if block_given?
57
87
  Blockenspiel.invoke( block, @in_collection)
@@ -70,24 +100,25 @@ module Toast
70
100
  def initialize model, base_config
71
101
  @model = model
72
102
  @fields = base_config.fields
73
- @exposed_attributes = base_config.exposed_attributes
74
- @exposed_associations = base_config.exposed_associations
103
+ @disallow_methods = []
104
+ # @exposed_attributes = base_config.exposed_attributes
105
+ # @exposed_associations = base_config.exposed_associations
75
106
  @media_type = "application/json"
76
107
  end
77
108
 
78
109
  def fields= *fields
79
110
  @fields = ConfigDSL.sanitize(fields,"fields")
80
111
 
81
- @exposed_attributes = []
82
- @exposed_associations = []
112
+ # @exposed_attributes = []
113
+ # @exposed_associations = []
83
114
 
84
- @fields.each do |attr_or_assoc|
85
- if @model.new.attributes.keys.include? attr_or_assoc
86
- @exposed_attributes << attr_or_assoc
87
- else
88
- @exposed_associations << attr_or_assoc
89
- end
90
- end
115
+ # @fields.each do |attr_or_assoc|
116
+ # if @model.new.attributes.keys.include? attr_or_assoc
117
+ # @exposed_attributes << attr_or_assoc
118
+ # else
119
+ # @exposed_associations << attr_or_assoc
120
+ # end
121
+ # end
91
122
  end
92
123
 
93
124
  def fields *arg
@@ -95,7 +126,26 @@ module Toast
95
126
  self.fields = *arg
96
127
  end
97
128
 
98
- attr_reader :exposed_attributes, :exposed_associations
129
+ def exposed_attributes
130
+ assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
131
+ @fields.select{|f| !assocs.include?(f)}
132
+ end
133
+
134
+ def exposed_associations
135
+ assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
136
+ @fields.select{|f| assocs.include?(f)}
137
+ end
138
+
139
+ def disallow_methods= *arg
140
+ @disallow_methods.push *ConfigDSL.sanitize(arg,"disallow methods")
141
+ end
142
+
143
+ def disallow_methods *arg
144
+ return(@disallow_methods) if arg.empty?
145
+ self.disallow_methods = *arg
146
+ end
147
+
148
+ # attr_reader :exposed_attributes, :exposed_associations
99
149
  end
100
150
 
101
151
 
data/lib/toast/engine.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  require 'toast/active_record_extensions.rb'
2
2
  require 'toast/resource.rb'
3
3
  require 'toast/root_collection'
4
- require 'toast/associate_collection'
4
+ require 'toast/association'
5
5
  require 'toast/attribute'
6
6
  require 'toast/record'
7
+ require 'toast/single'
8
+
7
9
  require 'action_dispatch/http/request'
8
10
 
9
11
  module Toast
@@ -19,7 +21,7 @@ module Toast
19
21
  begin
20
22
  Dir["#{Rails.root}/app/models/**/*.rb"].each{|m| require m }
21
23
 
22
- rescue ActiveRecord::StatementInvalid
24
+ rescue
23
25
  # raised when DB is not setup yet. (rake db:schema:load)
24
26
  end
25
27
 
data/lib/toast/record.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  module Toast
2
2
  class Record < Resource
3
3
 
4
- attr_reader :model
4
+ attr_reader :model
5
5
 
6
- def initialize model, id
6
+ def initialize model, id, format
7
7
  @model = model
8
8
  @record = model.find(id) rescue raise(ResourceNotFound.new)
9
+ @format = format
9
10
  end
10
11
 
11
12
  def post payload
@@ -24,23 +25,33 @@ module Toast
24
25
  raise PayloadFormatError
25
26
  end
26
27
 
27
- if payload.keys.to_set != @model.toast_config.exposed_attributes.to_set
28
+ if payload.keys.to_set != (@model.toast_config.exposed_attributes.to_set - @model.toast_config.auto_fields.to_set)
28
29
  raise PayloadInvalid
29
30
  end
30
31
 
31
32
  @record.update_attributes payload
32
- {
33
+ {
33
34
  :json => @record.exposed_attributes,
34
35
  :status => :ok,
35
36
  :location => @record.uri
36
37
  }
37
38
  end
38
39
 
39
- def get
40
- {
41
- :json => @record.exposed_attributes,
42
- :status => :ok
43
- }
40
+ def get
41
+ case @format
42
+ when "html", "xml"
43
+ {
44
+ :template => "resources/#{model.to_s.underscore}",
45
+ :locals => { model.to_s.underscore.to_sym => @record } # full record, view should filter
46
+ }
47
+ when "json"
48
+ {
49
+ :json => @record.exposed_attributes,
50
+ :status => :ok
51
+ }
52
+ else
53
+ raise ResourceNotFound
54
+ end
44
55
  end
45
56
 
46
57
  def delete
@@ -21,29 +21,26 @@ module Toast
21
21
  resource_name = params[:resource]
22
22
  id = params[:id]
23
23
  subresource_name = params[:subresource]
24
-
25
- uri_base = "#{request.protocol}#{request.host}"
26
- unless (request.protocol == "http://" and request.port == 80) or
27
- (request.protocol == "https://" and request.port == 443)
28
- uri_base += ":#{request.port}"
29
- end
24
+ format = params[:format]
30
25
 
31
26
  begin
32
-
27
+
33
28
  model = get_class_by_resource_name resource_name
34
-
35
- model.uri_base = uri_base
36
- model.uri_base += "/#{model.toast_config.namespace}" if model.toast_config.namespace
29
+
30
+ # base is complete URL until the resource name
31
+ model.uri_base = request.url.match(/(.*)\/#{resource_name}(?:\..+|\/|\z)/)[1]
37
32
 
38
33
  # decide which sub type
39
- rsc = if id.nil?
40
- Toast::RootCollection.new(model, subresource_name)
34
+ rsc = if id.nil? and model.toast_config.singles.include?(subresource_name)
35
+ Toast::Single.new(model, subresource_name, params.clone)
36
+ elsif id.nil?
37
+ Toast::RootCollection.new(model, subresource_name, params.clone)
41
38
  elsif subresource_name.nil?
42
- Toast::Record.new(model, id)
39
+ Toast::Record.new(model, id, format)
43
40
  elsif model.toast_config.exposed_associations.include? subresource_name
44
- Toast::AssociateCollection.new(model, id, subresource_name)
41
+ Toast::Association.new(model, id, subresource_name, format)
45
42
  elsif model.toast_config.exposed_attributes.include? subresource_name
46
- Toast::Attribute.new(model, id, subresource_name)
43
+ Toast::Attribute.new(model, id, subresource_name, format)
47
44
  else
48
45
  raise ResourceNotFound
49
46
  end
@@ -82,7 +79,7 @@ module Toast
82
79
  self.send(method.downcase)
83
80
  else
84
81
  raise MethodNotAllowed
85
- end
82
+ end
86
83
  end
87
84
  end
88
85
  end
@@ -3,7 +3,7 @@ module Toast
3
3
 
4
4
  attr_reader :model
5
5
 
6
- def initialize model, subresource_name
6
+ def initialize model, subresource_name, params
7
7
 
8
8
  subresource_name ||= "all"
9
9
 
@@ -13,14 +13,35 @@ module Toast
13
13
 
14
14
  @model = model
15
15
  @collection = subresource_name
16
+ @params = params
17
+ @format = params[:format]
16
18
  end
17
19
 
18
20
  def get
19
- records = @model.send(@collection)
20
- {
21
- :json => records.map{|r| r.exposed_attributes(:in_collection => true)},
22
- :status => :ok
23
- }
21
+ if @model.toast_config.in_collection.disallow_methods.include? "get"
22
+ raise MethodNotAllowed
23
+ end
24
+
25
+ records = if @model.toast_config.pass_params_to.include?(@collection)
26
+ @model.send(@collection, @params)
27
+ else
28
+ @model.send(@collection)
29
+ end
30
+
31
+ case @format
32
+ when "html"
33
+ {
34
+ :template => "resources/#{model.to_s.pluralize.underscore}",
35
+ :locals => { model.to_s.pluralize.underscore.to_sym => records }
36
+ }
37
+ when "json"
38
+ {
39
+ :json => records.map{|r| r.exposed_attributes(:in_collection => true)},
40
+ :status => :ok
41
+ }
42
+ else
43
+ raise ResourceNotFound
44
+ end
24
45
  end
25
46
 
26
47
  def put
@@ -28,6 +49,10 @@ module Toast
28
49
  end
29
50
 
30
51
  def post payload
52
+ if @model.toast_config.in_collection.disallow_methods.include? "post"
53
+ raise MethodNotAllowed
54
+ end
55
+
31
56
  if @collection != "all"
32
57
  raise MethodNotAllowed
33
58
  end
@@ -40,17 +65,23 @@ module Toast
40
65
  raise PayloadFormatError
41
66
  end
42
67
 
43
- if payload.keys.to_set != @model.toast_config.exposed_attributes.to_set
68
+ if payload.keys.to_set != (@model.toast_config.exposed_attributes.to_set - @model.toast_config.auto_fields.to_set)
44
69
  raise PayloadInvalid
45
70
  end
46
71
 
47
- record = @model.create payload
48
-
49
- {
50
- :json => record.exposed_attributes,
51
- :location => record.uri,
52
- :status => :created
53
- }
72
+
73
+ begin
74
+ record = @model.create! payload
75
+
76
+ {
77
+ :json => record.exposed_attributes,
78
+ :location => record.uri,
79
+ :status => :created
80
+ }
81
+
82
+ rescue ActiveRecord::RecordInvalid => e
83
+ raise PayloadInvalid.new(e.message)
84
+ end
54
85
  end
55
86
 
56
87
  def delete
@@ -0,0 +1,69 @@
1
+ module Toast
2
+
3
+ # A Single resource is queried without an ID, by custom class methods
4
+ # or scopes of the model or ActiveRecord single finders like:
5
+ # first, last
6
+
7
+ # The single resource name must be a class method of the model and
8
+ # must return nil or an instance.
9
+
10
+ # GET is the only allowed verb. To make changes the URI with ID has
11
+ # to be used.
12
+ class Single < Resource
13
+
14
+ attr_reader :model
15
+
16
+ def initialize model, subresource_name, params
17
+
18
+ unless model.toast_config.singles.include? subresource_name
19
+ raise ResourceNotFound
20
+ end
21
+
22
+ @model = model
23
+ @single_finder = subresource_name
24
+ @params = params
25
+ @format = params[:format]
26
+ end
27
+
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)
35
+ else
36
+ @model.send(@single_finder)
37
+ end
38
+
39
+ raise ResourceNotFound if record.nil?
40
+
41
+ case @format
42
+ when "html"
43
+ {
44
+ :template => "resources/#{model.to_s.underscore}",
45
+ :locals => { model.to_s.pluralize.underscore.to_sym => record }
46
+ }
47
+ when "json"
48
+ {
49
+ :json => record.exposed_attributes,
50
+ :status => :ok
51
+ }
52
+ else
53
+ raise ResourceNotFound
54
+ end
55
+ end
56
+
57
+ def put
58
+ raise MethodNotAllowed
59
+ end
60
+
61
+ def post payload
62
+ raise MethodNotAllowed
63
+ end
64
+
65
+ def delete
66
+ raise MethodNotAllowed
67
+ end
68
+ end
69
+ end
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toast
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
5
- prerelease:
4
+ prerelease: false
6
5
  segments:
7
6
  - 0
8
- - 2
9
- - 0
10
- version: 0.2.0
7
+ - 3
8
+ - 4
9
+ version: 0.3.4
11
10
  platform: ruby
12
11
  authors:
13
12
  - Robert Annies
@@ -15,18 +14,16 @@ autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
16
 
18
- date: 2011-07-25 00:00:00 +02:00
17
+ date: 2012-02-29 00:00:00 +01:00
19
18
  default_executable:
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
22
21
  name: blockenspiel
23
22
  prerelease: false
24
23
  requirement: &id001 !ruby/object:Gem::Requirement
25
- none: false
26
24
  requirements:
27
25
  - - ~>
28
26
  - !ruby/object:Gem::Version
29
- hash: 11
30
27
  segments:
31
28
  - 0
32
29
  - 4
@@ -50,13 +47,14 @@ files:
50
47
  - config/routes.rb
51
48
  - lib/toast.rb
52
49
  - lib/toast/active_record_extensions.rb
53
- - lib/toast/associate_collection.rb
50
+ - lib/toast/association.rb
54
51
  - lib/toast/attribute.rb
55
52
  - lib/toast/config_dsl.rb
56
53
  - lib/toast/engine.rb
57
54
  - lib/toast/record.rb
58
55
  - lib/toast/resource.rb
59
56
  - lib/toast/root_collection.rb
57
+ - lib/toast/single.rb
60
58
  - README.md
61
59
  has_rdoc: true
62
60
  homepage: https://github.com/robokopp/toast
@@ -68,27 +66,23 @@ rdoc_options: []
68
66
  require_paths:
69
67
  - lib
70
68
  required_ruby_version: !ruby/object:Gem::Requirement
71
- none: false
72
69
  requirements:
73
70
  - - ">="
74
71
  - !ruby/object:Gem::Version
75
- hash: 3
76
72
  segments:
77
73
  - 0
78
74
  version: "0"
79
75
  required_rubygems_version: !ruby/object:Gem::Requirement
80
- none: false
81
76
  requirements:
82
77
  - - ">="
83
78
  - !ruby/object:Gem::Version
84
- hash: 3
85
79
  segments:
86
80
  - 0
87
81
  version: "0"
88
82
  requirements: []
89
83
 
90
84
  rubyforge_project:
91
- rubygems_version: 1.6.2
85
+ rubygems_version: 1.3.6
92
86
  signing_key:
93
87
  specification_version: 3
94
88
  summary: Toast adds a RESTful interface to ActiveRecord models in Ruby on Rails.