toast 0.9.5 → 1.0.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.
- 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
|