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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +27 -0
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +29 -31
  5. data/ar_sync.gemspec +1 -1
  6. data/core/{ActioncableAdapter.d.ts → ActionCableAdapter.d.ts} +0 -0
  7. data/core/ActionCableAdapter.js +31 -0
  8. data/core/ArSyncApi.d.ts +8 -2
  9. data/core/ArSyncApi.js +123 -49
  10. data/core/ArSyncModel.js +69 -60
  11. data/core/ArSyncStore.js +522 -381
  12. data/core/ConnectionManager.d.ts +1 -1
  13. data/core/ConnectionManager.js +45 -38
  14. data/core/DataType.d.ts +14 -9
  15. data/core/hooks.d.ts +5 -0
  16. data/core/hooks.js +64 -36
  17. data/gemfiles/Gemfile-rails-6 +9 -0
  18. data/gemfiles/Gemfile-rails-7 +9 -0
  19. data/index.js +2 -2
  20. data/lib/ar_sync/class_methods.rb +71 -36
  21. data/lib/ar_sync/collection.rb +23 -19
  22. data/lib/ar_sync/core.rb +3 -3
  23. data/lib/ar_sync/instance_methods.rb +7 -4
  24. data/lib/ar_sync/rails.rb +1 -1
  25. data/lib/ar_sync/type_script.rb +50 -14
  26. data/lib/ar_sync/version.rb +1 -1
  27. data/lib/generators/ar_sync/install/install_generator.rb +1 -1
  28. data/package-lock.json +1706 -227
  29. data/package.json +1 -1
  30. data/src/core/{ActioncableAdapter.ts → ActionCableAdapter.ts} +0 -0
  31. data/src/core/ArSyncApi.ts +20 -7
  32. data/src/core/ArSyncStore.ts +177 -125
  33. data/src/core/ConnectionManager.ts +1 -0
  34. data/src/core/DataType.ts +15 -16
  35. data/src/core/hooks.ts +31 -7
  36. data/tsconfig.json +2 -2
  37. data/vendor/assets/javascripts/{ar_sync_actioncable_adapter.js.erb → ar_sync_action_cable_adapter.js.erb} +1 -1
  38. metadata +17 -16
  39. data/core/ActioncableAdapter.js +0 -29
  40. data/lib/ar_sync/field.rb +0 -96
@@ -11,7 +11,7 @@ export default class ConnectionManager {
11
11
  unsubscribe: () => void;
12
12
  };
13
13
  subscribe(key: any, func: any): {
14
- unsubscribe: () => void;
14
+ unsubscribe(): void;
15
15
  };
16
16
  connect(key: any): any;
17
17
  disconnect(key: any): void;
