toast 0.2.0 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
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.