jason-rails 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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)
|