jason-rails 0.6.3 → 0.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +7 -2
- data/README.md +2 -0
- data/app/controllers/jason/{api_controller.rb → jason_controller.rb} +1 -1
- data/app/controllers/jason/{api/pusher_controller.rb → pusher_controller.rb} +1 -1
- data/app/workers/jason/outbound_message_queue_worker.rb +21 -0
- data/client/lib/createJasonReducers.js +3 -4
- data/client/lib/createPayloadHandler.js +2 -2
- data/client/lib/pruneIdsMiddleware.js +1 -1
- data/client/lib/useJason.js +2 -1
- data/client/lib/useJason.test.js +33 -1
- data/client/package.json +1 -1
- data/client/src/createJasonReducers.ts +3 -4
- data/client/src/createPayloadHandler.ts +2 -2
- data/client/src/pruneIdsMiddleware.ts +2 -1
- data/client/src/useJason.test.ts +41 -1
- data/client/src/useJason.ts +1 -1
- data/config/routes.rb +6 -6
- data/jason-rails.gemspec +1 -0
- data/lib/jason.rb +5 -3
- data/lib/jason/broadcaster.rb +2 -1
- data/lib/jason/graph_helper.rb +15 -4
- data/lib/jason/publisher.rb +6 -2
- data/lib/jason/subscription.rb +29 -5
- data/lib/jason/version.rb +1 -1
- metadata +20 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 64f8392cc200b54c7584534cedb35ccbb1675d8c15d1193152e4cbdec558a934
|
4
|
+
data.tar.gz: eb285b6c8660017fda735dee46802b9617f08c121c67ce0e9e68efb448e6765c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7505f22ac0737faa51726c9ba5dbbaeab2b6634583e32e574f8e0c4ffc6ba8de4e14eb78f3184d3d9a961ac153cc396253d95ac225fc2f853ebaa6c805197d0f
|
7
|
+
data.tar.gz: 1f6b1f4472d04876566c0edb050a9890cc42b8a6ac083072de38c7d64f7f5780c9e14c1c16f7443eaf98e9ba2ef3b06f400542582c96d619bc0cb2f6645ce8e4
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
## v0.6.7
|
2
|
+
- Fix: Change names of controllers to be less likely to conflict with host app inflections
|
3
|
+
- Added: Pusher now pushes asychronously via Sidekiq using the Pusher batch API
|
4
|
+
|
5
|
+
## v0.6.6
|
6
|
+
- Fix: don't run the schema change detection and cache rebuild inside rake tasks or migrations
|
7
|
+
|
8
|
+
## v0.6.5
|
9
|
+
- 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.
|
10
|
+
- Added `enforce: boolean` option to GraphHelper
|
11
|
+
- 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.
|
4
|
+
jason-rails (0.6.7)
|
5
5
|
connection_pool (>= 2.2.3)
|
6
6
|
jsondiff
|
7
7
|
rails (>= 5)
|
@@ -93,7 +93,7 @@ GEM
|
|
93
93
|
mini_mime (1.0.2)
|
94
94
|
mini_portile2 (2.5.0)
|
95
95
|
minitest (5.14.3)
|
96
|
-
nio4r (2.5.
|
96
|
+
nio4r (2.5.7)
|
97
97
|
nokogiri (1.11.1)
|
98
98
|
mini_portile2 (~> 2.5.0)
|
99
99
|
racc (~> 1.4)
|
@@ -153,6 +153,10 @@ GEM
|
|
153
153
|
rspec-mocks (~> 3.10)
|
154
154
|
rspec-support (~> 3.10)
|
155
155
|
rspec-support (3.10.0)
|
156
|
+
sidekiq (6.1.3)
|
157
|
+
connection_pool (>= 2.2.2)
|
158
|
+
rack (~> 2.0)
|
159
|
+
redis (>= 4.2.0)
|
156
160
|
sprockets (4.0.2)
|
157
161
|
concurrent-ruby (~> 1.0)
|
158
162
|
rack (> 1, < 3)
|
@@ -178,6 +182,7 @@ DEPENDENCIES
|
|
178
182
|
rake (~> 12.0)
|
179
183
|
rspec (~> 3.0)
|
180
184
|
rspec-rails
|
185
|
+
sidekiq
|
181
186
|
sqlite3
|
182
187
|
|
183
188
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -163,10 +163,12 @@ Similarly to authorizing subscriptions, you can do this by providing an class to
|
|
163
163
|
## Roadmap
|
164
164
|
|
165
165
|
Development is primarily driven by the needs of projects we're using Jason in. In no particular order, being considered is:
|
166
|
+
- Better detection of when subscriptions drop, delete subscription
|
166
167
|
- Failure handling - rolling back local state in case of an error on the server
|
167
168
|
- 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.
|
168
169
|
- 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
170
|
- Benchmark and migrate if necessary ConnectionPool::Wrapper vs ConnectionPool
|
171
|
+
- Assess using RedisGraph for the graph diffing functionality, to see if this would provide a performance boost
|
170
172
|
|
171
173
|
## Development
|
172
174
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class Jason::
|
1
|
+
class Jason::JasonController < ::ApplicationController
|
2
2
|
before_action :load_and_authorize_subscription, only: [:create_subscription, :remove_subscription, :get_payload]
|
3
3
|
# config seems to be a reserved name, resulting in infinite loop
|
4
4
|
def configuration
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Jason::OutboundMessageQueueWorker
|
2
|
+
include Sidekiq::Worker
|
3
|
+
|
4
|
+
def perform
|
5
|
+
batch = get_batch
|
6
|
+
return if batch.size == 0
|
7
|
+
|
8
|
+
Jason.pusher.trigger_batch(batch)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def get_batch
|
14
|
+
batch_json = $redis_jason.multi do |r|
|
15
|
+
r.lrange("jason:outbound_message_queue", 0, 9) # get first 10 elements
|
16
|
+
r.ltrim("jason:outbound_message_queue", 10, -1) # delete first 10 elements
|
17
|
+
end[0]
|
18
|
+
|
19
|
+
batch_json.map { |event| JSON.parse(event).with_indifferent_access } # Pusher wants symbol keys
|
20
|
+
end
|
21
|
+
end
|
@@ -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
|
-
|
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.
|
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]];
|
@@ -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/
|
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) => {
|
data/client/lib/useJason.js
CHANGED
@@ -73,9 +73,10 @@ function useJason({ reducers, middleware = [], extraActions }) {
|
|
73
73
|
};
|
74
74
|
}
|
75
75
|
function removeSubscription(config) {
|
76
|
+
var _a;
|
76
77
|
transportAdapter.removeSubscription(config);
|
77
78
|
const md5Hash = blueimp_md5_1.default(JSON.stringify(config));
|
78
|
-
payloadHandlers[md5Hash].tearDown();
|
79
|
+
(_a = payloadHandlers[md5Hash]) === null || _a === void 0 ? void 0 : _a.tearDown(); // Race condition where component mounts then unmounts quickly
|
79
80
|
delete payloadHandlers[md5Hash];
|
80
81
|
delete configs[md5Hash];
|
81
82
|
delete subOptions[md5Hash];
|
data/client/lib/useJason.test.js
CHANGED
@@ -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
@@ -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
|
-
|
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] = _.
|
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
|
|
@@ -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/
|
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 })
|
data/client/src/useJason.test.ts
CHANGED
@@ -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/client/src/useJason.ts
CHANGED
@@ -90,7 +90,7 @@ export default function useJason({ reducers, middleware = [], extraActions }: {
|
|
90
90
|
function removeSubscription(config) {
|
91
91
|
transportAdapter.removeSubscription(config)
|
92
92
|
const md5Hash = md5(JSON.stringify(config))
|
93
|
-
payloadHandlers[md5Hash]
|
93
|
+
payloadHandlers[md5Hash]?.tearDown() // Race condition where component mounts then unmounts quickly
|
94
94
|
delete payloadHandlers[md5Hash]
|
95
95
|
delete configs[md5Hash]
|
96
96
|
delete subOptions[md5Hash]
|
data/config/routes.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
Jason::Engine.routes.draw do
|
2
|
-
get '/api/config', to: '
|
3
|
-
post '/api/action', to: '
|
4
|
-
post '/api/create_subscription', to: '
|
5
|
-
post '/api/remove_subscription', to: '
|
6
|
-
post '/api/get_payload', to: '
|
7
|
-
post '/api/pusher/auth', to: '
|
2
|
+
get '/api/config', to: 'jason#configuration'
|
3
|
+
post '/api/action', to: 'jason#action'
|
4
|
+
post '/api/create_subscription', to: 'jason#create_subscription'
|
5
|
+
post '/api/remove_subscription', to: 'jason#remove_subscription'
|
6
|
+
post '/api/get_payload', to: 'jason#get_payload'
|
7
|
+
post '/api/pusher/auth', to: 'pusher#auth'
|
8
8
|
end
|
data/jason-rails.gemspec
CHANGED
data/lib/jason.rb
CHANGED
@@ -24,20 +24,24 @@ module Jason
|
|
24
24
|
self.mattr_accessor :pusher_region
|
25
25
|
self.mattr_accessor :pusher_channel_prefix
|
26
26
|
self.mattr_accessor :authorization_service
|
27
|
+
self.mattr_accessor :sidekiq_queue
|
27
28
|
|
28
29
|
self.schema = {}
|
29
30
|
self.transport_service = :action_cable
|
30
31
|
self.pusher_region = 'eu'
|
31
32
|
self.pusher_channel_prefix = 'jason'
|
33
|
+
self.sidekiq_queue = 'default'
|
32
34
|
|
33
35
|
def self.init
|
36
|
+
# Don't run in AR migration / generator etc.
|
37
|
+
return if $PROGRAM_NAME == '-e' || ActiveRecord::Base.connection.migration_context.needs_migration?
|
38
|
+
|
34
39
|
# 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
40
|
got_lock = $redis_jason.set('jason:schema:lock', nx: true, ex: 3600) # Basic lock mechanism for multi-process environments
|
36
41
|
return if !got_lock
|
37
42
|
|
38
43
|
previous_schema = JSON.parse($redis_jason.get('jason:last_schema') || '{}')
|
39
44
|
current_schema = Jason.schema.deep_stringify_keys.deep_transform_values { |v| v.is_a?(Symbol) ? v.to_s : v }
|
40
|
-
pp current_schema
|
41
45
|
current_schema.each do |model, config|
|
42
46
|
if config != previous_schema[model]
|
43
47
|
puts "Config changed for #{model}"
|
@@ -52,8 +56,6 @@ module Jason
|
|
52
56
|
$redis_jason.set('jason:last_schema', current_schema.to_json)
|
53
57
|
ensure
|
54
58
|
$redis_jason.del('jason:schema:lock')
|
55
|
-
|
56
|
-
previous_config = 'test'
|
57
59
|
end
|
58
60
|
|
59
61
|
|
data/lib/jason/broadcaster.rb
CHANGED
@@ -13,7 +13,8 @@ class Jason::Broadcaster
|
|
13
13
|
if Jason.transport_service == :action_cable
|
14
14
|
ActionCable.server.broadcast(channel, message)
|
15
15
|
elsif Jason.transport_service == :pusher
|
16
|
-
|
16
|
+
$redis_jason.rpush("jason:outbound_message_queue", { channel: pusher_channel_name, name: 'changed', data: message }.to_json)
|
17
|
+
Jason::OutboundMessageQueueWorker.perform_async
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
data/lib/jason/graph_helper.rb
CHANGED
@@ -33,7 +33,8 @@ class Jason::GraphHelper
|
|
33
33
|
end
|
34
34
|
|
35
35
|
# Add and remove edges, return graph before and after
|
36
|
-
|
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
|
-
|
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
|
-
|
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)
|
data/lib/jason/publisher.rb
CHANGED
@@ -5,6 +5,7 @@ module Jason::Publisher
|
|
5
5
|
def self.cache_all
|
6
6
|
Rails.application.eager_load!
|
7
7
|
ActiveRecord::Base.descendants.each do |klass|
|
8
|
+
$redis_jason.del("jason:cache:#{klass.name.underscore}")
|
8
9
|
klass.cache_all if klass.respond_to?(:cache_all)
|
9
10
|
end
|
10
11
|
end
|
@@ -42,7 +43,9 @@ module Jason::Publisher
|
|
42
43
|
# - TODO: The value of an instance changes so that it enters/leaves a subscription
|
43
44
|
|
44
45
|
# TODO: Optimize this, by caching associations rather than checking each time instance is saved
|
45
|
-
jason_assocs = self.class.reflect_on_all_associations(:belongs_to)
|
46
|
+
jason_assocs = self.class.reflect_on_all_associations(:belongs_to)
|
47
|
+
.reject { |assoc| assoc.polymorphic? } # Can't get the class name of a polymorphic association, by
|
48
|
+
.select { |assoc| assoc.klass.respond_to?(:has_jason?) }
|
46
49
|
jason_assocs.each do |assoc|
|
47
50
|
if previous_changes[assoc.foreign_key].present?
|
48
51
|
Jason::Subscription.update_ids(
|
@@ -115,7 +118,8 @@ module Jason::Publisher
|
|
115
118
|
self.after_initialize -> {
|
116
119
|
@api_model = Jason::ApiModel.new(self.class.name.underscore)
|
117
120
|
}
|
118
|
-
self.after_commit :
|
121
|
+
self.after_commit :force_publish_json, on: [:create, :destroy]
|
122
|
+
self.after_commit :publish_json_if_changed, on: [:update]
|
119
123
|
end
|
120
124
|
|
121
125
|
def find_or_create_by_id(params)
|
data/lib/jason/subscription.rb
CHANGED
@@ -48,12 +48,15 @@ class Jason::Subscription
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
def self.all_for_model(model_name)
|
52
|
+
$redis_jason.smembers("jason:models:#{model_name}:all:subscriptions")
|
53
|
+
end
|
54
|
+
|
51
55
|
def self.for_instance(model_name, id, include_all = true)
|
52
56
|
subs = $redis_jason.smembers("jason:models:#{model_name}:#{id}:subscriptions")
|
53
57
|
if include_all
|
54
|
-
subs +=
|
58
|
+
subs += all_for_model(model_name)
|
55
59
|
end
|
56
|
-
|
57
60
|
subs
|
58
61
|
end
|
59
62
|
|
@@ -176,6 +179,13 @@ class Jason::Subscription
|
|
176
179
|
subscription.broadcast_id_changeset(id_changeset)
|
177
180
|
end
|
178
181
|
end
|
182
|
+
|
183
|
+
all_for_model(model_name).each do |sub_id|
|
184
|
+
subscription = find_by_id(sub_id)
|
185
|
+
ids.each do |id|
|
186
|
+
subscription.destroy(model_name, id)
|
187
|
+
end
|
188
|
+
end
|
179
189
|
end
|
180
190
|
|
181
191
|
# Add ID to any _all_ subscriptions
|
@@ -238,7 +248,6 @@ class Jason::Subscription
|
|
238
248
|
all_models = includes_helper.all_models(model_name)
|
239
249
|
|
240
250
|
relation = model_name.classify.constantize.all.eager_load(includes_tree)
|
241
|
-
|
242
251
|
if model_name == model
|
243
252
|
if conditions.blank?
|
244
253
|
$redis_jason.sadd("jason:models:#{model_name}:all:subscriptions", id)
|
@@ -269,7 +278,8 @@ class Jason::Subscription
|
|
269
278
|
|
270
279
|
# Build the tree
|
271
280
|
id_changeset = graph_helper.apply_update({
|
272
|
-
add: [edge_set]
|
281
|
+
add: [edge_set],
|
282
|
+
enforce: enforce
|
273
283
|
})
|
274
284
|
apply_id_changeset(id_changeset)
|
275
285
|
end
|
@@ -286,6 +296,7 @@ class Jason::Subscription
|
|
286
296
|
end
|
287
297
|
$redis_jason.del("jason:subscriptions:#{id}:ids:#{model_name}")
|
288
298
|
end
|
299
|
+
$redis_jason.del("jason:subscriptions:#{id}:graph")
|
289
300
|
end
|
290
301
|
|
291
302
|
def ids(model_name = model)
|
@@ -320,7 +331,7 @@ class Jason::Subscription
|
|
320
331
|
$redis_jason.hset("jason:consumers", consumer_id, Time.now.utc)
|
321
332
|
|
322
333
|
if before_consumer_count == 0
|
323
|
-
set_ids_for_sub_models
|
334
|
+
set_ids_for_sub_models(enforce: true)
|
324
335
|
end
|
325
336
|
end
|
326
337
|
|
@@ -375,6 +386,19 @@ class Jason::Subscription
|
|
375
386
|
}
|
376
387
|
end
|
377
388
|
|
389
|
+
# To be used as a fallback when some corruption of the subscription has taken place
|
390
|
+
def reset!(hard: false)
|
391
|
+
# Remove subscription state
|
392
|
+
if hard
|
393
|
+
clear_all_ids
|
394
|
+
end
|
395
|
+
|
396
|
+
set_ids_for_sub_models(enforce: true)
|
397
|
+
includes_helper.all_models.each do |model_name|
|
398
|
+
broadcaster.broadcast(get_for_model(model_name))
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
378
402
|
def add(model_name, instance_id)
|
379
403
|
idx = $redis_jason.incr("jason:subscription:#{id}:#{model_name}:idx")
|
380
404
|
payload = JSON.parse($redis_jason.hget("jason:cache:#{model_name}", instance_id) || '{}')
|
data/lib/jason/version.rb
CHANGED
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.
|
4
|
+
version: 0.6.8
|
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-
|
11
|
+
date: 2021-03-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sidekiq
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
111
125
|
description:
|
112
126
|
email:
|
113
127
|
- jarees@gmail.com
|
@@ -119,14 +133,16 @@ files:
|
|
119
133
|
- ".rspec"
|
120
134
|
- ".ruby-version"
|
121
135
|
- ".travis.yml"
|
136
|
+
- CHANGELOG.md
|
122
137
|
- CODE_OF_CONDUCT.md
|
123
138
|
- Gemfile
|
124
139
|
- Gemfile.lock
|
125
140
|
- LICENSE.txt
|
126
141
|
- README.md
|
127
142
|
- Rakefile
|
128
|
-
- app/controllers/jason/
|
129
|
-
- app/controllers/jason/
|
143
|
+
- app/controllers/jason/jason_controller.rb
|
144
|
+
- app/controllers/jason/pusher_controller.rb
|
145
|
+
- app/workers/jason/outbound_message_queue_worker.rb
|
130
146
|
- bin/console
|
131
147
|
- bin/setup
|
132
148
|
- client/babel.config.js
|