jason-rails 0.6.2 → 0.6.7

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: dae8eb61f7d3966c7e34ed81120a16e5c58f84d50ff6ea16ec87340d52cbf0ec
4
- data.tar.gz: cd4bc1d677ec413e2368c235632410e744ab3d47f94e7c65160b7665d3a6db45
3
+ metadata.gz: 48abc57aac9f4529a93812d46422ac21d48e58432fc21e570f1801b84a3ac436
4
+ data.tar.gz: ac21dab5464e5dd254a1a223a80581c1410c928bab4c0fbc9fe32cdfedb7c474
5
5
  SHA512:
6
- metadata.gz: 88130bf9bd8d557478de06bcdcb8901799425e3eeaf10a47a969152c13334659c8ffe36911fa2f3c18903a81c4291118e5f0c6daa72ac7a20545c095ab19f918
7
- data.tar.gz: 3e708e8b214b74cce9dc87ec07b04bf5a4d1453c7398050be657dfe0c34f7668708352d7bef7d9305ffbb1321728267cbab9ab61f03460af3f7987f7d0381253
6
+ metadata.gz: '09c42d2b035af32efdc006224e3e81fa398d2327199845640ff7c710c7cecbd0c5edd9d18e441b5b3fefb2a356f45159c77194ed72f866880171071fb302c422'
7
+ data.tar.gz: 1b5f4f16a5032ca96f8106dc4fff8c8a1a1d5792392b4ed28ad9f1c8974848dec5359e4bb0bc675463cc923e5f77552b6bcfda6c733bf72bb66f156648ce521c
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## v0.6.6
2
+ - Fix: don't run the schema change detection and cache rebuild inside rake tasks or migrations
3
+
4
+ ## v0.6.5
5
+ - 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.
6
+ - Added `enforce: boolean` option to GraphHelper
7
+ - 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.6)
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.5)
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
@@ -1,6 +1,6 @@
1
1
  # Jason
2
2
 
3
- Jason is still in a highly experimental phase with a rapidly changing API. Production use not recommended - but please give it a try!
3
+ Jason is still in an experimental phase with a rapidly changing API. It is being used in some production applications, however it is still in 0.x.x series versions, which means that any 0.x version bump could introduce breaking changes.
4
4
 
5
5
  ## The goal
6
6
 
@@ -14,6 +14,8 @@ I also wanted to avoid writing essentially the same code multiple times in diffe
14
14
 
15
15
  Jason attempts to minimize this repitition by auto-generating API endpoints, redux stores and actions from a single schema definition. Further it adds listeners to ActiveRecord models allowing the redux store to be subscribed to updates from a model or set of models.
16
16
 
17
+ An alternative way of thinking about Jason is "what if we applied the Flux/Redux state update pattern to make the _database_ the store?".
18
+
17
19
  ## Installation
18
20
 
19
21
  Add the gem and the NPM package
