jason-rails 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/Gemfile.lock +152 -2
- data/app/controllers/jason/api_controller.rb +1 -1
- data/client/lib/JasonContext.d.ts +6 -1
- data/client/lib/JasonProvider.d.ts +2 -2
- data/client/lib/JasonProvider.js +5 -124
- data/client/lib/createJasonReducers.js +41 -3
- data/client/lib/createOptDis.js +0 -2
- data/client/lib/createPayloadHandler.d.ts +6 -1
- data/client/lib/createPayloadHandler.js +42 -54
- data/client/lib/createServerActionQueue.d.ts +10 -0
- data/client/lib/createServerActionQueue.js +48 -0
- data/client/lib/createServerActionQueue.test.d.ts +1 -0
- data/client/lib/createServerActionQueue.test.js +37 -0
- data/client/lib/index.d.ts +3 -2
- data/client/lib/pruneIdsMiddleware.d.ts +2 -0
- data/client/lib/pruneIdsMiddleware.js +26 -0
- data/client/lib/restClient.d.ts +2 -0
- data/client/lib/restClient.js +17 -0
- data/client/lib/useJason.d.ts +5 -0
- data/client/lib/useJason.js +99 -0
- data/client/lib/useJason.test.d.ts +1 -0
- data/client/lib/useJason.test.js +79 -0
- data/client/lib/useSub.js +1 -0
- data/client/package.json +4 -3
- data/client/src/JasonProvider.tsx +5 -123
- data/client/src/createJasonReducers.ts +49 -3
- data/client/src/createOptDis.ts +0 -2
- data/client/src/createPayloadHandler.ts +47 -63
- data/client/src/createServerActionQueue.test.ts +42 -0
- data/client/src/createServerActionQueue.ts +47 -0
- data/client/src/pruneIdsMiddleware.ts +24 -0
- data/client/src/restClient.ts +13 -0
- data/client/src/useJason.test.ts +81 -0
- data/client/src/useJason.ts +115 -0
- data/client/src/useSub.ts +1 -0
- data/client/yarn.lock +59 -3
- data/jason-rails.gemspec +4 -0
- data/lib/jason.rb +12 -0
- data/lib/jason/api_model.rb +2 -12
- data/lib/jason/channel.rb +43 -21
- data/lib/jason/lua_generator.rb +49 -0
- data/lib/jason/publisher.rb +76 -35
- data/lib/jason/publisher_old.rb +112 -0
- data/lib/jason/subscription.rb +322 -99
- data/lib/jason/subscription_old.rb +171 -0
- data/lib/jason/version.rb +1 -1
- metadata +67 -3
@@ -0,0 +1,115 @@
|
|
1
|
+
import createActions from './createActions'
|
2
|
+
import createJasonReducers from './createJasonReducers'
|
3
|
+
import createPayloadHandler from './createPayloadHandler'
|
4
|
+
import createOptDis from './createOptDis'
|
5
|
+
import createServerActionQueue from './createServerActionQueue'
|
6
|
+
import restClient from './restClient'
|
7
|
+
import pruneIdsMiddleware from './pruneIdsMiddleware'
|
8
|
+
|
9
|
+
import { createConsumer } from "@rails/actioncable"
|
10
|
+
import { createEntityAdapter, createSlice, createReducer, configureStore } from '@reduxjs/toolkit'
|
11
|
+
|
12
|
+
import makeEager from './makeEager'
|
13
|
+
import { camelizeKeys } from 'humps'
|
14
|
+
import md5 from 'blueimp-md5'
|
15
|
+
import _ from 'lodash'
|
16
|
+
import React, { useState, useEffect } from 'react'
|
17
|
+
|
18
|
+
export default function useJason({ reducers, middleware = [], extraActions }: { reducers?: any, middleware?: any[], extraActions?: any }) {
|
19
|
+
const [store, setStore] = useState(null as any)
|
20
|
+
const [value, setValue] = useState(null as any)
|
21
|
+
const [connected, setConnected] = useState(false)
|
22
|
+
|
23
|
+
useEffect(() => {
|
24
|
+
restClient.get('/jason/api/schema')
|
25
|
+
.then(({ data: snakey_schema }) => {
|
26
|
+
const schema = camelizeKeys(snakey_schema)
|
27
|
+
console.debug({ schema })
|
28
|
+
|
29
|
+
const serverActionQueue = createServerActionQueue()
|
30
|
+
|
31
|
+
const consumer = createConsumer()
|
32
|
+
const allReducers = {
|
33
|
+
...reducers,
|
34
|
+
...createJasonReducers(schema)
|
35
|
+
}
|
36
|
+
|
37
|
+
console.debug({ allReducers })
|
38
|
+
|
39
|
+
const store = configureStore({ reducer: allReducers, middleware: [...middleware, pruneIdsMiddleware(schema)] })
|
40
|
+
const dispatch = store.dispatch
|
41
|
+
|
42
|
+
const optDis = createOptDis(schema, dispatch, restClient, serverActionQueue)
|
43
|
+
const actions = createActions(schema, store, restClient, optDis, extraActions)
|
44
|
+
const eager = makeEager(schema)
|
45
|
+
|
46
|
+
let payloadHandlers = {}
|
47
|
+
let configs = {}
|
48
|
+
|
49
|
+
function handlePayload(payload) {
|
50
|
+
const { md5Hash } = payload
|
51
|
+
|
52
|
+
const handler = payloadHandlers[md5Hash]
|
53
|
+
if (handler) {
|
54
|
+
handler(payload)
|
55
|
+
} else {
|
56
|
+
console.warn("Payload arrived with no handler", payload, payloadHandlers)
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
const subscription = (consumer.subscriptions.create({
|
61
|
+
channel: 'Jason::Channel'
|
62
|
+
}, {
|
63
|
+
connected: () => {
|
64
|
+
setConnected(true)
|
65
|
+
dispatch({ type: 'jason/upsert', payload: { connected: true } })
|
66
|
+
console.debug('Connected to ActionCable')
|
67
|
+
|
68
|
+
// When AC loses connection - all state is lost, so we need to re-initialize all subscriptions
|
69
|
+
_.values(configs).forEach(config => createSubscription(config))
|
70
|
+
},
|
71
|
+
received: payload => {
|
72
|
+
handlePayload(payload)
|
73
|
+
console.debug("ActionCable Payload received: ", payload)
|
74
|
+
},
|
75
|
+
disconnected: () => {
|
76
|
+
setConnected(false)
|
77
|
+
dispatch({ type: 'jason/upsert', payload: { connected: false } })
|
78
|
+
console.warn('Disconnected from ActionCable')
|
79
|
+
}
|
80
|
+
}));
|
81
|
+
|
82
|
+
function createSubscription(config) {
|
83
|
+
// We need the hash to be consistent in Ruby / Javascript
|
84
|
+
const hashableConfig = _({ conditions: {}, includes: {}, ...config }).toPairs().sortBy(0).fromPairs().value()
|
85
|
+
const md5Hash = md5(JSON.stringify(hashableConfig))
|
86
|
+
payloadHandlers[md5Hash] = createPayloadHandler({ dispatch, serverActionQueue, subscription, config })
|
87
|
+
configs[md5Hash] = hashableConfig
|
88
|
+
|
89
|
+
setTimeout(() => subscription.send({ createSubscription: hashableConfig }), 500)
|
90
|
+
|
91
|
+
return {
|
92
|
+
remove: () => removeSubscription(hashableConfig),
|
93
|
+
md5Hash
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
function removeSubscription(config) {
|
98
|
+
subscription.send({ removeSubscription: config })
|
99
|
+
const md5Hash = md5(JSON.stringify(config))
|
100
|
+
delete payloadHandlers[md5Hash]
|
101
|
+
delete configs[md5Hash]
|
102
|
+
}
|
103
|
+
|
104
|
+
setValue({
|
105
|
+
actions: actions,
|
106
|
+
subscribe: config => createSubscription(config),
|
107
|
+
eager,
|
108
|
+
handlePayload
|
109
|
+
})
|
110
|
+
setStore(store)
|
111
|
+
})
|
112
|
+
}, [])
|
113
|
+
|
114
|
+
return [store, value, connected]
|
115
|
+
}
|
data/client/src/useSub.ts
CHANGED
data/client/yarn.lock
CHANGED
@@ -813,7 +813,7 @@
|
|
813
813
|
"@babel/helper-validator-option" "^7.12.1"
|
814
814
|
"@babel/plugin-transform-typescript" "^7.12.1"
|
815
815
|
|
816
|
-
"@babel/runtime@^7.12.1", "@babel/runtime@^7.8.4":
|
816
|
+
"@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4":
|
817
817
|
version "7.12.5"
|
818
818
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
|
819
819
|
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
|
@@ -1082,6 +1082,18 @@
|
|
1082
1082
|
dependencies:
|
1083
1083
|
"@sinonjs/commons" "^1.7.0"
|
1084
1084
|
|
1085
|
+
"@testing-library/react-hooks@^5.0.3":
|
1086
|
+
version "5.0.3"
|
1087
|
+
resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-5.0.3.tgz#dd0d2048817b013b266d35ca45e3ea48a19fd87e"
|
1088
|
+
integrity sha512-UrnnRc5II7LMH14xsYNm/WRch/67cBafmrSQcyFh0v+UUmSf1uzfB7zn5jQXSettGwOSxJwdQUN7PgkT0w22Lg==
|
1089
|
+
dependencies:
|
1090
|
+
"@babel/runtime" "^7.12.5"
|
1091
|
+
"@types/react" ">=16.9.0"
|
1092
|
+
"@types/react-dom" ">=16.9.0"
|
1093
|
+
"@types/react-test-renderer" ">=16.9.0"
|
1094
|
+
filter-console "^0.1.1"
|
1095
|
+
react-error-boundary "^3.1.0"
|
1096
|
+
|
1085
1097
|
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
|
1086
1098
|
version "7.1.12"
|
1087
1099
|
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d"
|
@@ -1164,6 +1176,33 @@
|
|
1164
1176
|
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.5.tgz#b6ab3bba29e16b821d84e09ecfaded462b816b00"
|
1165
1177
|
integrity sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==
|
1166
1178
|
|
1179
|
+
"@types/prop-types@*":
|
1180
|
+
version "15.7.3"
|
1181
|
+
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
1182
|
+
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
|
1183
|
+
|
1184
|
+
"@types/react-dom@>=16.9.0":
|
1185
|
+
version "17.0.0"
|
1186
|
+
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.0.tgz#b3b691eb956c4b3401777ee67b900cb28415d95a"
|
1187
|
+
integrity sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g==
|
1188
|
+
dependencies:
|
1189
|
+
"@types/react" "*"
|
1190
|
+
|
1191
|
+
"@types/react-test-renderer@>=16.9.0":
|
1192
|
+
version "17.0.0"
|
1193
|
+
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.0.tgz#9be47b375eeb906fced37049e67284a438d56620"
|
1194
|
+
integrity sha512-nvw+F81OmyzpyIE1S0xWpLonLUZCMewslPuA8BtjSKc5XEbn8zEQBXS7KuOLHTNnSOEM2Pum50gHOoZ62tqTRg==
|
1195
|
+
dependencies:
|
1196
|
+
"@types/react" "*"
|
1197
|
+
|
1198
|
+
"@types/react@*", "@types/react@>=16.9.0":
|
1199
|
+
version "17.0.0"
|
1200
|
+
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8"
|
1201
|
+
integrity sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==
|
1202
|
+
dependencies:
|
1203
|
+
"@types/prop-types" "*"
|
1204
|
+
csstype "^3.0.2"
|
1205
|
+
|
1167
1206
|
"@types/stack-utils@^2.0.0":
|
1168
1207
|
version "2.0.0"
|
1169
1208
|
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
|
@@ -1742,6 +1781,11 @@ cssstyle@^2.2.0:
|
|
1742
1781
|
dependencies:
|
1743
1782
|
cssom "~0.3.6"
|
1744
1783
|
|
1784
|
+
csstype@^3.0.2:
|
1785
|
+
version "3.0.6"
|
1786
|
+
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.6.tgz#865d0b5833d7d8d40f4e5b8a6d76aea3de4725ef"
|
1787
|
+
integrity sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw==
|
1788
|
+
|
1745
1789
|
dashdash@^1.12.0:
|
1746
1790
|
version "1.14.1"
|
1747
1791
|
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
@@ -2081,6 +2125,11 @@ fill-range@^7.0.1:
|
|
2081
2125
|
dependencies:
|
2082
2126
|
to-regex-range "^5.0.1"
|
2083
2127
|
|
2128
|
+
filter-console@^0.1.1:
|
2129
|
+
version "0.1.1"
|
2130
|
+
resolved "https://registry.yarnpkg.com/filter-console/-/filter-console-0.1.1.tgz#6242be28982bba7415bcc6db74a79f4a294fa67c"
|
2131
|
+
integrity sha512-zrXoV1Uaz52DqPs+qEwNJWJFAWZpYJ47UNmpN9q4j+/EYsz85uV0DC9k8tRND5kYmoVzL0W+Y75q4Rg8sRJCdg==
|
2132
|
+
|
2084
2133
|
find-up@^4.0.0, find-up@^4.1.0:
|
2085
2134
|
version "4.1.0"
|
2086
2135
|
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
|
@@ -3601,7 +3650,7 @@ qs@~6.5.2:
|
|
3601
3650
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
3602
3651
|
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
3603
3652
|
|
3604
|
-
react-dom@^16.
|
3653
|
+
react-dom@^16.9.x:
|
3605
3654
|
version "16.14.0"
|
3606
3655
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89"
|
3607
3656
|
integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==
|
@@ -3611,6 +3660,13 @@ react-dom@^16.8.3:
|
|
3611
3660
|
prop-types "^15.6.2"
|
3612
3661
|
scheduler "^0.19.1"
|
3613
3662
|
|
3663
|
+
react-error-boundary@^3.1.0:
|
3664
|
+
version "3.1.0"
|
3665
|
+
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.0.tgz#9487443df2f9ba1db90d8ab52351814907ea4af3"
|
3666
|
+
integrity sha512-lmPrdi5SLRJR+AeJkqdkGlW/CRkAUvZnETahK58J4xb5wpbfDngasEGu+w0T1iXEhVrYBJZeW+c4V1hILCnMWQ==
|
3667
|
+
dependencies:
|
3668
|
+
"@babel/runtime" "^7.12.5"
|
3669
|
+
|
3614
3670
|
react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
|
3615
3671
|
version "16.13.1"
|
3616
3672
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
@@ -3632,7 +3688,7 @@ react-redux@^7.2.2:
|
|
3632
3688
|
prop-types "^15.7.2"
|
3633
3689
|
react-is "^16.13.1"
|
3634
3690
|
|
3635
|
-
react@^16.
|
3691
|
+
react@^16.9.x:
|
3636
3692
|
version "16.14.0"
|
3637
3693
|
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
|
3638
3694
|
integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==
|
data/jason-rails.gemspec
CHANGED
@@ -27,4 +27,8 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_dependency "connection_pool", ">= 2.2.3"
|
28
28
|
spec.add_dependency "redis", ">= 4"
|
29
29
|
spec.add_dependency "jsondiff"
|
30
|
+
|
31
|
+
spec.add_development_dependency "rspec-rails"
|
32
|
+
spec.add_development_dependency "sqlite3"
|
33
|
+
spec.add_development_dependency "pry"
|
30
34
|
end
|
data/lib/jason.rb
CHANGED
@@ -8,9 +8,21 @@ require 'jason/channel'
|
|
8
8
|
require 'jason/publisher'
|
9
9
|
require 'jason/subscription'
|
10
10
|
require 'jason/engine'
|
11
|
+
require 'jason/lua_generator'
|
11
12
|
|
12
13
|
module Jason
|
13
14
|
class Error < StandardError; end
|
14
15
|
|
15
16
|
$redis_jason = ::ConnectionPool::Wrapper.new(size: 5, timeout: 3) { ::Redis.new(url: ENV['REDIS_URL']) }
|
17
|
+
|
18
|
+
|
19
|
+
self.mattr_accessor :schema
|
20
|
+
self.schema = {}
|
21
|
+
# add default values of more config vars here
|
22
|
+
|
23
|
+
# this function maps the vars from your app into your engine
|
24
|
+
def self.setup(&block)
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
|
16
28
|
end
|
data/lib/jason/api_model.rb
CHANGED
@@ -8,7 +8,7 @@ class Jason::ApiModel
|
|
8
8
|
|
9
9
|
def initialize(name)
|
10
10
|
@name = name
|
11
|
-
@model = OpenStruct.new(
|
11
|
+
@model = OpenStruct.new(Jason.schema[name.to_sym])
|
12
12
|
end
|
13
13
|
|
14
14
|
def allowed_params
|
@@ -19,10 +19,6 @@ class Jason::ApiModel
|
|
19
19
|
model.allowed_object_params || []
|
20
20
|
end
|
21
21
|
|
22
|
-
def include_models
|
23
|
-
model.include_models || []
|
24
|
-
end
|
25
|
-
|
26
22
|
def include_methods
|
27
23
|
model.include_methods || []
|
28
24
|
end
|
@@ -52,12 +48,6 @@ class Jason::ApiModel
|
|
52
48
|
end
|
53
49
|
|
54
50
|
def as_json_config
|
55
|
-
|
56
|
-
reflection = name.classify.constantize.reflect_on_association(assoc.to_sym)
|
57
|
-
api_model = Jason::ApiModel.new(reflection.klass.name.underscore)
|
58
|
-
{ assoc => { only: api_model.subscribed_fields, methods: api_model.include_methods } }
|
59
|
-
end
|
60
|
-
|
61
|
-
{ only: subscribed_fields, include: include_configs, methods: include_methods }
|
51
|
+
{ only: subscribed_fields, methods: include_methods }
|
62
52
|
end
|
63
53
|
end
|
data/lib/jason/channel.rb
CHANGED
@@ -1,32 +1,27 @@
|
|
1
1
|
class Jason::Channel < ActionCable::Channel::Base
|
2
2
|
attr_accessor :subscriptions
|
3
3
|
|
4
|
+
def subscribe
|
5
|
+
stream_from 'jason'
|
6
|
+
end
|
7
|
+
|
4
8
|
def receive(message)
|
5
|
-
|
9
|
+
handle_message(message)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def handle_message(message)
|
15
|
+
pp message['createSubscription']
|
16
|
+
@subscriptions ||= []
|
6
17
|
|
7
18
|
begin # ActionCable swallows errors in this message - ensure they're output to logs.
|
8
19
|
if (config = message['createSubscription'])
|
9
|
-
|
10
|
-
subscriptions.push(subscription)
|
11
|
-
subscription.add_consumer(identifier)
|
12
|
-
config.keys.each do |model|
|
13
|
-
transmit(subscription.get(model.to_s.underscore))
|
14
|
-
end
|
15
|
-
stream_from subscription.channel
|
20
|
+
create_subscription(config['model'], config['conditions'], config['includes'])
|
16
21
|
elsif (config = message['removeSubscription'])
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# Rails for some reason removed stop_stream_from, so we need to stop all and then restart the other streams
|
22
|
-
# stop_all_streams
|
23
|
-
# subscriptions.each do |s|
|
24
|
-
# stream_from s.channel
|
25
|
-
# end
|
26
|
-
elsif (data = message['getPayload'])
|
27
|
-
config = data['config']
|
28
|
-
model = data['model']
|
29
|
-
Jason::Subscription.new(config: config).get(model.to_s.underscore)
|
22
|
+
remove_subscription(config)
|
23
|
+
elsif (config = message['getPayload'])
|
24
|
+
get_payload(config)
|
30
25
|
end
|
31
26
|
rescue => e
|
32
27
|
puts e.message
|
@@ -34,4 +29,31 @@ class Jason::Channel < ActionCable::Channel::Base
|
|
34
29
|
raise e
|
35
30
|
end
|
36
31
|
end
|
32
|
+
|
33
|
+
def create_subscription(model, conditions, includes)
|
34
|
+
subscription = Jason::Subscription.upsert_by_config(model, conditions: conditions || {}, includes: includes || nil)
|
35
|
+
stream_from subscription.channel
|
36
|
+
|
37
|
+
subscriptions.push(subscription)
|
38
|
+
subscription.add_consumer(identifier)
|
39
|
+
subscription.get.each do |payload|
|
40
|
+
pp payload
|
41
|
+
transmit(payload) if payload.present?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove_subscription(config)
|
46
|
+
subscription = Jason::Subscription.upsert_by_config(config['model'], conditions: config['conditions'], includes: config['includes'])
|
47
|
+
subscriptions.reject! { |s| s.id == subscription.id }
|
48
|
+
subscription.remove_consumer(identifier)
|
49
|
+
|
50
|
+
# TODO Stop streams
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_payload(config)
|
54
|
+
subscription = Jason::Subscription.upsert_by_config(config['model'], conditions: config['conditions'], includes: config['includes'])
|
55
|
+
subscription.get.each do |payload|
|
56
|
+
transmit(payload) if payload.present?
|
57
|
+
end
|
58
|
+
end
|
37
59
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Jason::LuaGenerator
|
2
|
+
## TODO load these scripts and evalsha
|
3
|
+
def cache_json(model_name, id, payload)
|
4
|
+
cmd = <<~LUA
|
5
|
+
local gidx = redis.call('INCR', 'jason:gidx')
|
6
|
+
redis.call( 'set', 'jason:cache:' .. ARGV[1] .. ':' .. ARGV[2] .. ':gidx', gidx )
|
7
|
+
redis.call( 'hset', 'jason:cache:' .. ARGV[1], ARGV[2], ARGV[3] )
|
8
|
+
return gidx
|
9
|
+
LUA
|
10
|
+
|
11
|
+
result = $redis_jason.eval cmd, [], [model_name, id, payload.to_json]
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_payload(model_name, sub_id)
|
15
|
+
# If value has changed, return old value and new idx. Otherwise do nothing.
|
16
|
+
cmd = <<~LUA
|
17
|
+
local t = {}
|
18
|
+
local models = {}
|
19
|
+
local ids = redis.call('smembers', 'jason:subscriptions:' .. ARGV[2] .. ':ids:' .. ARGV[1])
|
20
|
+
|
21
|
+
for k,id in pairs(ids) do
|
22
|
+
models[#models+1] = redis.call( 'hget', 'jason:cache:' .. ARGV[1], id)
|
23
|
+
end
|
24
|
+
|
25
|
+
t[#t+1] = models
|
26
|
+
t[#t+1] = redis.call( 'get', 'jason:subscription:' .. ARGV[2] .. ':' .. ARGV[1] .. ':idx' )
|
27
|
+
|
28
|
+
return t
|
29
|
+
LUA
|
30
|
+
|
31
|
+
$redis_jason.eval cmd, [], [model_name, sub_id]
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_subscription(model_name, id, sub_id, gidx)
|
35
|
+
# If value has changed, return old value and new idx. Otherwise do nothing.
|
36
|
+
cmd = <<~LUA
|
37
|
+
local last_gidx = redis.call('get', 'jason:cache:' .. ARGV[1] .. ':' .. ARGV[2] .. ':gidx') or 0
|
38
|
+
|
39
|
+
if (ARGV[4] >= last_gidx) then
|
40
|
+
local sub_idx = redis.call( 'incr', 'jason:subscription:' .. ARGV[3] .. ':' .. ARGV[1] .. ':idx' )
|
41
|
+
return sub_idx
|
42
|
+
else
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
LUA
|
46
|
+
|
47
|
+
result = $redis_jason.eval cmd, [], [model_name, id, sub_id, gidx]
|
48
|
+
end
|
49
|
+
end
|
data/lib/jason/publisher.rb
CHANGED
@@ -1,26 +1,83 @@
|
|
1
1
|
module Jason::Publisher
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
+
# Warning: Could be expensive. Mainly useful for rebuilding cache after changing Jason config or on deploy
|
5
|
+
def self.cache_all
|
6
|
+
Rails.application.eager_load!
|
7
|
+
ActiveRecord::Base.descendants.each do |klass|
|
8
|
+
klass.cache_all if klass.respond_to?(:cache_all)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
4
12
|
def cache_json
|
5
13
|
as_json_config = api_model.as_json_config
|
6
14
|
scope = api_model.scope
|
7
15
|
|
16
|
+
# Exists
|
8
17
|
if self.persisted? && (scope.blank? || self.class.unscoped.send(scope).exists?(self.id))
|
9
18
|
payload = self.reload.as_json(as_json_config)
|
10
|
-
|
19
|
+
gidx = Jason::LuaGenerator.new.cache_json(self.class.name.underscore, self.id, payload)
|
20
|
+
return [payload, gidx]
|
21
|
+
# Has been destroyed
|
11
22
|
else
|
12
|
-
$redis_jason.hdel("jason:#{self.class.name.underscore}
|
23
|
+
$redis_jason.hdel("jason:cache:#{self.class.name.underscore}", self.id)
|
24
|
+
return []
|
13
25
|
end
|
14
26
|
end
|
15
27
|
|
16
28
|
def publish_json
|
17
|
-
cache_json
|
29
|
+
payload, gidx = cache_json
|
30
|
+
subs = jason_subscriptions # Get this first, because could be changed
|
31
|
+
|
32
|
+
# Situations where IDs may need to change and this can't be immediately determined
|
33
|
+
# - An instance is created where it belongs_to an instance under a subscription
|
34
|
+
# - An instance belongs_to association changes - e.g. comment.post_id changes to/from one with a subscription
|
35
|
+
# - TODO: The value of an instance changes so that it enters/leaves a subscription
|
36
|
+
|
37
|
+
# TODO: Optimize this, by caching associations rather than checking each time instance is saved
|
38
|
+
jason_assocs = self.class.reflect_on_all_associations(:belongs_to).select { |assoc| assoc.klass.has_jason? }
|
39
|
+
jason_assocs.each do |assoc|
|
40
|
+
if self.previous_changes[assoc.foreign_key].present?
|
41
|
+
|
42
|
+
Jason::Subscription.update_ids(
|
43
|
+
self.class.name.underscore,
|
44
|
+
id,
|
45
|
+
assoc.klass.name.underscore,
|
46
|
+
self.previous_changes[assoc.foreign_key][0],
|
47
|
+
self.previous_changes[assoc.foreign_key][1]
|
48
|
+
)
|
49
|
+
elsif (persisted? && @was_a_new_record && send(assoc.foreign_key).present?)
|
50
|
+
Jason::Subscription.update_ids(
|
51
|
+
self.class.name.underscore,
|
52
|
+
id,
|
53
|
+
assoc.klass.name.underscore,
|
54
|
+
nil,
|
55
|
+
send(assoc.foreign_key)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if !persisted? # Deleted
|
61
|
+
Jason::Subscription.remove_ids(
|
62
|
+
self.class.name.underscore,
|
63
|
+
[id]
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
# - An instance is created where it belongs_to an _all_ subscription
|
68
|
+
if self.previous_changes['id'].present?
|
69
|
+
Jason::Subscription.add_id(self.class.name.underscore, id)
|
70
|
+
end
|
71
|
+
|
18
72
|
return if skip_publish_json
|
19
|
-
self.class.jason_subscriptions.each do |id, config_json|
|
20
|
-
config = JSON.parse(config_json)
|
21
73
|
|
22
|
-
|
23
|
-
|
74
|
+
if self.persisted?
|
75
|
+
jason_subscriptions.each do |sub_id|
|
76
|
+
Jason::Subscription.new(id: sub_id).update(self.class.name.underscore, id, payload, gidx)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
subs.each do |sub_id|
|
80
|
+
Jason::Subscription.new(id: sub_id).destroy(self.class.name.underscore, id)
|
24
81
|
end
|
25
82
|
end
|
26
83
|
end
|
@@ -30,47 +87,31 @@ module Jason::Publisher
|
|
30
87
|
publish_json if (self.previous_changes.keys.map(&:to_sym) & subscribed_fields).present? || !self.persisted?
|
31
88
|
end
|
32
89
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
90
|
+
def jason_subscriptions
|
91
|
+
Jason::Subscription.for_instance(self.class.name.underscore, id)
|
92
|
+
end
|
37
93
|
|
38
|
-
|
39
|
-
|
94
|
+
class_methods do
|
95
|
+
def cache_all
|
96
|
+
all.each(&:cache_json)
|
40
97
|
end
|
41
98
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
subscriptions.each do |id, config_json|
|
46
|
-
Jason::Subscription.new(id: id).update(self.name.underscore)
|
47
|
-
end
|
99
|
+
def has_jason?
|
100
|
+
true
|
48
101
|
end
|
49
102
|
|
50
103
|
def flush_cache
|
51
|
-
$redis_jason.del("jason:#{self.name.underscore}
|
104
|
+
$redis_jason.del("jason:cache:#{self.name.underscore}")
|
52
105
|
end
|
53
106
|
|
54
107
|
def setup_json
|
108
|
+
self.before_save -> {
|
109
|
+
@was_a_new_record = new_record?
|
110
|
+
}
|
55
111
|
self.after_initialize -> {
|
56
112
|
@api_model = Jason::ApiModel.new(self.class.name.underscore)
|
57
113
|
}
|
58
114
|
self.after_commit :publish_json_if_changed
|
59
|
-
|
60
|
-
include_models = Jason::ApiModel.new(self.name.underscore).include_models
|
61
|
-
|
62
|
-
include_models.map do |assoc|
|
63
|
-
puts assoc
|
64
|
-
reflection = self.reflect_on_association(assoc.to_sym)
|
65
|
-
reflection.klass.after_commit -> {
|
66
|
-
subscribed_fields = Jason::ApiModel.new(self.class.name.underscore).subscribed_fields
|
67
|
-
puts subscribed_fields.inspect
|
68
|
-
|
69
|
-
if (self.previous_changes.keys.map(&:to_sym) & subscribed_fields).present?
|
70
|
-
self.send(reflection.inverse_of.name)&.publish_json
|
71
|
-
end
|
72
|
-
}
|
73
|
-
end
|
74
115
|
end
|
75
116
|
|
76
117
|
def find_or_create_by_id(params)
|