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.
@@ -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
@@ -1,233 +1,68 @@
1
1
  module Toast
2
2
  module ConfigDSL
3
3
 
4
- class Base
5
- include Blockenspiel::DSL
6
- dsl_attr_accessor :namespace, :media_type
7
-
8
- def initialize model
9
- @model = model
10
- @readables = []
11
- @writables = []
12
- @field_comments = {}
13
- @collections = []
14
- @singles = []
15
- @deletable = false
16
- @postable = false
17
- @pass_params_to = []
18
- @in_collection = ConfigDSL::InCollection.new model, self
19
- @media_type = "application/json"
20
- @apidoc = {}
21
- @paginations = {}
22
- @model.attr_accessible []
23
- end
24
-
25
- def exposed_attributes
26
- assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
27
- (@writables + @readables).uniq.select{|f| !assocs.include?(f)}
28
- end
29
-
30
- def exposed_associations
31
- assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
32
- (@writables + @readables).uniq.select{|f| assocs.include?(f)}
33
- end
34
-
35
- def readables= arg
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
- class InCollection
135
- include Blockenspiel::DSL
136
-
137
- dsl_attr_accessor :media_type
138
-
139
- def initialize model, base_config
140
- @model = model
141
- @media_type = "application/json"
142
- @readables = base_config.readables # must assign a reference
143
- @writables = base_config.writables # must assign a reference
144
- end
145
-
146
- def readables= readables
147
- @writables = [] # forget inherited writables
148
- @readables = ConfigDSL.normalize(readables,"readables")
149
- end
150
-
151
- def readables *arg
152
- return(@readables) if arg.empty?
153
- self.readables = arg
154
- end
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
- rescue
25
- # raised when DB is not setup yet. (rake db:schema:load)
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
- # Monkey patch the request class for Rails 3.0, Rack 1.2
29
- # Backport from Rack 1.3
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
- if scheme == "https" && port != 443 ||
37
- scheme == "http" && port != 80
38
- url << ":#{port}"
39
- end
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
- url
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
@@ -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