@@ -1,75 +1,82 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- class ConnectionManager {
4
- constructor(adapter) {
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
- this.unsubscribeAll();
12
- this.triggerNetworkChange(false);
11
+ adapter.ondisconnect = function () {
12
+ _this.unsubscribeAll();
13
+ _this.triggerNetworkChange(false);
13
14
  };
14
- adapter.onreconnect = () => this.triggerNetworkChange(true);
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 (const id in this.networkListeners)
21
+ for (var id in this.networkListeners)
21
22
  this.networkListeners[id](status);
22
- }
23
- unsubscribeAll() {
24
- for (const id in this.subscriptions) {
25
- const subscription = this.subscriptions[id];
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
- const id = this.networkListenerSerial++;
31
+ };
32
+ ConnectionManager.prototype.subscribeNetwork = function (func) {
33
+ var _this = this;
34
+ var id = this.networkListenerSerial++;
33
35
  this.networkListeners[id] = func;
34
- const unsubscribe = () => {
35
- delete this.networkListeners[id];
36
+ var unsubscribe = function () {
37
+ delete _this.networkListeners[id];
36
38
  };
37
- return { unsubscribe };
38
- }
39
- subscribe(key, func) {
40
- const subscription = this.connect(key);
41
- const id = subscription.serial++;
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
- const unsubscribe = () => {
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
- this.disconnect(key);
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
- const connection = this.adapter.subscribe(key, data => this.received(key, data));
58
- return this.subscriptions[key] = { connection, listeners: {}, ref: 0, serial: 0 };
59
- }
60
- disconnect(key) {
61
- const subscription = this.subscriptions[key];
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
- const subscription = this.subscriptions[key];
72
+ };
73
+ ConnectionManager.prototype.received = function (key, data) {
74
+ var subscription = this.subscriptions[key];
69
75
  if (!subscription)
70
76
  return;
71
- for (const id in subscription.listeners)
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 extends {
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, Path> = IsAnyCompareLeftType extends Type ? null : Type extends ExtraFieldErrorType ? Path : Type extends (infer R)[] ? _CollectExtraFields<R> : _CollectExtraFields<Type>;
37
- declare type _CollectExtraFields<Type> = Type extends object ? (keyof (Type) extends never ? null : Values<{
38
- [key in keyof Type]: CollectExtraFields<Type[key], key>;
39
- }>) : null;
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: SelectString<Extra>;
48
+ extraFields: Extra;
44
49
  };
45
50
  };
46
- declare type ValidateDataTypeExtraFileds<Type> = _ValidateDataTypeExtraFileds<CollectExtraFields<Type, []>, 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
- const ArSyncApi_1 = require("./ArSyncApi");
4
- const ArSyncModel_1 = require("./ArSyncModel");
5
- let useState;
6
- let useEffect;
7
- let useMemo;
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
- const initialResult = [null, { complete: false, notfound: undefined, connected: true }];
21
+ var initialResult = [null, { complete: false, notfound: undefined, connected: true }];
19
22
  function useArSyncModel(request) {
23
+ var _a;
20
24
  checkHooks();
21
- const [result, setResult] = useState(initialResult);
22
- const requestString = JSON.stringify(request && request.params);
23
- useEffect(() => {
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
- const model = new ArSyncModel_1.default(request, { immutable: true });
34
+ var model = new ArSyncModel_1.default(request, { immutable: true });
29
35
  function update() {
30
- const { complete, notfound, connected, data } = model;
31
- setResult(resultWas => {
32
- const [, statusWas] = resultWas;
33
- const statusPersisted = statusWas.complete === complete && statusWas.notfound === notfound && statusWas.connected === connected;
34
- const status = statusPersisted ? statusWas : { complete, notfound, connected };
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 () => model.release();
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
- const initialFetchState = { data: null, status: { complete: false, notfound: undefined } };
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
- const [state, setState] = useState(initialFetchState);
55
- const requestString = JSON.stringify(request && request.params);
56
- const loader = useMemo(() => {
57
- let lastLoadId = 0;
58
- let timer = null;
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
- const currentLoadingId = lastLoadId;
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(() => fetch(request, retryCount + 1), 1000 * Math.min(4 ** retryCount, 30));
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
- const { data, status } = state;
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 () => loader.cancel();
128
+ return function () { return loader.cancel(); };
102
129
  }, [requestString]);
103
- return [state.data, state.status, loader.update];
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.ArSyncModel = ArSyncModel_1.default;
4
+ Object.defineProperty(exports, "ArSyncModel", { enumerable: true, get: function () { return ArSyncModel_1.default; } });
5
5
  var ArSyncApi_1 = require("./core/ArSyncApi");
6
- exports.ArSyncApi = ArSyncApi_1.default;
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
- instance_variable_defined? '@_sync_self'
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(&block)
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, order: :asc, limit: nil, preload: nil, association: nil, **option, &data_block)
64
- raise "order not in [:asc, :desc] : #{order}" unless %i[asc desc].include? order
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
- underscore_name = name.to_s.underscore.to_sym
67
- preload = lambda do |records, _context, params|
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, records, association || underscore_name,
70
- order: (!limit && params && params[:order]) || order,
71
- limit: [params && params[:limit]&.to_i, limit].compact.min
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] || [] : send(name)
76
- next records unless limit || order == :asc
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
- order: (!limit && params && params[:order]) || order,
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] || [] : send(name)
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, limit: nil, order: :asc)
137
+ def sync_define_collection(name, first: nil, last: nil, direction: :asc)
108
138
  _initialize_sync_callbacks
109
- collection = ArSync::Collection.new self, name, limit: limit, order: order
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
- self.class.default_scoped.scoping do
116
- @_sync_watch_values_before_mutation ||= _sync_current_watch_values
117
- @_sync_parents_info_before_mutation ||= _sync_current_parents_info
118
- @_sync_belongs_to_info_before_mutation ||= _sync_current_belongs_to_info
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 instance_variable_defined? '@_sync_callbacks_initialized'
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
- _sync_define :defaults, namespace: :sync do |current_user|
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?
@@ -1,21 +1,26 @@
1
- require_relative 'field'
2
-
3
1
  class ArSync::Collection
4
- attr_reader :klass, :name, :limit, :order
5
- def initialize(klass, name, limit: nil, order: nil)
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
- @limit = limit
9
- @order = order
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
- all = klass.all
16
- all = all.order id: order if order
17
- all = all.limit limit if limit
18
- all
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::CompositeValue
49
- def initialize(records, order:, limit:)
50
- @records = records
51
- @order = { mode: order, limit: limit }
52
- end
53
-
54
- def ar_serializer_build_sub_calls
55
- values = @records.map { {} }
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, include_id: true, use: :sync
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, include_id: true, use: :sync
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
- order: { mode: target.order, limit: target.limit },
95
+ ordering: target.ordering,
96
96
  collection: serialized
97
97
  }
98
98
  when ActiveRecord::Relation, Array