ar_sync 1.0.3 → 1.1.0
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/.github/workflows/test.yml +27 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +29 -31
- data/ar_sync.gemspec +1 -1
- data/core/{ActioncableAdapter.d.ts → ActionCableAdapter.d.ts} +0 -0
- data/core/ActionCableAdapter.js +31 -0
- data/core/ArSyncApi.d.ts +8 -2
- data/core/ArSyncApi.js +123 -49
- data/core/ArSyncModel.js +69 -60
- data/core/ArSyncStore.js +522 -381
- data/core/ConnectionManager.d.ts +1 -1
- data/core/ConnectionManager.js +45 -38
- data/core/DataType.d.ts +14 -9
- data/core/hooks.d.ts +5 -0
- data/core/hooks.js +64 -36
- data/gemfiles/Gemfile-rails-6 +9 -0
- data/gemfiles/Gemfile-rails-7 +9 -0
- data/index.js +2 -2
- data/lib/ar_sync/class_methods.rb +71 -36
- data/lib/ar_sync/collection.rb +23 -19
- data/lib/ar_sync/core.rb +3 -3
- data/lib/ar_sync/instance_methods.rb +7 -4
- data/lib/ar_sync/rails.rb +1 -1
- data/lib/ar_sync/type_script.rb +50 -14
- data/lib/ar_sync/version.rb +1 -1
- data/lib/generators/ar_sync/install/install_generator.rb +1 -1
- data/package-lock.json +1706 -227
- data/package.json +1 -1
- data/src/core/{ActioncableAdapter.ts → ActionCableAdapter.ts} +0 -0
- data/src/core/ArSyncApi.ts +20 -7
- data/src/core/ArSyncStore.ts +177 -125
- data/src/core/ConnectionManager.ts +1 -0
- data/src/core/DataType.ts +15 -16
- data/src/core/hooks.ts +31 -7
- data/tsconfig.json +2 -2
- data/vendor/assets/javascripts/{ar_sync_actioncable_adapter.js.erb → ar_sync_action_cable_adapter.js.erb} +1 -1
- metadata +17 -16
- data/core/ActioncableAdapter.js +0 -29
- data/lib/ar_sync/field.rb +0 -96
data/core/ConnectionManager.d.ts
CHANGED
data/core/ConnectionManager.js
CHANGED
@@ -1,75 +1,82 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
|
4
|
-
|
3
|
+
var ConnectionManager = /** @class */ (function () {
|
4
|
+
function ConnectionManager(adapter) {
|
5
|
+
var _this = this;
|
5
6
|
this.subscriptions = {};
|
6
7
|
this.adapter = adapter;
|
7
8
|
this.networkListeners = {};
|
8
9
|
this.networkListenerSerial = 0;
|
9
10
|
this.networkStatus = true;
|
10
|
-
adapter.ondisconnect = ()
|
11
|
-
|
12
|
-
|
11
|
+
adapter.ondisconnect = function () {
|
12
|
+
_this.unsubscribeAll();
|
13
|
+
_this.triggerNetworkChange(false);
|
13
14
|
};
|
14
|
-
adapter.onreconnect = ()
|
15
|
+
adapter.onreconnect = function () { return _this.triggerNetworkChange(true); };
|
15
16
|
}
|
16
|
-
triggerNetworkChange(status) {
|
17
|
+
ConnectionManager.prototype.triggerNetworkChange = function (status) {
|
17
18
|
if (this.networkStatus == status)
|
18
19
|
return;
|
19
20
|
this.networkStatus = status;
|
20
|
-
for (
|
21
|
+
for (var id in this.networkListeners)
|
21
22
|
this.networkListeners[id](status);
|
22
|
-
}
|
23
|
-
unsubscribeAll() {
|
24
|
-
for (
|
25
|
-
|
23
|
+
};
|
24
|
+
ConnectionManager.prototype.unsubscribeAll = function () {
|
25
|
+
for (var id in this.subscriptions) {
|
26
|
+
var subscription = this.subscriptions[id];
|
26
27
|
subscription.listeners = {};
|
27
28
|
subscription.connection.unsubscribe();
|
28
29
|
}
|
29
30
|
this.subscriptions = {};
|
30
|
-
}
|
31
|
-
subscribeNetwork(func) {
|
32
|
-
|
31
|
+
};
|
32
|
+
ConnectionManager.prototype.subscribeNetwork = function (func) {
|
33
|
+
var _this = this;
|
34
|
+
var id = this.networkListenerSerial++;
|
33
35
|
this.networkListeners[id] = func;
|
34
|
-
|
35
|
-
delete
|
36
|
+
var unsubscribe = function () {
|
37
|
+
delete _this.networkListeners[id];
|
36
38
|
};
|
37
|
-
return { unsubscribe };
|
38
|
-
}
|
39
|
-
subscribe(key, func) {
|
40
|
-
|
41
|
-
|
39
|
+
return { unsubscribe: unsubscribe };
|
40
|
+
};
|
41
|
+
ConnectionManager.prototype.subscribe = function (key, func) {
|
42
|
+
var _this = this;
|
43
|
+
if (!this.networkStatus)
|
44
|
+
return { unsubscribe: function () { } };
|
45
|
+
var subscription = this.connect(key);
|
46
|
+
var id = subscription.serial++;
|
42
47
|
subscription.ref++;
|
43
48
|
subscription.listeners[id] = func;
|
44
|
-
|
49
|
+
var unsubscribe = function () {
|
45
50
|
if (!subscription.listeners[id])
|
46
51
|
return;
|
47
52
|
delete subscription.listeners[id];
|
48
53
|
subscription.ref--;
|
49
54
|
if (subscription.ref === 0)
|
50
|
-
|
55
|
+
_this.disconnect(key);
|
51
56
|
};
|
52
|
-
return { unsubscribe };
|
53
|
-
}
|
54
|
-
connect(key) {
|
57
|
+
return { unsubscribe: unsubscribe };
|
58
|
+
};
|
59
|
+
ConnectionManager.prototype.connect = function (key) {
|
60
|
+
var _this = this;
|
55
61
|
if (this.subscriptions[key])
|
56
62
|
return this.subscriptions[key];
|
57
|
-
|
58
|
-
return this.subscriptions[key] = { connection, listeners: {}, ref: 0, serial: 0 };
|
59
|
-
}
|
60
|
-
disconnect(key) {
|
61
|
-
|
63
|
+
var connection = this.adapter.subscribe(key, function (data) { return _this.received(key, data); });
|
64
|
+
return this.subscriptions[key] = { connection: connection, listeners: {}, ref: 0, serial: 0 };
|
65
|
+
};
|
66
|
+
ConnectionManager.prototype.disconnect = function (key) {
|
67
|
+
var subscription = this.subscriptions[key];
|
62
68
|
if (!subscription || subscription.ref !== 0)
|
63
69
|
return;
|
64
70
|
delete this.subscriptions[key];
|
65
71
|
subscription.connection.unsubscribe();
|
66
|
-
}
|
67
|
-
received(key, data) {
|
68
|
-
|
72
|
+
};
|
73
|
+
ConnectionManager.prototype.received = function (key, data) {
|
74
|
+
var subscription = this.subscriptions[key];
|
69
75
|
if (!subscription)
|
70
76
|
return;
|
71
|
-
for (
|
77
|
+
for (var id in subscription.listeners)
|
72
78
|
subscription.listeners[id](data);
|
73
|
-
}
|
74
|
-
|
79
|
+
};
|
80
|
+
return ConnectionManager;
|
81
|
+
}());
|
75
82
|
exports.default = ConnectionManager;
|
data/core/DataType.d.ts
CHANGED
@@ -3,9 +3,7 @@ declare type RecordType = {
|
|
3
3
|
query: any;
|
4
4
|
};
|
5
5
|
};
|
6
|
-
declare type Values<T> = T
|
7
|
-
[K in keyof T]: infer U;
|
8
|
-
} ? U : never;
|
6
|
+
declare type Values<T> = T[keyof T];
|
9
7
|
declare type AddNullable<Test, Type> = null extends Test ? Type | null : Type;
|
10
8
|
declare type DataTypeExtractField<BaseType, Key extends keyof BaseType> = Exclude<BaseType[Key], null> extends RecordType ? AddNullable<BaseType[Key], {}> : BaseType[Key] extends RecordType[] ? {}[] : BaseType[Key];
|
11
9
|
declare type DataTypeExtractFieldsFromQuery<BaseType, Fields> = '*' extends Fields ? {
|
@@ -33,17 +31,24 @@ declare type CheckAttributesField<P, Q> = Q extends {
|
|
33
31
|
declare type IsAnyCompareLeftType = {
|
34
32
|
__any: never;
|
35
33
|
};
|
36
|
-
declare type CollectExtraFields<Type,
|
37
|
-
|
38
|
-
|
39
|
-
}
|
34
|
+
declare type CollectExtraFields<Type, Key> = IsAnyCompareLeftType extends Type ? never : Type extends ExtraFieldErrorType ? Key : Type extends (infer R)[] ? {
|
35
|
+
0: Values<{
|
36
|
+
[key in keyof R]: CollectExtraFields<R[key], key>;
|
37
|
+
}>;
|
38
|
+
1: never;
|
39
|
+
}[R extends object ? 0 : 1] : {
|
40
|
+
0: Values<{
|
41
|
+
[key in keyof Type]: CollectExtraFields<Type[key], key>;
|
42
|
+
}>;
|
43
|
+
1: never;
|
44
|
+
}[Type extends object ? 0 : 1];
|
40
45
|
declare type SelectString<T> = T extends string ? T : never;
|
41
46
|
declare type _ValidateDataTypeExtraFileds<Extra, Type> = SelectString<Extra> extends never ? Type : {
|
42
47
|
error: {
|
43
|
-
extraFields:
|
48
|
+
extraFields: Extra;
|
44
49
|
};
|
45
50
|
};
|
46
|
-
declare type ValidateDataTypeExtraFileds<Type> = _ValidateDataTypeExtraFileds<CollectExtraFields<Type,
|
51
|
+
declare type ValidateDataTypeExtraFileds<Type> = _ValidateDataTypeExtraFileds<CollectExtraFields<Type, never>, Type>;
|
47
52
|
declare type RequestBase = {
|
48
53
|
api: string;
|
49
54
|
query: any;
|
data/core/hooks.d.ts
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
declare let useState: <T>(t: T | (() => T)) => [T, (t: T | ((t: T) => T)) => void];
|
2
2
|
declare let useEffect: (f: (() => void) | (() => (() => void)), deps: any[]) => void;
|
3
3
|
declare let useMemo: <T>(f: () => T, deps: any[]) => T;
|
4
|
+
declare let useRef: <T>(value: T) => {
|
5
|
+
current: T;
|
6
|
+
};
|
4
7
|
declare type InitializeHooksParams = {
|
5
8
|
useState: typeof useState;
|
6
9
|
useEffect: typeof useEffect;
|
7
10
|
useMemo: typeof useMemo;
|
11
|
+
useRef: typeof useRef;
|
8
12
|
};
|
9
13
|
export declare function initializeHooks(hooks: InitializeHooksParams): void;
|
10
14
|
interface ModelStatus {
|
@@ -16,6 +20,7 @@ export declare type DataAndStatus<T> = [T | null, ModelStatus];
|
|
16
20
|
export interface Request {
|
17
21
|
api: string;
|
18
22
|
params?: any;
|
23
|
+
id?: number;
|
19
24
|
query: any;
|
20
25
|
}
|
21
26
|
export declare function useArSyncModel<T>(request: Request | null): DataAndStatus<T>;
|
data/core/hooks.js
CHANGED
@@ -1,37 +1,45 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
exports.useArSyncFetch = exports.useArSyncModel = exports.initializeHooks = void 0;
|
4
|
+
var ArSyncApi_1 = require("./ArSyncApi");
|
5
|
+
var ArSyncModel_1 = require("./ArSyncModel");
|
6
|
+
var useState;
|
7
|
+
var useEffect;
|
8
|
+
var useMemo;
|
9
|
+
var useRef;
|
8
10
|
function initializeHooks(hooks) {
|
9
11
|
useState = hooks.useState;
|
10
12
|
useEffect = hooks.useEffect;
|
11
13
|
useMemo = hooks.useMemo;
|
14
|
+
useRef = hooks.useRef;
|
12
15
|
}
|
13
16
|
exports.initializeHooks = initializeHooks;
|
14
17
|
function checkHooks() {
|
15
18
|
if (!useState)
|
16
|
-
throw 'uninitialized. needs `initializeHooks({ useState, useEffect, useMemo })`';
|
19
|
+
throw 'uninitialized. needs `initializeHooks({ useState, useEffect, useMemo, useRef })`';
|
17
20
|
}
|
18
|
-
|
21
|
+
var initialResult = [null, { complete: false, notfound: undefined, connected: true }];
|
19
22
|
function useArSyncModel(request) {
|
23
|
+
var _a;
|
20
24
|
checkHooks();
|
21
|
-
|
22
|
-
|
23
|
-
|
25
|
+
var _b = useState(initialResult), result = _b[0], setResult = _b[1];
|
26
|
+
var requestString = JSON.stringify((_a = request === null || request === void 0 ? void 0 : request.id) !== null && _a !== void 0 ? _a : request === null || request === void 0 ? void 0 : request.params);
|
27
|
+
var prevRequestStringRef = useRef(requestString);
|
28
|
+
useEffect(function () {
|
29
|
+
prevRequestStringRef.current = requestString;
|
24
30
|
if (!request) {
|
25
31
|
setResult(initialResult);
|
26
|
-
return ()
|
32
|
+
return function () { };
|
27
33
|
}
|
28
|
-
|
34
|
+
var model = new ArSyncModel_1.default(request, { immutable: true });
|
29
35
|
function update() {
|
30
|
-
|
31
|
-
setResult(resultWas
|
32
|
-
|
33
|
-
|
34
|
-
|
36
|
+
var complete = model.complete, notfound = model.notfound, connected = model.connected, data = model.data;
|
37
|
+
setResult(function (resultWas) {
|
38
|
+
var dataWas = resultWas[0], statusWas = resultWas[1];
|
39
|
+
var statusPersisted = statusWas.complete === complete && statusWas.notfound === notfound && statusWas.connected === connected;
|
40
|
+
if (dataWas === data && statusPersisted)
|
41
|
+
return resultWas;
|
42
|
+
var status = statusPersisted ? statusWas : { complete: complete, notfound: notfound, connected: connected };
|
35
43
|
return [data, status];
|
36
44
|
});
|
37
45
|
}
|
@@ -41,21 +49,39 @@ function useArSyncModel(request) {
|
|
41
49
|
else {
|
42
50
|
setResult(initialResult);
|
43
51
|
}
|
52
|
+
model.subscribe('load', update);
|
44
53
|
model.subscribe('change', update);
|
45
54
|
model.subscribe('connection', update);
|
46
|
-
return ()
|
55
|
+
return function () { return model.release(); };
|
47
56
|
}, [requestString]);
|
48
|
-
return result;
|
57
|
+
return prevRequestStringRef.current === requestString ? result : initialResult;
|
49
58
|
}
|
50
59
|
exports.useArSyncModel = useArSyncModel;
|
51
|
-
|
60
|
+
var initialFetchState = { data: null, status: { complete: false, notfound: undefined } };
|
61
|
+
function extractParams(query, output) {
|
62
|
+
if (output === void 0) { output = []; }
|
63
|
+
if (typeof (query) !== 'object' || query == null || Array.isArray(query))
|
64
|
+
return output;
|
65
|
+
if ('params' in query)
|
66
|
+
output.push(query.params);
|
67
|
+
for (var key in query) {
|
68
|
+
extractParams(query[key], output);
|
69
|
+
}
|
70
|
+
return output;
|
71
|
+
}
|
52
72
|
function useArSyncFetch(request) {
|
73
|
+
var _a;
|
53
74
|
checkHooks();
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
75
|
+
var _b = useState(initialFetchState), state = _b[0], setState = _b[1];
|
76
|
+
var query = request && request.query;
|
77
|
+
var resourceIdentifier = (_a = request === null || request === void 0 ? void 0 : request.id) !== null && _a !== void 0 ? _a : request === null || request === void 0 ? void 0 : request.params;
|
78
|
+
var requestString = useMemo(function () {
|
79
|
+
return JSON.stringify(extractParams(query, [resourceIdentifier]));
|
80
|
+
}, [query, resourceIdentifier]);
|
81
|
+
var prevRequestStringRef = useRef(requestString);
|
82
|
+
var loader = useMemo(function () {
|
83
|
+
var lastLoadId = 0;
|
84
|
+
var timer = null;
|
59
85
|
function cancel() {
|
60
86
|
if (timer)
|
61
87
|
clearTimeout(timer);
|
@@ -64,28 +90,28 @@ function useArSyncFetch(request) {
|
|
64
90
|
}
|
65
91
|
function fetch(request, retryCount) {
|
66
92
|
cancel();
|
67
|
-
|
68
|
-
ArSyncApi_1.default.fetch(request).then((response)
|
93
|
+
var currentLoadingId = lastLoadId;
|
94
|
+
ArSyncApi_1.default.fetch(request).then(function (response) {
|
69
95
|
if (currentLoadingId !== lastLoadId)
|
70
96
|
return;
|
71
97
|
setState({ data: response, status: { complete: true, notfound: false } });
|
72
|
-
}).catch(e
|
98
|
+
}).catch(function (e) {
|
73
99
|
if (currentLoadingId !== lastLoadId)
|
74
100
|
return;
|
75
101
|
if (!e.retry) {
|
76
102
|
setState({ data: null, status: { complete: true, notfound: true } });
|
77
103
|
return;
|
78
104
|
}
|
79
|
-
timer = setTimeout(()
|
105
|
+
timer = setTimeout(function () { return fetch(request, retryCount + 1); }, 1000 * Math.min(Math.pow(4, retryCount), 30));
|
80
106
|
});
|
81
107
|
}
|
82
108
|
function update() {
|
83
109
|
if (request) {
|
84
|
-
setState(state
|
85
|
-
|
110
|
+
setState(function (state) {
|
111
|
+
var data = state.data, status = state.status;
|
86
112
|
if (!status.complete && status.notfound === undefined)
|
87
113
|
return state;
|
88
|
-
return { data, status: { complete: false, notfound: undefined } };
|
114
|
+
return { data: data, status: { complete: false, notfound: undefined } };
|
89
115
|
});
|
90
116
|
fetch(request, 0);
|
91
117
|
}
|
@@ -93,13 +119,15 @@ function useArSyncFetch(request) {
|
|
93
119
|
setState(initialFetchState);
|
94
120
|
}
|
95
121
|
}
|
96
|
-
return { update, cancel };
|
122
|
+
return { update: update, cancel: cancel };
|
97
123
|
}, [requestString]);
|
98
|
-
useEffect(()
|
124
|
+
useEffect(function () {
|
125
|
+
prevRequestStringRef.current = requestString;
|
99
126
|
setState(initialFetchState);
|
100
127
|
loader.update();
|
101
|
-
return ()
|
128
|
+
return function () { return loader.cancel(); };
|
102
129
|
}, [requestString]);
|
103
|
-
|
130
|
+
var responseState = prevRequestStringRef.current === requestString ? state : initialFetchState;
|
131
|
+
return [responseState.data, responseState.status, loader.update];
|
104
132
|
}
|
105
133
|
exports.useArSyncFetch = useArSyncFetch;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in ar_sync.gemspec
|
6
|
+
gemspec path: ".."
|
7
|
+
|
8
|
+
gem "activerecord", "~> 6.0.0"
|
9
|
+
gem 'ar_serializer', github: 'tompng/ar_serializer'
|
@@ -0,0 +1,9 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in ar_sync.gemspec
|
6
|
+
gemspec path: ".."
|
7
|
+
|
8
|
+
gem "activerecord", "~> 7.0.0"
|
9
|
+
gem 'ar_serializer', github: 'tompng/ar_serializer'
|
data/index.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
var ArSyncModel_1 = require("./core/ArSyncModel");
|
4
|
-
exports
|
4
|
+
Object.defineProperty(exports, "ArSyncModel", { enumerable: true, get: function () { return ArSyncModel_1.default; } });
|
5
5
|
var ArSyncApi_1 = require("./core/ArSyncApi");
|
6
|
-
exports
|
6
|
+
Object.defineProperty(exports, "ArSyncApi", { enumerable: true, get: function () { return ArSyncApi_1.default; } });
|
@@ -1,9 +1,10 @@
|
|
1
|
-
require_relative 'field'
|
2
1
|
require_relative 'collection'
|
3
2
|
|
4
3
|
module ArSync::ModelBase::ClassMethods
|
5
4
|
def _sync_self?
|
6
|
-
|
5
|
+
return true if defined?(@_sync_self)
|
6
|
+
|
7
|
+
superclass._sync_self? if superclass < ActiveRecord::Base
|
7
8
|
end
|
8
9
|
|
9
10
|
def _sync_parents_info
|
@@ -21,7 +22,7 @@ module ArSync::ModelBase::ClassMethods
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def _each_sync_parent(&block)
|
24
|
-
_sync_parents_info.each(
|
25
|
+
_sync_parents_info.each { |parent, options| block.call(parent, **options) }
|
25
26
|
superclass._each_sync_parent(&block) if superclass < ActiveRecord::Base
|
26
27
|
end
|
27
28
|
|
@@ -46,44 +47,73 @@ module ArSync::ModelBase::ClassMethods
|
|
46
47
|
@_sync_self = true
|
47
48
|
names.each do |name|
|
48
49
|
_sync_children_info[name] = nil
|
49
|
-
_sync_define name, option, &data_block
|
50
|
+
_sync_define name, **option, &data_block
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
54
|
def sync_has_many(name, **option, &data_block)
|
54
55
|
_sync_children_info[name] = [:many, option, data_block]
|
55
|
-
_sync_has_many name, option, &data_block
|
56
|
+
_sync_has_many name, **option, &data_block
|
56
57
|
end
|
57
58
|
|
58
59
|
def sync_has_one(name, **option, &data_block)
|
59
60
|
_sync_children_info[name] = [:one, option, data_block]
|
60
|
-
_sync_define name, option, &data_block
|
61
|
+
_sync_define name, **option, &data_block
|
61
62
|
end
|
62
63
|
|
63
|
-
def _sync_has_many(name,
|
64
|
-
raise
|
64
|
+
def _sync_has_many(name, direction: :asc, first: nil, last: nil, preload: nil, association: nil, **option, &data_block)
|
65
|
+
raise ArgumentError, 'direction not in [:asc, :desc]' unless %i[asc desc].include? direction
|
66
|
+
raise ArgumentError, 'first and last cannot be both specified' if first && last
|
67
|
+
raise ArgumentError, 'cannot use first or last with direction: :desc' if direction != :asc && !first && !last
|
65
68
|
if data_block.nil? && preload.nil?
|
66
|
-
|
67
|
-
|
69
|
+
association_name = association || name.to_s.underscore.to_sym
|
70
|
+
order_option_from_params = lambda do |params|
|
71
|
+
if first || last
|
72
|
+
params_first = first && [first, params[:first]&.to_i].compact.min
|
73
|
+
params_last = last && [last, params[:last]&.to_i].compact.min
|
74
|
+
{ direction: direction, first: params_first, last: params_last }
|
75
|
+
else
|
76
|
+
{
|
77
|
+
first: params[:first]&.to_i,
|
78
|
+
last: params[:last]&.to_i,
|
79
|
+
order_by: params[:order_by],
|
80
|
+
direction: params[:direction] || :asc
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
preload = lambda do |records, _context, **params|
|
68
85
|
ArSerializer::Field.preload_association(
|
69
|
-
self,
|
70
|
-
|
71
|
-
|
86
|
+
self,
|
87
|
+
records,
|
88
|
+
association_name,
|
89
|
+
**order_option_from_params.call(params)
|
72
90
|
)
|
73
91
|
end
|
74
|
-
data_block = lambda do |preloaded, _context, params|
|
75
|
-
records = preloaded ? preloaded[id] || [] :
|
76
|
-
next records unless
|
92
|
+
data_block = lambda do |preloaded, _context, **params|
|
93
|
+
records = preloaded ? preloaded[id] || [] : __send__(name)
|
94
|
+
next records unless first || last
|
77
95
|
ArSync::CollectionWithOrder.new(
|
78
96
|
records,
|
79
|
-
|
80
|
-
limit: [params && params[:limit]&.to_i, limit].compact.min
|
97
|
+
**order_option_from_params.call(params)
|
81
98
|
)
|
82
99
|
end
|
83
|
-
serializer_data_block = lambda do |preloaded, _context, _params|
|
84
|
-
preloaded ? preloaded[id] || [] :
|
100
|
+
serializer_data_block = lambda do |preloaded, _context, **_params|
|
101
|
+
preloaded ? preloaded[id] || [] : __send__(name)
|
102
|
+
end
|
103
|
+
if first
|
104
|
+
params_type = { first?: :int }
|
105
|
+
elsif last
|
106
|
+
params_type = { last?: :int }
|
107
|
+
else
|
108
|
+
params_type = lambda do
|
109
|
+
orderable_keys = reflect_on_association(association_name)&.klass&._serializer_orderable_field_keys || []
|
110
|
+
orderable_keys &= [*option[:only]].map(&:to_s) if option[:only]
|
111
|
+
orderable_keys -= [*option[:except]].map(&:to_s) if option[:except]
|
112
|
+
orderable_keys |= ['id']
|
113
|
+
order_by = orderable_keys.size == 1 ? orderable_keys.first : orderable_keys.sort
|
114
|
+
{ first?: :int, last?: :int, direction?: %w[asc desc], orderBy?: order_by }
|
115
|
+
end
|
85
116
|
end
|
86
|
-
params_type = { limit?: :int, order?: [{ :* => %w[asc desc] }, 'asc', 'desc'] }
|
87
117
|
else
|
88
118
|
params_type = {}
|
89
119
|
end
|
@@ -104,18 +134,25 @@ module ArSync::ModelBase::ClassMethods
|
|
104
134
|
serializer_field name, **option, namespace: :sync, &data_block
|
105
135
|
end
|
106
136
|
|
107
|
-
def sync_define_collection(name,
|
137
|
+
def sync_define_collection(name, first: nil, last: nil, direction: :asc)
|
108
138
|
_initialize_sync_callbacks
|
109
|
-
collection = ArSync::Collection.new self, name,
|
139
|
+
collection = ArSync::Collection.new self, name, first: first, last: last, direction: direction
|
110
140
|
sync_parent collection, inverse_of: [self, name]
|
111
141
|
end
|
112
142
|
|
113
143
|
module WriteHook
|
114
144
|
def _initialize_sync_info_before_mutation
|
115
|
-
|
116
|
-
|
117
|
-
@
|
118
|
-
@
|
145
|
+
return unless defined? @_initialized
|
146
|
+
if new_record?
|
147
|
+
@_sync_watch_values_before_mutation ||= {}
|
148
|
+
@_sync_parents_info_before_mutation ||= {}
|
149
|
+
@_sync_belongs_to_info_before_mutation ||= {}
|
150
|
+
else
|
151
|
+
self.class.default_scoped.scoping do
|
152
|
+
@_sync_watch_values_before_mutation ||= _sync_current_watch_values
|
153
|
+
@_sync_parents_info_before_mutation ||= _sync_current_parents_info
|
154
|
+
@_sync_belongs_to_info_before_mutation ||= _sync_current_belongs_to_info
|
155
|
+
end
|
119
156
|
end
|
120
157
|
end
|
121
158
|
def _write_attribute(attr_name, value)
|
@@ -129,7 +166,7 @@ module ArSync::ModelBase::ClassMethods
|
|
129
166
|
end
|
130
167
|
|
131
168
|
def _initialize_sync_callbacks
|
132
|
-
return if
|
169
|
+
return if defined? @_sync_callbacks_initialized
|
133
170
|
@_sync_callbacks_initialized = true
|
134
171
|
prepend WriteHook
|
135
172
|
attr_reader :_sync_parents_info_before_mutation, :_sync_belongs_to_info_before_mutation, :_sync_watch_values_before_mutation
|
@@ -140,8 +177,12 @@ module ArSync::ModelBase::ClassMethods
|
|
140
177
|
ArSync.sync_keys self, current_user
|
141
178
|
end
|
142
179
|
|
143
|
-
|
144
|
-
{ sync_keys: ArSync.sync_keys(self, current_user) }
|
180
|
+
serializer_defaults namespace: :sync do |current_user|
|
181
|
+
{ id: id, sync_keys: ArSync.sync_keys(self, current_user) }
|
182
|
+
end
|
183
|
+
|
184
|
+
after_initialize do
|
185
|
+
@_initialized = true
|
145
186
|
end
|
146
187
|
|
147
188
|
before_destroy do
|
@@ -150,12 +191,6 @@ module ArSync::ModelBase::ClassMethods
|
|
150
191
|
@_sync_belongs_to_info_before_mutation ||= _sync_current_belongs_to_info
|
151
192
|
end
|
152
193
|
|
153
|
-
before_save on: :create do
|
154
|
-
@_sync_parents_info_before_mutation ||= {}
|
155
|
-
@_sync_watch_values_before_mutation ||= {}
|
156
|
-
@_sync_belongs_to_info_before_mutation ||= {}
|
157
|
-
end
|
158
|
-
|
159
194
|
%i[create update destroy].each do |action|
|
160
195
|
after_commit on: action do
|
161
196
|
next if ArSync.skip_notification?
|
data/lib/ar_sync/collection.rb
CHANGED
@@ -1,21 +1,26 @@
|
|
1
|
-
require_relative 'field'
|
2
|
-
|
3
1
|
class ArSync::Collection
|
4
|
-
attr_reader :klass, :name, :
|
5
|
-
def initialize(klass, name,
|
2
|
+
attr_reader :klass, :name, :first, :last, :direction, :ordering
|
3
|
+
def initialize(klass, name, first: nil, last: nil, direction: nil)
|
4
|
+
direction ||= :asc
|
6
5
|
@klass = klass
|
7
6
|
@name = name
|
8
|
-
@
|
9
|
-
@
|
7
|
+
@first = first
|
8
|
+
@last = last
|
9
|
+
@direction = direction
|
10
|
+
@ordering = { first: first, last: last, direction: direction }.compact
|
10
11
|
self.class.defined_collections[[klass, name]] = self
|
11
12
|
define_singleton_method(name) { to_a }
|
12
13
|
end
|
13
14
|
|
14
15
|
def to_a
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
if first
|
17
|
+
klass.order(id: direction).limit(first).to_a
|
18
|
+
elsif last
|
19
|
+
rev = direction == :asc ? :desc : :asc
|
20
|
+
klass.order(id: rev).limit(last).reverse
|
21
|
+
else
|
22
|
+
klass.all.to_a
|
23
|
+
end
|
19
24
|
end
|
20
25
|
|
21
26
|
def self.defined_collections
|
@@ -45,14 +50,13 @@ class ArSync::Collection
|
|
45
50
|
end
|
46
51
|
end
|
47
52
|
|
48
|
-
class ArSync::CollectionWithOrder < ArSerializer::
|
49
|
-
def initialize(records,
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
[{ order: @order, collection: values }, @records.zip(values)]
|
53
|
+
class ArSync::CollectionWithOrder < ArSerializer::CustomSerializable
|
54
|
+
def initialize(records, direction:, first: nil, last: nil)
|
55
|
+
super records do |results|
|
56
|
+
{
|
57
|
+
ordering: { direction: direction || :asc, first: first, last: last }.compact,
|
58
|
+
collection: records.map(&results).compact
|
59
|
+
}
|
60
|
+
end
|
57
61
|
end
|
58
62
|
end
|
data/lib/ar_sync/core.rb
CHANGED
@@ -82,17 +82,17 @@ module ArSync
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def self.serialize(record_or_records, query, user: nil)
|
85
|
-
ArSerializer.serialize record_or_records, query, context: user,
|
85
|
+
ArSerializer.serialize record_or_records, query, context: user, use: :sync
|
86
86
|
end
|
87
87
|
|
88
88
|
def self.sync_serialize(target, user, query)
|
89
89
|
case target
|
90
90
|
when ArSync::Collection, ArSync::ModelBase
|
91
|
-
serialized = ArSerializer.serialize target, query, context: user,
|
91
|
+
serialized = ArSerializer.serialize target, query, context: user, use: :sync
|
92
92
|
return serialized if target.is_a? ArSync::ModelBase
|
93
93
|
{
|
94
94
|
sync_keys: ArSync.sync_keys(target, user),
|
95
|
-
|
95
|
+
ordering: target.ordering,
|
96
96
|
collection: serialized
|
97
97
|
}
|
98
98
|
when ActiveRecord::Relation, Array
|