reactive-record 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,287 @@
1
+ module ReactiveRecord
2
+ class Base
3
+
4
+ # Its all about lazy loading. This prevents us from grabbing enormous association collections, or large attributes
5
+ # unless they are explicitly requested.
6
+
7
+ # During prerendering we get each attribute as its requested and fill it in both on the javascript side, as well as
8
+ # remember that the attribute needs to be part of the download to client.
9
+
10
+ # On the client we fill in the record data with empty values (nil, or one element collections) but only as the attribute
11
+ # is requested. Each request queues up a request to get the real data from the server.
12
+
13
+ # The ReactiveRecord class serves two purposes. First it is the unique data corresponding to the last known state of a
14
+ # database record. This means All records matching a specific database record are unique. This is unlike AR but is
15
+ # important both for the lazy loading and also so that when values change react can be informed of the change.
16
+
17
+ # Secondly it serves as name space for all the ReactiveRecord specific methods, so every AR Instance has a ReactiveRecord
18
+
19
+ # Because there is no point in generating a new ar_instance everytime a search is made we cache the first ar_instance created.
20
+ # Its possible however during loading to create a new ar_instances that will in the end point to the same record.
21
+
22
+ # VECTORS... are an important concept. They are the substitute for a primary key before a record is loaded.
23
+ # Vectors have the form [ModelClass, method_call, method_call, method_call...]
24
+
25
+ # Each method call is either a simple method name or an array in the form [method_name, param, param ...]
26
+ # Example [User, [find, 123], todos, active, [due, "1/1/2016"], title]
27
+ # Roughly corresponds to this query: User.find(123).todos.active.due("1/1/2016").select(:title)
28
+
29
+ attr_accessor :ar_instance
30
+ attr_accessor :vector
31
+ attr_accessor :model
32
+
33
+ # While data is being loaded from the server certain internal behaviors need to change
34
+ # for example records all record changes are synced as they happen.
35
+ # This is implemented this way so that the ServerDataCache class can use pure active
36
+ # record methods in its implementation
37
+
38
+ def self.data_loading?
39
+ @data_loading
40
+ end
41
+
42
+ def data_loading?
43
+ self.class.data_loading?
44
+ end
45
+
46
+ def self.load_from_json(json, target = nil)
47
+ @data_loading = true
48
+ ServerDataCache.load_from_json(json, target)
49
+ @data_loading = false
50
+ end
51
+
52
+ def self.find(model, attribute, value)
53
+
54
+ # will return the unique record with this attribute-value pair
55
+ # value cannot be an association or aggregation
56
+
57
+ model = model.base_class
58
+ # already have a record with this attribute-value pair?
59
+ record = @records[model].detect { |record| record.attributes[attribute] == value}
60
+
61
+ unless record
62
+ # if not, and then the record may be loaded, but not have this attribute set yet,
63
+ # so find the id of of record with the attribute-value pair, and see if that is loaded.
64
+ # find_in_db returns nil if we are not prerendering which will force us to create a new record
65
+ # because there is no way of knowing the id.
66
+ if attribute != model.primary_key and id = find_in_db(model, attribute, value)
67
+ record = @records[model].detect { |record| record.id == id}
68
+ end
69
+ # if we don't have a record then create one
70
+ (record = new(model)).vector = [model, ["find_by_#{attribute}", value]] unless record
71
+ # and set the value
72
+ record.sync_attribute(attribute, value)
73
+ # and set the primary if we have one
74
+ record.sync_attribute(model.primary_key, id) if id
75
+ end
76
+
77
+ # finally initialize and return the ar_instance
78
+ record.ar_instance ||= infer_type_from_hash(model, record.attributes).new(record)
79
+ end
80
+
81
+ def self.new_from_vector(model, aggregate_parent, *vector)
82
+ # this is the equivilent of find but for associations and aggregations
83
+ # because we are not fetching a specific attribute yet, there is NO communication with the
84
+ # server. That only happens during find.
85
+
86
+ model = model.base_class
87
+
88
+ # do we already have a record with this vector? If so return it, otherwise make a new one.
89
+
90
+ record = @records[model].detect { |record| record.vector == vector}
91
+ (record = new(model)).vector = vector unless record
92
+ record.ar_instance ||= infer_type_from_hash(model, record.attributes).new(record)
93
+
94
+ end
95
+
96
+ def initialize(model, hash = {}, ar_instance = nil)
97
+ @model = model
98
+ @attributes = hash
99
+ @synced_attributes = {}
100
+ @ar_instance = ar_instance
101
+ records[model] << self
102
+ end
103
+
104
+ def find(*args)
105
+ self.find(*args)
106
+ end
107
+
108
+ def new_from_vector(*args)
109
+ self.class.new_from_vector(*args)
110
+ end
111
+
112
+ def primary_key
113
+ @model.primary_key
114
+ end
115
+
116
+ def id
117
+ attributes[primary_key]
118
+ end
119
+
120
+ def id=(value)
121
+ # we need to keep the id unique
122
+ existing_record = records[@model].detect { |record| record.attributes[primary_key] == value}
123
+ if existing_record
124
+ @ar_instance.instance_eval { @backing_record = existing_record }
125
+ existing_record.attributes.merge!(attributes) { |key, v1, v2| v1 }
126
+ else
127
+ attributes[primary_key] = value
128
+ end
129
+ end
130
+
131
+ def attributes
132
+ @last_access_at = Time.now
133
+ @attributes
134
+ end
135
+
136
+ def reactive_get!(attribute)
137
+ unless @destroyed
138
+ apply_method(attribute) unless @attributes.has_key? attribute
139
+ React::State.get_state(self, attribute) unless data_loading?
140
+ attributes[attribute]
141
+ end
142
+ end
143
+
144
+ def reactive_set!(attribute, value)
145
+ unless @destroyed or attributes[attribute] == value
146
+ if association = @model.reflect_on_association(attribute)
147
+ if association.collection?
148
+ collection = Collection.new(association.klass, @ar_instance, association)
149
+ collection.replace(value || [])
150
+ value = collection
151
+ else
152
+ inverse_of = association.inverse_of
153
+ inverse_association = association.klass.reflect_on_association(inverse_of)
154
+ if inverse_association.collection?
155
+ if !value
156
+ attributes[attribute].attributes[inverse_of].delete(@ar_instance)
157
+ elsif value.attributes[inverse_of]
158
+ value.attributes[inverse_of] << @ar_instance
159
+ else
160
+ value.attributes[inverse_of] = Collection.new(@model, value, inverse_association)
161
+ value.attributes[inverse_of].replace [@ar_instance]
162
+ end
163
+ elsif value
164
+ attributes[attribute].attributes[inverse_of] = nil if attributes[attribute]
165
+ value.attributes[inverse_of] = @ar_instance
166
+ React::State.set_state(value.instance_variable_get(:@backing_record), inverse_of, @ar_instance) unless data_loading?
167
+ else
168
+ attributes[attribute].attributes[inverse_of] = nil
169
+ end
170
+ end
171
+ end
172
+ attributes[attribute] = value
173
+ React::State.set_state(self, attribute, value) unless data_loading?
174
+ end
175
+ value
176
+ end
177
+
178
+ def changed?(*args)
179
+ if args.count == 0
180
+ React::State.get_state(self, self)
181
+ @attributes != @synced_attributes
182
+ else
183
+ key = args[0]
184
+ React::State.get_state(@attributes, key)
185
+ @attributes.has_key?(key) != @synced_attributes.has_key?(key) or @attributes[key] != @synced_attributes[key]
186
+ end
187
+ end
188
+
189
+ def sync!(hash = {})
190
+ @attributes.merge! hash
191
+ @synced_attributes = @attributes.dup
192
+ @synced_attributes.each { |key, value| @synced_attributes[key] = value.dup if value.is_a? Collection }
193
+ @saving = false
194
+ React::State.set_state(self, self, :synced) unless data_loading?
195
+ self
196
+ end
197
+
198
+ def sync_attribute(attribute, value)
199
+ @synced_attributes[attribute] = attributes[attribute] = value
200
+ @synced_attributes[attribute] = value.dup if value.is_a? ReactiveRecord::Collection
201
+ value
202
+ end
203
+
204
+ def revert
205
+ @attributes.each do |attribute, value|
206
+ @ar_instance.send("#{attribute}=", @synced_attributes[attribute])
207
+ end
208
+ @attributes.delete_if { |attribute, value| !@synced_attributes.has_key?(attribute) }
209
+ end
210
+
211
+ def saving!
212
+ React::State.set_state(self, self, :saving) unless data_loading?
213
+ @saving = true
214
+ end
215
+
216
+ def saving?
217
+ React::State.get_state(self, self)
218
+ @saving
219
+ end
220
+
221
+ def find_association(association, id)
222
+
223
+ inverse_of = association.inverse_of
224
+
225
+ instance = if id
226
+ find(association.klass, association.klass.primary_key, id)
227
+ else
228
+ new_from_vector(association.klass, nil, *vector, association.attribute)
229
+ end
230
+
231
+ instance_backing_record_attributes = instance.instance_variable_get(:@backing_record).attributes
232
+ inverse_association = association.klass.reflect_on_association(inverse_of)
233
+
234
+ if inverse_association.collection?
235
+ instance_backing_record_attributes[inverse_of] = if id and id != ""
236
+ Collection.new(@model, instance, inverse_association, association.klass, ["find", id], inverse_of)
237
+ else
238
+ Collection.new(@model, instance, inverse_association, *vector, association.attribute, inverse_of)
239
+ end unless instance_backing_record_attributes[inverse_of]
240
+ instance_backing_record_attributes[inverse_of].replace [@ar_instance]
241
+ else
242
+ instance_backing_record_attributes[inverse_of] = @ar_instance
243
+ end if inverse_of
244
+ instance
245
+ end
246
+
247
+ def apply_method(method)
248
+ # Fills in the value returned by sending "method" to the corresponding server side db instance
249
+ if id or vector
250
+ sync_attribute(
251
+ method,
252
+ if association = @model.reflect_on_association(method)
253
+ if association.collection?
254
+ Collection.new(association.klass, @ar_instance, association, *vector, method)
255
+ else
256
+ find_association(association, (id and id != "" and self.class.fetch_from_db([@model, [:find, id], method, @model.primary_key])))
257
+ end
258
+ elsif aggregation = @model.reflect_on_aggregation(method)
259
+ new_from_vector(aggregation.klass, self, *vector, method)
260
+ elsif id and id != ""
261
+ self.class.fetch_from_db([@model, [:find, id], method]) || self.class.load_from_db(*vector, method)
262
+ else # its a attribute in an aggregate or we are on the client and don't know the id
263
+ self.class.fetch_from_db([*vector, method]) || self.class.load_from_db(*vector, method)
264
+ end
265
+ )
266
+ elsif association = @model.reflect_on_association(method) and association.collection?
267
+ @attributes[method] = Collection.new(association.klass, @ar_instance, association)
268
+ elsif aggregation = @model.reflect_on_aggregation(method)
269
+ @attributes[method] = aggregation.klass.new
270
+ end
271
+ end
272
+
273
+ def self.infer_type_from_hash(klass, hash)
274
+ klass = klass.base_class
275
+ return klass unless hash
276
+ type = hash[klass.inheritance_column]
277
+ begin
278
+ return Object.const_get(type)
279
+ rescue Exeception => e
280
+ message = "Could not subclass #{@model_klass.model_name} as #{type}. Perhaps #{type} class has not been required. Exception: #{e}"
281
+ `console.error(#{message})`
282
+ end if type
283
+ klass
284
+ end
285
+
286
+ end
287
+ end
@@ -0,0 +1,100 @@
1
+ module ReactiveRecord
2
+
3
+ class Collection
4
+
5
+ def initialize(target_klass, owner = nil, association = nil, *vector)
6
+ if association and (association.macro != :has_many or association.klass != target_klass)
7
+ message = "unimplemented association #{owner} :#{association.macro} #{association.attribute}"
8
+ `console.error(#{message})`
9
+ end
10
+ @owner = owner # can be nil if this is an outer most scope
11
+ @association = association
12
+ @target_klass = target_klass
13
+ if owner and !owner.id and !owner.vector
14
+ @synced_collection = @collection = []
15
+ else
16
+ @vector = vector.count == 0 ? [target_klass] : vector
17
+ end
18
+ @scopes = {}
19
+ end
20
+
21
+ def all
22
+ unless @collection
23
+ @collection = []
24
+ if ids = ReactiveRecord::Base.fetch_from_db([*@vector, "*all"])
25
+ ids.each do |id|
26
+ @collection << @target_klass.find_by(@target_klass.primary_key => id)
27
+ end
28
+ else
29
+ ReactiveRecord::Base.load_from_db(*@vector, "*all")
30
+ @collection << ReactiveRecord::Base.new_from_vector(@target_klass, nil, *@vector, "*")
31
+ end
32
+ end
33
+ @collection
34
+ end
35
+
36
+
37
+ def ==(other_collection)
38
+ if @collection
39
+ @collection == other_collection.all
40
+ else
41
+ !other_collection.instance_variable_get(:@collection)
42
+ end
43
+ end
44
+
45
+ def apply_scope(scope)
46
+ # The value returned is another ReactiveRecordCollection with the scope added to the vector
47
+ # no additional action is taken
48
+ @scopes[scope] ||= new(@target_klass, @owner, @association, *vector, scope)
49
+ end
50
+
51
+ def proxy_association
52
+ @association
53
+ end
54
+
55
+
56
+ def <<(item)
57
+ inverse_of = @association.inverse_of
58
+ if @owner and inverse_of = @association.inverse_of
59
+ item.attributes[inverse_of].attributes[@association.attribute].delete(item) if item.attributes[inverse_of] and item.attributes[inverse_of].attributes[@association.attribute]
60
+ item.attributes[inverse_of] = @owner
61
+ backing_record = item.instance_variable_get(:@backing_record)
62
+ React::State.set_state(backing_record, inverse_of, @owner) unless backing_record.data_loading?
63
+ end
64
+ all << item unless all.include? item
65
+ self
66
+ end
67
+
68
+ def replace(new_array)
69
+ return new_array if @collection == new_array
70
+ if @collection
71
+ @collection.dup.each { |item| delete(item) }
72
+ else
73
+ @collection = []
74
+ end
75
+ new_array.each { |item| self << item }
76
+ new_array
77
+ end
78
+
79
+ def delete(item)
80
+ if @owner and inverse_of = @association.inverse_of
81
+ item.attributes[inverse_of] = nil
82
+ backing_record = item.instance_variable_get(:@backing_record)
83
+ React::State.set_state(backing_record, inverse_of, nil) unless backing_record.data_loading?
84
+ end
85
+ all.delete(item)
86
+ end
87
+
88
+ def method_missing(method, *args, &block)
89
+ if [].respond_to? method
90
+ all.send(method, *args, &block)
91
+ elsif @target_klass.respond_to? method
92
+ apply_scope(method)
93
+ else
94
+ super
95
+ end
96
+ end
97
+
98
+ end
99
+
100
+ end
@@ -0,0 +1,274 @@
1
+ require 'json'
2
+
3
+ module ReactiveRecord
4
+
5
+ class Base
6
+
7
+ include React::IsomorphicHelpers
8
+
9
+ before_first_mount do
10
+ if RUBY_ENGINE != 'opal'
11
+ @server_data_cache = ReactiveRecord::ServerDataCache.new
12
+ else
13
+ @records = Hash.new { |hash, key| hash[key] = [] }
14
+ if on_opal_client?
15
+ @pending_fetches = []
16
+ @last_fetch_at = Time.now
17
+ JSON.from_object(`window.ReactiveRecordInitialData`).each do |hash|
18
+ load_from_json hash
19
+ end unless `typeof window.ReactiveRecordInitialData === 'undefined'`
20
+ end
21
+ end
22
+ end
23
+
24
+ def records
25
+ self.class.instance_variable_get(:@records)
26
+ end
27
+
28
+ # Prerendering db access (returns nil if on client):
29
+ # at end of prerendering dumps all accessed records in the footer
30
+
31
+ isomorphic_method(:fetch_from_db) do |f, vector|
32
+ # vector must end with either "*all", or be a simple attribute
33
+ f.send_to_server [vector.shift.name, *vector] if RUBY_ENGINE == 'opal'
34
+ f.when_on_server { @server_data_cache[*vector] }
35
+ end
36
+
37
+ isomorphic_method(:find_in_db) do |f, klass, attribute, value|
38
+ f.send_to_server klass.name, attribute, value if RUBY_ENGINE == 'opal'
39
+ f.when_on_server { @server_data_cache[klass, ["find_by_#{attribute}", value], :id] }
40
+ end
41
+
42
+ prerender_footer do
43
+ json = @server_data_cache.as_json.to_json # can this just be to_json?
44
+ @server_data_cache.clear_requests
45
+ path = ::Rails.application.routes.routes.detect { |route| route.app.app == ReactiveRecord::Engine }.path.spec
46
+ "<script type='text/javascript'>\n"+
47
+ "window.ReactiveRecordEnginePath = '#{path}';\n"+
48
+ "if (typeof window.ReactiveRecordInitialData === 'undefined') { window.ReactiveRecordInitialData = [] }\n" +
49
+ "window.ReactiveRecordInitialData.push(#{json})\n"+
50
+ "</script>\n"
51
+ end if RUBY_ENGINE != 'opal'
52
+
53
+ # Client side db access (never called during prerendering):
54
+ # queue up fetches, and at the end of each rendering cycle fetch the records
55
+ # notify that loads are pending
56
+
57
+ def self.load_from_db(*vector)
58
+ # only called from the client side
59
+ # pushes the value of vector onto the a list of vectors that will be loaded from the server when the next
60
+ # rendering cycle completes.
61
+ # takes care of informing react that there are things to load, and schedules the loader to run
62
+ # Note there is no equivilent to find_in_db, because each vector implicitly does a find.
63
+ return "" if data_loading?
64
+ ReactiveRecord.loads_pending!
65
+ ReactiveRecord::WhileLoading.loading! # inform react that the current value is bogus
66
+ @pending_fetches << vector
67
+ schedule_fetch
68
+ ""
69
+ end
70
+
71
+ def self.schedule_fetch
72
+ @fetch_scheduled ||= after(0.001) do
73
+ last_fetch_at = @last_fetch_at
74
+ HTTP.post(`window.ReactiveRecordEnginePath`, payload: {pending_fetches: @pending_fetches.uniq}).then do |response|
75
+ begin
76
+ ReactiveRecord::Base.load_from_json(response.json)
77
+ rescue Exception => e
78
+ message = "Exception raised while loading json from server: #{e}"
79
+ `console.error(#{message})`
80
+ end
81
+ ReactiveRecord.run_blocks_to_load
82
+ ReactiveRecord::WhileLoading.loaded_at last_fetch_at
83
+ end if @pending_fetches.count > 0
84
+ @pending_fetches = []
85
+ @last_fetch_at = Time.now
86
+ @fetch_scheduled = nil
87
+ end
88
+ end
89
+
90
+ def self.get_type_hash(record)
91
+ {record.class.inheritance_column => record[record.class.inheritance_column]}
92
+ end
93
+
94
+ # save records
95
+
96
+ if RUBY_ENGINE == 'opal'
97
+
98
+ def save(&block)
99
+
100
+ if data_loading?
101
+
102
+ sync!
103
+
104
+ elsif changed?
105
+
106
+ # we want to pass not just the model data to save, but also enough information so that on return from the server
107
+ # we can update the models on the client
108
+
109
+ # input
110
+ records_to_process = [self] # list of records to process, will grow as we chase associations
111
+ # outputs
112
+ models = [] # the actual data to save {id: record.object_id, model: record.model.model_name, attributes: changed_attributes}
113
+ associations = [] # {parent_id: record.object_id, attribute: attribute, child_id: assoc_record.object_id}
114
+ # used to keep track of records that have been processed for effeciency
115
+ backing_records = {self.object_id => self} # for quick lookup of records that have been or will be processed [record.object_id] => record
116
+
117
+ add_new_association = lambda do |record, attribute, assoc_record|
118
+ if assoc_record.changed?
119
+ unless backing_records[assoc_record.object_id]
120
+ records_to_process << assoc_record
121
+ backing_records[assoc_record.object_id] = assoc_record
122
+ end
123
+ associations << {parent_id: record.object_id, attribute: attribute, child_id: assoc_record.object_id}
124
+ end
125
+ end
126
+
127
+ record_index = 0
128
+ while(record_index < records_to_process.count)
129
+ record = records_to_process[record_index]
130
+ output_attributes = {record.model.primary_key => record.id}
131
+ models << {id: record.object_id, model: record.model.model_name, attributes: output_attributes}
132
+ record.attributes.each do |attribute, value|
133
+ if association = record.model.reflect_on_association(attribute)
134
+ if association.collection?
135
+ value.each { |assoc| add_new_association.call record, attribute, assoc.instance_variable_get(:@backing_record) }
136
+ else
137
+ add_new_association.call record, attribute, value.instance_variable_get(:@backing_record)
138
+ end
139
+ elsif record.model.reflect_on_aggregation(attribute)
140
+ add_new_association.call record, attribute, value.instance_variable_get(:@backing_record)
141
+ elsif record.changed?(attribute)
142
+ output_attributes[attribute] = value
143
+ end
144
+ end
145
+ record_index += 1
146
+ end
147
+
148
+ backing_records.each { |id, record| record.saving! }
149
+
150
+ promise = Promise.new
151
+
152
+ HTTP.post(`window.ReactiveRecordEnginePath`+"/save", payload: {models: models, associations: associations}).then do |response|
153
+
154
+ response.json[:saved_models].each do |item|
155
+ internal_id, klass, attributes = item
156
+ backing_records[internal_id].sync!(attributes)
157
+ end
158
+ yield response.json[:success], response.json[:message] if block
159
+ promise.resolve response.json[:success], response.json[:message]
160
+ end
161
+ promise
162
+ end
163
+ end
164
+
165
+ else
166
+
167
+ def self.save_records(models, associations)
168
+
169
+ reactive_records = {}
170
+
171
+ models.each do |model_to_save|
172
+ attributes = model_to_save[:attributes]
173
+ model = Object.const_get(model_to_save[:model])
174
+ id = attributes[model.primary_key] # if we are saving existing model primary key value will be present
175
+ reactive_records[model_to_save[:id]] = if id
176
+ record = model.find(id)
177
+ keys = record.attributes.keys
178
+ attributes.each do |key, value|
179
+ record[key] = value if keys.include? key
180
+ end
181
+ record
182
+ else
183
+ record = model.new
184
+ keys = record.attributes.keys
185
+ attributes.each do |key, value|
186
+ record[key] = value if keys.include? key
187
+ end
188
+ record
189
+ end
190
+ end
191
+
192
+ associations.each do |association|
193
+ begin
194
+ if reactive_records[association[:parent_id]].class.reflect_on_aggregation(association[:attribute].to_sym)
195
+ reactive_records[association[:parent_id]].send("#{association[:attribute]}=", reactive_records[association[:child_id]])
196
+ elsif reactive_records[association[:parent_id]].class.reflect_on_association(association[:attribute].to_sym).collection?
197
+ reactive_records[association[:parent_id]].send("#{association[:attribute]}") << reactive_records[association[:child_id]]
198
+ else
199
+ reactive_records[association[:parent_id]].send("#{association[:attribute]}=", reactive_records[association[:child_id]])
200
+ end
201
+ end
202
+ end if associations
203
+
204
+ saved_models = reactive_records.collect do |reactive_record_id, model|
205
+ unless model.frozen?
206
+ saved = model.save
207
+ [reactive_record_id, model.class.name, model.attributes, saved]
208
+ end
209
+ end.compact
210
+
211
+ {success: true, saved_models: saved_models || []}
212
+
213
+ rescue Exception => e
214
+
215
+ {success: false, saved_models: saved_models || [], message: e.message}
216
+
217
+ end
218
+
219
+ end
220
+
221
+ # destroy records
222
+
223
+ if RUBY_ENGINE == 'opal'
224
+
225
+ def destroy(&block)
226
+
227
+ return if @destroyed
228
+
229
+ model.reflect_on_all_associations.each do |association|
230
+ if association.collection?
231
+ attributes[association.attribute].replace([]) if attributes[association.attribute]
232
+ else
233
+ @ar_instance.send("#{association.attribute}=", nil)
234
+ end
235
+ end
236
+
237
+ promise = Promise.new
238
+
239
+ if id or vector
240
+ HTTP.post(`window.ReactiveRecordEnginePath`+"/destroy", payload: {model: ar_instance.model_name, id: id, vector: vector}).then do |response|
241
+ yield response.json[:success], response.json[:message] if block
242
+ promise.resolve response.json[:success], response.json[:message]
243
+ end
244
+ else
245
+ yield true, nil if block
246
+ promise.resolve true, nil
247
+ end
248
+
249
+ @attributes = {}
250
+ sync!
251
+ @destroyed = true
252
+
253
+ promise
254
+ end
255
+
256
+ else
257
+
258
+ def self.destroy_record(model, id, vector)
259
+ model = Object.const_get(model)
260
+ record = if id
261
+ model.find(id)
262
+ else
263
+ ServerDataCache.new[*vector]
264
+ end
265
+ record.destroy
266
+ {success: true, attributes: {}}
267
+ rescue Exception => e
268
+ {success: false, record: record, message: e.message}
269
+ end
270
+
271
+ end
272
+ end
273
+
274
+ end