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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e62faa4e4f1246fa42810c324eec1f8fe96b8026ccdbe12f5b8bd7bc2df3bd4a
4
- data.tar.gz: e4f829ce397c7192c173ba6e712a4b5873bd065d687cebf5ad72025f0436f51a
3
+ metadata.gz: 64f8392cc200b54c7584534cedb35ccbb1675d8c15d1193152e4cbdec558a934
4
+ data.tar.gz: eb285b6c8660017fda735dee46802b9617f08c121c67ce0e9e68efb448e6765c
5
5
  SHA512:
6
- metadata.gz: 6e3abfbfd6e0fbbe6c40dc7c0540448091cd440aaeec3580951a6148d6f1f92e61d5808b39ca02e8c564e1aa57beaf1de4d4cddb34e27b96ace09ca61d856d9a
7
- data.tar.gz: b58a34f35981aea9b991f036cb83f9e059fa17a5932d9e9df78b6e86b5532484fe0dc115531365d5044d2f7df42355b5481a353233dcbd6744077b8fcbd58f18
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.0)
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.4)
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::ApiController < ::ApplicationController
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
@@ -1,4 +1,4 @@
1
- class Jason::Api::PusherController < ApplicationController
1
+ class Jason::PusherController < ApplicationController
2
2
  skip_before_action :verify_authenticity_token
3
3
 
4
4
  def auth
@@ -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
- 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]];
@@ -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) => {
@@ -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];
@@ -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.2",
3
+ "version": "0.6.7",
4
4
  "module": "./lib/index.js",
5
5
  "types": "./lib/index.d.ts",
6
6
  "scripts": {
@@ -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
 
@@ -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 })
@@ -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
  })
@@ -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].tearDown()
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: 'api#configuration'
3
- post '/api/action', to: 'api#action'
4
- post '/api/create_subscription', to: 'api#create_subscription'
5
- post '/api/remove_subscription', to: 'api#remove_subscription'
6
- post '/api/get_payload', to: 'api#get_payload'
7
- post '/api/pusher/auth', to: 'api/pusher#auth'
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
@@ -31,4 +31,5 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency "rspec-rails"
32
32
  spec.add_development_dependency "sqlite3"
33
33
  spec.add_development_dependency "pry"
34
+ spec.add_development_dependency "sidekiq"
34
35
  end
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
 
@@ -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
- Jason.pusher.trigger(pusher_channel_name, 'changed', message)
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
@@ -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)
@@ -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).select { |assoc| assoc.klass.respond_to?(:has_jason?) }
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 :publish_json_if_changed
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)
@@ -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 += $redis_jason.smembers("jason:models:#{model_name}:all:subscriptions")
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
@@ -1,3 +1,3 @@
1
1
  module Jason
2
- VERSION = "0.6.3"
2
+ VERSION = "0.6.8"
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.3
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-02-17 00:00:00.000000000 Z
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/api/pusher_controller.rb
129
- - app/controllers/jason/api_controller.rb
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