@@ -81,7 +83,7 @@ function extraActions(dispatch, store, restClient, act) {
81
83
  - `middleware` - Passed directly to `configureStore` with additional Jason middleware
82
84
 
83
85
  ## Usage
84
- Jason provides two custom hooks to access functionality.
86
+ Jason provides three custom hooks to access functionality.
85
87
 
86
88
  ### useAct
87
89
  This returns an object which allows you to access actions which both update models on the server, and perform an optimistic update to the Redux store.
@@ -126,6 +128,28 @@ export default function PostsList() {
126
128
  }
127
129
  ```
128
130
 
131
+ ### useEager
132
+ Jason stores all the data in a normalized form - one redux slice per model. Often you might want to get nested data from several slices for use in components. The `useEager` hook provides an API for doing that. Under the hood it's just a wrapper around useSelector, which aims to mimic the behaviour of Rails eager loading.
133
+
134
+ Example
135
+ This will fetch the comment as well as the post and user linked to it.
136
+
137
+ ```jsx
138
+ import React from 'react'
139
+ import { useSelector } from 'react-redux'
140
+ import { useEager } from '@jamesr2323/jason'
141
+ import _ from 'lodash'
142
+
143
+ export default function Comment({ id }) {
144
+ const comment = useEager('comments', id, ['post', 'user'])
145
+
146
+ return <div>
147
+ <p>{ comment.body }</p>
148
+ <p>Made on post { comment.post.name } by { comment.user.name }</p>
149
+ </div>
150
+ }
151
+ ```
152
+
129
153
  ## Authorization
130
154
 
131
155
  By default all models can be subscribed to and updated without authentication or authorization. Probably you want to lock down access.
@@ -143,6 +167,7 @@ Development is primarily driven by the needs of projects we're using Jason in. I
143
167
  - Authorization - more thorough authorization integration, with utility functions for common authorizations. Allowing authorization of access to particular fields such as restricting the fields of a user that are publicly broadcast.
144
168
  - Utilities for "Draft editing" - both storing client-side copies of model trees which can be committed or discarded, as well as persisting a shadow copy to the database (to allow resumable editing, or possibly collaborative editing features)
145
169
  - Benchmark and migrate if necessary ConnectionPool::Wrapper vs ConnectionPool
170
+ - Assess using RedisGraph for the graph diffing functionality, to see if this would provide a performance boost
146
171
 
147
172
  ## Development
148
173
 
@@ -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
@@ -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)
@@ -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) => {
@@ -0,0 +1 @@
1
+ export default function useEager(entity: any, id?: null, relations?: never[]): void;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const JasonContext_1 = __importDefault(require("./JasonContext"));
7
+ const react_1 = require("react");
8
+ function useEager(entity, id = null, relations = []) {
9
+ const { eager } = react_1.useContext(JasonContext_1.default);
10
+ return eager(entity, id, relations);
11
+ }
12
+ exports.default = useEager;
@@ -81,5 +81,37 @@ test('pruning IDs', () => __awaiter(void 0, void 0, void 0, function* () {
81
81
  idx: 2
82
82
  });
83
83
  // The ID 4 should have been pruned
84
- expect(store.getState().posts.ids).toStrictEqual([5]);
84
+ expect(store.getState().posts.ids).toStrictEqual(['5']);
85
+ }));
86
+ test('pruning IDs by destroy', () => __awaiter(void 0, void 0, void 0, function* () {
87
+ const resp = { data: {
88
+ schema: { post: {} },
89
+ transportService: 'action_cable'
90
+ } };
91
+ // @ts-ignore
92
+ restClient_1.default.get.mockResolvedValue(resp);
93
+ const { result, waitForNextUpdate } = react_hooks_1.renderHook(() => useJason_1.default({ reducers: {
94
+ test: (s, a) => s || {}
95
+ } }));
96
+ yield waitForNextUpdate();
97
+ const [store, value, connected] = result.current;
98
+ const { handlePayload, subscribe } = value;
99
+ const subscription = subscribe({ post: {} });
100
+ handlePayload({
101
+ type: 'payload',
102
+ model: 'post',
103
+ payload: [{ id: 4, name: 'test' }, { id: 5, name: 'test it out' }],
104
+ md5Hash: subscription.md5Hash,
105
+ idx: 1
106
+ });
107
+ expect(store.getState().posts.ids).toStrictEqual(['4', '5']);
108
+ handlePayload({
109
+ destroy: true,
110
+ model: 'post',
111
+ id: 5,
112
+ md5Hash: subscription.md5Hash,
113
+ idx: 2
114
+ });
115
+ // The ID 4 should have been pruned
116
+ expect(store.getState().posts.ids).toStrictEqual(['4']);
85
117
  }));
data/client/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jamesr2323/jason",
3
- "version": "0.6.1",
3
+ "version": "0.6.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 })
@@ -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/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)
@@ -42,7 +42,9 @@ module Jason::Publisher
42
42
  # - TODO: The value of an instance changes so that it enters/leaves a subscription
43
43
 
44
44
  # 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?) }
45
+ jason_assocs = self.class.reflect_on_all_associations(:belongs_to)
46
+ .reject { |assoc| assoc.polymorphic? } # Can't get the class name of a polymorphic association, by
47
+ .select { |assoc| assoc.klass.respond_to?(:has_jason?) }
46
48
  jason_assocs.each do |assoc|
47
49
  if previous_changes[assoc.foreign_key].present?
48
50
  Jason::Subscription.update_ids(
@@ -269,7 +269,8 @@ class Jason::Subscription
269
269
 
270
270
  # Build the tree
271
271
  id_changeset = graph_helper.apply_update({
272
- add: [edge_set]
272
+ add: [edge_set],
273
+ enforce: enforce
273
274
  })
274
275
  apply_id_changeset(id_changeset)
275
276
  end
@@ -320,7 +321,7 @@ class Jason::Subscription
320
321
  $redis_jason.hset("jason:consumers", consumer_id, Time.now.utc)
321
322
 
322
323
  if before_consumer_count == 0
323
- set_ids_for_sub_models
324
+ set_ids_for_sub_models(enforce: true)
324
325
  end
325
326
  end
326
327
 
@@ -375,6 +376,20 @@ class Jason::Subscription
375
376
  }
376
377
  end
377
378
 
379
+ # To be used as a fallback when some corruption of the subscription has taken place
380
+ def reset!(hard: false)
381
+ # Remove subscription state
382
+ if hard
383
+ clear_all_ids
384
+ $redis_jason.del("jason:subscriptions:#{id}:graph")
385
+ end
386
+
387
+ set_ids_for_sub_models(enforce: true)
388
+ includes_helper.all_models.each do |model_name|
389
+ broadcaster.broadcast(get_for_model(model_name))
390
+ end
391
+ end
392
+
378
393
  def add(model_name, instance_id)
379
394
  idx = $redis_jason.incr("jason:subscription:#{id}:#{model_name}:idx")
380
395
  payload = JSON.parse($redis_jason.hget("jason:cache:#{model_name}", instance_id) || '{}')
data/lib/jason/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Jason
2
- VERSION = "0.6.2"
2
+ VERSION = "0.6.7"
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.2
4
+ version: 0.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Rees
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-16 00:00:00.000000000 Z
11
+ date: 2021-02-24 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
@@ -168,6 +184,8 @@ files:
168
184
  - client/lib/transportAdapters/pusherAdapter.js
169
185
  - client/lib/useAct.d.ts
170
186
  - client/lib/useAct.js
187
+ - client/lib/useEager.d.ts
188
+ - client/lib/useEager.js
171
189
  - client/lib/useJason.d.ts
172
190
  - client/lib/useJason.js
173
191
  - client/lib/useJason.test.d.ts
@@ -194,6 +212,7 @@ files:
194
212
  - client/src/transportAdapters/actionCableAdapter.ts
195
213
  - client/src/transportAdapters/pusherAdapter.ts
196
214
  - client/src/useAct.ts
215
+ - client/src/useEager.ts
197
216
  - client/src/useJason.test.ts
198
217
  - client/src/useJason.ts
199
218
  - client/src/useSub.ts