jason-rails 0.6.1 → 0.6.6

Sign up to get free protection for your applications and to get access to all the features.
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