nested 0.0.25 → 0.0.26

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: da43c009c3ff14c2773df3a11e9ea8d5ae2f8de0
4
- data.tar.gz: f63ecccb28294309d354362ca5bcff05937dd11a
3
+ metadata.gz: f3204fac8586400602f52773cfa5cd557acaaf15
4
+ data.tar.gz: 4e017941f4c4bbc7ae02ee77df82a9b2944d26fd
5
5
  SHA512:
6
- metadata.gz: dc57e5a73bea891b475bc0810cecd6b9e3f48f3710d173f6b30c358c5345bf19e5075c9e8424effaf46aa5196b23951b522e09e3c0e717f2c8b314ff3049a469
7
- data.tar.gz: 3b753f26a8ac5d8d57133b707637e78ce1ce80751ae8132b3979be3037edb1030ab72415a789a74e4409f3ca9021fa158b5da150c6a55e0c5d3e83f14c6389d1
6
+ metadata.gz: 781858c122e8512d79e0fde3f0753ff97e473636c38e3bd5f4c5146a05b5954d0874ffb2a7980fbc04b944653b751026bc6506179d4732b2833d5b8325951b8f
7
+ data.tar.gz: db049be70b1b9899c2a08301c34534f6bdca917f470fe3af50bf3d6f9a73b0851c473618e8d54f38a296838c9353c3cbba6a960c84c64ec31b6f7574ded308ac
data/Gemfile CHANGED
@@ -4,5 +4,6 @@ source 'https://rubygems.org'
4
4
 
5
5
  gem "activesupport"
6
6
  gem "activerecord"
7
+ gem "sinatra"
7
8
  gem "mocha"
8
9
  gem "json"
