hyper-resource 1.0.0.lap34

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8f56e1c745c9154f2197accee8cbd76fb6b623fcf112f11caedc29d4dcc0ee7f
4
+ data.tar.gz: c74e55520edf0064cb02ff378a70e55145a0400c21c65976de466ec283446a60
5
+ SHA512:
6
+ metadata.gz: d2fc69f4ad34347ecae9d5eac09c00c3f023b86306b1ceaab8b9593521b74db02751bbc8cb611e842f1a8f7b02f5cff2f954db9f5f2f0755facaccdd64a220c5
7
+ data.tar.gz: 553518e63790f5e7bb2d8b57d24113c86a074d2d985aaf787ebb263f3b4e7409d22d136d2dada4e5a7c601a353c0588f2d5ef46854540cff3f62be786f3663d0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Jan Biedermann
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,109 @@
1
+ # hyper-resource
2
+
3
+ HyperResource is an affective way of moving data between your server and clients when using Hyperloop and Rails.
4
+
5
+ [![Reactivity Demo](http://img.youtube.com/vi/fPSpESBbeMQ/0.jpg)](http://www.youtube.com/watch?v=fPSpESBbeMQ "Reactivity Demo")
6
+
7
+ ## Motivation
8
+
9
+ + To co-exist with a resource based REST API
10
+ + To have ActiveRecord type Models shared by both the client and server code
11
+ + To be ORM/database agnostic (tested with ActiveRecord on Postgres and Neo4j.rb on Neo4j)
12
+ + To fit the 'Rails way' as far as possible (under the covers, HyperResource is a traditional REST API)
13
+ + To keep all Policy checking and authorisation logic in the Rails Controllers
14
+ + To allow a stages implementation
15
+
16
+ ## Staged implementation
17
+
18
+ HyperResource is designed to be implemented in stages and each stage delivers value in its own right, so the developer only needs to go as far as they like.
19
+
20
+ A record can be of any ORM but the ORM must implement:
21
+ ```ruby
22
+ record_class.find(id) # to get a record
23
+ record.id # a identifier
24
+ record.updated_at # a time stamp
25
+ record.destroyed? # to identify if its scheduled for destruction
26
+
27
+ # when using relations controller
28
+ record.touch # to update updated_at, identicating that something about that record changed
29
+ # for example it has been added to a relation
30
+ ```
31
+
32
+ ### Stage 1 - Wrap a REST API with Ruby classes to represent Models
33
+
34
+ The simplest implementation of HyperResource is a client side only wrapper of an existing REST API which treats each REST Resource as a Ruby class.
35
+
36
+ ```ruby
37
+ # in your client-cide code
38
+ class Customer
39
+ include ApplicationHyperRecord
40
+ end
41
+
42
+ # then work with the Customer class as if it were an ActiveRecord
43
+ customer = Customer.new(name: 'John Smith')
44
+ customer.save # ---> POST api/customer.json ... {name: 'John Smith' }
45
+ puts customer.id # 123
46
+
47
+ # to find a record
48
+ customer = Customer.find(123) # ---> GET api/customer/123.json
49
+ puts customer.name # `John Smith`
50
+ ```
51
+
52
+ ### Stage 2 - Adapt your Models so the client and server code share the same Models
53
+
54
+ HyperResource supports ActiveRecord associations and scopes so you can DRY up your code and the client an server can share the same Models.
55
+
56
+ ```ruby
57
+ module ApplicationHyperRecord
58
+ def self.included(base)
59
+ if RUBY_ENGINE == 'opal'
60
+ base.include(HyperRecord)
61
+ else
62
+ base.extend(HyperRecord::ServerClassMethods)
63
+ end
64
+ end
65
+ end
66
+
67
+ class Customer
68
+ include ApplicationHyperRecord
69
+ has_many :addresses
70
+
71
+ unless RUBY_ENGINE == 'opal'
72
+ # methods which should only exist on the server
73
+ end
74
+ end
75
+
76
+ customer = Customer.find(123) # ---> GET api/customer/123.json
77
+ customer.addresses.each do |address|
78
+ puts address.post_code
79
+ end
80
+ ```
81
+
82
+ ### Stage 3 - Implement a Redis based pub-sub mechanism so the client code is notified when the server data changes
83
+
84
+ ```ruby
85
+ class ApplicationController
86
+ include Hyperloop::Resource::PubSub
87
+
88
+ def my_action
89
+ # available methods for pubsub
90
+ publish_collection(base_record, collection_name, record = nil)
91
+ publish_record(record)
92
+ publish_scope(record_class, scope_name)
93
+
94
+ subscribe_collection(collection, base_record = nil, collection_name = nil)
95
+ subscribe_record(record)
96
+ subscribe_scope(collection, record_class = nil, scope_name = nil)
97
+
98
+ pub_sub_collection(collection, base_record, collection_name, causing_record = nil)
99
+ pub_sub_record(record)
100
+ pub_sub_scope(collection, record_class, scope_name)
101
+ end
102
+ end
103
+ ```
104
+
105
+ EXAMPLE
106
+
107
+ ## Implementation
108
+
109
+ How to install....
@@ -0,0 +1,30 @@
1
+ require_relative "lib/hyperloop/resource/version"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "hyper-resource"
5
+ s.version = Hyperloop::Resource::VERSION
6
+ s.author = "Jan Biedermann"
7
+ s.email = "jan@kursator.de"
8
+ s.homepage = "https://github.com/janbiedermann/hyper-resource"
9
+ s.summary = "Transparent Opal Ruby Data/Resource Access from the browser for Ruby-Hyperloop"
10
+ s.description = "Write Browser Apps that transparently access server side resources like 'MyModel.first_name', with ease"
11
+
12
+ s.files = `git ls-files`.split("\n")
13
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.require_paths = ["lib"]
16
+
17
+ s.add_runtime_dependency "opal", "~> 0.11.0"
18
+ s.add_runtime_dependency "opal-activesupport", "~> 0.3.1"
19
+ s.add_runtime_dependency 'hyper-component' , '~> 1.0.0.lap27'
20
+ s.add_runtime_dependency 'hyper-store' , '~> 1.0.0.lap27'
21
+ s.add_runtime_dependency "hyperloop-config", "~> 1.0.0.lap27"
22
+ s.add_development_dependency "hyperloop", "~> 1.0.0.lap27"
23
+ s.add_development_dependency "hyper-spec", "~> 1.0.0.lap27"
24
+ s.add_development_dependency "listen"
25
+ s.add_development_dependency "rake", ">= 11.3.0"
26
+ s.add_development_dependency "rails", ">= 5.1.0"
27
+ s.add_development_dependency "redis"
28
+ s.add_development_dependency "rspec-rails"
29
+ s.add_development_dependency "sqlite3"
30
+ end
@@ -0,0 +1,20 @@
1
+ require 'hyperloop-config'
2
+ require 'opal-activesupport'
3
+ Hyperloop.import 'pusher/source/pusher.js', client_only: true
4
+ require 'hyperloop/resource/version'
5
+ require 'hyperloop/resource/client_drivers' # initialize options for the client
6
+ require 'hyperloop/resource/config'
7
+ require 'hyper-store'
8
+ Hyperloop.import 'hyper-resource'
9
+
10
+
11
+ if RUBY_ENGINE == 'opal'
12
+ require 'hyperloop/resource/http'
13
+ require 'hyper-store'
14
+ require 'hyper_record'
15
+ else
16
+ require 'hyperloop/resource/pub_sub' # server side, controller helper methods
17
+ require 'hyperloop/resource/security_guards' # server side, controller helper methods
18
+ require 'hyper_record'
19
+ Opal.append_path __dir__.untaint
20
+ end
@@ -0,0 +1,21 @@
1
+ if RUBY_ENGINE == 'opal'
2
+ require 'hyper_record/dummy_value'
3
+ require 'hyper_record/collection'
4
+ require 'hyper_record/class_methods'
5
+ require 'hyper_record/client_instance_methods'
6
+
7
+ module HyperRecord
8
+ def self.included(base)
9
+ base.include(Hyperloop::Store::Mixin)
10
+ base.extend(HyperRecord::ClassMethods)
11
+ base.include(HyperRecord::ClientInstanceMethods)
12
+ base.class_eval do
13
+ state :record_state
14
+ end
15
+ end
16
+ end
17
+ else
18
+ require 'hyper_record/server_class_methods'
19
+ end
20
+
21
+
@@ -0,0 +1,413 @@
1
+ module HyperRecord
2
+ module ClassMethods
3
+
4
+ def new(record_hash = {})
5
+ if record_hash.has_key?(:id)
6
+ record = _record_cache[record_hash[:id]]
7
+ if record
8
+ record.instance_variable_get(:@properties_hash).merge!(record_hash)
9
+ return record
10
+ end
11
+ end
12
+ super(record_hash)
13
+ end
14
+
15
+ def all
16
+ _register_class_observer
17
+ if _class_fetch_states[:all] == 'f'
18
+ record_collection = HyperRecord::Collection.new
19
+ _record_cache.each_value { |record| record_collection.push(record) }
20
+ return record_collection
21
+ end
22
+ _promise_get("#{resource_base_uri}.json").then do |response|
23
+ klass_name = self.to_s.underscore
24
+ klass_key = klass_name.pluralize
25
+ response.json[klass_key].each do |record_json|
26
+ self.new(record_json[klass_name])
27
+ end
28
+ _class_fetch_states[:all] = 'f'
29
+ _notify_class_observers
30
+ record_collection = HyperRecord::Collection.new
31
+ _record_cache.each_value { |record| record_collection.push(record) }
32
+ record_collection
33
+ end.fail do |response|
34
+ error_message = "#{self.to_s}.all failed to fetch records!"
35
+ `console.error(error_message)`
36
+ response
37
+ end
38
+ HyperRecord::Collection.new
39
+ end
40
+
41
+ def belongs_to(direction, name = nil, options = { type: nil })
42
+ if name.is_a?(Hash)
43
+ options.merge(name)
44
+ name = direction
45
+ direction = nil
46
+ elsif name.nil?
47
+ name = direction
48
+ end
49
+ reflections[name] = { direction: direction, type: options[:type], kind: :belongs_to }
50
+ define_method(name) do
51
+ _register_observer
52
+ if @fetch_states[name] == 'f'
53
+ @relations[name]
54
+ elsif self.id
55
+ self.class._promise_get("#{self.class.resource_base_uri}/#{self.id}/relations/#{name}.json").then do |response|
56
+ @relations[name] = self.class._convert_json_hash_to_record(response.json[self.class.to_s.underscore][name])
57
+ @fetch_states[name] = 'f'
58
+ _notify_observers
59
+ @relations[name]
60
+ end.fail do |response|
61
+ error_message = "#{self.class.to_s}[#{self.id}].#{name}, a has_one association, failed to fetch records!"
62
+ `console.error(error_message)`
63
+ response
64
+ end
65
+ @relations[name]
66
+ else
67
+ @relations[name]
68
+ end
69
+ end
70
+ define_method("#{name}=") do |arg|
71
+ _register_observer
72
+ @relations[name] = arg
73
+ @fetch_states[name] == 'f'
74
+ @relations[name]
75
+ end
76
+ end
77
+
78
+ def create(record_hash = {})
79
+ record = new(record_hash)
80
+ record.save
81
+ record
82
+ end
83
+
84
+ def find(id)
85
+ return _record_cache[id] if _record_cache.has_key?(id)
86
+
87
+ observer = React::State.current_observer
88
+ record_in_progress = self.new
89
+
90
+ record_in_progress_key = "#{self.to_s}_#{record_in_progress.object_id}"
91
+ React::State.get_state(observer, record_in_progress_key) if observer
92
+
93
+ _promise_get("#{resource_base_uri}/#{id}.json").then do |response|
94
+ klass_key = self.to_s.underscore
95
+ reflections.keys.each do |relation|
96
+ if response.json[klass_key].has_key?(relation)
97
+ response.json[klass_key][r_or_s] = _convert_array_to_collection(response.json[klass_key][relation])
98
+ record_in_progress.instance_variable_get(:@fetch_states)[relation] = 'f'
99
+ end
100
+ end
101
+ record_in_progress._initialize_from_hash(response.json[klass_key]) if response.json[klass_key]
102
+ _record_cache[record_in_progress.id] = record_in_progress
103
+ React::State.set_state(observer, record_in_progress_key, `Date.now() + Math.random()`) if observer
104
+ record_in_progress
105
+ end.fail do |response|
106
+ error_message = "#{self.to_s}.find(#{id}) failed to fetch record!"
107
+ `console.error(error_message)`
108
+ response
109
+ end
110
+
111
+ record_in_progress
112
+ end
113
+
114
+ def find_record(id)
115
+ # TODO this is bogus, needs some attention
116
+ _promise_get("#{resource_base_uri}/#{id}.json").then do |response|
117
+ klass_name = self.to_s.underscore
118
+ self.new(response.json[klass_name])
119
+ end
120
+ end
121
+
122
+ def find_record_by(hash)
123
+ return _record_cache[hash] if _record_cache.has_key?(hash)
124
+ # TODO needs clarification about how to call the endpoint
125
+ _promise_get("#{resource_base_uri}/#{id}.json").then do |reponse|
126
+ record = self.new(response.json[self.to_s.underscore])
127
+ _record_cache[hash] = record
128
+ end
129
+ end
130
+
131
+ def has_many(direction, name = nil, options = { type: nil })
132
+ if name.is_a?(Hash)
133
+ options.merge(name)
134
+ name = direction
135
+ direction = nil
136
+ elsif name.nil?
137
+ name = direction
138
+ end
139
+ reflections[name] = { direction: direction, type: options[:type], kind: :has_many }
140
+ define_method(name) do
141
+ _register_observer
142
+ if @fetch_states[name] == 'f'
143
+ @relations[name]
144
+ elsif self.id
145
+ self.class._promise_get("#{self.class.resource_base_uri}/#{self.id}/relations/#{name}.json").then do |response|
146
+ collection = self.class._convert_array_to_collection(response.json[self.class.to_s.underscore][name], self, name)
147
+ @relations[name] = collection
148
+ @fetch_states[name] = 'f'
149
+ _notify_observers
150
+ @relations[name]
151
+ end.fail do |response|
152
+ error_message = "#{self.class.to_s}[#{self.id}].#{name}, a has_many association, failed to fetch records!"
153
+ `console.error(error_message)`
154
+ response
155
+ end
156
+ @relations[name]
157
+ else
158
+ @relations[name]
159
+ end
160
+ end
161
+ define_method("#{name}=") do |arg|
162
+ _register_observer
163
+ collection = if arg.is_a?(Array)
164
+ HyperRecord::Collection.new(arg, self, name)
165
+ elsif arg.is_a?(HyperRecord::Collection)
166
+ arg
167
+ else
168
+ raise "Argument must be a HyperRecord::Collection or a Array"
169
+ end
170
+ @relations[name] = collection
171
+ @fetch_states[name] == 'f'
172
+ @relations[name]
173
+ end
174
+ end
175
+
176
+ def has_one(direction, name, options = { type: nil })
177
+ if name.is_a?(Hash)
178
+ options.merge(name)
179
+ name = direction
180
+ direction = nil
181
+ elsif name.nil?
182
+ name = direction
183
+ end
184
+ reflections[name] = { direction: direction, type: options[:type], kind: :has_one }
185
+ define_method(name) do
186
+ _register_observer
187
+ if @fetch_states[name] == 'f'
188
+ @relations[name]
189
+ elsif self.id
190
+ self.class._promise_get("#{self.class.resource_base_uri}/#{self.id}/relations/#{name}.json").then do |response|
191
+ @relations[name] = self.class._convert_json_hash_to_record(response.json[self.class.to_s.underscore][name])
192
+ @fetch_states[name] = 'f'
193
+ _notify_observers
194
+ @relations[name]
195
+ end.fail do |response|
196
+ error_message = "#{self.class.to_s}[#{self.id}].#{name}, a has_one association, failed to fetch records!"
197
+ `console.error(error_message)`
198
+ response
199
+ end
200
+ @relations[name]
201
+ else
202
+ @relations[name]
203
+ end
204
+ end
205
+ define_method("#{name}=") do |arg|
206
+ _register_observer
207
+ @relations[name] = arg
208
+ @fetch_states[name] == 'f'
209
+ @relations[name]
210
+ end
211
+ end
212
+
213
+ def record_cached?(id)
214
+ _record_cache.has_key?(id)
215
+ end
216
+
217
+ def property(name, options = {})
218
+ # ToDo options maybe, ddefault value? type check?
219
+ _property_options[name] = options
220
+ define_method(name) do
221
+ _register_observer
222
+ if @properties_hash[:id]
223
+ if @changed_properties_hash.has_key?(name)
224
+ @changed_properties_hash[name]
225
+ else
226
+ @properties_hash[name]
227
+ end
228
+ else
229
+ # record has not been fetched or is new and not yet saved
230
+ if @properties_hash[name].nil?
231
+ # TODO move default to initializer?
232
+ if self.class._property_options[name].has_key?(:default)
233
+ self.class._property_options[name][:default]
234
+ elsif self.class._property_options[name].has_key?(:type)
235
+ HyperRecord::DummyValue.new(self.class._property_options[name][:type])
236
+ else
237
+ HyperRecord::DummyValue.new(Nil)
238
+ end
239
+ else
240
+ @properties_hash[name]
241
+ end
242
+ end
243
+ end
244
+ define_method("#{name}=") do |value|
245
+ _register_observer
246
+ @changed_properties_hash[name] = value
247
+ end
248
+ end
249
+
250
+ def reflections
251
+ @reflections ||= {}
252
+ end
253
+
254
+ def rest_method(name, options = { default_result: '...' })
255
+ rest_methods[name] = options
256
+ define_method(name) do |*args|
257
+ _register_observer
258
+ if self.id && (@rest_methods_hash[name][:force] || !@rest_methods_hash[name].has_key?(:result))
259
+ self.class._rest_method_get_or_patch(name, self.id, *args).then do |result|
260
+ @rest_methods_hash[name][:result] = result # result is parsed json
261
+ _notify_observers
262
+ @rest_methods_hash[name][:result]
263
+ end.fail do |response|
264
+ error_message = "#{self.class.to_s}[#{self.id}].#{name}, a rest_method, failed to fetch records!"
265
+ `console.error(error_message)`
266
+ response
267
+ end
268
+ end
269
+ if @rest_methods_hash[name].has_key?(:result)
270
+ @rest_methods_hash[name][:result]
271
+ else
272
+ self.class.rest_methods[name][:default_result]
273
+ end
274
+ end
275
+ end
276
+
277
+ def rest_methods
278
+ @rest_methods_hash ||= {}
279
+ end
280
+
281
+ def resource_base_uri
282
+ @resource ||= "#{Hyperloop::Resource::ClientDrivers.opts[:resource_api_base_path]}/#{self.to_s.underscore.pluralize}"
283
+ end
284
+
285
+ def scope(name, options)
286
+ scopes[name] = HyperRecord::Collection.new
287
+ define_singleton_method(name) do
288
+ if _class_fetch_states[name] == 'f'
289
+ scopes[name]
290
+ else
291
+ _register_class_observer
292
+ self._promise_get("#{resource_base_uri}/scopes/#{name}.json").then do |response|
293
+ scopes[name] = _convert_array_to_collection(response.json[self.to_s.underscore][name])
294
+ _class_fetch_states[name] = 'f'
295
+ _notify_class_observers
296
+ scopes[name]
297
+ end.fail do |response|
298
+ error_message = "#{self.class.to_s}.#{name}, a scope, failed to fetch records!"
299
+ `console.error(error_message)`
300
+ response
301
+ end
302
+ scopes[name]
303
+ end
304
+ end
305
+ end
306
+
307
+ def scopes
308
+ @scopes ||= {}
309
+ end
310
+
311
+ ### internal
312
+
313
+ def _convert_array_to_collection(array, record = nil, relation_name = nil)
314
+ res = array.map do |record_hash|
315
+ _convert_json_hash_to_record(record_hash)
316
+ end
317
+ HyperRecord::Collection.new(res, record, relation_name)
318
+ end
319
+
320
+ def _convert_json_hash_to_record(record_hash)
321
+ return nil if !record_hash
322
+ klass_key = record_hash.keys.first
323
+ return nil if klass_key == "nil_class"
324
+ return nil if !record_hash[klass_key]
325
+ return nil if record_hash[klass_key].keys.size == 0
326
+ record_class = klass_key.camelize.constantize
327
+ if record_hash[klass_key][:id].nil?
328
+ record_class.new(record_hash[klass_key])
329
+ else
330
+ record = record_class._record_cache[record_hash[klass_key][:id]]
331
+ if record.nil?
332
+ record = record_class.new(record_hash[klass_key])
333
+ else
334
+ record._initialize_from_hash(record_hash[klass_key])
335
+ end
336
+ record
337
+ end
338
+ end
339
+
340
+ def _class_fetch_states
341
+ @_class_fetch_states ||= { all: 'n' }
342
+ @_class_fetch_states
343
+ end
344
+
345
+ def _class_observers
346
+ @_class_observers ||= Set.new
347
+ @_class_observers
348
+ end
349
+
350
+ def _class_state_key
351
+ @_class_state_key ||= self.to_s
352
+ @_class_state_key
353
+ end
354
+
355
+ def _notify_class_observers
356
+ _class_observers.each do |observer|
357
+ React::State.set_state(observer, _class_state_key, `Date.now() + Math.random()`)
358
+ end
359
+ _class_observers = Set.new
360
+ end
361
+
362
+ def _promise_get(uri)
363
+ Hyperloop::Resource::HTTP.get(uri, headers: { 'Content-Type' => 'application/json' })
364
+ end
365
+
366
+ def _promise_delete(uri)
367
+ Hyperloop::Resource::HTTP.delete(uri, headers: { 'Content-Type' => 'application/json' })
368
+ end
369
+
370
+ def _promise_patch(uri, payload)
371
+ Hyperloop::Resource::HTTP.patch(uri, payload: payload,
372
+ headers: { 'Content-Type' => 'application/json' },
373
+ dataType: :json)
374
+ end
375
+
376
+ def _promise_post(uri, payload)
377
+ Hyperloop::Resource::HTTP.post(uri, payload: payload,
378
+ headers: { 'Content-Type' => 'application/json' },
379
+ dataType: :json)
380
+ end
381
+
382
+ def _property_options
383
+ @property_options ||= {}
384
+ end
385
+
386
+ def _record_cache
387
+ @record_cache ||= {}
388
+ end
389
+
390
+ def _register_class_observer
391
+ observer = React::State.current_observer
392
+ if observer
393
+ React::State.get_state(observer, _class_state_key)
394
+ _class_observers << observer # @observers is a set, observers get added only once
395
+ end
396
+ end
397
+
398
+ def _rest_method_get_or_patch(name, id, *args)
399
+ uri = "#{resource_base_uri}/#{id}/methods/#{name}.json?timestamp=#{`Date.now() + Math.random()`}" # timestamp to invalidate browser caches
400
+ if args && args.size > 0
401
+ payload = { params: args }
402
+ _promise_patch(uri, payload).then do |result|
403
+ result.json[:result]
404
+ end
405
+ else
406
+ _promise_get(uri).then do |result|
407
+ result.json[:result]
408
+ end
409
+ end
410
+ end
411
+ end
412
+
413
+ end