jason-rails 0.6.1 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a641c2277ccd7336d71cb79cd3f65710ebb0078db5cef102441d9c3fc2e6102d
4
- data.tar.gz: 467d4fff10d1f4c9a1ee847e409d4b8cc3ab8f8a7a0e93e9404fe275a35a3e47
3
+ metadata.gz: 627690fe30b3378cde9c9f148cc8d8d6941bcd540cbbe4777898fa8917f86dc8
4
+ data.tar.gz: bd3af795626563a049c02045b8dc82018882067f9b2d582e4152e1bd6b34af4c
5
5
  SHA512:
6
- metadata.gz: 606d1919bfe6c512e98a565619662b23f76f639da7ce55defafef7ced5b2e0112af8e4d61e63aa00b6335803faaaa6b863d621a114259ca5998c574d4ed5d59b
7
- data.tar.gz: a4f1c02c5c8c6a14fc6aa128b55ebc25ba54e2293561dd335dd87bb75b49e32df7af9434186f247efee6b81e16dae59e02d56a87f696ef8b217a01f167ac9f8c
6
+ metadata.gz: 4ed1a231161d3eafc616549eebd38c531c29203842f1ab8d0697e953f0d9af8a4766285b8733fa0d92ae1cd7893560f3d660c920385c0b7335f41cfa5f8c9387
7
+ data.tar.gz: 7289710ebc49e0c53c7bdd52106d0e8153bc4505d98b26251966ccff63895e4c6c5a6ac2009b904bbf3d7d3b552c71ffebe0f17786998aaf9eaedafbf12710fb
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ ## v0.6.5
2
+ - Added `reset!` and `reset!(hard: true)` methods to `Subscription`. Reset will load the IDs that should be part of the subscription from the database, and ensure that the graph matches those. It then re-broadcasts the payloads to all connected clients. Hard reset will do the same, but also clear all cached IDs and subscription hooks on instances - this is equivalent from starting from scratch.
3
+ - Added `enforce: boolean` option to GraphHelper
4
+ - When subscriptions are re-activated they now set the IDs with `enforce: true`, as there could be conditions where updates that were made while a subscription was not active would not be properly registered.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jason-rails (0.6.0)
4
+ jason-rails (0.6.4)
5
5
  connection_pool (>= 2.2.3)
6
6
  jsondiff
7
7
  rails (>= 5)
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Jason
2
2
 
3
- Jason is still in a highly experimental phase with a rapidly changing API. Production use not recommended - but please give it a try!
3
+ Jason is still in an experimental phase with a rapidly changing API. It is being used in some production applications, however it is still in 0.x.x series versions, which means that any 0.x version bump could introduce breaking changes.
4
4
 
5
5
  ## The goal
6
6
 
@@ -14,6 +14,8 @@ I also wanted to avoid writing essentially the same code multiple times in diffe
14
14
 
15
15
  Jason attempts to minimize this repitition by auto-generating API endpoints, redux stores and actions from a single schema definition. Further it adds listeners to ActiveRecord models allowing the redux store to be subscribed to updates from a model or set of models.
16
16
 
17
+ An alternative way of thinking about Jason is "what if we applied the Flux/Redux state update pattern to make the _database_ the store?".
18
+
17
19
  ## Installation
18
20
 
19
21
  Add the gem and the NPM package
