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,49 @@
|
|
1
|
+
class Toast::ConfigDSL::ViaVerb
|
2
|
+
include Toast::ConfigDSL::Common
|
3
|
+
|
4
|
+
def allow &block
|
5
|
+
stack_push 'allow' do
|
6
|
+
|
7
|
+
unless block.arity.in? [3, -2, -1]
|
8
|
+
raise_config_error 'Allow rule must list arguments as |*all|, |auth, *rest| or |auth, request_model, uri_params|'
|
9
|
+
end
|
10
|
+
|
11
|
+
@config_data.permissions << block
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def handler &block
|
16
|
+
stack_push 'handler' do
|
17
|
+
|
18
|
+
# arity check for custom handlers
|
19
|
+
expected_arity = case Toast::ConfigDSL.stack[-3]
|
20
|
+
when /\Asingle/
|
21
|
+
1
|
22
|
+
when /\Acollection/
|
23
|
+
case Toast::ConfigDSL.stack[-2]
|
24
|
+
when 'via_get' then 1
|
25
|
+
when 'via_post' then 2
|
26
|
+
end
|
27
|
+
|
28
|
+
when /\Aassociation/
|
29
|
+
case Toast::ConfigDSL.stack[-2]
|
30
|
+
when 'via_get' then 2
|
31
|
+
when /\Avia_(post|link|unlink)\z/ then 3
|
32
|
+
end
|
33
|
+
|
34
|
+
when /\Aexpose/
|
35
|
+
case Toast::ConfigDSL.stack[-2]
|
36
|
+
when /\Avia_(get|delete)\z/ then 2
|
37
|
+
when 'via_patch' then 3
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
if block.arity != expected_arity
|
43
|
+
raise_config_error "Handler block must take exactly #{expected_arity} argument#{expected_arity==1?'':'s'}"
|
44
|
+
end
|
45
|
+
|
46
|
+
@config_data.handler = block
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/toast/config_dsl.rb
CHANGED
@@ -1,233 +1,68 @@
|
|
1
1
|
module Toast
|
2
2
|
module ConfigDSL
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
@field_comments.merge! ConfigDSL.get_comments(arg, 'ro')
|
37
|
-
@readables.push *ConfigDSL.normalize(arg,"readables")
|
38
|
-
end
|
39
|
-
|
40
|
-
# args: Array or :all, :except => Array
|
41
|
-
def readables *arg
|
42
|
-
return(@readables) if arg.empty?
|
43
|
-
self.readables = arg
|
44
|
-
end
|
45
|
-
|
46
|
-
def writables= arg
|
47
|
-
@field_comments.merge! ConfigDSL.get_comments(arg, 'rw')
|
48
|
-
@writables.push *ConfigDSL.normalize(arg,"writables")
|
49
|
-
@model.attr_accessible *@writables
|
50
|
-
end
|
51
|
-
|
52
|
-
# args: Array or :all, :except => Array
|
53
|
-
def writables *arg
|
54
|
-
return(@writables) if arg.empty?
|
55
|
-
self.writables = arg
|
56
|
-
end
|
57
|
-
|
58
|
-
def deletable
|
59
|
-
@deletable = true
|
60
|
-
end
|
61
|
-
|
62
|
-
def deletable?
|
63
|
-
@deletable
|
64
|
-
end
|
65
|
-
|
66
|
-
def postable
|
67
|
-
@postable = true
|
68
|
-
end
|
69
|
-
|
70
|
-
def postable?
|
71
|
-
@postable
|
72
|
-
end
|
73
|
-
|
74
|
-
def pass_params_to= arg
|
75
|
-
@pass_params_to.push *ConfigDSL.normalize(arg,"pass_params_to")
|
76
|
-
end
|
77
|
-
|
78
|
-
def pass_params_to *arg
|
79
|
-
return(@pass_params_to) if arg.empty?
|
80
|
-
self.pass_params_to = arg
|
81
|
-
end
|
82
|
-
|
83
|
-
def collections= collections=[]
|
84
|
-
@collections = ConfigDSL.normalize(collections, "collections")
|
85
|
-
end
|
86
|
-
|
87
|
-
def collections *arg
|
88
|
-
return(@collections) if arg.empty?
|
89
|
-
self.collections = arg
|
90
|
-
end
|
91
|
-
|
92
|
-
def singles= singles=[]
|
93
|
-
@singles = ConfigDSL.normalize(singles, "singles")
|
94
|
-
end
|
95
|
-
|
96
|
-
def singles *arg
|
97
|
-
return(@singles) if arg.empty?
|
98
|
-
self.singles = arg
|
99
|
-
end
|
100
|
-
|
101
|
-
def in_collection &block
|
102
|
-
if block_given?
|
103
|
-
Blockenspiel.invoke( block, @in_collection)
|
104
|
-
else
|
105
|
-
@in_collection
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def apidoc arg=nil
|
110
|
-
return(@apidoc) if arg.nil?
|
111
|
-
raise "Toast Config Error (#{model.class}): apidoc argument must be a Hash." unless arg.is_a?(Hash)
|
112
|
-
@apidoc = arg
|
113
|
-
end
|
114
|
-
|
115
|
-
def paginate collection_name, options={}
|
116
|
-
options.reverse_merge! :page_size => 30
|
117
|
-
|
118
|
-
@paginations[collection_name.to_s] = {:page_size => options[:page_size]}
|
119
|
-
end
|
120
|
-
|
121
|
-
# non DSL methods
|
122
|
-
dsl_methods false
|
123
|
-
|
124
|
-
def field_comments
|
125
|
-
@field_comments
|
126
|
-
end
|
127
|
-
|
128
|
-
def paginations
|
129
|
-
@paginations
|
130
|
-
end
|
131
|
-
|
4
|
+
# the currently process config file name
|
5
|
+
mattr_accessor :cfg_name
|
6
|
+
|
7
|
+
# traces the descent into the configuration, while gathering config items
|
8
|
+
mattr_accessor :stack
|
9
|
+
|
10
|
+
# Interprets one config file and stores config info in the @expositions Array using OpenStruct.
|
11
|
+
#
|
12
|
+
# Params:
|
13
|
+
# +config+:: [String] The configuration file's content as string
|
14
|
+
# +fname+:: [String] The file name of the config file with path relative to +Rails.root+
|
15
|
+
#
|
16
|
+
# Return: +nil+
|
17
|
+
def self.get_config _toast_config, _toast_cfg_name
|
18
|
+
|
19
|
+
# using _toast_ prefix for all locals here, because they end up in the
|
20
|
+
# handler/allow block scope (closure)
|
21
|
+
# also freeze them to prevent accidental changes
|
22
|
+
# is there a way to remove closure vars with instance_eval?
|
23
|
+
|
24
|
+
_toast_config.freeze
|
25
|
+
_toast_cfg_name.freeze
|
26
|
+
|
27
|
+
@@cfg_name = _toast_cfg_name
|
28
|
+
@@stack = []
|
29
|
+
|
30
|
+
_toast_base = ConfigDSL::Base.new
|
31
|
+
_toast_base.freeze
|
32
|
+
_toast_base.instance_eval(_toast_config, _toast_cfg_name)
|
33
|
+
|
34
|
+
rescue ArgumentError, NameError => _toast_error
|
35
|
+
_toast_base.raise_config_error _toast_error.message
|
132
36
|
end
|
133
37
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
def writables *arg
|
157
|
-
self.writables = 42
|
158
|
-
end
|
159
|
-
|
160
|
-
def writables= arg
|
161
|
-
puts
|
162
|
-
puts "Toast Config Warning (#{@model.class}): Defining \"writables\" in collection definition has no effect."
|
163
|
-
puts
|
164
|
-
end
|
165
|
-
|
166
|
-
def namespace *arg
|
167
|
-
self.writables = 42
|
168
|
-
end
|
169
|
-
|
170
|
-
def namespace= arg
|
171
|
-
puts
|
172
|
-
puts "Toast Config Warning (#{@model.class}): Defining \"namespace\" in collection definition has no effect."
|
173
|
-
puts
|
174
|
-
end
|
175
|
-
|
176
|
-
def exposed_attributes
|
177
|
-
assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
|
178
|
-
(@readables + @writables).uniq.select{|f| !assocs.include?(f)}
|
179
|
-
end
|
180
|
-
|
181
|
-
def exposed_associations
|
182
|
-
assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
|
183
|
-
(@readables + @writables).uniq.select{|f| assocs.include?(f)}
|
184
|
-
end
|
185
|
-
|
186
|
-
end # class InCollection
|
187
|
-
|
188
|
-
# Helper
|
189
|
-
|
190
|
-
# checks if list is made of symbols and strings
|
191
|
-
# converts a single value to an Array
|
192
|
-
# converts all symbols to strings
|
193
|
-
def self.normalize list, name
|
194
|
-
# make single element list
|
195
|
-
list = [list] unless list.is_a? Array
|
196
|
-
|
197
|
-
# remove all comments
|
198
|
-
list = list.map{|x| x.is_a?(Array) ? x.first : x}
|
199
|
-
|
200
|
-
# flatten
|
201
|
-
list = [list].flatten
|
202
|
-
|
203
|
-
# check class and stringify
|
204
|
-
list.map do |x|
|
205
|
-
if (!x.is_a?(Symbol) && !x.is_a?(String))
|
206
|
-
raise "Toast Config Error: '#{name}' should be a list of Symbols"
|
207
|
-
else
|
208
|
-
x.to_s
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
# fields (readables and writables) can have comments , if passed by Arrays of the form:
|
214
|
-
# [symbol, comment]
|
215
|
-
# returns a Hash of all commented or uncommented fields as:
|
216
|
-
# { FIELD_NAME => {:comment => COMMENT, :type => type } }
|
217
|
-
def self.get_comments arg, access
|
218
|
-
comments = {}
|
219
|
-
|
220
|
-
if arg.is_a? Array
|
221
|
-
arg.each do |f|
|
222
|
-
if f.is_a? Array
|
223
|
-
comments[ f.first.to_s ] = {:comment => f[1].to_s, :access => access, :type => f[2]}
|
224
|
-
else
|
225
|
-
comments[ f.to_s ] = {:comment => '[ no comment ]', :access => access, :type => nil}
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
comments
|
38
|
+
# Interprets the global settings file and stores data using OpenStruct
|
39
|
+
# Params:
|
40
|
+
# +config+:: [String] The configuration file's content as string
|
41
|
+
# +fname+:: [String] The file name of the config file with path relative to +Rails.root+
|
42
|
+
#
|
43
|
+
# Return: +nil+
|
44
|
+
def self.get_settings config, cfg_name
|
45
|
+
@@cfg_name = cfg_name
|
46
|
+
@@stack = []
|
47
|
+
|
48
|
+
# defaults
|
49
|
+
Toast.settings = OpenStruct.new
|
50
|
+
Toast.settings.max_window = 42
|
51
|
+
Toast.settings.link_unlink_via_post = false
|
52
|
+
Toast.settings.authenticate = lambda{|r| r}
|
53
|
+
|
54
|
+
settings = ConfigDSL::Settings.new
|
55
|
+
settings.instance_eval(config, cfg_name)
|
56
|
+
|
57
|
+
rescue ArgumentError, NameError => error
|
58
|
+
settings.raise_config_error error.message
|
231
59
|
end
|
232
60
|
end
|
233
61
|
end
|
62
|
+
|
63
|
+
require 'toast/config_dsl/common'
|
64
|
+
require 'toast/config_dsl/base'
|
65
|
+
require 'toast/config_dsl/expose'
|
66
|
+
require 'toast/config_dsl/association'
|
67
|
+
require 'toast/config_dsl/via_verb'
|
68
|
+
require 'toast/config_dsl/settings'
|
data/lib/toast/engine.rb
CHANGED
@@ -1,45 +1,34 @@
|
|
1
|
-
require 'toast/active_record_extensions.rb'
|
2
|
-
require 'toast/resource.rb'
|
3
|
-
require 'toast/collection'
|
4
|
-
require 'toast/association'
|
5
|
-
require 'toast/record'
|
6
|
-
require 'toast/single'
|
7
|
-
|
8
|
-
require 'action_dispatch/http/request'
|
9
|
-
require 'rack/accept_media_types'
|
10
|
-
|
11
1
|
module Toast
|
12
2
|
class Engine < Rails::Engine
|
13
3
|
|
14
4
|
# configure our plugin on boot. other extension points such
|
15
5
|
# as configuration, rake tasks, etc, are also available
|
16
6
|
initializer "toast.initialize" do |app|
|
17
|
-
# Add 'acts_as_resource' declaration to ActiveRecord::Base
|
18
|
-
ActiveRecord::Base.extend Toast::ActiveRecordExtensions
|
19
|
-
|
20
|
-
# Load all models in app/models early to setup routing
|
21
|
-
begin
|
22
|
-
Dir["#{Rails.root}/app/models/**/*.rb"].each{|m| require_dependency m }
|
23
7
|
|
24
|
-
|
25
|
-
|
8
|
+
# allow LINK and UNLINK methods
|
9
|
+
if ActionDispatch::Request::HTTP_METHOD_LOOKUP['LINK'] == :link and
|
10
|
+
ActionDispatch::Request::HTTP_METHOD_LOOKUP['UNLINK'] == :unlink
|
11
|
+
Toast.info "INFO: LINK and UNLINK allowed by this Rails version: #{Rails.version}"
|
12
|
+
else
|
13
|
+
ActionDispatch::Request::HTTP_METHOD_LOOKUP['LINK'] = :link
|
14
|
+
ActionDispatch::Request::HTTP_METHOD_LOOKUP['UNLINK'] = :unlink
|
26
15
|
end
|
27
16
|
|
28
|
-
#
|
29
|
-
|
30
|
-
if Rack.release == "1.2"
|
31
|
-
class Rack::Request
|
32
|
-
def base_url
|
33
|
-
url = scheme + "://"
|
34
|
-
url << host
|
17
|
+
# for LINK/UNLINK via POST and x-http-method-override header
|
18
|
+
app.middleware.unshift Rack::MethodOverride
|
35
19
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
20
|
+
# skip init if in test mode: Toast.init should be called in each test
|
21
|
+
unless Rails.env == 'test'
|
22
|
+
begin
|
23
|
+
Toast.info 'Loading Toast'
|
24
|
+
Toast.init
|
25
|
+
Toast.info "Exposed model classes: #{Toast.expositions.map{|e| e.model_class.name}.join(' ')}"
|
40
26
|
|
41
|
-
|
27
|
+
rescue Toast::ConfigError => error
|
28
|
+
error.message.split("\n").each do |line|
|
29
|
+
Toast.info line
|
42
30
|
end
|
31
|
+
Toast.disable
|
43
32
|
end
|
44
33
|
end
|
45
34
|
end
|
data/lib/toast/errors.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Toast::Errors
|
2
|
+
class ConfigNotFound < StandardError; end
|
3
|
+
|
4
|
+
class HandlerError < StandardError
|
5
|
+
attr_accessor :orig_error, :source_location
|
6
|
+
|
7
|
+
def initialize orig_error, source_location
|
8
|
+
@orig_error = orig_error
|
9
|
+
@source_location = source_location
|
10
|
+
super ''
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class AllowError < HandlerError; end
|
15
|
+
|
16
|
+
class NotAllowed < StandardError
|
17
|
+
attr_accessor :source_location
|
18
|
+
|
19
|
+
def initialize source_location
|
20
|
+
@source_location = source_location
|
21
|
+
super ''
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class BadRequest < StandardError
|
26
|
+
attr_accessor :source_location
|
27
|
+
|
28
|
+
def initialize message, source_location
|
29
|
+
@source_location = source_location
|
30
|
+
super message
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class CustomAuthFailure < StandardError
|
35
|
+
attr_accessor :response_data
|
36
|
+
|
37
|
+
def initialize response_data
|
38
|
+
@response_data = response_data
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Toast::HttpRange
|
2
|
+
attr_reader :start, :end, :size
|
3
|
+
|
4
|
+
def initialize range
|
5
|
+
if range.nil?
|
6
|
+
@start = 0
|
7
|
+
@end = nil
|
8
|
+
@size = nil
|
9
|
+
else
|
10
|
+
range =~ /\Aitems=(\d*)-(\d*)/
|
11
|
+
@start = Integer($1) rescue 0
|
12
|
+
@end = Integer($2) rescue nil
|
13
|
+
@end = nil if (@end and (@end < @start))
|
14
|
+
@size = @end - @start + 1 rescue nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|