live_record 0.3.4 → 0.3.6
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 +4 -4
- data/.blade.yml +11 -0
- data/.gitignore +4 -1
- data/.npmignore +0 -0
- data/Gemfile +2 -0
- data/README.md +6 -1
- data/app/assets/javascripts/live_record.coffee +1 -0
- data/app/assets/javascripts/live_record/helpers.coffee +1 -1
- data/app/assets/javascripts/live_record/helpers/case_converter.coffee +1 -1
- data/app/assets/javascripts/live_record/helpers/load_records.coffee +1 -1
- data/app/assets/javascripts/live_record/helpers/spaceship.coffee +1 -1
- data/app/assets/javascripts/live_record/model.coffee +1 -1
- data/app/assets/javascripts/live_record/model/all.coffee +1 -1
- data/app/assets/javascripts/live_record/model/create.coffee +1 -1
- data/app/assets/javascripts/live_record/plugins.coffee +1 -1
- data/app/assets/javascripts/live_record/plugins/live_dom.coffee +2 -2
- data/app/assets/javascripts/live_record/plugins/live_dom/apply_to_model.coffee +1 -1
- data/developer_guide.md +24 -0
- data/lib/assets/compiled/live_record.js +603 -0
- data/lib/live_record/version.rb +1 -1
- data/live_record.gemspec +2 -0
- data/package-lock.json +13 -0
- data/package.json +19 -0
- metadata +36 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 509801a74ef9c87f3520ed9312e6cf8cb8c6861f4c250dc2075e7ba8822882c2
|
4
|
+
data.tar.gz: 220bc5c24e415c54c215b94b983b41bb7ced614090d82d89d347c4544c52ea95
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 949adea6b2fecd574a8fa9c69a4168f87c2231d3e4cf5b3e4920c6d19c155533d320b8bc0393fd93ca4eb2e8354a472c623f948eb199a836cacca041c7cdd4a0
|
7
|
+
data.tar.gz: 4822d238761d68875d963456ff7c11248368f9a91dfd40ea3f7f98e7d677104a7bc53166af8fcb05b0938bfebdd5f3d152329e44b9252d1e961ba2dc4530a6d4
|
data/.blade.yml
ADDED
data/.gitignore
CHANGED
data/.npmignore
ADDED
File without changes
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -157,7 +157,7 @@
|
|
157
157
|
1. Add the following to your `Gemfile`:
|
158
158
|
|
159
159
|
```ruby
|
160
|
-
gem 'live_record', '~> 0.3.
|
160
|
+
gem 'live_record', '~> 0.3.6'
|
161
161
|
```
|
162
162
|
|
163
163
|
2. Run:
|
@@ -779,7 +779,12 @@ end
|
|
779
779
|
## License
|
780
780
|
* MIT
|
781
781
|
|
782
|
+
## Developer Guide
|
783
|
+
* see [developer_guide.md](developer_guide.md)
|
784
|
+
|
782
785
|
## Changelog
|
786
|
+
* 0.3.6
|
787
|
+
* set up as a Node module
|
783
788
|
* 0.3.4
|
784
789
|
* now supports Rails `~> 5.2` after being tested to work
|
785
790
|
* update dependency to Rails (and other dev gems) to use semantic versioning: `~> 5.0`, instead of `>= 5.0, < 5.3`
|
@@ -1,4 +1,4 @@
|
|
1
|
-
LiveRecord.helpers.loadRecords = (args) ->
|
1
|
+
this.LiveRecord.helpers.loadRecords = (args) ->
|
2
2
|
args['modelName'] || throw new Error(':modelName argument required')
|
3
3
|
throw new Error(':modelName is not defined in LiveRecord.Model.all') if LiveRecord.Model.all[args['modelName']] == undefined
|
4
4
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# ref: https://stackoverflow.com/questions/34852855/combined-comparison-spaceship-operator-in-javascript
|
2
2
|
|
3
|
-
LiveRecord.helpers.spaceship = (val1, val2) ->
|
3
|
+
this.LiveRecord.helpers.spaceship = (val1, val2) ->
|
4
4
|
if val1 == null or val2 == null or typeof val1 != typeof val2
|
5
5
|
return null
|
6
6
|
if typeof val1 == 'string'
|
@@ -1 +1 @@
|
|
1
|
-
LiveRecord.Model.all = {}
|
1
|
+
this.LiveRecord.Model.all = {}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
LiveRecord.Model.create = (config) ->
|
1
|
+
this.LiveRecord.Model.create = (config) ->
|
2
2
|
config.modelName != undefined || throw new Error('missing :modelName argument')
|
3
3
|
config.callbacks != undefined || config.callbacks = {}
|
4
4
|
config.plugins != undefined || config.callbacks = {}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#= require_self
|
2
2
|
#= require_directory ./live_dom/
|
3
3
|
|
4
|
-
LiveRecord.plugins.LiveDOM ||= {}
|
4
|
+
this.LiveRecord.plugins.LiveDOM ||= {}
|
5
5
|
|
6
|
-
if window.jQuery == undefined
|
6
|
+
if window.jQuery == undefined
|
7
7
|
throw new Error('jQuery is not loaded yet, and is a dependency of LiveRecord')
|
data/developer_guide.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
## Development
|
2
|
+
|
3
|
+
### Publishing as Ruby Gem
|
4
|
+
|
5
|
+
```bash
|
6
|
+
# [increment gem VERSION]
|
7
|
+
gem build live_record
|
8
|
+
gem push live_record-X.X.X.gem
|
9
|
+
```
|
10
|
+
|
11
|
+
### Publishing as Node Module
|
12
|
+
|
13
|
+
```bash
|
14
|
+
# [increment gem VERSION]
|
15
|
+
bundle exec blade build
|
16
|
+
# see .blade.yml: which will sprockets-compile app/assets/javascripts/live_record.coffee into lib/assets/compile/live_record.js
|
17
|
+
npm publish --access=public
|
18
|
+
```
|
19
|
+
|
20
|
+
## Test
|
21
|
+
|
22
|
+
```bash
|
23
|
+
bundle exec rspec
|
24
|
+
```
|
@@ -0,0 +1,603 @@
|
|
1
|
+
(function() {
|
2
|
+
this.LiveRecord || (this.LiveRecord = {});
|
3
|
+
|
4
|
+
}).call(this);
|
5
|
+
(function() {
|
6
|
+
var base;
|
7
|
+
|
8
|
+
(base = this.LiveRecord).helpers || (base.helpers = {});
|
9
|
+
|
10
|
+
}).call(this);
|
11
|
+
(function() {
|
12
|
+
this.LiveRecord.helpers.caseConverter = {
|
13
|
+
toCamel: function(string) {
|
14
|
+
return string.replace(/(\-[a-z])/g, function($1) {
|
15
|
+
return $1.toUpperCase().replace('-', '');
|
16
|
+
});
|
17
|
+
},
|
18
|
+
toUnderscore: function(string) {
|
19
|
+
return string.replace(/([A-Z])/g, function($1) {
|
20
|
+
return "_" + $1.toLowerCase();
|
21
|
+
});
|
22
|
+
}
|
23
|
+
};
|
24
|
+
|
25
|
+
}).call(this);
|
26
|
+
(function() {
|
27
|
+
this.LiveRecord.helpers.loadRecords = function(args) {
|
28
|
+
args['modelName'] || (function() {
|
29
|
+
throw new Error(':modelName argument required');
|
30
|
+
})();
|
31
|
+
if (LiveRecord.Model.all[args['modelName']] === void 0) {
|
32
|
+
throw new Error(':modelName is not defined in LiveRecord.Model.all');
|
33
|
+
}
|
34
|
+
args['url'] || (args['url'] = window.location.href);
|
35
|
+
return $.getJSON(args['url']).done(function(data) {
|
36
|
+
var i, len, record, record_attributes, record_or_records, records, records_attributes;
|
37
|
+
record_or_records = void 0;
|
38
|
+
if ($.isArray(data)) {
|
39
|
+
records_attributes = data;
|
40
|
+
records = [];
|
41
|
+
for (i = 0, len = records_attributes.length; i < len; i++) {
|
42
|
+
record_attributes = records_attributes[i];
|
43
|
+
record = new LiveRecord.Model.all[args['modelName']](record_attributes);
|
44
|
+
record.create();
|
45
|
+
records.push(record);
|
46
|
+
}
|
47
|
+
record_or_records = records;
|
48
|
+
} else if (data) {
|
49
|
+
record_attributes = data;
|
50
|
+
record = new LiveRecord.Model.all[args['modelName']](record_attributes);
|
51
|
+
record.create();
|
52
|
+
record_or_records = record;
|
53
|
+
}
|
54
|
+
if (args['onLoad']) {
|
55
|
+
return args['onLoad'].call(this, record_or_records);
|
56
|
+
}
|
57
|
+
}).fail(function(jqxhr, textStatus, error) {
|
58
|
+
if (args['onError']) {
|
59
|
+
return args['onError'].call(this, jqxhr, textStatus, error);
|
60
|
+
}
|
61
|
+
});
|
62
|
+
};
|
63
|
+
|
64
|
+
}).call(this);
|
65
|
+
(function() {
|
66
|
+
this.LiveRecord.helpers.spaceship = function(val1, val2) {
|
67
|
+
if (val1 === null || val2 === null || typeof val1 !== typeof val2) {
|
68
|
+
return null;
|
69
|
+
}
|
70
|
+
if (typeof val1 === 'string') {
|
71
|
+
return val1.localeCompare(val2);
|
72
|
+
} else {
|
73
|
+
if (val1 > val2) {
|
74
|
+
return 1;
|
75
|
+
} else if (val1 < val2) {
|
76
|
+
return -1;
|
77
|
+
}
|
78
|
+
return 0;
|
79
|
+
}
|
80
|
+
};
|
81
|
+
|
82
|
+
}).call(this);
|
83
|
+
(function() {
|
84
|
+
var base;
|
85
|
+
|
86
|
+
(base = this.LiveRecord).Model || (base.Model = {});
|
87
|
+
|
88
|
+
}).call(this);
|
89
|
+
(function() {
|
90
|
+
this.LiveRecord.Model.all = {};
|
91
|
+
|
92
|
+
}).call(this);
|
93
|
+
(function() {
|
94
|
+
this.LiveRecord.Model.create = function(config) {
|
95
|
+
var Model, callbackFunction, callbackFunctions, callbackKey, i, index, len, methodKey, methodValue, pluginKey, pluginValue, ref, ref1, ref2, ref3;
|
96
|
+
config.modelName !== void 0 || (function() {
|
97
|
+
throw new Error('missing :modelName argument');
|
98
|
+
})();
|
99
|
+
config.callbacks !== void 0 || (config.callbacks = {});
|
100
|
+
config.plugins !== void 0 || (config.callbacks = {});
|
101
|
+
Model = function(attributes) {
|
102
|
+
if (attributes == null) {
|
103
|
+
attributes = {};
|
104
|
+
}
|
105
|
+
this.attributes = attributes;
|
106
|
+
Object.keys(this.attributes).forEach(function(attribute_key) {
|
107
|
+
if (Model.prototype[attribute_key] === void 0) {
|
108
|
+
return Model.prototype[attribute_key] = function() {
|
109
|
+
return this.attributes[attribute_key];
|
110
|
+
};
|
111
|
+
}
|
112
|
+
});
|
113
|
+
this._callbacks = {
|
114
|
+
'on:connect': [],
|
115
|
+
'on:disconnect': [],
|
116
|
+
'on:responseError': [],
|
117
|
+
'before:create': [],
|
118
|
+
'after:create': [],
|
119
|
+
'before:update': [],
|
120
|
+
'after:update': [],
|
121
|
+
'before:destroy': [],
|
122
|
+
'after:destroy': []
|
123
|
+
};
|
124
|
+
return this;
|
125
|
+
};
|
126
|
+
Model.modelName = config.modelName;
|
127
|
+
Model.associations = {
|
128
|
+
hasMany: config.hasMany,
|
129
|
+
belongsTo: config.belongsTo
|
130
|
+
};
|
131
|
+
if (Model.associations.hasMany) {
|
132
|
+
Object.keys(Model.associations.hasMany).forEach(function(key, index) {
|
133
|
+
var associationConfig, associationName;
|
134
|
+
associationName = key;
|
135
|
+
associationConfig = Model.associations.hasMany[associationName];
|
136
|
+
return Model.prototype[associationName] = function() {
|
137
|
+
var associatedModel, associatedRecords, id, isAssociated, record, ref, self;
|
138
|
+
self = this;
|
139
|
+
associatedModel = LiveRecord.Model.all[associationConfig.modelName];
|
140
|
+
if (!associatedModel) {
|
141
|
+
throw new Error('No defined model for "' + associationConfig.modelName + '"');
|
142
|
+
}
|
143
|
+
associatedRecords = [];
|
144
|
+
ref = associatedModel.all;
|
145
|
+
for (id in ref) {
|
146
|
+
record = ref[id];
|
147
|
+
isAssociated = record[associationConfig.foreignKey]() === self.id();
|
148
|
+
if (isAssociated) {
|
149
|
+
associatedRecords.push(record);
|
150
|
+
}
|
151
|
+
}
|
152
|
+
return associatedRecords;
|
153
|
+
};
|
154
|
+
});
|
155
|
+
}
|
156
|
+
if (Model.associations.belongsTo) {
|
157
|
+
Object.keys(Model.associations.belongsTo).forEach(function(key, index) {
|
158
|
+
var associationConfig, associationName;
|
159
|
+
associationName = key;
|
160
|
+
associationConfig = Model.associations.belongsTo[associationName];
|
161
|
+
return Model.prototype[associationName] = function() {
|
162
|
+
var associatedModel, belongsToID, self;
|
163
|
+
self = this;
|
164
|
+
associatedModel = LiveRecord.Model.all[associationConfig.modelName];
|
165
|
+
if (!associatedModel) {
|
166
|
+
throw new Error('No defined model for "' + associationConfig.modelName + '"');
|
167
|
+
}
|
168
|
+
belongsToID = self[associationConfig.foreignKey]();
|
169
|
+
return associatedModel.all[belongsToID];
|
170
|
+
};
|
171
|
+
});
|
172
|
+
}
|
173
|
+
Model.all = {};
|
174
|
+
Model.subscriptions = [];
|
175
|
+
Model.autoload = function(config) {
|
176
|
+
var subscription;
|
177
|
+
if (config == null) {
|
178
|
+
config = {};
|
179
|
+
}
|
180
|
+
config.callbacks || (config.callbacks = {});
|
181
|
+
config.reload || (config.reload = false);
|
182
|
+
if (config.callbacks.afterReload && !config.reload) {
|
183
|
+
throw new Error('`afterReload` callback only works with `reload: true`');
|
184
|
+
}
|
185
|
+
subscription = App.cable.subscriptions.create({
|
186
|
+
channel: 'LiveRecord::AutoloadsChannel',
|
187
|
+
model_name: Model.modelName,
|
188
|
+
where: config.where
|
189
|
+
}, {
|
190
|
+
connected: function() {
|
191
|
+
if (config.reload) {
|
192
|
+
config.reload = false;
|
193
|
+
this.syncRecords();
|
194
|
+
}
|
195
|
+
if (this.liveRecord._staleSince !== void 0) {
|
196
|
+
this.syncRecords();
|
197
|
+
}
|
198
|
+
if (config.callbacks['on:connect']) {
|
199
|
+
return config.callbacks['on:connect'].call(this);
|
200
|
+
}
|
201
|
+
},
|
202
|
+
disconnected: function() {
|
203
|
+
if (!this.liveRecord._staleSince) {
|
204
|
+
this.liveRecord._staleSince = (new Date()).toISOString();
|
205
|
+
}
|
206
|
+
if (config.callbacks['on:disconnect']) {
|
207
|
+
return config.callbacks['on:disconnect'].call(this);
|
208
|
+
}
|
209
|
+
},
|
210
|
+
received: function(data) {
|
211
|
+
if (data.error) {
|
212
|
+
if (!this.liveRecord._staleSince) {
|
213
|
+
this.liveRecord._staleSince = (new Date()).toISOString();
|
214
|
+
}
|
215
|
+
return this.onError[data.error.code].call(this, data);
|
216
|
+
} else {
|
217
|
+
return this.onAction[data.action].call(this, data);
|
218
|
+
}
|
219
|
+
},
|
220
|
+
onAction: {
|
221
|
+
createOrUpdate: function(data) {
|
222
|
+
var doesRecordAlreadyExist, record;
|
223
|
+
record = Model.all[data.attributes.id];
|
224
|
+
if (record) {
|
225
|
+
doesRecordAlreadyExist = true;
|
226
|
+
} else {
|
227
|
+
record = new Model(data.attributes);
|
228
|
+
doesRecordAlreadyExist = false;
|
229
|
+
}
|
230
|
+
if (config.callbacks['before:createOrUpdate']) {
|
231
|
+
config.callbacks['before:createOrUpdate'].call(this, record);
|
232
|
+
}
|
233
|
+
if (doesRecordAlreadyExist) {
|
234
|
+
record.update(data.attributes);
|
235
|
+
} else {
|
236
|
+
record.create();
|
237
|
+
}
|
238
|
+
if (config.callbacks['after:createOrUpdate']) {
|
239
|
+
return config.callbacks['after:createOrUpdate'].call(this, record);
|
240
|
+
}
|
241
|
+
},
|
242
|
+
afterReload: function(data) {
|
243
|
+
if (config.callbacks['after:reload']) {
|
244
|
+
return config.callbacks['after:reload'].call(this, data.recordIds);
|
245
|
+
}
|
246
|
+
}
|
247
|
+
},
|
248
|
+
onError: {
|
249
|
+
forbidden: function(data) {
|
250
|
+
return console.error('[LiveRecord Response Error]', data.error.code, ':', data.error.message, 'for', this);
|
251
|
+
},
|
252
|
+
bad_request: function(data) {
|
253
|
+
return console.error('[LiveRecord Response Error]', data.error.code, ':', data.error.message, 'for', this);
|
254
|
+
}
|
255
|
+
},
|
256
|
+
syncRecords: function() {
|
257
|
+
this.perform('sync_records', {
|
258
|
+
model_name: Model.modelName,
|
259
|
+
where: config.where,
|
260
|
+
stale_since: this.liveRecord._staleSince
|
261
|
+
});
|
262
|
+
return this.liveRecord._staleSince = void 0;
|
263
|
+
}
|
264
|
+
});
|
265
|
+
subscription.liveRecord = {};
|
266
|
+
subscription.liveRecord.modelName = Model.modelName;
|
267
|
+
subscription.liveRecord.where = config.where;
|
268
|
+
subscription.liveRecord.callbacks = config.callbacks;
|
269
|
+
this.subscriptions.push(subscription);
|
270
|
+
return subscription;
|
271
|
+
};
|
272
|
+
Model.subscribe = function(config) {
|
273
|
+
var subscription;
|
274
|
+
if (config == null) {
|
275
|
+
config = {};
|
276
|
+
}
|
277
|
+
config.callbacks || (config.callbacks = {});
|
278
|
+
config.reload || (config.reload = false);
|
279
|
+
if (config.callbacks.afterReload && !config.reload) {
|
280
|
+
throw new Error('`afterReload` callback only works with `reload: true`');
|
281
|
+
}
|
282
|
+
subscription = App.cable.subscriptions.create({
|
283
|
+
channel: 'LiveRecord::PublicationsChannel',
|
284
|
+
model_name: Model.modelName,
|
285
|
+
where: config.where
|
286
|
+
}, {
|
287
|
+
connected: function() {
|
288
|
+
if (config.reload) {
|
289
|
+
config.reload = false;
|
290
|
+
this.syncRecords();
|
291
|
+
}
|
292
|
+
if (this.liveRecord._staleSince !== void 0) {
|
293
|
+
this.syncRecords();
|
294
|
+
}
|
295
|
+
if (config.callbacks['on:connect']) {
|
296
|
+
return config.callbacks['on:connect'].call(this);
|
297
|
+
}
|
298
|
+
},
|
299
|
+
disconnected: function() {
|
300
|
+
if (!this.liveRecord._staleSince) {
|
301
|
+
this.liveRecord._staleSince = (new Date()).toISOString();
|
302
|
+
}
|
303
|
+
if (config.callbacks['on:disconnect']) {
|
304
|
+
return config.callbacks['on:disconnect'].call(this);
|
305
|
+
}
|
306
|
+
},
|
307
|
+
received: function(data) {
|
308
|
+
if (data.error) {
|
309
|
+
if (!this.liveRecord._staleSince) {
|
310
|
+
this.liveRecord._staleSince = (new Date()).toISOString();
|
311
|
+
}
|
312
|
+
return this.onError[data.error.code].call(this, data);
|
313
|
+
} else {
|
314
|
+
return this.onAction[data.action].call(this, data);
|
315
|
+
}
|
316
|
+
},
|
317
|
+
onAction: {
|
318
|
+
create: function(data) {
|
319
|
+
var record;
|
320
|
+
record = new Model(data.attributes);
|
321
|
+
if (config.callbacks['before:create']) {
|
322
|
+
config.callbacks['before:create'].call(this, record);
|
323
|
+
}
|
324
|
+
record.create();
|
325
|
+
if (config.callbacks['after:create']) {
|
326
|
+
return config.callbacks['after:create'].call(this, record);
|
327
|
+
}
|
328
|
+
},
|
329
|
+
afterReload: function(data) {
|
330
|
+
if (config.callbacks['after:reload']) {
|
331
|
+
return config.callbacks['after:reload'].call(this, data.recordIds);
|
332
|
+
}
|
333
|
+
}
|
334
|
+
},
|
335
|
+
onError: {
|
336
|
+
forbidden: function(data) {
|
337
|
+
return console.error('[LiveRecord Response Error]', data.error.code, ':', data.error.message, 'for', this);
|
338
|
+
},
|
339
|
+
bad_request: function(data) {
|
340
|
+
return console.error('[LiveRecord Response Error]', data.error.code, ':', data.error.message, 'for', this);
|
341
|
+
}
|
342
|
+
},
|
343
|
+
syncRecords: function() {
|
344
|
+
this.perform('sync_records', {
|
345
|
+
model_name: Model.modelName,
|
346
|
+
where: config.where,
|
347
|
+
stale_since: this.liveRecord._staleSince
|
348
|
+
});
|
349
|
+
return this.liveRecord._staleSince = void 0;
|
350
|
+
}
|
351
|
+
});
|
352
|
+
subscription.liveRecord = {};
|
353
|
+
subscription.liveRecord.modelName = Model.modelName;
|
354
|
+
subscription.liveRecord.where = config.where;
|
355
|
+
subscription.liveRecord.callbacks = config.callbacks;
|
356
|
+
this.subscriptions.push(subscription);
|
357
|
+
return subscription;
|
358
|
+
};
|
359
|
+
Model.unsubscribe = function(subscription) {
|
360
|
+
var index;
|
361
|
+
index = this.subscriptions.indexOf(subscription);
|
362
|
+
if (index === -1) {
|
363
|
+
throw new Error('`subscription` argument does not exist in ' + this.modelName + ' subscriptions list');
|
364
|
+
}
|
365
|
+
App.cable.subscriptions.remove(subscription);
|
366
|
+
this.subscriptions.splice(index, 1);
|
367
|
+
return subscription;
|
368
|
+
};
|
369
|
+
ref = config.classMethods;
|
370
|
+
for (methodKey in ref) {
|
371
|
+
methodValue = ref[methodKey];
|
372
|
+
if (Model[methodKey] !== void 0) {
|
373
|
+
throw new Error('Cannot use reserved name as class method: ', methodKey);
|
374
|
+
}
|
375
|
+
Model[methodKey] = methodValue;
|
376
|
+
}
|
377
|
+
Model.prototype.subscribe = function(config) {
|
378
|
+
var subscription;
|
379
|
+
if (config == null) {
|
380
|
+
config = {};
|
381
|
+
}
|
382
|
+
if (this.subscription !== void 0) {
|
383
|
+
return this.subscription;
|
384
|
+
}
|
385
|
+
config.reload || (config.reload = false);
|
386
|
+
subscription = App['live_record_' + this.modelName() + '_' + this.id()] = App.cable.subscriptions.create({
|
387
|
+
channel: 'LiveRecord::ChangesChannel',
|
388
|
+
model_name: this.modelName(),
|
389
|
+
record_id: this.id()
|
390
|
+
}, {
|
391
|
+
record: function() {
|
392
|
+
var identifier;
|
393
|
+
if (this._record) {
|
394
|
+
return this._record;
|
395
|
+
}
|
396
|
+
identifier = JSON.parse(this.identifier);
|
397
|
+
return this._record = Model.all[identifier.record_id];
|
398
|
+
},
|
399
|
+
connected: function() {
|
400
|
+
if (config.reload) {
|
401
|
+
config.reload = false;
|
402
|
+
this.syncRecord(this.record());
|
403
|
+
}
|
404
|
+
if (this.record()._staleSince !== void 0) {
|
405
|
+
this.syncRecord(this.record());
|
406
|
+
}
|
407
|
+
return this.record()._callCallbacks('on:connect', void 0);
|
408
|
+
},
|
409
|
+
disconnected: function() {
|
410
|
+
if (!this.record()._staleSince) {
|
411
|
+
this.record()._staleSince = (new Date()).toISOString();
|
412
|
+
}
|
413
|
+
return this.record()._callCallbacks('on:disconnect', void 0);
|
414
|
+
},
|
415
|
+
received: function(data) {
|
416
|
+
if (data.error) {
|
417
|
+
if (!this.record()._staleSince) {
|
418
|
+
this.record()._staleSince = (new Date()).toISOString();
|
419
|
+
}
|
420
|
+
this.onError[data.error.code].call(this, data);
|
421
|
+
this.record()._callCallbacks('on:responseError', [data.error.code]);
|
422
|
+
return delete this.record()['subscription'];
|
423
|
+
} else {
|
424
|
+
return this.onAction[data.action].call(this, data);
|
425
|
+
}
|
426
|
+
},
|
427
|
+
onAction: {
|
428
|
+
update: function(data) {
|
429
|
+
this.record()._setChangesFrom(data.attributes);
|
430
|
+
this.record().update(data.attributes);
|
431
|
+
return this.record()._unsetChanges();
|
432
|
+
},
|
433
|
+
destroy: function(data) {
|
434
|
+
return this.record().destroy();
|
435
|
+
}
|
436
|
+
},
|
437
|
+
onError: {
|
438
|
+
forbidden: function(data) {
|
439
|
+
return console.error('[LiveRecord Response Error]', data.error.code, ':', data.error.message, 'for', this.record());
|
440
|
+
},
|
441
|
+
bad_request: function(data) {
|
442
|
+
return console.error('[LiveRecord Response Error]', data.error.code, ':', data.error.message, 'for', this.record());
|
443
|
+
}
|
444
|
+
},
|
445
|
+
syncRecord: function() {
|
446
|
+
this.perform('sync_record', {
|
447
|
+
model_name: this.record().modelName(),
|
448
|
+
record_id: this.record().id(),
|
449
|
+
stale_since: this.record()._staleSince
|
450
|
+
});
|
451
|
+
return this.record()._staleSince = void 0;
|
452
|
+
}
|
453
|
+
});
|
454
|
+
return this.subscription = subscription;
|
455
|
+
};
|
456
|
+
Model.prototype.model = function() {
|
457
|
+
return Model;
|
458
|
+
};
|
459
|
+
Model.prototype.modelName = function() {
|
460
|
+
return Model.modelName;
|
461
|
+
};
|
462
|
+
Model.prototype.unsubscribe = function() {
|
463
|
+
if (this.subscription === void 0) {
|
464
|
+
return;
|
465
|
+
}
|
466
|
+
App.cable.subscriptions.remove(this.subscription);
|
467
|
+
return delete this['subscription'];
|
468
|
+
};
|
469
|
+
Model.prototype.isSubscribed = function() {
|
470
|
+
return this.subscription !== void 0;
|
471
|
+
};
|
472
|
+
Model.prototype.create = function(options) {
|
473
|
+
if (Model.all[this.attributes.id]) {
|
474
|
+
throw new Error(Model.modelName + '(' + this.id() + ') is already in the store');
|
475
|
+
}
|
476
|
+
this._callCallbacks('before:create', void 0);
|
477
|
+
Model.all[this.attributes.id] = this;
|
478
|
+
this.subscribe({
|
479
|
+
reload: true
|
480
|
+
});
|
481
|
+
this._callCallbacks('after:create', void 0);
|
482
|
+
return this;
|
483
|
+
};
|
484
|
+
Model.prototype.update = function(attributes) {
|
485
|
+
var self;
|
486
|
+
self = this;
|
487
|
+
Object.keys(attributes).forEach(function(attribute_key) {
|
488
|
+
if (Model.prototype[attribute_key] === void 0) {
|
489
|
+
return Model.prototype[attribute_key] = function() {
|
490
|
+
return this.attributes[attribute_key];
|
491
|
+
};
|
492
|
+
}
|
493
|
+
});
|
494
|
+
this._callCallbacks('before:update', void 0);
|
495
|
+
Object.keys(attributes).forEach(function(attribute_key) {
|
496
|
+
return self.attributes[attribute_key] = attributes[attribute_key];
|
497
|
+
});
|
498
|
+
this._callCallbacks('after:update', void 0);
|
499
|
+
return true;
|
500
|
+
};
|
501
|
+
Model.prototype.destroy = function() {
|
502
|
+
this._callCallbacks('before:destroy', void 0);
|
503
|
+
this.unsubscribe();
|
504
|
+
delete Model.all[this.attributes.id];
|
505
|
+
this._callCallbacks('after:destroy', void 0);
|
506
|
+
return this;
|
507
|
+
};
|
508
|
+
Model._callbacks = {
|
509
|
+
'on:connect': [],
|
510
|
+
'on:disconnect': [],
|
511
|
+
'on:responseError': [],
|
512
|
+
'before:create': [],
|
513
|
+
'after:create': [],
|
514
|
+
'before:update': [],
|
515
|
+
'after:update': [],
|
516
|
+
'before:destroy': [],
|
517
|
+
'after:destroy': []
|
518
|
+
};
|
519
|
+
Model.prototype.addCallback = Model.addCallback = function(callbackKey, callbackFunction) {
|
520
|
+
var index;
|
521
|
+
index = this._callbacks[callbackKey].indexOf(callbackFunction);
|
522
|
+
if (index === -1) {
|
523
|
+
this._callbacks[callbackKey].push(callbackFunction);
|
524
|
+
return callbackFunction;
|
525
|
+
}
|
526
|
+
};
|
527
|
+
Model.prototype.removeCallback = Model.removeCallback = function(callbackKey, callbackFunction) {
|
528
|
+
var index;
|
529
|
+
index = this._callbacks[callbackKey].indexOf(callbackFunction);
|
530
|
+
if (index !== -1) {
|
531
|
+
this._callbacks[callbackKey].splice(index, 1);
|
532
|
+
return callbackFunction;
|
533
|
+
}
|
534
|
+
};
|
535
|
+
Model.prototype._callCallbacks = function(callbackKey, args) {
|
536
|
+
var callback, i, j, len, len1, ref1, ref2, results;
|
537
|
+
ref1 = Model._callbacks[callbackKey];
|
538
|
+
for (i = 0, len = ref1.length; i < len; i++) {
|
539
|
+
callback = ref1[i];
|
540
|
+
callback.apply(this, args);
|
541
|
+
}
|
542
|
+
ref2 = this._callbacks[callbackKey];
|
543
|
+
results = [];
|
544
|
+
for (j = 0, len1 = ref2.length; j < len1; j++) {
|
545
|
+
callback = ref2[j];
|
546
|
+
results.push(callback.apply(this, args));
|
547
|
+
}
|
548
|
+
return results;
|
549
|
+
};
|
550
|
+
Model.prototype._setChangesFrom = function(attributes) {
|
551
|
+
var attributeName, attributeValue, results;
|
552
|
+
this.changes = {};
|
553
|
+
results = [];
|
554
|
+
for (attributeName in attributes) {
|
555
|
+
attributeValue = attributes[attributeName];
|
556
|
+
if (this.attributes[attributeName] !== attributeValue) {
|
557
|
+
results.push(this.changes[attributeName] = [this.attributes[attributeName], attributeValue]);
|
558
|
+
} else {
|
559
|
+
results.push(void 0);
|
560
|
+
}
|
561
|
+
}
|
562
|
+
return results;
|
563
|
+
};
|
564
|
+
Model.prototype._unsetChanges = function() {
|
565
|
+
return delete this['changes'];
|
566
|
+
};
|
567
|
+
ref1 = config.instanceMethods;
|
568
|
+
for (methodKey in ref1) {
|
569
|
+
methodValue = ref1[methodKey];
|
570
|
+
if (Model.prototype[methodKey] !== void 0) {
|
571
|
+
throw new Error('Cannot use reserved name as instance method: ', methodKey);
|
572
|
+
}
|
573
|
+
Model.prototype[methodKey] = methodValue;
|
574
|
+
}
|
575
|
+
ref2 = config.callbacks;
|
576
|
+
for (callbackKey in ref2) {
|
577
|
+
callbackFunctions = ref2[callbackKey];
|
578
|
+
for (i = 0, len = callbackFunctions.length; i < len; i++) {
|
579
|
+
callbackFunction = callbackFunctions[i];
|
580
|
+
Model.addCallback(callbackKey, callbackFunction);
|
581
|
+
}
|
582
|
+
}
|
583
|
+
ref3 = config.plugins;
|
584
|
+
for (pluginKey in ref3) {
|
585
|
+
pluginValue = ref3[pluginKey];
|
586
|
+
if (LiveRecord.plugins) {
|
587
|
+
index = Object.keys(LiveRecord.plugins).indexOf(pluginKey);
|
588
|
+
if (index !== -1) {
|
589
|
+
LiveRecord.plugins[pluginKey].applyToModel(Model, pluginValue);
|
590
|
+
}
|
591
|
+
}
|
592
|
+
}
|
593
|
+
LiveRecord.Model.all[config.modelName] = Model;
|
594
|
+
return Model;
|
595
|
+
};
|
596
|
+
|
597
|
+
}).call(this);
|
598
|
+
(function() {
|
599
|
+
var base;
|
600
|
+
|
601
|
+
(base = this.LiveRecord).plugins || (base.plugins = {});
|
602
|
+
|
603
|
+
}).call(this);
|
data/lib/live_record/version.rb
CHANGED
data/live_record.gemspec
CHANGED
@@ -22,9 +22,11 @@ Gem::Specification.new do |s|
|
|
22
22
|
s.add_development_dependency 'byebug', '~> 9.0'
|
23
23
|
|
24
24
|
s.add_development_dependency 'sprockets-rails', '~> 3.2'
|
25
|
+
s.add_development_dependency 'sprockets-export', '~> 1.0'
|
25
26
|
s.add_development_dependency 'coffee-rails', '~> 4.2'
|
26
27
|
s.add_development_dependency 'jquery-rails', '~> 4.3'
|
27
28
|
s.add_development_dependency 'jbuilder', '~> 2.7'
|
29
|
+
s.add_development_dependency 'blade', '~> 0.7'
|
28
30
|
|
29
31
|
s.add_development_dependency 'sqlite3', '~> 1.3'
|
30
32
|
s.add_development_dependency 'redis', '~> 3.3'
|
data/package-lock.json
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
{
|
2
|
+
"name": "@jrpolidario/live_record",
|
3
|
+
"version": "0.3.6",
|
4
|
+
"lockfileVersion": 1,
|
5
|
+
"requires": true,
|
6
|
+
"dependencies": {
|
7
|
+
"actioncable": {
|
8
|
+
"version": "5.2.0",
|
9
|
+
"resolved": "https://registry.npmjs.org/actioncable/-/actioncable-5.2.0.tgz",
|
10
|
+
"integrity": "sha512-5o0wKTz2egSGDf7SubQLqoew/lGJhkjyJSdbMiaAfci/vNMjs7xjL2X+ELhstJmaN5tY17O3Wx6c5q2GgQE/JQ=="
|
11
|
+
}
|
12
|
+
}
|
13
|
+
}
|
data/package.json
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
{
|
2
|
+
"name": "@jrpolidario/live_record",
|
3
|
+
"version": "0.3.6",
|
4
|
+
"description": "Auto-syncs records in client-side JS (through a Model DSL) from changes (updates/destroy) in the backend Rails server through ActionCable.\\n Also supports streaming newly created records to client-side JS.\\n Supports lost connection restreaming for both new records (create), and record-changes (updates/destroy).\\n Auto-updates DOM elements mapped to a record attribute, from changes (updates/destroy).",
|
5
|
+
"main": "lib/assets/compiled/live_record.js",
|
6
|
+
"repository": {
|
7
|
+
"type": "git",
|
8
|
+
"url": "git+https://github.com/jrpolidario/live_record.git"
|
9
|
+
},
|
10
|
+
"author": "Jules Roman B. Polidario",
|
11
|
+
"license": "MIT",
|
12
|
+
"bugs": {
|
13
|
+
"url": "https://github.com/jrpolidario/live_record/issues"
|
14
|
+
},
|
15
|
+
"homepage": "https://github.com/jrpolidario/live_record#readme",
|
16
|
+
"dependencies": {
|
17
|
+
"actioncable": "^5.0.0"
|
18
|
+
}
|
19
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: live_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jules Roman B. Polidario
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-06-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '3.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sprockets-export
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: coffee-rails
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,6 +150,20 @@ dependencies:
|
|
136
150
|
- - "~>"
|
137
151
|
- !ruby/object:Gem::Version
|
138
152
|
version: '2.7'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: blade
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0.7'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0.7'
|
139
167
|
- !ruby/object:Gem::Dependency
|
140
168
|
name: sqlite3
|
141
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -272,7 +300,9 @@ executables: []
|
|
272
300
|
extensions: []
|
273
301
|
extra_rdoc_files: []
|
274
302
|
files:
|
303
|
+
- ".blade.yml"
|
275
304
|
- ".gitignore"
|
305
|
+
- ".npmignore"
|
276
306
|
- ".rspec"
|
277
307
|
- ".travis.yml"
|
278
308
|
- Gemfile
|
@@ -297,6 +327,8 @@ files:
|
|
297
327
|
- app/channels/live_record/publications_channel.rb
|
298
328
|
- config.ru
|
299
329
|
- config/puma.rb
|
330
|
+
- developer_guide.md
|
331
|
+
- lib/assets/compiled/live_record.js
|
300
332
|
- lib/live_record.rb
|
301
333
|
- lib/live_record/action_view_extensions/view_helper.rb
|
302
334
|
- lib/live_record/configure.rb
|
@@ -313,6 +345,8 @@ files:
|
|
313
345
|
- lib/live_record/model/callbacks.rb
|
314
346
|
- lib/live_record/version.rb
|
315
347
|
- live_record.gemspec
|
348
|
+
- package-lock.json
|
349
|
+
- package.json
|
316
350
|
- spec/factories/categories.rb
|
317
351
|
- spec/factories/posts.rb
|
318
352
|
- spec/factories/users.rb
|