jason-rails 0.6.0 → 0.6.5

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: 453e1a0d649445d973de2e3adf27f88f91c5bf983e65b3c2d993f090816db72b
4
- data.tar.gz: ec2600988c60bcb6f661b8835cd3bcb7d3a4b740749ab0f21109e9b6a513b4ca
3
+ metadata.gz: 3dcf3cd2febb358e030ebe12ca382b463a8ca97dcdbb228d9a4354f2687b2cd1
4
+ data.tar.gz: fb385f90db387e2e4dfc1ce8016b1d800ac3ba6b78e3a54f59a1ec17d670aaef
5
5
  SHA512:
6
- metadata.gz: cc2ba8529669a1fe4c48d2ee2ba595a7e3ae907a0d10234f12d68d797de63177143187c5c3767cf32ab35bd053c3c9f3bfa14461f8b6be71d8df437e3e997c7d
7
- data.tar.gz: c93e0002f3a7e2379ed29483b6a8323b4b492a319ccc4f9948f0638c47e62a9eeb8b416fad2bbe2cc6b5c13f11a103eab462b7a35f5a36145037981e49c99cc6
6
+ metadata.gz: 17ff6a77e7076e887bc2730ae32607d5c7afcd272dbe535f66259a758867296a759580be3f4385bd878fd908d22e883b0047fbd0bee0134f443457422edf1975
7
+ data.tar.gz: 398111401a126b189398baff186284e11f580e653af6391be7a0576c9d1c4832ec6915400071fd66e4819b676deeeae52d35b75fe9499b480c73c5c88c135cfb
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jason-rails (0.5.1)
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.
@@ -142,6 +166,7 @@ Development is primarily driven by the needs of projects we're using Jason in. I
142
166
  - Failure handling - rolling back local state in case of an error on the server
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)
169
+ - Benchmark and migrate if necessary ConnectionPool::Wrapper vs ConnectionPool
145
170
 
146
171
  ## Development
147
172
 
@@ -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.0",
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
@@ -30,20 +30,46 @@ module Jason
30
30
  self.pusher_region = 'eu'
31
31
  self.pusher_channel_prefix = 'jason'
32
32
 
33
- # add default values of more config vars here
33
+ def self.init
34
+ # 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
+ got_lock = $redis_jason.set('jason:schema:lock', nx: true, ex: 3600) # Basic lock mechanism for multi-process environments
36
+ return if !got_lock
37
+
38
+ previous_schema = JSON.parse($redis_jason.get('jason:last_schema') || '{}')
39
+ current_schema = Jason.schema.deep_stringify_keys.deep_transform_values { |v| v.is_a?(Symbol) ? v.to_s : v }
40
+ current_schema.each do |model, config|
41
+ if config != previous_schema[model]
42
+ puts "Config changed for #{model}"
43
+ puts "Old config was #{previous_schema[model]}"
44
+ puts "New config is #{config}"
45
+ puts "Rebuilding cache for #{model}"
46
+ model.classify.constantize.cache_all
47
+ puts "Done"
48
+ end
49
+ end
50
+
51
+ $redis_jason.set('jason:last_schema', current_schema.to_json)
52
+ ensure
53
+ $redis_jason.del('jason:schema:lock')
54
+
55
+ previous_config = 'test'
56
+ end
57
+
34
58
 
35
59
  # this function maps the vars from your app into your engine
36
60
  def self.setup(&block)
37
61
  yield self
38
- end
39
62
 
40
- $redis_jason = self.redis || ::ConnectionPool::Wrapper.new(size: 5, timeout: 3) { ::Redis.new(url: ENV['REDIS_URL']) }
63
+ $redis_jason = self.redis || ::ConnectionPool::Wrapper.new(size: 5, timeout: 3) { ::Redis.new(url: ENV['REDIS_URL']) }
41
64
 
42
- if ![:action_cable, :pusher].include?(self.transport_service)
43
- raise "Unknown transport service '#{self.transport_service}' specified"
44
- end
65
+ if ![:action_cable, :pusher].include?(self.transport_service)
66
+ raise "Unknown transport service '#{self.transport_service}' specified"
67
+ end
68
+
69
+ if self.transport_service == :pusher && self.pusher.blank?
70
+ raise "Pusher specified as transport service but no Pusher client provided. Please configure with config.pusher = Pusher::Client.new(...)"
71
+ end
45
72
 
46
- if self.transport_service == :pusher && self.pusher.blank?
47
- raise "Pusher specified as transport service but no Pusher client provided. Please configure with config.pusher = Pusher::Client.new(...)"
73
+ init
48
74
  end
49
75
  end
@@ -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)
@@ -91,6 +91,10 @@ module Jason::Publisher
91
91
  Jason::Subscription.for_instance(self.class.name.underscore, id)
92
92
  end
93
93
 
94
+ def jason_cached_value
95
+ JSON.parse($redis_jason.hget("jason:cache:#{self.class.name.underscore}", id) || '{}')
96
+ end
97
+
94
98
  class_methods do
95
99
  def cache_all
96
100
  all.each(&:cache_json)
@@ -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
 
@@ -362,8 +363,6 @@ class Jason::Subscription
362
363
  instance_jsons, idx = Jason::LuaGenerator.new.get_payload(model_name, id)
363
364
  end
364
365
 
365
- return if instance_jsons.blank?
366
-
367
366
  payload = instance_jsons.map do |instance_json|
368
367
  instance_json ? JSON.parse(instance_json) : {}
369
368
  end
@@ -377,6 +376,20 @@ class Jason::Subscription
377
376
  }
378
377
  end
379
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
+
380
393
  def add(model_name, instance_id)
381
394
  idx = $redis_jason.incr("jason:subscription:#{id}:#{model_name}:idx")
382
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.0"
2
+ VERSION = "0.6.5"
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.0
4
+ version: 0.6.5
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-13 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
@@ -168,6 +168,8 @@ files:
168
168
  - client/lib/transportAdapters/pusherAdapter.js
169
169
  - client/lib/useAct.d.ts
170
170
  - client/lib/useAct.js
171
+ - client/lib/useEager.d.ts
172
+ - client/lib/useEager.js
171
173
  - client/lib/useJason.d.ts
172
174
  - client/lib/useJason.js
173
175
  - client/lib/useJason.test.d.ts
@@ -194,6 +196,7 @@ files:
194
196
  - client/src/transportAdapters/actionCableAdapter.ts
195
197
  - client/src/transportAdapters/pusherAdapter.ts
196
198
  - client/src/useAct.ts
199
+ - client/src/useEager.ts
197
200
  - client/src/useJason.test.ts
198
201
  - client/src/useJason.ts
199
202
  - client/src/useSub.ts