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,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
|