data/lib/nested/app.rb ADDED
@@ -0,0 +1,31 @@
1
+ module Nested
2
+ class App
3
+ def self.inherited(clazz)
4
+ (class << clazz; self; end).instance_eval do
5
+ attr_accessor :sinatra
6
+ end
7
+ clazz.sinatra = Class.new(Sinatra::Base)
8
+ clazz.instance_variable_set("@config", {})
9
+ end
10
+
11
+ def self.child_resource(name, clazz, resource_if_block, init_block, &block)
12
+ clazz.new(sinatra, name, nil, resource_if_block, init_block)
13
+ .tap{|r| r.instance_eval(&(block||Proc.new{ }))}
14
+ end
15
+
16
+ def self.before(&block)
17
+ sinatra.before(&block)
18
+ end
19
+
20
+ def self.after(&block)
21
+ sinatra.after(&block)
22
+ end
23
+
24
+ def self.config(opts={})
25
+ @config.tap{|c| c.merge!(opts)}
26
+ end
27
+
28
+ extend WithSingleton
29
+ extend WithMany
30
+ end
31
+ end
@@ -0,0 +1,119 @@
1
+ module Nested
2
+ module Integration
3
+ module Angular
4
+ def self.extended(clazz)
5
+ (class << clazz; self; end).instance_eval do
6
+ attr_accessor :angular_config
7
+ end
8
+ clazz.angular_config = {}
9
+
10
+ def clazz.angular(opts={})
11
+ @angular_config.merge!(opts)
12
+ end
13
+
14
+ def child_resource(*args, &block)
15
+ Helper::angularize(self, super)
16
+ end
17
+ end
18
+
19
+ module Helper
20
+ def self.angular_add_functions(app, js, resource)
21
+ resource.actions.each do |e|
22
+ method, action, block = e.values_at :method, :action, :block
23
+ block_args = block.parameters.map(&:last)
24
+
25
+ fun_name = Nested::Js::generate_function_name(resource, method, action)
26
+
27
+ args = Nested::Js::function_arguments(resource)
28
+
29
+ route_args = args.inject({}) do |memo, e|
30
+ idx = args.index(e)
31
+ memo[:"#{e}_id"] = "'+(typeof(values[#{idx}]) == 'number' ? values[#{idx}].toString() : (values[#{idx}].id || values[#{idx}]))+'"
32
+ memo
33
+ end
34
+ route = "#{app.config[:prefix]}" + resource.route_replace(resource.route(action), route_args)
35
+ when_args = args.map{|a| "$q.when(#{a})"}
36
+
37
+ js << " impl.#{fun_name}Url = function(#{args.join(',')}){"
38
+ js << " var deferred = $q.defer()"
39
+ js << " $q.all([#{when_args.join(',')}]).then(function(values){"
40
+ js << " deferred.resolve('#{route}')"
41
+ js << " })"
42
+ js << " return deferred.promise"
43
+ js << " }"
44
+
45
+ if [:get, :delete].include?(method)
46
+ args << "data" if !block_args.empty?
47
+
48
+ js << " impl.#{fun_name} = function(#{args.join(',')}){"
49
+ js << " var deferred = $q.defer()"
50
+ js << " $q.all([#{when_args.join(',')}]).then(function(values){"
51
+ js << " $http({"
52
+ js << " method: '#{method}', "
53
+ js << (" url: '#{route}'" + (block_args.empty? ? "" : ","))
54
+ js << " params: data" unless block_args.empty?
55
+ js << " })"
56
+ js << " .success(function(responseData){"
57
+ js << " deferred[responseData.ok ? 'resolve' : 'reject'](responseData.data)"
58
+ js << " })"
59
+ js << " .error(function(){ deferred.reject() })"
60
+ js << " });"
61
+ js << " return deferred.promise"
62
+ js << " }"
63
+ elsif method == :post
64
+ js << " impl.#{fun_name} = function(#{(args+['data']).join(',')}){"
65
+ js << " var deferred = $q.defer()"
66
+ js << " $q.all([#{when_args.join(',')}]).then(function(values){"
67
+ js << " $http({method: '#{method}', url: '#{route}', data: data})"
68
+ js << " .success(function(responseData){"
69
+ js << " deferred[responseData.ok ? 'resolve' : 'reject'](responseData.data)"
70
+ js << " })"
71
+ js << " .error(function(){ deferred.reject() })"
72
+ js << " });"
73
+ js << " return deferred.promise"
74
+ js << " }"
75
+ elsif method == :put
76
+ args << "data" if args.empty? || !block_args.empty?
77
+
78
+ js << " impl.#{fun_name} = function(#{args.join(',')}){"
79
+ js << " var deferred = $q.defer()"
80
+ js << " $q.all([#{when_args.join(',')}]).then(function(values){"
81
+ js << " $http({method: '#{method}', url: '#{route}', data: #{args.last}})"
82
+ js << " .success(function(responseData){"
83
+ js << " deferred[responseData.ok ? 'resolve' : 'reject'](responseData.data)"
84
+ js << " })"
85
+ js << " .error(function(){ deferred.reject() })"
86
+ js << " });"
87
+ js << " return deferred.promise"
88
+ js << " }"
89
+ end
90
+ end
91
+
92
+ resource.resources.each {|r| angular_add_functions(app, js, r) }
93
+ end
94
+
95
+ def self.angularize(app, resource)
96
+ js = []
97
+
98
+ module_name = "nested_#{resource.name}".camelcase(:lower)
99
+
100
+ js << "angular.module('#{module_name}#{app.angular_config[:service_suffix]}', [])"
101
+ js << ".factory('#{resource.name.to_s.camelcase.capitalize}#{app.angular_config[:service_suffix]}', function($http, $q){"
102
+
103
+ js << " var impl = {}"
104
+ angular_add_functions(app, js, resource)
105
+ js << " return impl"
106
+
107
+ js << "})"
108
+
109
+ response_transform = app.angular_config[:response_transform] || ->(code){ code }
110
+
111
+ app.sinatra.get "/#{resource.name}.js" do
112
+ content_type :js
113
+ instance_exec(js.join("\n"), &response_transform)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
data/lib/nested/js.rb ADDED
@@ -0,0 +1,42 @@
1
+ module Nested
2
+ module Js
3
+ def self.generate_function_name(resource, method, action)
4
+ arr = []
5
+
6
+ arr << "update" if method == :put
7
+ arr << "create" if method == :post
8
+ arr << "destroy" if method == :delete
9
+
10
+ all = resource.self_and_parents.reverse
11
+
12
+ all.each do |e|
13
+ if e.is_a?(Many)
14
+ if e == all.last
15
+ if method == :post
16
+ arr << e.name.to_s.singularize.to_sym
17
+ else
18
+ arr << e.name
19
+ end
20
+ else
21
+ arr << e.name unless all[all.index(e) + 1].is_a?(One)
22
+ end
23
+ else
24
+ arr << e.name
25
+ end
26
+ end
27
+
28
+ arr << action if action
29
+
30
+ arr.map(&:to_s).join("_").camelcase(:lower)
31
+ end
32
+
33
+ def self.function_arguments(resource)
34
+ resource
35
+ .self_and_parents.select{|r| r.is_a?(One)}
36
+ .map{|r| r.name || r.parent.name}
37
+ .map(&:to_s)
38
+ .map(&:singularize)
39
+ .reverse
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ module Nested
2
+ class Many < Resource
3
+ def one(&block)
4
+ one_if(PROC_TRUE, &block)
5
+ end
6
+
7
+ def one_if(resource_if_block, &block)
8
+ child_resource(self.name.to_s.singularize.to_sym, One, resource_if_block, nil, &block)
9
+ end
10
+
11
+ def default_init_block
12
+ if parent
13
+ Proc.new{ instance_variable_get("@#{@__resource.parent.instance_variable_name}").send(@__resource.name) }
14
+ else
15
+ Proc.new { nil }
16
+ end
17
+ end
18
+ end
19
+ end
data/lib/nested/one.rb ADDED
@@ -0,0 +1,29 @@
1
+ module Nested
2
+ class One < Resource
3
+ include WithMany
4
+
5
+ def initialize_serializer_factory
6
+ Serializer.new(parent.serializer.includes)
7
+ end
8
+
9
+ def default_init_block
10
+ if parent
11
+ Proc.new do
12
+ instance_variable_get("@#{@__resource.parent.instance_variable_name}")
13
+ .where(id: params[:"#{@__resource.parent.name.to_s.singularize.to_sym}_id"])
14
+ .first
15
+ end
16
+ else
17
+ Proc.new { nil }
18
+ end
19
+ end
20
+
21
+ def to_route_part
22
+ "/:#{@name}_id"
23
+ end
24
+
25
+ def instance_variable_name
26
+ parent.name.to_s.singularize.to_sym
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ module Nested
2
+ class Redirect
3
+ attr_reader :url
4
+ def initialize(url)
5
+ @url = url
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,268 @@
1
+ module Nested
2
+ class Resource
3
+ attr_reader :name, :parent, :actions, :resources, :serializer, :init_block, :sinatra
4
+
5
+ include WithSingleton
6
+
7
+ def initialize(sinatra, name, parent, resource_if_block, init_block)
8
+ raise "resource must be given a name" unless name
9
+
10
+ @sinatra = sinatra
11
+ @name = name
12
+ @parent = parent
13
+ @resources = []
14
+ @actions = []
15
+
16
+ raise "resource_if_block is nil, pass Nested::PROC_TRUE instead" unless resource_if_block
17
+ @resource_if_block = resource_if_block
18
+
19
+ unless @init_block = init_block
20
+ if is_a?(One)
21
+ @init_block = default_init_block
22
+ else
23
+ @init_block = resource_if_block != PROC_TRUE ? parent.try(:init_block) : default_init_block
24
+ end
25
+ end
26
+
27
+ raise "no init_block passed and could not lookup any parent or default init_block" unless @init_block
28
+
29
+ @before_blocks = []
30
+ @after_blocks = []
31
+
32
+ @serializer = initialize_serializer_factory
33
+ end
34
+
35
+ def initialize_serializer_factory
36
+ Serializer.new([])
37
+ end
38
+
39
+ def child_resource(name, clazz, resource_if_block, init_block, &block)
40
+ clazz.new(@sinatra, name, self, resource_if_block, init_block)
41
+ .tap{|r| r.instance_eval(&(block||Proc.new{ }))}
42
+ .tap{|r| @resources << r}
43
+ end
44
+
45
+ def to_route_part
46
+ "/#{@name}"
47
+ end
48
+
49
+ def before(&block)
50
+ @before_blocks << block
51
+ self
52
+ end
53
+
54
+ def after(&block)
55
+ @after_blocks << block
56
+ self
57
+ end
58
+
59
+ def serialize(*args)
60
+ args.each {|arg| serializer + arg }
61
+ serializer
62
+ end
63
+
64
+ def serialize_include_if(condition, *args)
65
+ args.each {|arg| @serializer + SerializerField.new(arg, condition) }
66
+ end
67
+
68
+ def serialize_exclude_if(condition, *args)
69
+ args.each {|arg| @serializer - SerializerField.new(arg, condition) }
70
+ end
71
+
72
+ def route_replace(route, args)
73
+ args.each do |k, v|
74
+ route = route.gsub(":#{k}", "#{v}")
75
+ end
76
+ route
77
+ end
78
+
79
+ def route(action=nil)
80
+ "".tap do |r|
81
+ r << @parent.route if @parent
82
+ r << to_route_part
83
+ r << "/#{action}" if action
84
+ end
85
+ end
86
+
87
+ [:get, :post, :put, :delete].each do |method|
88
+ instance_eval do
89
+ define_method method do |action=nil, &block|
90
+ send(:"#{method}_if", PROC_TRUE, action, &block)
91
+ end
92
+
93
+ define_method :"#{method}_if" do |method_if_block, action=nil, &block|
94
+ create_sinatra_route method, action, method_if_block, &(block||proc {})
95
+ end
96
+ end
97
+ end
98
+
99
+ def instance_variable_name
100
+ @name
101
+ end
102
+
103
+ def parents
104
+ (@parent ? @parent.parents + [@parent] : [])
105
+ end
106
+
107
+ def self_and_parents
108
+ (self.parents + [self]).reverse
109
+ end
110
+
111
+ # --------------------------
112
+
113
+ def sinatra_set_instance_variable(sinatra, name, value)
114
+ raise "variable @#{name} already defined" if sinatra.instance_variable_defined?(:"@#{name}")
115
+ sinatra.instance_variable_set(:"@#{name}", value)
116
+ end
117
+
118
+ def sinatra_init(sinatra)
119
+ sinatra.instance_variable_set("@__resource", self)
120
+
121
+ raise "resource_if is false for resource: #{self.name} " unless sinatra.instance_exec(&@resource_if_block)
122
+
123
+ @before_blocks.each{|e| sinatra.instance_exec(&e)}
124
+
125
+ sinatra.instance_variable_set("@#{self.instance_variable_name}", sinatra.instance_exec(&@init_block))
126
+
127
+ @after_blocks.each{|e| sinatra.instance_exec(&e)}
128
+ end
129
+
130
+ def sinatra_exec_get_block(sinatra, &block)
131
+ sinatra_init_data(:get, sinatra, &block)
132
+ sinatra.instance_exec(*sinatra.instance_variable_get("@__data"), &block)
133
+ end
134
+
135
+ def sinatra_exec_delete_block(sinatra, &block)
136
+ sinatra_init_data(:delete, sinatra, &block)
137
+ sinatra.instance_exec(*sinatra.instance_variable_get("@__data"), &block)
138
+ end
139
+
140
+ def sinatra_init_data(method, sinatra, &block)
141
+ raw_data = if [:put, :post].include?(method)
142
+ sinatra.request.body.rewind
143
+ HashWithIndifferentAccess.new(JSON.parse(sinatra.request.body.read))
144
+ elsif [:get, :delete].include?(method)
145
+ sinatra.params
146
+ else
147
+ {}
148
+ end
149
+
150
+ sinatra.instance_variable_set("@__raw_data", raw_data)
151
+ sinatra.instance_variable_set("@__data", raw_data.values_at(*block.parameters.map(&:last)))
152
+ end
153
+
154
+ def sinatra_exec_put_block(sinatra, &block)
155
+ sinatra_init_data(:put, sinatra, &block)
156
+ sinatra.instance_exec(*sinatra.instance_variable_get("@__data"), &block)
157
+ end
158
+
159
+ def sinatra_exec_post_block(sinatra, &block)
160
+ sinatra_init_data(:post, sinatra, &block)
161
+ res = sinatra.instance_exec(*sinatra.instance_variable_get("@__data"), &block)
162
+ sinatra.instance_variable_set("@#{self.instance_variable_name}", res)
163
+ # TODO: do we need to check for existing variables here?
164
+ # sinatra_set_instance_variable(sinatra, self.instance_variable_name, res)
165
+ end
166
+
167
+ def sinatra_response_type(response)
168
+ (response.is_a?(ActiveModel::Errors) || (response.respond_to?(:errors) && !response.errors.empty?)) ? :error : (response.is_a?(Nested::Redirect) ? :redirect : :data)
169
+ end
170
+
171
+ def sinatra_response(sinatra, method)
172
+ response = if sinatra.errors.empty?
173
+ sinatra.instance_variable_get("@#{self.instance_variable_name}")
174
+ else
175
+ sinatra.errors
176
+ end
177
+
178
+ response = self.send(:"sinatra_response_create_#{sinatra_response_type(response)}", sinatra, response, method)
179
+
180
+ case response
181
+ when Nested::Redirect then
182
+ sinatra.redirect(response.url)
183
+ when String then
184
+ response
185
+ else
186
+ response.to_json
187
+ end
188
+ end
189
+
190
+ def sinatra_response_create_redirect(sinatra, response, method)
191
+ response
192
+ end
193
+
194
+ def sinatra_response_create_data(sinatra, response, method)
195
+ data = if response && (is_a?(Many) || response.is_a?(Array)) && method != :post
196
+ response.to_a.map{|e| sinatra.instance_exec(e, &@serializer.serialize) }
197
+ else
198
+ sinatra.instance_exec(response, &@serializer.serialize)
199
+ end
200
+
201
+ {data: data, ok: true}
202
+ end
203
+
204
+ def sinatra_errors_to_hash(errors)
205
+ errors.to_hash.inject({}) do |memo, e|
206
+ memo[e[0]] = e[1][0]
207
+ memo
208
+ end
209
+ end
210
+
211
+ def sinatra_response_create_error(sinatra, response, method)
212
+ errors = response.is_a?(ActiveModel::Errors) ? response : response.errors
213
+ {data: sinatra_errors_to_hash(errors), ok: false}
214
+ end
215
+
216
+ def create_sinatra_route(method, action, method_if_block, &block)
217
+ @actions << {method: method, action: action, block: block}
218
+
219
+ resource = self
220
+
221
+ route = resource.route(action)
222
+
223
+ @sinatra.send(method, route) do
224
+ def self.error(message)
225
+ errors.add(:base, message)
226
+ end
227
+
228
+ def self.errors
229
+ @__errors ||= ActiveModel::Errors.new({})
230
+ end
231
+
232
+ begin
233
+ content_type :json
234
+
235
+ resource.self_and_parents.reverse.each do |res|
236
+ res.sinatra_init(self)
237
+ end
238
+
239
+ raise "method_if_block returns false for method: #{method}, action: #{action}, resource: #{resource.name}" unless instance_exec(&method_if_block)
240
+
241
+ resource.send(:"sinatra_exec_#{method}_block", self, &block)
242
+
243
+ resource.sinatra_response(self, method)
244
+ rescue Exception => e
245
+ context_arr = []
246
+ context_arr << "route: #{route}"
247
+ context_arr << "method: #{method}"
248
+ context_arr << "action: #{action}"
249
+
250
+ context_arr << "resource: #{resource.name} (#{resource.class.name})"
251
+ resource_object = instance_variable_get("@#{resource.instance_variable_name}")
252
+ context_arr << "@#{resource.instance_variable_name}: #{resource_object.inspect}"
253
+
254
+ parent = resource.try(:parent)
255
+
256
+ if parent
257
+ context_arr << "parent: #{parent.name} (#{parent.class.name})"
258
+ parent_object = instance_variable_get("@#{parent.instance_variable_name}")
259
+ context_arr << "@#{parent.instance_variable_name}: #{parent_object.inspect}"
260
+ end
261
+
262
+ puts context_arr.join("\n")
263
+ raise e
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,45 @@
1
+ module Nested
2
+ class Serializer
3
+ attr_accessor :includes, :excludes
4
+
5
+ def initialize(includes=[])
6
+ @includes = includes.clone
7
+ @excludes = []
8
+ end
9
+
10
+ def +(field)
11
+ field = field.is_a?(SerializerField) ? field : SerializerField.new(field, ->{ true })
12
+
13
+ @includes << field unless @includes.detect{|e| e.name == field.name}
14
+ @excludes = @excludes.reject{|e| e.name == field.name}
15
+ self
16
+ end
17
+
18
+ def -(field)
19
+ field = field.is_a?(SerializerField) ? field : SerializerField.new(field, ->{ true })
20
+
21
+ @excludes << field unless @excludes.detect{|e| e.name == field.name}
22
+ self
23
+ end
24
+
25
+ def serialize
26
+ this = self
27
+ ->(obj) do
28
+ excludes = this.excludes.select{|e| instance_exec(&e.condition)}
29
+
30
+ this.includes.reject{|e| excludes.detect{|e2| e2.name == e.name}}.inject({}) do |memo, field|
31
+ if instance_exec(&field.condition)
32
+ case field.name
33
+ when Symbol
34
+ memo[field.name] = obj.is_a?(Hash) ? obj[field.name] : obj.send(field.name)
35
+ when Hash
36
+ field_name, proc = field.name.to_a.first
37
+ memo[field_name] = instance_exec(obj, &proc)
38
+ end
39
+ end
40
+ memo
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,9 @@
1
+ module Nested
2
+ class SerializerField
3
+ attr_accessor :name, :condition
4
+ def initialize(name, condition)
5
+ @name = name
6
+ @condition = condition
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Nested
2
+ class Singleton < Resource
3
+ include WithMany
4
+
5
+ def default_init_block
6
+ if parent
7
+ Proc.new{ instance_variable_get("@#{@__resource.parent.instance_variable_name}").send(@__resource.name) }
8
+ else
9
+ Proc.new { nil }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module Nested
2
+ module WithMany
3
+ def many(name, init_block=nil, &block)
4
+ many_if(PROC_TRUE, name, init_block, &block)
5
+ end
6
+
7
+ def many_if(resource_if_block, name, init_block=nil, &block)
8
+ child_resource(name, Many, resource_if_block, init_block, &block)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Nested
2
+ module WithSingleton
3
+ def singleton(name, init_block=nil, &block)
4
+ singleton_if(PROC_TRUE, name, init_block, &block)
5
+ end
6
+
7
+ def singleton_if(resource_if_block, name, init_block=nil, &block)
8
+ child_resource(name, Singleton, resource_if_block, init_block, &block)
9
+ end
10
+ end
11
+ end