nested 0.0.25 → 0.0.26

Sign up to get free protection for your applications and to get access to all the features.
data/lib/nested.rb CHANGED
@@ -1,576 +1,26 @@
1
- require "json"
2
-
3
1
  module Nested
2
+ PROC_TRUE = Proc.new{ true }
3
+ end
4
4
 
5
- class ManyInManyError < StandardError
6
- end
7
-
8
- class SingletonAndCollectionError < StandardError
9
- end
10
-
11
- class NameMissingError < StandardError
12
- end
13
-
14
- class Redirect
15
- attr_reader :url
16
- def initialize(url)
17
- @url = url
18
- end
19
- end
20
-
21
- class SerializerField
22
- attr_accessor :name, :condition
23
- def initialize(name, condition)
24
- @name = name
25
- @condition = condition
26
- end
27
- end
28
-
29
- class Serializer
30
- attr_accessor :includes, :excludes
31
-
32
- def initialize(includes=[])
33
- @includes = includes.clone
34
- @excludes = []
35
- end
36
-
37
- def +(field)
38
- field = field.is_a?(SerializerField) ? field : SerializerField.new(field, ->{ true })
39
-
40
- @includes << field unless @includes.detect{|e| e.name == field.name}
41
- @excludes = @excludes.reject{|e| e.name == field.name}
42
- self
43
- end
44
-
45
- def -(field)
46
- field = field.is_a?(SerializerField) ? field : SerializerField.new(field, ->{ true })
47
-
48
- @excludes << field unless @excludes.detect{|e| e.name == field.name}
49
- self
50
- end
51
-
52
- def serialize
53
- this = self
54
- ->(obj) do
55
- excludes = this.excludes.select{|e| instance_exec(&e.condition)}
56
-
57
- this.includes.reject{|e| excludes.detect{|e2| e2.name == e.name}}.inject({}) do |memo, field|
58
- if instance_exec(&field.condition)
59
- case field.name
60
- when Symbol
61
- memo[field.name] = obj.is_a?(Hash) ? obj[field.name] : obj.send(field.name)
62
- when Hash
63
- field_name, proc = field.name.to_a.first
64
- memo[field_name] = instance_exec(obj, &proc)
65
- end
66
- end
67
- memo
68
- end
69
- end
70
- end
71
- end
72
-
73
- class Resource
74
- attr_reader :name, :parent, :actions, :resources, :serializer
75
-
76
- def initialize(sinatra, name, singleton, collection, parent, init_block)
77
- raise SingletonAndCollectionError.new if singleton && collection
78
- raise NameMissingError.new if (singleton || collection) && !name
79
-
80
- @sinatra = sinatra
81
- @name = name
82
- @singleton = singleton
83
- @collection = collection
84
- @parent = parent
85
- @resources = []
86
- @actions = []
87
- @init_block = init_block
88
- @run_blocks = []
89
-
90
- @serializer = Serializer.new(member? ? parent.serializer.includes : [])
91
- end
92
-
93
- def singleton?
94
- @singleton == true
95
- end
96
-
97
- def member?
98
- !singleton? && !collection?
99
- end
100
-
101
- def collection?
102
- @collection == true
103
- end
104
-
105
- def run(&block)
106
- @run_blocks << block
107
- self
108
- end
109
-
110
- def type
111
- if singleton?
112
- :singleton
113
- elsif member?
114
- :member
115
- elsif collection?
116
- :collection
117
- else
118
- raise "undefined"
119
- end
120
- end
121
-
122
- def serialize(*args)
123
- args.each {|arg| serializer + arg }
124
- serializer
125
- end
126
-
127
- def serialize_include_if(condition, *args)
128
- args.each {|arg| @serializer + SerializerField.new(arg, condition) }
129
- end
130
-
131
- def serialize_exclude_if(condition, *args)
132
- args.each {|arg| @serializer - SerializerField.new(arg, condition) }
133
- end
134
-
135
- def route_replace(route, args)
136
- args.each do |k, v|
137
- route = route.gsub(":#{k}", "#{v}")
138
- end
139
- route
140
- end
141
-
142
- def route(action=nil)
143
- "".tap do |r|
144
- r << @parent.route if @parent
145
-
146
-
147
- if singleton? || collection?
148
- r << "/#{@name}"
149
- else
150
- r << "/:#{@name}_id"
151
- end
152
-
153
- r << "/#{action}" if action
154
- end
155
- end
156
-
157
- def get(action=nil, &block)
158
- create_sinatra_route :get, action, &(block||proc {})
159
- end
160
-
161
- def post(action=nil, &block)
162
- create_sinatra_route :post, action, &block
163
- end
164
-
165
- def put(action=nil, &block)
166
- create_sinatra_route :put, action, &block
167
- end
168
-
169
- def delete(action=nil, &block)
170
- create_sinatra_route :delete, action, &block
171
- end
172
-
173
- def singleton(name, init_block=nil, &block)
174
- child_resource(name, true, false, init_block, &(block||Proc.new{ }))
175
- end
176
-
177
- def many(name, init_block=nil, &block)
178
- raise ManyInManyError.new "do not nest many in many" if collection?
179
- child_resource(name, false, true, init_block, &(block||Proc.new{ }))
180
- end
181
-
182
- def default_init_block_singleton
183
- if parent
184
- Proc.new{ instance_variable_get("@#{@__resource.parent.instance_variable_name}").send(@__resource.name) }
185
- else
186
- Proc.new { nil }
187
- end
188
- end
189
-
190
- def default_init_block_many
191
- if parent
192
- Proc.new{ instance_variable_get("@#{@__resource.parent.instance_variable_name}").send(@__resource.name) }
193
- else
194
- Proc.new { nil }
195
- end
196
- end
197
-
198
- def default_init_block_one
199
- if parent
200
- Proc.new do
201
- instance_variable_get("@#{@__resource.parent.instance_variable_name}")
202
- .where(id: params[:"#{@__resource.parent.name.to_s.singularize.to_sym}_id"])
203
- .first
204
- end
205
- else
206
- Proc.new { nil }
207
- end
208
- end
209
-
210
- def one(&block)
211
- raise "calling one() is only allowed within many() resource" unless collection?
212
- child_resource(self.name.to_s.singularize.to_sym, false, false, nil, &(block||Proc.new{ }))
213
- end
214
-
215
- def child_resource(name, singleton, collection, init_block, &block)
216
- Resource.new(@sinatra, name, singleton, collection, self, init_block)
217
- .tap{|r| r.instance_eval(&block)}
218
- .tap{|r| @resources << r}
219
- end
220
-
221
- def instance_variable_name
222
- member? ? parent.name.to_s.singularize.to_sym : @name
223
- end
224
-
225
- def parents
226
- (@parent ? @parent.parents + [@parent] : [])
227
- end
228
-
229
- def self_and_parents
230
- (self.parents + [self]).reverse
231
- end
232
-
233
- # --------------------------
234
-
235
- def sinatra_set_instance_variable(sinatra, name, value)
236
- raise "variable @#{name} already defined" if sinatra.instance_variable_defined?(:"@#{name}")
237
- sinatra.instance_variable_set(:"@#{name}", value)
238
- end
239
-
240
- def sinatra_init(sinatra)
241
- sinatra.instance_variable_set("@__resource", self)
242
-
243
- init_block = if @init_block
244
- @init_block
245
- else
246
- if singleton?
247
- default_init_block_singleton
248
- elsif collection?
249
- default_init_block_many
250
- else
251
- default_init_block_one
252
- end
253
- end
254
-
255
- sinatra.instance_variable_set("@#{self.instance_variable_name}", sinatra.instance_exec(&init_block))
256
-
257
- @run_blocks.each{|e| sinatra.instance_exec(&e)}
258
- end
259
-
260
- def sinatra_exec_get_block(sinatra, &block)
261
- sinatra_init_data(:get, sinatra, &block)
262
- sinatra.instance_exec(*sinatra.instance_variable_get("@__data"), &block)
263
- end
264
-
265
- def sinatra_exec_delete_block(sinatra, &block)
266
- sinatra_init_data(:delete, sinatra, &block)
267
- sinatra.instance_exec(*sinatra.instance_variable_get("@__data"), &block)
268
- end
269
-
270
- def sinatra_init_data(method, sinatra, &block)
271
- raw_data = if [:put, :post].include?(method)
272
- sinatra.request.body.rewind
273
- HashWithIndifferentAccess.new(JSON.parse(sinatra.request.body.read))
274
- elsif [:get, :delete].include?(method)
275
- sinatra.params
276
- else
277
- {}
278
- end
279
-
280
- sinatra.instance_variable_set("@__raw_data", raw_data)
281
- sinatra.instance_variable_set("@__data", raw_data.values_at(*block.parameters.map(&:last)))
282
- end
283
-
284
- def sinatra_exec_put_block(sinatra, &block)
285
- sinatra_init_data(:put, sinatra, &block)
286
- sinatra.instance_exec(*sinatra.instance_variable_get("@__data"), &block)
287
- end
288
-
289
- def sinatra_exec_post_block(sinatra, &block)
290
- sinatra_init_data(:post, sinatra, &block)
291
- res = sinatra.instance_exec(*sinatra.instance_variable_get("@__data"), &block)
292
- sinatra.instance_variable_set("@#{self.instance_variable_name}", res)
293
- # TODO: do we need to check for existing variables here?
294
- # sinatra_set_instance_variable(sinatra, self.instance_variable_name, res)
295
- end
296
-
297
- def sinatra_response_type(response)
298
- (response.is_a?(ActiveModel::Errors) || (response.respond_to?(:errors) && !response.errors.empty?)) ? :error : (response.is_a?(Nested::Redirect) ? :redirect : :data)
299
- end
300
-
301
- def sinatra_response(sinatra, method)
302
- response = if sinatra.errors.empty?
303
- sinatra.instance_variable_get("@#{self.instance_variable_name}")
304
- else
305
- sinatra.errors
306
- end
307
-
308
- response = self.send(:"sinatra_response_create_#{sinatra_response_type(response)}", sinatra, response, method)
309
-
310
- case response
311
- when Nested::Redirect then
312
- sinatra.redirect(response.url)
313
- when String then
314
- response
315
- else
316
- response.to_json
317
- end
318
- end
319
-
320
- def sinatra_response_create_redirect(sinatra, response, method)
321
- response
322
- end
323
-
324
- def sinatra_response_create_data(sinatra, response, method)
325
- data = if response && (collection? || response.is_a?(Array)) && method != :post
326
- response.to_a.map{|e| sinatra.instance_exec(e, &@serializer.serialize) }
327
- else
328
- sinatra.instance_exec(response, &@serializer.serialize)
329
- end
330
-
331
- {data: data, ok: true}
332
- end
333
-
334
- def sinatra_errors_to_hash(errors)
335
- errors.to_hash.inject({}) do |memo, e|
336
- memo[e[0]] = e[1][0]
337
- memo
338
- end
339
- end
340
-
341
- def sinatra_response_create_error(sinatra, response, method)
342
- errors = response.is_a?(ActiveModel::Errors) ? response : response.errors
343
- {data: sinatra_errors_to_hash(errors), ok: false}
344
- end
345
-
346
- def create_sinatra_route(method, action, &block)
347
- @actions << {method: method, action: action, block: block}
348
-
349
- resource = self
350
-
351
- route = resource.route(action)
352
-
353
- @sinatra.send(method, route) do
354
- def self.error(message)
355
- errors.add(:base, message)
356
- end
357
-
358
- def self.errors
359
- @__errors ||= ActiveModel::Errors.new({})
360
- end
361
-
362
- begin
363
- content_type :json
364
-
365
- resource.self_and_parents.reverse.each do |res|
366
- res.sinatra_init(self)
367
- end
368
-
369
- resource.send(:"sinatra_exec_#{method}_block", self, &block)
370
-
371
- resource.sinatra_response(self, method)
372
- rescue Exception => e
373
- context_arr = []
374
- context_arr << "route: #{route}"
375
- context_arr << "method: #{method}"
376
- context_arr << "action: #{action}"
377
-
378
- context_arr << "resource: #{resource.name} (#{resource.type})"
379
- resource_object = instance_variable_get("@#{resource.instance_variable_name}")
380
- context_arr << "@#{resource.instance_variable_name}: #{resource_object.inspect}"
381
-
382
- parent = resource.try(:parent)
383
-
384
- if parent
385
- context_arr << "parent: #{parent.try(:name)} (#{parent.try(:type)})"
386
- parent_object = instance_variable_get("@#{parent.try(:instance_variable_name)}")
387
- context_arr << "@#{parent.try(:instance_variable_name)}: #{parent_object.inspect}"
388
- end
389
-
390
- puts context_arr.join("\n")
391
- raise e
392
- end
393
- end
394
- end
395
- end
396
-
397
- module Angular
398
- def self.extended(base)
399
- base.send(:extend, Nested::Angular::Sinatra)
400
- end
401
-
402
- module Sinatra
403
- # def create_resource(name, singleton, collection, &block)
404
- def create_resource(*args, &block)
405
- angularize(super)
406
- end
407
-
408
- def nested_angular_config(config=nil)
409
- if config
410
- @nested_angular_config = config
411
- else
412
- @nested_angular_config ||= {}
413
- end
414
- end
415
-
416
- end
417
-
418
- def angular_add_functions(js, resource)
419
- resource.actions.each do |e|
420
- method, action, block = e.values_at :method, :action, :block
421
- block_args = block.parameters.map(&:last)
422
-
423
- fun_name = Nested::JsUtil::generate_function_name(resource, method, action)
424
-
425
- args = Nested::JsUtil::function_arguments(resource)
426
-
427
- route_args = args.inject({}) do |memo, e|
428
- idx = args.index(e)
429
- memo[:"#{e}_id"] = "'+(typeof(values[#{idx}]) == 'number' ? values[#{idx}].toString() : (values[#{idx}].id || values[#{idx}]))+'"
430
- memo
431
- end
432
- route = "#{self.nested_config[:prefix]}" + resource.route_replace(resource.route(action), route_args)
433
- when_args = args.map{|a| "$q.when(#{a})"}
434
-
435
- js << " impl.#{fun_name}Url = function(#{args.join(',')}){"
436
- js << " var deferred = $q.defer()"
437
- js << " $q.all([#{when_args.join(',')}]).then(function(values){"
438
- js << " deferred.resolve('#{route}')"
439
- js << " })"
440
- js << " return deferred.promise"
441
- js << " }"
442
-
443
- if [:get, :delete].include?(method)
444
- args << "data" if !block_args.empty?
445
-
446
- js << " impl.#{fun_name} = function(#{args.join(',')}){"
447
- js << " var deferred = $q.defer()"
448
- js << " $q.all([#{when_args.join(',')}]).then(function(values){"
449
- js << " $http({"
450
- js << " method: '#{method}', "
451
- js << (" url: '#{route}'" + (block_args.empty? ? "" : ","))
452
- js << " params: data" unless block_args.empty?
453
- js << " })"
454
- js << " .success(function(responseData){"
455
- js << " deferred[responseData.ok ? 'resolve' : 'reject'](responseData.data)"
456
- js << " })"
457
- js << " .error(function(){ deferred.reject() })"
458
- js << " });"
459
- js << " return deferred.promise"
460
- js << " }"
461
- elsif method == :post
462
- js << " impl.#{fun_name} = function(#{(args+['data']).join(',')}){"
463
- js << " var deferred = $q.defer()"
464
- js << " $q.all([#{when_args.join(',')}]).then(function(values){"
465
- js << " $http({method: '#{method}', url: '#{route}', data: data})"
466
- js << " .success(function(responseData){"
467
- js << " deferred[responseData.ok ? 'resolve' : 'reject'](responseData.data)"
468
- js << " })"
469
- js << " .error(function(){ deferred.reject() })"
470
- js << " });"
471
- js << " return deferred.promise"
472
- js << " }"
473
- elsif method == :put
474
- args << "data" if args.empty? || !block_args.empty?
475
-
476
- js << " impl.#{fun_name} = function(#{args.join(',')}){"
477
- js << " var deferred = $q.defer()"
478
- js << " $q.all([#{when_args.join(',')}]).then(function(values){"
479
- js << " $http({method: '#{method}', url: '#{route}', data: #{args.last}})"
480
- js << " .success(function(responseData){"
481
- js << " deferred[responseData.ok ? 'resolve' : 'reject'](responseData.data)"
482
- js << " })"
483
- js << " .error(function(){ deferred.reject() })"
484
- js << " });"
485
- js << " return deferred.promise"
486
- js << " }"
487
- end
488
- end
489
-
490
- resource.resources.each {|r| angular_add_functions(js, r) }
491
- end
492
-
493
- def angularize(resource)
494
- js = []
495
-
496
- module_name = "nested_#{resource.name}".camelcase(:lower)
497
-
498
- js << "angular.module('#{module_name}#{nested_angular_config[:service_suffix]}', [])"
499
- js << ".factory('#{resource.name.to_s.camelcase.capitalize}#{nested_angular_config[:service_suffix]}', function($http, $q){"
500
-
501
- js << " var impl = {}"
502
- angular_add_functions(js, resource)
503
- js << " return impl"
504
-
505
- js << "})"
506
-
507
- response_transform = nested_angular_config[:response_transform] || ->(code){ code }
508
-
509
- get "/#{resource.name}.js" do
510
- content_type :js
511
- instance_exec(js.join("\n"), &response_transform)
512
- end
513
- end
514
- end
515
-
516
- module JsUtil
517
- def self.generate_function_name(resource, method, action)
518
- arr = []
5
+ require "json"
6
+ require "sinatra/base"
519
7
 
