toast 0.9.5 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +171 -86
- data/config/routes.rb +1 -27
- data/lib/generators/toast/USAGE +1 -0
- data/lib/generators/toast/templates/toast-api.rb.erb +10 -0
- data/lib/generators/toast/toast_generator.rb +9 -0
- data/lib/toast/canonical_request.rb +179 -0
- data/lib/toast/collection_request.rb +161 -0
- data/lib/toast/config_dsl/association.rb +72 -0
- data/lib/toast/config_dsl/base.rb +50 -0
- data/lib/toast/config_dsl/collection.rb +45 -0
- data/lib/toast/config_dsl/common.rb +38 -0
- data/lib/toast/config_dsl/default_handlers.rb +83 -0
- data/lib/toast/config_dsl/expose.rb +176 -0
- data/lib/toast/config_dsl/settings.rb +35 -0
- data/lib/toast/config_dsl/single.rb +15 -0
- data/lib/toast/config_dsl/via_verb.rb +49 -0
- data/lib/toast/config_dsl.rb +60 -225
- data/lib/toast/engine.rb +19 -30
- data/lib/toast/errors.rb +41 -0
- data/lib/toast/http_range.rb +17 -0
- data/lib/toast/plural_assoc_request.rb +285 -0
- data/lib/toast/rack_app.rb +133 -0
- data/lib/toast/request_helpers.rb +134 -0
- data/lib/toast/single_request.rb +66 -0
- data/lib/toast/singular_assoc_request.rb +207 -0
- data/lib/toast/version.rb +2 -2
- data/lib/toast.rb +100 -1
- metadata +83 -116
- data/app/controller/toast_controller.rb +0 -103
- data/lib/toast/active_record_extensions.rb +0 -85
- data/lib/toast/association.rb +0 -219
- data/lib/toast/collection.rb +0 -139
- data/lib/toast/record.rb +0 -123
- data/lib/toast/resource.rb +0 -175
- data/lib/toast/single.rb +0 -89
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'toast/request_helpers'
|
2
|
+
require 'toast/http_range'
|
3
|
+
|
4
|
+
class Toast::CollectionRequest
|
5
|
+
include Toast::RequestHelpers
|
6
|
+
include Toast::Errors
|
7
|
+
|
8
|
+
def initialize config, base_config, auth, request
|
9
|
+
@config = config
|
10
|
+
@base_config = base_config
|
11
|
+
@base_uri = base_uri(request)
|
12
|
+
@verb = request.request_method.downcase
|
13
|
+
@requested_range = Toast::HttpRange.new(request.env['HTTP_RANGE'])
|
14
|
+
@selected_attributes = request.query_parameters.delete(:toast_select).try(:split,',')
|
15
|
+
@uri_params = request.query_parameters
|
16
|
+
@auth = auth
|
17
|
+
@request = request
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond
|
21
|
+
if @verb.in? %w(get post)
|
22
|
+
self.send(@verb)
|
23
|
+
else
|
24
|
+
response :method_not_allowed,
|
25
|
+
headers: {'Allow' => allowed_methods(@base_config)},
|
26
|
+
msg: "method #{@verb.upcase} not supported for collection URIs"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def get
|
32
|
+
if @config.via_get.nil?
|
33
|
+
# not declared
|
34
|
+
response :method_not_allowed,
|
35
|
+
headers: {'Allow' => allowed_methods(@config)},
|
36
|
+
msg: "GET not configured"
|
37
|
+
else
|
38
|
+
begin
|
39
|
+
|
40
|
+
range_start = @requested_range.start
|
41
|
+
window = if (@requested_range.size.nil? || @requested_range.size > @config.max_window)
|
42
|
+
@config.max_window
|
43
|
+
else
|
44
|
+
@requested_range.size
|
45
|
+
end
|
46
|
+
|
47
|
+
relation = call_handler(@config.via_get.handler, @uri_params)
|
48
|
+
call_allow(@config.via_get.permissions,
|
49
|
+
@auth, relation, @uri_params)
|
50
|
+
|
51
|
+
if relation.is_a?(ActiveRecord::Relation) and
|
52
|
+
relation.model == @config.base_model_class
|
53
|
+
|
54
|
+
# count = relation.count doesn't always work
|
55
|
+
# fix problematic select extensions for counting (-> { select(...) })
|
56
|
+
# this fails if the where clause depends on the the extended select
|
57
|
+
count = relation.count_by_sql relation.to_sql.sub(/SELECT.+FROM/,'SELECT COUNT(*) FROM')
|
58
|
+
headers = {"Content-Type" => @config.media_type}
|
59
|
+
|
60
|
+
if count > 0
|
61
|
+
range_end = if (range_start + window - 1) > (count - 1) # behind last
|
62
|
+
count - 1
|
63
|
+
else
|
64
|
+
(range_start + window - 1)
|
65
|
+
end
|
66
|
+
|
67
|
+
headers[ "Content-Range"] = "items=#{range_start}-#{range_end}/#{count}"
|
68
|
+
end
|
69
|
+
|
70
|
+
response :ok,
|
71
|
+
headers: headers,
|
72
|
+
body: represent(relation.limit(window).offset(range_start), @base_config),
|
73
|
+
msg: "sent #{count} of #{@config.mode_class}"
|
74
|
+
|
75
|
+
else
|
76
|
+
# wrong class/model_class
|
77
|
+
response :internal_server_error,
|
78
|
+
msg: "collection method returned #{relation.class}, expected ActiveRecord::Relation of #{@config.base_model_class}"
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
rescue NotAllowed => error
|
83
|
+
return response :unauthorized,
|
84
|
+
msg: "not authorized by allow block in: #{error.source_location}"
|
85
|
+
|
86
|
+
rescue BadRequest => error
|
87
|
+
response :bad_request, msg: "`#{error.message}' in: #{error.source_location}"
|
88
|
+
|
89
|
+
rescue HandlerError => error
|
90
|
+
return response :internal_server_error,
|
91
|
+
msg: "exception raised in via_get handler: `#{error.orig_error.message}' in #{error.source_location}"
|
92
|
+
rescue AllowError => error
|
93
|
+
return response :internal_server_error,
|
94
|
+
msg: "exception raised in allow block: `#{error.orig_error.message}' in #{error.source_location}"
|
95
|
+
rescue => error
|
96
|
+
response :internal_server_error,
|
97
|
+
msg: "exception from via_get handler in: #{error.backtrace.first.sub(Rails.root.to_s+'/', '')}: #{error.message}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def post
|
103
|
+
if @config.via_post.nil?
|
104
|
+
# not declared
|
105
|
+
response :method_not_allowed,
|
106
|
+
headers: {'Allow' => allowed_methods(@config)},
|
107
|
+
msg: "POST not configured"
|
108
|
+
else
|
109
|
+
begin
|
110
|
+
payload = JSON.parse(@request.body.read)
|
111
|
+
|
112
|
+
call_allow(@config.via_post.permissions,
|
113
|
+
@auth, nil, @uri_params)
|
114
|
+
|
115
|
+
# remove all attributes not in writables from payload
|
116
|
+
payload.delete_if do |attr,val|
|
117
|
+
unless attr.to_sym.in?(@base_config.writables)
|
118
|
+
Toast.logger.warn "<POST #{@request.fullpath}> received attribute `#{attr}' is not writable or unknown"
|
119
|
+
true
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
new_instance = call_handler(@config.via_post.handler, payload, @uri_params)
|
124
|
+
|
125
|
+
if new_instance.persisted?
|
126
|
+
response :created,
|
127
|
+
headers: {"Content-Type" => @config.media_type},
|
128
|
+
msg: "created #{new_instance.class}##{new_instance.id}",
|
129
|
+
body: represent(new_instance, @base_config)
|
130
|
+
else
|
131
|
+
message = new_instance.errors.count > 0 ?
|
132
|
+
": " + new_instance.errors.full_messages.join(',') : ''
|
133
|
+
|
134
|
+
response :conflict,
|
135
|
+
msg: "creation of #{new_instance.class} aborted#{message}"
|
136
|
+
end
|
137
|
+
|
138
|
+
rescue JSON::ParserError => error
|
139
|
+
return response :internal_server_error, msg: "expect JSON body"
|
140
|
+
|
141
|
+
rescue NotAllowed => error
|
142
|
+
return response :unauthorized,
|
143
|
+
msg: "not authorized by allow block in: #{error.source_location}"
|
144
|
+
|
145
|
+
rescue BadRequest => error
|
146
|
+
response :bad_request, msg: "`#{error.message}' in: #{error.source_location}"
|
147
|
+
|
148
|
+
rescue HandlerError => error
|
149
|
+
return response :internal_server_error,
|
150
|
+
msg: "exception raised in via_post handler: `#{error.orig_error.message}' in #{error.source_location}"
|
151
|
+
rescue AllowError => error
|
152
|
+
return response :internal_server_error,
|
153
|
+
msg: "exception raised in allow block: `#{error.orig_error.message}' in #{error.source_location}"
|
154
|
+
rescue => error
|
155
|
+
response :internal_server_error,
|
156
|
+
msg: "exception from via_post handler in: #{error.backtrace.first.sub(Rails.root.to_s+'/', '')}: #{error.message}"
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'toast/config_dsl/via_verb.rb'
|
2
|
+
require 'toast/config_dsl/default_handlers.rb'
|
3
|
+
|
4
|
+
class Toast::ConfigDSL::Association
|
5
|
+
include Toast::ConfigDSL::Common
|
6
|
+
include Toast::ConfigDSL::DefaultHandlers
|
7
|
+
|
8
|
+
def via_get &block
|
9
|
+
stack_push 'via_get' do
|
10
|
+
@config_data.via_get =
|
11
|
+
OpenStruct.new(permissions: [],
|
12
|
+
handler: (@config_data.singular ?
|
13
|
+
singular_assoc_get_handler(@config_data.assoc_name) :
|
14
|
+
plural_assoc_get_handler(@config_data.assoc_name)))
|
15
|
+
|
16
|
+
Toast::ConfigDSL::ViaVerb.new(@config_data.via_get).instance_eval &block
|
17
|
+
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def via_post &block
|
23
|
+
stack_push 'via_post' do
|
24
|
+
if @config_data.singular
|
25
|
+
raise_config_error "`via_post' is not allowed for singular associations"
|
26
|
+
end
|
27
|
+
|
28
|
+
@config_data.via_post = OpenStruct.new(permissions: [],
|
29
|
+
handler: plural_assoc_post_handler(@config_data.assoc_name))
|
30
|
+
|
31
|
+
Toast::ConfigDSL::ViaVerb.new(@config_data.via_post).instance_eval &block
|
32
|
+
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def via_link &block
|
38
|
+
stack_push 'via_link' do
|
39
|
+
@config_data.via_link = OpenStruct.new(permissions: [],
|
40
|
+
handler: (@config_data.singular ?
|
41
|
+
singular_assoc_link_handler(@config_data.assoc_name) :
|
42
|
+
plural_assoc_link_handler(@config_data.assoc_name)))
|
43
|
+
|
44
|
+
Toast::ConfigDSL::ViaVerb.new(@config_data.via_link).instance_eval &block
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def via_unlink &block
|
50
|
+
stack_push 'via_unlink' do
|
51
|
+
@config_data.via_unlink = OpenStruct.new(permissions: [],
|
52
|
+
handler: (@config_data.singular ?
|
53
|
+
singular_assoc_unlink_handler(@config_data.assoc_name) :
|
54
|
+
plural_assoc_unlink_handler(@config_data.assoc_name)))
|
55
|
+
|
56
|
+
Toast::ConfigDSL::ViaVerb.new(@config_data.via_unlink).instance_eval &block
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def max_window size
|
62
|
+
stack_push 'max_window' do
|
63
|
+
if size.is_a?(Fixnum) and size > 0
|
64
|
+
@config_data.max_window = size
|
65
|
+
elsif size == :unlimited
|
66
|
+
@config_data.max_window = 10**6 # yes that's inifinity
|
67
|
+
else
|
68
|
+
raise_config_error 'max_window must a positive integer or :unlimited'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'toast/config_dsl/expose'
|
2
|
+
|
3
|
+
class Toast::ConfigDSL::Base
|
4
|
+
include Toast::ConfigDSL::Common
|
5
|
+
|
6
|
+
def expose model_class, as: 'application/json', under: '', &block
|
7
|
+
|
8
|
+
stack_push "expose(#{model_class})" do
|
9
|
+
|
10
|
+
begin
|
11
|
+
unless model_class.new.is_a?(ActiveRecord::Base)
|
12
|
+
raise_config_error 'Directive requires an ActiveRecord::Base descendant.'
|
13
|
+
end
|
14
|
+
rescue ActiveRecord::StatementInvalid => error
|
15
|
+
# may be raised when tables are not setup yet during database setup
|
16
|
+
raise_config_error error.message
|
17
|
+
end
|
18
|
+
|
19
|
+
unless block_given?
|
20
|
+
raise_config_error 'Block expected.'
|
21
|
+
end
|
22
|
+
|
23
|
+
config_data = OpenStruct.new
|
24
|
+
|
25
|
+
config_data.instance_eval do
|
26
|
+
self.source_location = block.source_location.first
|
27
|
+
|
28
|
+
self.model_class = model_class
|
29
|
+
self.media_type = as
|
30
|
+
self.url_path_prefix = under.split('/').delete_if(&:blank?)
|
31
|
+
|
32
|
+
# defaults
|
33
|
+
self.readables = []
|
34
|
+
self.writables = []
|
35
|
+
self.collections = {}
|
36
|
+
self.singles = {}
|
37
|
+
self.associations = {}
|
38
|
+
end
|
39
|
+
|
40
|
+
if Toast.expositions.detect{|exp| exp.model_class == config_data.model_class}
|
41
|
+
raise_config_error "Model class #{exp.model_class} has already another configuration."
|
42
|
+
end
|
43
|
+
|
44
|
+
Toast.expositions << config_data
|
45
|
+
|
46
|
+
# evaluate expose block
|
47
|
+
Toast::ConfigDSL::Expose.new(config_data).instance_eval &block
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'toast/config_dsl/via_verb.rb'
|
2
|
+
require 'toast/config_dsl/default_handlers.rb'
|
3
|
+
|
4
|
+
class Toast::ConfigDSL::Collection
|
5
|
+
include Toast::ConfigDSL::Common
|
6
|
+
include Toast::ConfigDSL::DefaultHandlers
|
7
|
+
|
8
|
+
def via_get &block
|
9
|
+
stack_push 'via_get' do
|
10
|
+
@config_data.via_get =
|
11
|
+
OpenStruct.new(permissions: [],
|
12
|
+
handler: collection_get_handler(@config_data.base_model_class,
|
13
|
+
@config_data.collection_name))
|
14
|
+
|
15
|
+
Toast::ConfigDSL::ViaVerb.new(@config_data.via_get).instance_eval &block
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def via_post &block
|
21
|
+
stack_push 'via_post' do
|
22
|
+
unless @config_data.collection_name == :all
|
23
|
+
raise_config_error "POST is supported for the `all' collection only"
|
24
|
+
end
|
25
|
+
|
26
|
+
@config_data.via_post = OpenStruct.new(permissions: [],
|
27
|
+
handler: collection_post_handler(@config_data.base_model_class))
|
28
|
+
|
29
|
+
Toast::ConfigDSL::ViaVerb.new(@config_data.via_post).instance_eval &block
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def max_window size
|
35
|
+
stack_push 'max_window' do
|
36
|
+
if size.is_a?(Integer) and size > 0
|
37
|
+
@config_data.max_window = size
|
38
|
+
elsif size == :unlimited
|
39
|
+
@config_data.max_window = 10**6 # yes that's inifinity
|
40
|
+
else
|
41
|
+
raise_config_error 'max_window must a positive integer or :unlimited'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Toast::ConfigDSL::Common
|
2
|
+
def initialize config_data=nil
|
3
|
+
@config_data = config_data
|
4
|
+
end
|
5
|
+
|
6
|
+
def method_missing method, *args
|
7
|
+
raise_config_error "Unknown directive: `#{method}'"
|
8
|
+
end
|
9
|
+
|
10
|
+
def check_symbol_list list
|
11
|
+
unless list.is_a?(Array) and list.all?{|x| x.is_a? Symbol}
|
12
|
+
raise_config_error "Directive requires a list of symbols.\n"+
|
13
|
+
" #{list.map{|x| x.inspect}.join(', ')} ?"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def raise_config_error message=''
|
18
|
+
match = caller.grep(/#{Toast::ConfigDSL.cfg_name}/).first
|
19
|
+
|
20
|
+
file_line = if match.nil?
|
21
|
+
Toast::ConfigDSL.cfg_name
|
22
|
+
else
|
23
|
+
match.split(':in').first
|
24
|
+
end
|
25
|
+
|
26
|
+
message += "\n directive: /#{Toast::ConfigDSL.stack.join('/')}"
|
27
|
+
message += "\n in file : #{file_line}"
|
28
|
+
|
29
|
+
Toast.raise_config_error message
|
30
|
+
end
|
31
|
+
|
32
|
+
# ... to not forget to pop use:
|
33
|
+
def stack_push level, &block
|
34
|
+
Toast::ConfigDSL.stack << level
|
35
|
+
yield
|
36
|
+
Toast::ConfigDSL.stack.pop
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Toast::ConfigDSL::DefaultHandlers
|
2
|
+
def plural_assoc_get_handler assoc_name
|
3
|
+
lambda do |source, uri_params|
|
4
|
+
source.send(assoc_name)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def singular_assoc_get_handler assoc_name
|
9
|
+
lambda do |source, uri_params|
|
10
|
+
source.send(assoc_name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def single_get_handler model_class, single_name
|
15
|
+
lambda do |uri_params|
|
16
|
+
model_class.send(single_name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def collection_get_handler model_class, coll_name
|
21
|
+
lambda do |uri_params|
|
22
|
+
model_class.send(coll_name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def canonical_get_handler
|
27
|
+
lambda do |model, uri_params|
|
28
|
+
model
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def canonical_patch_handler
|
33
|
+
lambda do |model, payload, uri_params|
|
34
|
+
model.update payload
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def collection_post_handler model_class
|
39
|
+
lambda do |payload, uri_params|
|
40
|
+
model_class.create payload
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def plural_assoc_post_handler assoc_name
|
45
|
+
lambda do |source, payload, uri_params|
|
46
|
+
source.send(assoc_name).create payload
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def canonical_delete_handler
|
51
|
+
lambda do |model, uri_params|
|
52
|
+
model.destroy
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def singular_assoc_link_handler assoc_name
|
57
|
+
lambda do |source, target, uri_params|
|
58
|
+
source.send("#{assoc_name}=", target)
|
59
|
+
source.save
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def plural_assoc_link_handler assoc_name
|
64
|
+
lambda do |source, target, uri_params|
|
65
|
+
source.send(assoc_name) << target
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def singular_assoc_unlink_handler assoc_name
|
70
|
+
lambda do |source, target, uri_params|
|
71
|
+
if source.send(assoc_name) == target
|
72
|
+
source.send("#{assoc_name}=", nil)
|
73
|
+
source.save
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def plural_assoc_unlink_handler name
|
79
|
+
lambda do |source, target, uri_params|
|
80
|
+
source.send(name).delete(target)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'toast/config_dsl/association'
|
2
|
+
require 'toast/config_dsl/collection'
|
3
|
+
require 'toast/config_dsl/single'
|
4
|
+
|
5
|
+
# context for expose{} blocks
|
6
|
+
class Toast::ConfigDSL::Expose
|
7
|
+
include Toast::ConfigDSL::Common
|
8
|
+
include Toast::ConfigDSL::DefaultHandlers
|
9
|
+
|
10
|
+
# directives
|
11
|
+
def writables *attributes
|
12
|
+
stack_push "writables(#{attributes.map(&:inspect).join(',')})" do
|
13
|
+
|
14
|
+
check_symbol_list attributes
|
15
|
+
|
16
|
+
model_class = @config_data.model_class
|
17
|
+
|
18
|
+
attributes.each do |attr|
|
19
|
+
|
20
|
+
model = model_class.new
|
21
|
+
setter = (attr.to_s + '=').to_sym
|
22
|
+
|
23
|
+
unless (model.respond_to?(setter) and model.method(setter).arity == 1)
|
24
|
+
raise_config_error "Exposed attribute setter not found: `#{model_class.name}##{attr}='. Typo?"
|
25
|
+
end
|
26
|
+
|
27
|
+
unless (model.respond_to?(attr) and model.method(attr).arity.in?([-1,0]))
|
28
|
+
raise_config_error "Exposed attribute getter not found `#{model_class.name}##{attr}'. Typo?"
|
29
|
+
end
|
30
|
+
|
31
|
+
@config_data.writables << attr
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def readables *attributes
|
37
|
+
stack_push "readables(#{attributes.map(&:inspect).join(',')})" do
|
38
|
+
|
39
|
+
check_symbol_list attributes
|
40
|
+
|
41
|
+
model_class = @config_data.model_class
|
42
|
+
|
43
|
+
attributes.each do |attr|
|
44
|
+
model = model_class.new
|
45
|
+
|
46
|
+
unless (model.respond_to?(attr) and model.method(attr).arity.in?([-1,0]))
|
47
|
+
raise_config_error "Exposed attribute getter not found `#{model_class.name}##{attr}'. Typo?"
|
48
|
+
end
|
49
|
+
|
50
|
+
@config_data.readables << attr
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def via_get &block
|
56
|
+
stack_push 'via_get' do
|
57
|
+
@config_data.via_get =
|
58
|
+
OpenStruct.new(permissions: [],
|
59
|
+
handler: canonical_get_handler)
|
60
|
+
|
61
|
+
Toast::ConfigDSL::ViaVerb.new(@config_data.via_get).instance_eval &block
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def via_patch &block
|
67
|
+
stack_push 'via_patch' do
|
68
|
+
@config_data.via_patch =
|
69
|
+
OpenStruct.new(permissions: [],
|
70
|
+
handler: canonical_patch_handler)
|
71
|
+
|
72
|
+
Toast::ConfigDSL::ViaVerb.new(@config_data.via_patch).instance_eval &block
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def via_delete &block
|
78
|
+
stack_push 'via_delete' do
|
79
|
+
@config_data.via_delete =
|
80
|
+
OpenStruct.new(permissions: [],
|
81
|
+
handler: canonical_delete_handler)
|
82
|
+
|
83
|
+
Toast::ConfigDSL::ViaVerb.new(@config_data.via_delete).instance_eval &block
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def collection name, as: 'application/json', &block
|
90
|
+
stack_push "collection(#{name.inspect})" do
|
91
|
+
model_class = @config_data.model_class
|
92
|
+
|
93
|
+
unless block_given?
|
94
|
+
raise_config_error 'Block expected.'
|
95
|
+
end
|
96
|
+
|
97
|
+
unless name.is_a?(Symbol)
|
98
|
+
raise_config_error "collection name expected as Symbol"
|
99
|
+
end
|
100
|
+
|
101
|
+
unless model_class.respond_to?(name)
|
102
|
+
raise_config_error "`#{name}' must be a callable class method."
|
103
|
+
end
|
104
|
+
|
105
|
+
@config_data.collections[name] =
|
106
|
+
OpenStruct.new(base_model_class: model_class,
|
107
|
+
collection_name: name,
|
108
|
+
media_type: as,
|
109
|
+
max_window: Toast.settings.max_window)
|
110
|
+
|
111
|
+
Toast::ConfigDSL::Collection.new(@config_data.collections[name]).
|
112
|
+
instance_eval(&block)
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def single name, &block
|
118
|
+
stack_push "single(#{name.inspect})" do
|
119
|
+
|
120
|
+
model_class = @config_data.model_class
|
121
|
+
|
122
|
+
unless model_class.respond_to?(name)
|
123
|
+
raise_config_error "`#{name}' must be a callable class method."
|
124
|
+
end
|
125
|
+
|
126
|
+
unless block_given?
|
127
|
+
raise_config_error 'Block expected.'
|
128
|
+
end
|
129
|
+
|
130
|
+
@config_data.singles[name] = OpenStruct.new(name: name, model_class: model_class)
|
131
|
+
|
132
|
+
Toast::ConfigDSL::Single.new(@config_data.singles[name]).
|
133
|
+
instance_eval(&block)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def association name, as: 'application/json', &block
|
138
|
+
stack_push "association(#{name.inspect})" do
|
139
|
+
|
140
|
+
|
141
|
+
model_class = @config_data.model_class
|
142
|
+
|
143
|
+
unless name.is_a?(Symbol)
|
144
|
+
raise_config_error "association name expected as Symbol"
|
145
|
+
end
|
146
|
+
|
147
|
+
unless model_class.reflections[name.to_s].
|
148
|
+
try(:macro).in?([:has_many,
|
149
|
+
:has_one,
|
150
|
+
:belongs_to,
|
151
|
+
:has_and_belongs_to_many])
|
152
|
+
raise_config_error 'Association expected'
|
153
|
+
end
|
154
|
+
|
155
|
+
unless block_given?
|
156
|
+
raise_config_error 'Block expected.'
|
157
|
+
end
|
158
|
+
|
159
|
+
target_model_class = model_class.reflections[name.to_s].klass
|
160
|
+
macro = model_class.reflections[name.to_s].macro
|
161
|
+
singular = macro.in? [:belongs_to, :has_one]
|
162
|
+
|
163
|
+
@config_data.associations[name] =
|
164
|
+
OpenStruct.new(base_model_class: model_class,
|
165
|
+
target_model_class: target_model_class,
|
166
|
+
assoc_name: name,
|
167
|
+
media_type: as,
|
168
|
+
macro: macro,
|
169
|
+
singular: singular,
|
170
|
+
max_window: singular ? nil : Toast.settings.max_window)
|
171
|
+
|
172
|
+
Toast::ConfigDSL::Association.new(@config_data.associations[name]).
|
173
|
+
instance_eval(&block)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'toast/errors'
|
2
|
+
|
3
|
+
class Toast::ConfigDSL::Settings
|
4
|
+
include Toast::ConfigDSL::Common
|
5
|
+
|
6
|
+
class AuthenticateContext
|
7
|
+
def fail_with hash
|
8
|
+
raise Toast::Errors::CustomAuthFailure.new(hash)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def toast_settings &block
|
13
|
+
stack_push 'toast_settings' do
|
14
|
+
self.instance_eval &block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def max_window size
|
19
|
+
if size.is_a?(Integer) and size > 0
|
20
|
+
Toast.settings.max_window = size
|
21
|
+
elsif size == :unlimited
|
22
|
+
Toast.settings.max_window = 10**6 # yes that's inifinity
|
23
|
+
else
|
24
|
+
raise_config_error 'max_window must a positive integer or :unlimited'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def link_unlink_via_post boolean
|
29
|
+
Toast.settings.link_unlink_via_post = boolean
|
30
|
+
end
|
31
|
+
|
32
|
+
def authenticate &block
|
33
|
+
Toast.settings.authenticate = block
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Toast::ConfigDSL::Single
|
2
|
+
include Toast::ConfigDSL::Common
|
3
|
+
include Toast::ConfigDSL::DefaultHandlers
|
4
|
+
|
5
|
+
def via_get &block
|
6
|
+
stack_push 'via_get' do
|
7
|
+
|
8
|
+
@config_data.via_get =
|
9
|
+
OpenStruct.new(permissions: [],
|
10
|
+
handler: single_get_handler(@config_data.model_class, @config_data.name))
|
11
|
+
|
12
|
+
Toast::ConfigDSL::ViaVerb.new(@config_data.via_get).instance_eval &block
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|