@@ -81,7 +83,7 @@ function extraActions(dispatch, store, restClient, act) {
81
83
  - `middleware` - Passed directly to `configureStore` with additional Jason middleware
82
84
 
83
85
  ## Usage
84
- Jason provides two custom hooks to access functionality.
86
+ Jason provides three custom hooks to access functionality.
85
87
 
86
88
  ### useAct
87
89
  This returns an object which allows you to access actions which both update models on the server, and perform an optimistic update to the Redux store.
@@ -126,6 +128,28 @@ export default function PostsList() {
126
128
  }
127
129
  ```
128
130
 
131
+ ### useEager
132
+ Jason stores all the data in a normalized form - one redux slice per model. Often you might want to get nested data from several slices for use in components. The `useEager` hook provides an API for doing that. Under the hood it's just a wrapper around useSelector, which aims to mimic the behaviour of Rails eager loading.
133
+
134
+ Example
135
+ This will fetch the comment as well as the post and user linked to it.
136
+
137
+ ```jsx
138
+ import React from 'react'
139
+ import { useSelector } from 'react-redux'
140
+ import { useEager } from '@jamesr2323/jason'
141
+ import _ from 'lodash'
142
+
143
+ export default function Comment({ id }) {
144
+ const comment = useEager('comments', id, ['post', 'user'])
145
+
146
+ return <div>
147
+ <p>{ comment.body }</p>
148
+ <p>Made on post { comment.post.name } by { comment.user.name }</p>
149
+ </div>
150
+ }
151
+ ```
152
+
129
153
  ## Authorization
130
154
 
131
155
  By default all models can be subscribed to and updated without authentication or authorization. Probably you want to lock down access.
@@ -143,6 +167,7 @@ Development is primarily driven by the needs of projects we're using Jason in. I
143
167
  - Authorization - more thorough authorization integration, with utility functions for common authorizations. Allowing authorization of access to particular fields such as restricting the fields of a user that are publicly broadcast.
144
168
  - Utilities for "Draft editing" - both storing client-side copies of model trees which can be committed or discarded, as well as persisting a shadow copy to the database (to allow resumable editing, or possibly collaborative editing features)
145
169
  - Benchmark and migrate if necessary ConnectionPool::Wrapper vs ConnectionPool
170
+ - Assess using RedisGraph for the graph diffing functionality, to see if this would provide a performance boost
146
171
 
147
172
  ## Development
148
173
 
@@ -35,10 +35,10 @@ class Jason::ApiController < ::ApplicationController
35
35
  all_instance_ids.insert(priority.to_i, instance.id)
36
36
 
37
37
  all_instance_ids.each_with_index do |id, i|
38
- model.find(id).update!(priority: i, skip_publish_json: true)
38
+ model.find(id).update!(priority: i)
39
39
  end
40
40
 
41
- model.publish_all(model.find(all_instance_ids))
41
+ model.find(all_instance_ids).each(&:force_publish_json)
42
42
  elsif action == 'upsert' || action == 'add'
43
43
  payload = api_model.permit(params)
44
44
  return render json: model.find_or_create_by_id!(payload).as_json(api_model.as_json_config)
@@ -2,6 +2,6 @@
2
2
  declare const context: import("react").Context<{
3
3
  actions: any;
4
4
  subscribe: null;
5
- eager: null;
5
+ eager: (entity: any, id: any, relations: any) => void;
6
6
  }>;
7
7
  export default context;
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const react_1 = require("react");
4
- const context = react_1.createContext({ actions: {}, subscribe: null, eager: null });
4
+ const eager = function (entity, id, relations) {
5
+ console.error("Eager called but is not implemented");
6
+ };
7
+ const context = react_1.createContext({ actions: {}, subscribe: null, eager });
5
8
  exports.default = context;
@@ -42,18 +42,17 @@ function generateJasonSlices(models) {
42
42
  setSubscriptionIds(s, a) {
43
43
  const { payload } = a;
44
44
  const { subscriptionId, model, ids } = payload;
45
- console.log({ initialState });
46
- s[model][subscriptionId] = ids;
45
+ s[model][subscriptionId] = ids.map(id => String(id));
47
46
  },
48
47
  addSubscriptionId(s, a) {
49
48
  const { payload } = a;
50
49
  const { subscriptionId, model, id } = payload;
51
- s[model][subscriptionId] = lodash_1.default.union(s[model][subscriptionId] || [], [id]);
50
+ s[model][subscriptionId] = lodash_1.default.union(s[model][subscriptionId] || [], [String(id)]);
52
51
  },
53
52
  removeSubscriptionId(s, a) {
54
53
  const { payload } = a;
55
54
  const { subscriptionId, model, id } = payload;
56
- s[model][subscriptionId] = lodash_1.default.remove(s[model][subscriptionId] || [], id);
55
+ s[model][subscriptionId] = lodash_1.default.difference(s[model][subscriptionId] || [], [String(id)]);
57
56
  },
58
57
  removeSubscription(s, a) {
59
58
  const { payload: { subscriptionId } } = a;
@@ -36,7 +36,7 @@ function createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, c
36
36
  }
37
37
  const { payload, destroy, id, type } = patchQueue[model][idx[model]];
38
38
  if (type === 'payload') {
39
- dispatch({ type: `${pluralize_1.default(model)}/upsertMany`, payload });
39
+ dispatch({ type: `${pluralize_1.default(model)}/upsertMany`, payload: payload.map(m => (Object.assign(Object.assign({}, m), { id: String(m.id) }))) });
40
40
  const ids = payload.map(instance => instance.id);
41
41
  dispatch({ type: `jasonModels/setSubscriptionIds`, payload: { model, subscriptionId, ids } });
42
42
  }
@@ -45,7 +45,7 @@ function createPayloadHandler({ dispatch, serverActionQueue, transportAdapter, c
45
45
  dispatch({ type: `jasonModels/removeSubscriptionId`, payload: { model, subscriptionId, id } });
46
46
  }
47
47
  else {
48
- dispatch({ type: `${pluralize_1.default(model)}/upsert`, payload });
48
+ dispatch({ type: `${pluralize_1.default(model)}/upsert`, payload: Object.assign(Object.assign({}, payload), { id: String(payload.id) }) });
49
49
  dispatch({ type: `jasonModels/addSubscriptionId`, payload: { model, subscriptionId, id } });
50
50
  }
51
51
  delete patchQueue[model][idx[model]];
@@ -1,6 +1,7 @@
1
1
  /// <reference types="react" />
2
2
  import _useAct from './useAct';
3
3
  import _useSub from './useSub';
4
+ import _useEager from './useEager';
4
5
  export declare const JasonProvider: ({ reducers, middleware, extraActions, children }: {
5
6
  reducers?: any;
6
7
  middleware?: any;
@@ -9,3 +10,4 @@ export declare const JasonProvider: ({ reducers, middleware, extraActions, child
9
10
  }) => JSX.Element;
10
11
  export declare const useAct: typeof _useAct;
11
12
  export declare const useSub: typeof _useSub;
13
+ export declare const useEager: typeof _useEager;
data/client/lib/index.js CHANGED
@@ -3,10 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.useSub = exports.useAct = exports.JasonProvider = void 0;
6
+ exports.useEager = exports.useSub = exports.useAct = exports.JasonProvider = void 0;
7
7
  const JasonProvider_1 = __importDefault(require("./JasonProvider"));
8
8
  const useAct_1 = __importDefault(require("./useAct"));
9
9
  const useSub_1 = __importDefault(require("./useSub"));
10
+ const useEager_1 = __importDefault(require("./useEager"));
10
11
  exports.JasonProvider = JasonProvider_1.default;
11
12
  exports.useAct = useAct_1.default;
12
13
  exports.useSub = useSub_1.default;
14
+ exports.useEager = useEager_1.default;
@@ -40,10 +40,10 @@ function default_1(schema) {
40
40
  }
41
41
  function useEager(entity, id = null, relations = []) {
42
42
  if (id) {
43
- return react_redux_1.useSelector(s => addRelations(s, Object.assign({}, s[entity].entities[String(id)]), entity, relations));
43
+ return react_redux_1.useSelector(s => addRelations(s, Object.assign({}, s[entity].entities[String(id)]), entity, relations), lodash_1.default.isEqual);
44
44
  }
45
45
  else {
46
- return react_redux_1.useSelector(s => addRelations(s, lodash_1.default.values(s[entity].entities), entity, relations));
46
+ return react_redux_1.useSelector(s => addRelations(s, lodash_1.default.values(s[entity].entities), entity, relations), lodash_1.default.isEqual);
47
47
  }
48
48
  }
49
49
  return useEager;
@@ -9,7 +9,7 @@ const pruneIdsMiddleware = schema => store => next => action => {
9
9
  const { type, payload } = action;
10
10
  const result = next(action);
11
11
  const state = store.getState();
12
- if (type === 'jasonModels/setSubscriptionIds' || type === 'jasonModels/removeSubscriptionIds') {
12
+ if (type === 'jasonModels/setSubscriptionIds' || type === 'jasonModels/removeSubscriptionId') {
13
13
  const { model, ids } = payload;
14
14
  let idsInSubs = [];
15
15
  lodash_1.default.map(state.jasonModels[model], (subscribedIds, k) => {
@@ -0,0 +1 @@
1
+ export default function useEager(entity: any, id?: null, relations?: never[]): void;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const JasonContext_1 = __importDefault(require("./JasonContext"));
7
+ const react_1 = require("react");
8
+ function useEager(entity, id = null, relations = []) {
9
+ const { eager } = react_1.useContext(JasonContext_1.default);
10
+ return eager(entity, id, relations);
11
+ }
12
+ exports.default = useEager;
@@ -81,5 +81,37 @@ test('pruning IDs', () => __awaiter(void 0, void 0, void 0, function* () {
81
81
  idx: 2
82
82
  });
83
83
  // The ID 4 should have been pruned
84
- expect(store.getState().posts.ids).toStrictEqual([5]);
84
+ expect(store.getState().posts.ids).toStrictEqual(['5']);
85
+ }));
86
+ test('pruning IDs by destroy', () => __awaiter(void 0, void 0, void 0, function* () {
87
+ const resp = { data: {
88
+ schema: { post: {} },
89
+ transportService: 'action_cable'
90
+ } };
91
+ // @ts-ignore
92
+ restClient_1.default.get.mockResolvedValue(resp);
93
+ const { result, waitForNextUpdate } = react_hooks_1.renderHook(() => useJason_1.default({ reducers: {
94
+ test: (s, a) => s || {}
95
+ } }));
96
+ yield waitForNextUpdate();
97
+ const [store, value, connected] = result.current;
98
+ const { handlePayload, subscribe } = value;
99
+ const subscription = subscribe({ post: {} });
100
+ handlePayload({
101
+ type: 'payload',
102
+ model: 'post',
103
+ payload: [{ id: 4, name: 'test' }, { id: 5, name: 'test it out' }],
104
+ md5Hash: subscription.md5Hash,
105
+ idx: 1
106
+ });
107
+ expect(store.getState().posts.ids).toStrictEqual(['4', '5']);
108
+ handlePayload({
109
+ destroy: true,
110
+ model: 'post',
111
+ id: 5,
112
+ md5Hash: subscription.md5Hash,
113
+ idx: 2
114
+ });
115
+ // The ID 4 should have been pruned
116
+ expect(store.getState().posts.ids).toStrictEqual(['4']);
85
117
  }));
data/client/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jamesr2323/jason",
3
- "version": "0.6.1",
3
+ "version": "0.6.5",
4
4
  "module": "./lib/index.js",
5
5
  "types": "./lib/index.d.ts",
6
6
  "scripts": {
@@ -1,5 +1,8 @@
1
1
  import { createContext } from 'react'
2
+ const eager = function(entity, id, relations) {
3
+ console.error("Eager called but is not implemented")
4
+ }
2
5
 
3
- const context = createContext({ actions: {} as any, subscribe: null, eager: null })
6
+ const context = createContext({ actions: {} as any, subscribe: null, eager })
4
7
 
5
8
  export default context
@@ -42,18 +42,17 @@ function generateJasonSlices(models) {
42
42
  setSubscriptionIds(s,a) {
43
43
  const { payload } = a
44
44
  const { subscriptionId, model, ids } = payload
45
- console.log({ initialState })
46
- s[model][subscriptionId] = ids
45
+ s[model][subscriptionId] = ids.map(id => String(id))
47
46
  },
48
47
  addSubscriptionId(s,a) {
49
48
  const { payload } = a
50
49
  const { subscriptionId, model, id } = payload
51
- s[model][subscriptionId] = _.union(s[model][subscriptionId] || [], [id])
50
+ s[model][subscriptionId] = _.union(s[model][subscriptionId] || [], [String(id)])
52
51
  },
53
52
  removeSubscriptionId(s,a) {
54
53
  const { payload } = a
55
54
  const { subscriptionId, model, id } = payload
56
- s[model][subscriptionId] = _.remove(s[model][subscriptionId] || [], id)
55
+ s[model][subscriptionId] = _.difference(s[model][subscriptionId] || [], [String(id)])
57
56
  },
58
57
  removeSubscription(s, a) {
59
58
  const { payload: { subscriptionId } } = a
@@ -42,14 +42,14 @@ export default function createPayloadHandler({ dispatch, serverActionQueue, tran
42
42
  const { payload, destroy, id, type } = patchQueue[model][idx[model]]
43
43
 
44
44
  if (type === 'payload') {
45
- dispatch({ type: `${pluralize(model)}/upsertMany`, payload })
45
+ dispatch({ type: `${pluralize(model)}/upsertMany`, payload: payload.map(m => ({ ...m, id: String(m.id) })) })
46
46
  const ids = payload.map(instance => instance.id)
47
47
  dispatch({ type: `jasonModels/setSubscriptionIds`, payload: { model, subscriptionId, ids }})
48
48
  } else if (destroy) {
49
49
  // Middleware will determine if this model should be removed if it isn't in any other subscriptions
50
50
  dispatch({ type: `jasonModels/removeSubscriptionId`, payload: { model, subscriptionId, id }})
51
51
  } else {
52
- dispatch({ type: `${pluralize(model)}/upsert`, payload })
52
+ dispatch({ type: `${pluralize(model)}/upsert`, payload: { ...payload, id: String(payload.id) } })
53
53
  dispatch({ type: `jasonModels/addSubscriptionId`, payload: { model, subscriptionId, id }})
54
54
  }
55
55
 
data/client/src/index.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import _JasonProvider from './JasonProvider'
2
2
  import _useAct from './useAct'
3
3
  import _useSub from './useSub'
4
+ import _useEager from './useEager'
4
5
 
5
6
  export const JasonProvider = _JasonProvider
6
7
  export const useAct = _useAct
7
- export const useSub = _useSub
8
+ export const useSub = _useSub
9
+ export const useEager = _useEager
@@ -36,9 +36,9 @@ export default function (schema) {
36
36
 
37
37
  function useEager(entity, id = null, relations = []) {
38
38
  if (id) {
39
- return useSelector(s => addRelations(s, { ...s[entity].entities[String(id)] }, entity, relations))
39
+ return useSelector(s => addRelations(s, { ...s[entity].entities[String(id)] }, entity, relations), _.isEqual)
40
40
  } else {
41
- return useSelector(s => addRelations(s, _.values(s[entity].entities), entity, relations))
41
+ return useSelector(s => addRelations(s, _.values(s[entity].entities), entity, relations), _.isEqual)
42
42
  }
43
43
  }
44
44
 
@@ -6,13 +6,14 @@ const pruneIdsMiddleware = schema => store => next => action => {
6
6
  const result = next(action)
7
7
 
8
8
  const state = store.getState()
9
- if (type === 'jasonModels/setSubscriptionIds' || type === 'jasonModels/removeSubscriptionIds') {
9
+ if (type === 'jasonModels/setSubscriptionIds' || type === 'jasonModels/removeSubscriptionId') {
10
10
  const { model, ids } = payload
11
11
 
12
12
  let idsInSubs = []
13
13
  _.map(state.jasonModels[model], (subscribedIds, k) => {
14
14
  idsInSubs = _.union(idsInSubs, subscribedIds)
15
15
  })
16
+
16
17
  // Find IDs currently in Redux that aren't in any subscription
17
18
  const idsToRemove = _.difference(state[pluralize(model)].ids, idsInSubs)
18
19
  store.dispatch({ type: `${pluralize(model)}/removeMany`, payload: idsToRemove })
@@ -0,0 +1,9 @@
1
+ import JasonContext from './JasonContext'
2
+ import { useContext } from 'react'
3
+
4
+ export default function useEager(entity, id = null, relations = []) {
5
+ const { eager } = useContext(JasonContext)
6
+
7
+ return eager(entity, id, relations)
8
+ }
9
+
@@ -83,5 +83,45 @@ test('pruning IDs', async () => {
83
83
  })
84
84
 
85
85
  // The ID 4 should have been pruned
86
- expect(store.getState().posts.ids).toStrictEqual([5])
86
+ expect(store.getState().posts.ids).toStrictEqual(['5'])
87
+ })
88
+
89
+ test('pruning IDs by destroy', async () => {
90
+ const resp = { data: {
91
+ schema: { post: {} },
92
+ transportService: 'action_cable'
93
+ } };
94
+
95
+ // @ts-ignore
96
+ restClient.get.mockResolvedValue(resp);
97
+
98
+ const { result, waitForNextUpdate } = renderHook(() => useJason({ reducers: {
99
+ test: (s,a) => s || {}
100
+ }}));
101
+
102
+ await waitForNextUpdate()
103
+ const [store, value, connected] = result.current
104
+ const { handlePayload, subscribe } = value
105
+
106
+ const subscription = subscribe({ post: {} })
107
+
108
+ handlePayload({
109
+ type: 'payload',
110
+ model: 'post',
111
+ payload: [{ id: 4, name: 'test' }, { id: 5, name: 'test it out' }],
112
+ md5Hash: subscription.md5Hash,
113
+ idx: 1
114
+ })
115
+ expect(store.getState().posts.ids).toStrictEqual(['4', '5'])
116
+
117
+ handlePayload({
118
+ destroy: true,
119
+ model: 'post',
120
+ id: 5,
121
+ md5Hash: subscription.md5Hash,
122
+ idx: 2
123
+ })
124
+
125
+ // The ID 4 should have been pruned
126
+ expect(store.getState().posts.ids).toStrictEqual(['4'])
87
127
  })
data/lib/jason.rb CHANGED
@@ -31,13 +31,15 @@ module Jason
31
31
  self.pusher_channel_prefix = 'jason'
32
32
 
33
33
  def self.init
34
+ # Don't run in AR migration / generator etc.
35
+ return if $PROGRAM_NAME == '-e' || ActiveRecord::Base.connection.migration_context.needs_migration?
36
+
34
37
  # Check if the schema has changed since last time app was started. If so, do some work to ensure cache contains the correct data
35
38
  got_lock = $redis_jason.set('jason:schema:lock', nx: true, ex: 3600) # Basic lock mechanism for multi-process environments
36
39
  return if !got_lock
37
40
 
38
41
  previous_schema = JSON.parse($redis_jason.get('jason:last_schema') || '{}')
39
42
  current_schema = Jason.schema.deep_stringify_keys.deep_transform_values { |v| v.is_a?(Symbol) ? v.to_s : v }
40
- pp current_schema
41
43
  current_schema.each do |model, config|
42
44
  if config != previous_schema[model]
43
45
  puts "Config changed for #{model}"
@@ -52,8 +54,6 @@ module Jason
52
54
  $redis_jason.set('jason:last_schema', current_schema.to_json)
53
55
  ensure
54
56
  $redis_jason.del('jason:schema:lock')
55
-
56
- previous_config = 'test'
57
57
  end
58
58
 
59
59
 
@@ -33,7 +33,8 @@ class Jason::GraphHelper
33
33
  end
34
34
 
35
35
  # Add and remove edges, return graph before and after
36
- def apply_update(add: nil, remove: nil)
36
+ # Enforce means make the graph contain only the add_edges
37
+ def apply_update(add: nil, remove: nil, enforce: false)
37
38
  add_edges = []
38
39
  remove_edges = []
39
40
 
@@ -48,11 +49,21 @@ class Jason::GraphHelper
48
49
  remove_edges += build_edges(edge_set[:model_names], edge_set[:instance_ids], include_root: false)
49
50
  end
50
51
  end
51
- diff_edges_from_graph(add_edges: add_edges, remove_edges: remove_edges)
52
+
53
+ diff_edges_from_graph(add_edges: add_edges, remove_edges: remove_edges, enforce: enforce)
52
54
  end
53
55
 
54
- def diff_edges_from_graph(add_edges: [], remove_edges: [])
55
- old_edges, new_edges = Jason::LuaGenerator.new.update_set_with_diff("jason:subscriptions:#{id}:graph", add_edges.flatten, remove_edges.flatten)
56
+ def diff_edges_from_graph(add_edges: [], remove_edges: [], enforce: false)
57
+ if enforce
58
+ old_edges = $redis_jason.multi do |r|
59
+ r.smembers("jason:subscriptions:#{id}:graph")
60
+ r.del("jason:subscriptions:#{id}:graph")
61
+ r.sadd("jason:subscriptions:#{id}:graph", add_edges) if add_edges.present?
62
+ end[0]
63
+ new_edges = add_edges
64
+ else
65
+ old_edges, new_edges = Jason::LuaGenerator.new.update_set_with_diff("jason:subscriptions:#{id}:graph", add_edges.flatten, remove_edges.flatten)
66
+ end
56
67
 
57
68
  old_graph = build_graph_from_edges(old_edges)
58
69
  new_graph = build_graph_from_edges(new_edges)
@@ -269,7 +269,8 @@ class Jason::Subscription
269
269
 
270
270
  # Build the tree
271
271
  id_changeset = graph_helper.apply_update({
272
- add: [edge_set]
272
+ add: [edge_set],
273
+ enforce: enforce
273
274
  })
274
275
  apply_id_changeset(id_changeset)
275
276
  end
@@ -320,7 +321,7 @@ class Jason::Subscription
320
321
  $redis_jason.hset("jason:consumers", consumer_id, Time.now.utc)
321
322
 
322
323
  if before_consumer_count == 0
323
- set_ids_for_sub_models
324
+ set_ids_for_sub_models(enforce: true)
324
325
  end
325
326
  end
326
327
 
@@ -375,6 +376,20 @@ class Jason::Subscription
375
376
  }
376
377
  end
377
378
 
379
+ # To be used as a fallback when some corruption of the subscription has taken place
380
+ def reset!(hard: false)
381
+ # Remove subscription state
382
+ if hard
383
+ clear_all_ids
384
+ $redis_jason.del("jason:subscriptions:#{id}:graph")
385
+ end
386
+
387
+ set_ids_for_sub_models(enforce: true)
388
+ includes_helper.all_models.each do |model_name|
389
+ broadcaster.broadcast(get_for_model(model_name))
390
+ end
391
+ end
392
+
378
393
  def add(model_name, instance_id)
379
394
  idx = $redis_jason.incr("jason:subscription:#{id}:#{model_name}:idx")
380
395
  payload = JSON.parse($redis_jason.hget("jason:cache:#{model_name}", instance_id) || '{}')
data/lib/jason/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Jason
2
- VERSION = "0.6.1"
2
+ VERSION = "0.6.6"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jason-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Rees
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-16 00:00:00.000000000 Z
11
+ date: 2021-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -119,6 +119,7 @@ files:
119
119
  - ".rspec"
120
120
  - ".ruby-version"
121
121
  - ".travis.yml"
122
+ - CHANGELOG.md
122
123
  - CODE_OF_CONDUCT.md
123
124
  - Gemfile
124
125
  - Gemfile.lock
@@ -168,6 +169,8 @@ files:
168
169
  - client/lib/transportAdapters/pusherAdapter.js
169
170
  - client/lib/useAct.d.ts
170
171
  - client/lib/useAct.js
172
+ - client/lib/useEager.d.ts
173
+ - client/lib/useEager.js
171
174
  - client/lib/useJason.d.ts
172
175
  - client/lib/useJason.js
173
176
  - client/lib/useJason.test.d.ts
@@ -194,6 +197,7 @@ files:
194
197
  - client/src/transportAdapters/actionCableAdapter.ts
195
198
  - client/src/transportAdapters/pusherAdapter.ts
196
199
  - client/src/useAct.ts
200
+ - client/src/useEager.ts
197
201
  - client/src/useJason.test.ts
198
202
  - client/src/useJason.ts
199
203
  - client/src/useSub.ts