520
- arr << "update" if method == :put
521
- arr << "create" if method == :post
522
- arr << "destroy" if method == :delete
8
+ require "nested/js"
523
9
 
524
- all = resource.self_and_parents.reverse
10
+ require "nested/redirect"
525
11
 
526
- all.each do |e|
527
- if e.collection?
528
- if e == all.last
529
- if method == :post
530
- arr << e.name.to_s.singularize.to_sym
531
- else
532
- arr << e.name
533
- end
534
- else
535
- arr << e.name unless all[all.index(e) + 1].member?
536
- end
537
- else
538
- arr << e.name
539
- end
540
- end
12
+ require "nested/with_many"
13
+ require "nested/with_singleton"
541
14
 
542
- arr << action if action
15
+ require "nested/resource"
543
16
 
544
- arr.map(&:to_s).join("_").camelcase(:lower)
545
- end
17
+ require "nested/one"
18
+ require "nested/singleton"
19
+ require "nested/many"
546
20
 
547
- def self.function_arguments(resource)
548
- resource
549
- .self_and_parents.select{|r| r.member?}
550
- .map{|r| r.name || r.parent.name}
551
- .map(&:to_s)
552
- .map(&:singularize)
553
- .reverse
554
- end
555
- end
21
+ require "nested/serializer"
22
+ require "nested/serializer_field"
556
23
 
557
- module Sinatra
558
- def nested_config(config=nil)
559
- if config
560
- @nested_config = config
561
- else
562
- @nested_config ||= {}
563
- end
564
- end
565
- def singleton(name, init_block=nil, &block)
566
- create_resource(name, true, false, init_block, &block)
567
- end
568
- def many(name, init_block=nil, &block)
569
- create_resource(name, false, true, init_block, &block)
570
- end
571
- def create_resource(name, singleton, collection, init_block, &block)
572
- ::Nested::Resource.new(self, name, singleton, collection, nil, init_block).tap{|r| r.instance_eval(&block) }
573
- end
574
- end
24
+ require "nested/app"
575
25
 
576
- end
26
+ require "nested/integration/angular"
data/nested.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "nested"
3
- s.version = "0.0.25"
3
+ s.version = "0.0.26"
4
4
 
5
5
  s.authors = ["Jan Zimmek"]
6
6
  s.email = %q{jan.zimmek@web.de}