jason-rails 0.6.3 → 0.6.8

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: 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