jason-rails 0.4.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.ruby-version +1 -0
  4. data/Gemfile.lock +152 -2
  5. data/README.md +117 -5
  6. data/app/controllers/jason/api/pusher_controller.rb +15 -0
  7. data/app/controllers/jason/api_controller.rb +44 -2
  8. data/client/lib/JasonContext.d.ts +6 -1
  9. data/client/lib/JasonContext.js +4 -1
  10. data/client/lib/JasonProvider.d.ts +2 -2
  11. data/client/lib/JasonProvider.js +5 -124
  12. data/client/lib/createJasonReducers.js +48 -3
  13. data/client/lib/createOptDis.js +0 -2
  14. data/client/lib/createPayloadHandler.d.ts +9 -1
  15. data/client/lib/createPayloadHandler.js +47 -55
  16. data/client/lib/createServerActionQueue.d.ts +10 -0
  17. data/client/lib/createServerActionQueue.js +48 -0
  18. data/client/lib/createServerActionQueue.test.d.ts +1 -0
  19. data/client/lib/createServerActionQueue.test.js +37 -0
  20. data/client/lib/createTransportAdapter.d.ts +5 -0
  21. data/client/lib/createTransportAdapter.js +20 -0
  22. data/client/lib/index.d.ts +5 -2
  23. data/client/lib/index.js +3 -1
  24. data/client/lib/makeEager.js +2 -2
  25. data/client/lib/pruneIdsMiddleware.d.ts +2 -0
  26. data/client/lib/pruneIdsMiddleware.js +24 -0
  27. data/client/lib/restClient.d.ts +2 -0
  28. data/client/lib/restClient.js +17 -0
  29. data/client/lib/transportAdapters/actionCableAdapter.d.ts +5 -0
  30. data/client/lib/transportAdapters/actionCableAdapter.js +35 -0
  31. data/client/lib/transportAdapters/pusherAdapter.d.ts +5 -0
  32. data/client/lib/transportAdapters/pusherAdapter.js +68 -0
  33. data/client/lib/useJason.d.ts +5 -0
  34. data/client/lib/useJason.js +94 -0
  35. data/client/lib/useJason.test.d.ts +1 -0
  36. data/client/lib/useJason.test.js +85 -0
  37. data/client/lib/useSub.d.ts +1 -1
  38. data/client/lib/useSub.js +6 -3
  39. data/client/package.json +5 -3
  40. data/client/src/JasonContext.ts +4 -1
  41. data/client/src/JasonProvider.tsx +5 -123
  42. data/client/src/createJasonReducers.ts +56 -3
  43. data/client/src/createOptDis.ts +0 -2
  44. data/client/src/createPayloadHandler.ts +53 -64
  45. data/client/src/createServerActionQueue.test.ts +42 -0
  46. data/client/src/createServerActionQueue.ts +47 -0
  47. data/client/src/createTransportAdapter.ts +13 -0
  48. data/client/src/index.ts +3 -1
  49. data/client/src/makeEager.ts +2 -2
  50. data/client/src/pruneIdsMiddleware.ts +24 -0
  51. data/client/src/restClient.ts +14 -0
  52. data/client/src/transportAdapters/actionCableAdapter.ts +38 -0
  53. data/client/src/transportAdapters/pusherAdapter.ts +72 -0
  54. data/client/src/useJason.test.ts +87 -0
  55. data/client/src/useJason.ts +110 -0
  56. data/client/src/useSub.ts +6 -3
  57. data/client/yarn.lock +71 -3
  58. data/config/routes.rb +5 -1
  59. data/jason-rails.gemspec +4 -0
  60. data/lib/jason.rb +61 -1
  61. data/lib/jason/api_model.rb +2 -12
  62. data/lib/jason/broadcaster.rb +19 -0
  63. data/lib/jason/channel.rb +50 -21
  64. data/lib/jason/graph_helper.rb +165 -0
  65. data/lib/jason/includes_helper.rb +108 -0
  66. data/lib/jason/lua_generator.rb +71 -0
  67. data/lib/jason/publisher.rb +82 -37
  68. data/lib/jason/publisher_old.rb +112 -0
  69. data/lib/jason/subscription.rb +349 -97
  70. data/lib/jason/subscription_old.rb +171 -0
  71. data/lib/jason/version.rb +1 -1
  72. metadata +80 -3
data/client/src/useSub.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import JasonContext from './JasonContext'
2
2
  import { useContext, useEffect } from 'react'
3
3
 
4
- export default function useSub(config) {
4
+ export default function useSub(config, options = {}) {
5
+ // useEffect uses strict equality
6
+ const configJson = JSON.stringify(config)
5
7
  const subscribe = useContext(JasonContext).subscribe
6
8
 
7
9
  useEffect(() => {
8
- return subscribe(config)
9
- }, [])
10
+ // @ts-ignore
11
+ return subscribe(config, options).remove
12
+ }, [configJson])
10
13
  }
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"
@@ -3596,12 +3645,19 @@ punycode@^2.1.0, punycode@^2.1.1:
3596
3645
  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
3597
3646
  integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
3598
3647
 
3648
+ pusher-js@^7.0.3:
3649
+ version "7.0.3"
3650
+ resolved "https://registry.yarnpkg.com/pusher-js/-/pusher-js-7.0.3.tgz#f81c78cdf2ad32f546caa7532ec7f9081ef00b8d"
3651
+ integrity sha512-HIfCvt00CAqgO4W0BrdpPsDcAwy51rB6DN0VMC+JeVRRbo8mn3XTeUeIFjmmlRLZLX8rPhUtLRo7vPag6b8GCw==
3652
+ dependencies:
3653
+ tweetnacl "^1.0.3"
3654
+
3599
3655
  qs@~6.5.2:
3600
3656
  version "6.5.2"
3601
3657
  resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
3602
3658
  integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
3603
3659
 
3604
- react-dom@^16.8.3:
3660
+ react-dom@^16.9.x:
3605
3661
  version "16.14.0"
3606
3662
  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89"
3607
3663
  integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==
@@ -3611,6 +3667,13 @@ react-dom@^16.8.3:
3611
3667
  prop-types "^15.6.2"
3612
3668
  scheduler "^0.19.1"
3613
3669
 
3670
+ react-error-boundary@^3.1.0:
3671
+ version "3.1.0"
3672
+ resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.0.tgz#9487443df2f9ba1db90d8ab52351814907ea4af3"
3673
+ integrity sha512-lmPrdi5SLRJR+AeJkqdkGlW/CRkAUvZnETahK58J4xb5wpbfDngasEGu+w0T1iXEhVrYBJZeW+c4V1hILCnMWQ==
3674
+ dependencies:
3675
+ "@babel/runtime" "^7.12.5"
3676
+
3614
3677
  react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
3615
3678
  version "16.13.1"
3616
3679
  resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -3632,7 +3695,7 @@ react-redux@^7.2.2:
3632
3695
  prop-types "^15.7.2"
3633
3696
  react-is "^16.13.1"
3634
3697
 
3635
- react@^16.8.3:
3698
+ react@^16.9.x:
3636
3699
  version "16.14.0"
3637
3700
  resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
3638
3701
  integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==
@@ -4302,6 +4365,11 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
4302
4365
  resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
4303
4366
  integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
4304
4367
 
4368
+ tweetnacl@^1.0.3:
4369
+ version "1.0.3"
4370
+ resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
4371
+ integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
4372
+
4305
4373
  type-check@~0.3.2:
4306
4374
  version "0.3.2"
4307
4375
  resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
data/config/routes.rb CHANGED
@@ -1,4 +1,8 @@
1
1
  Jason::Engine.routes.draw do
2
- get '/api/schema', to: 'api#schema'
2
+ get '/api/config', to: 'api#configuration'
3
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'
4
8
  end
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
@@ -7,10 +7,70 @@ require 'jason/api_model'
7
7
  require 'jason/channel'
8
8
  require 'jason/publisher'
9
9
  require 'jason/subscription'
10
+ require 'jason/broadcaster'
10
11
  require 'jason/engine'
12
+ require 'jason/lua_generator'
13
+ require 'jason/includes_helper'
14
+ require 'jason/graph_helper'
11
15
 
12
16
  module Jason
13
17
  class Error < StandardError; end
14
18
 
15
- $redis_jason = ::ConnectionPool::Wrapper.new(size: 5, timeout: 3) { ::Redis.new(url: ENV['REDIS_URL']) }
19
+ self.mattr_accessor :schema
20
+ self.mattr_accessor :transport_service
21
+ self.mattr_accessor :redis
22
+ self.mattr_accessor :pusher
23
+ self.mattr_accessor :pusher_key
24
+ self.mattr_accessor :pusher_region
25
+ self.mattr_accessor :pusher_channel_prefix
26
+ self.mattr_accessor :authorization_service
27
+
28
+ self.schema = {}
29
+ self.transport_service = :action_cable
30
+ self.pusher_region = 'eu'
31
+ self.pusher_channel_prefix = 'jason'
32
+
33
+ def self.init
34
+ # 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
+ got_lock = $redis_jason.set('jason:schema:lock', nx: true, ex: 3600) # Basic lock mechanism for multi-process environments
36
+ return if !got_lock
37
+
38
+ previous_schema = JSON.parse($redis_jason.get('jason:last_schema') || '{}')
39
+ current_schema = Jason.schema.deep_stringify_keys.deep_transform_values { |v| v.is_a?(Symbol) ? v.to_s : v }
40
+ pp current_schema
41
+ current_schema.each do |model, config|
42
+ if config != previous_schema[model]
43
+ puts "Config changed for #{model}"
44
+ puts "Old config was #{previous_schema[model]}"
45
+ puts "New config is #{config}"
46
+ puts "Rebuilding cache for #{model}"
47
+ model.classify.constantize.cache_all
48
+ puts "Done"
49
+ end
50
+ end
51
+
52
+ $redis_jason.set('jason:last_schema', current_schema.to_json)
53
+ ensure
54
+ $redis_jason.del('jason:schema:lock')
55
+
56
+ previous_config = 'test'
57
+ end
58
+
59
+
60
+ # this function maps the vars from your app into your engine
61
+ def self.setup(&block)
62
+ yield self
63
+
64
+ $redis_jason = self.redis || ::ConnectionPool::Wrapper.new(size: 5, timeout: 3) { ::Redis.new(url: ENV['REDIS_URL']) }
65
+
66
+ if ![:action_cable, :pusher].include?(self.transport_service)
67
+ raise "Unknown transport service '#{self.transport_service}' specified"
68
+ end
69
+
70
+ if self.transport_service == :pusher && self.pusher.blank?
71
+ raise "Pusher specified as transport service but no Pusher client provided. Please configure with config.pusher = Pusher::Client.new(...)"
72
+ end
73
+
74
+ init
75
+ end
16
76
  end
@@ -8,7 +8,7 @@ class Jason::ApiModel
8
8
 
9
9
  def initialize(name)
10
10
  @name = name
11
- @model = OpenStruct.new(JASON_API_MODEL[name.to_sym])
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
- include_configs = include_models.map do |assoc|
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
@@ -0,0 +1,19 @@
1
+ class Jason::Broadcaster
2
+ attr_reader :channel
3
+
4
+ def initialize(channel)
5
+ @channel = channel
6
+ end
7
+
8
+ def pusher_channel_name
9
+ "private-#{Jason.pusher_channel_prefix}-#{channel}"
10
+ end
11
+
12
+ def broadcast(message)
13
+ if Jason.transport_service == :action_cable
14
+ ActionCable.server.broadcast(channel, message)
15
+ elsif Jason.transport_service == :pusher
16
+ Jason.pusher.trigger(pusher_channel_name, 'changed', message)
17
+ end
18
+ end
19
+ 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
- subscriptions ||= []
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
- subscription = Jason::Subscription.new(config: config)
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
- subscription = Jason::Subscription.new(config: config)
18
- subscriptions.reject! { |s| s.id == subscription.id }
19
- subscription.remove_consumer(identifier)
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, message['forceRefresh'])
30
25
  end
31
26
  rescue => e
32
27
  puts e.message
@@ -34,4 +29,38 @@ 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
+
36
+ return if !subscription.user_can_access?(current_user)
37
+ stream_from subscription.channel
38
+
39
+ subscriptions.push(subscription)
40
+ subscription.add_consumer(identifier)
41
+ subscription.get.each do |payload|
42
+ pp payload
43
+ transmit(payload) if payload.present?
44
+ end
45
+ end
46
+
47
+ def remove_subscription(config)
48
+ subscription = Jason::Subscription.upsert_by_config(config['model'], conditions: config['conditions'], includes: config['includes'])
49
+ subscriptions.reject! { |s| s.id == subscription.id }
50
+ subscription.remove_consumer(identifier)
51
+
52
+ # TODO Stop streams
53
+ end
54
+
55
+ def get_payload(config, force_refresh = false)
56
+ subscription = Jason::Subscription.upsert_by_config(config['model'], conditions: config['conditions'], includes: config['includes'])
57
+
58
+ return if !subscription.user_can_access?(current_user)
59
+ if force_refresh
60
+ subscription.set_ids_for_sub_models
61
+ end
62
+ subscription.get.each do |model_name, payload|
63
+ transmit(payload) if payload.present?
64
+ end
65
+ end
37
66
  end
@@ -0,0 +1,165 @@
1
+ class Jason::GraphHelper
2
+ attr_reader :id, :includes_helper
3
+
4
+ def initialize(id, includes_helper)
5
+ @id = id
6
+ @includes_helper = includes_helper
7
+ end
8
+
9
+ def add_edge(parent_model, parent_id, child_model, child_id)
10
+ edge = "#{parent_model}:#{parent_id}/#{child_model}:#{child_id}"
11
+ $redis_jason.sadd("jason:subscriptions:#{id}:graph", edge)
12
+ end
13
+
14
+ def remove_edge(parent_model, parent_id, child_model, child_id)
15
+ edge = "#{parent_model}:#{parent_id}/#{child_model}:#{child_id}"
16
+ $redis_jason.srem("jason:subscriptions:#{id}:graph", edge)
17
+ end
18
+
19
+ def add_edges(all_models, instance_ids)
20
+ edges = build_edges(all_models, instance_ids)
21
+ $redis_jason.sadd("jason:subscriptions:#{id}:graph", edges)
22
+ end
23
+
24
+ def remove_edges(all_models, instance_ids)
25
+ edges = build_edges(all_models, instance_ids)
26
+ $redis_jason.srem("jason:subscriptions:#{id}:graph", edges)
27
+ end
28
+
29
+ def apply_remove_node(node)
30
+ edges = $redis_jason.smembers("jason:subscriptions:#{id}:graph")
31
+ edges = find_edges_with_node(edges, node)
32
+ diff_edges_from_graph(remove_edges: edges)
33
+ end
34
+
35
+ # Add and remove edges, return graph before and after
36
+ def apply_update(add: nil, remove: nil)
37
+ add_edges = []
38
+ remove_edges = []
39
+
40
+ if add.present?
41
+ add.each do |edge_set|
42
+ add_edges += build_edges(edge_set[:model_names], edge_set[:instance_ids])
43
+ end
44
+ end
45
+
46
+ if remove.present?
47
+ remove.each do |edge_set|
48
+ remove_edges += build_edges(edge_set[:model_names], edge_set[:instance_ids], include_root: false)
49
+ end
50
+ end
51
+ diff_edges_from_graph(add_edges: add_edges, remove_edges: remove_edges)
52
+ end
53
+
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
+
57
+ old_graph = build_graph_from_edges(old_edges)
58
+ new_graph = build_graph_from_edges(new_edges)
59
+
60
+ old_nodes = (old_graph.values + old_graph.keys).flatten.uniq - ['root']
61
+ new_nodes = (new_graph.values + new_graph.keys).flatten.uniq - ['root']
62
+ orphan_nodes = find_orphans_in_graph(new_graph)
63
+
64
+ added_nodes = new_nodes - old_nodes - orphan_nodes
65
+ removed_nodes = old_nodes - new_nodes + orphan_nodes
66
+
67
+ orphaned_edges = orphan_nodes.map do |node|
68
+ find_edges_with_node(new_edges, node)
69
+ end.flatten
70
+
71
+ if orphaned_edges.present?
72
+ $redis_jason.srem("jason:subscriptions:#{id}:graph", orphaned_edges)
73
+ end
74
+
75
+ ids_to_add = {}
76
+ ids_to_remove = {}
77
+
78
+ added_nodes.each do |node|
79
+ model_name, instance_id = node.split(':')
80
+ ids_to_add[model_name] ||= []
81
+ ids_to_add[model_name].push(instance_id)
82
+ end
83
+
84
+ removed_nodes.each do |node|
85
+ model_name, instance_id = node.split(':')
86
+ ids_to_remove[model_name] ||= []
87
+ ids_to_remove[model_name].push(instance_id)
88
+ end
89
+
90
+ { ids_to_remove: ids_to_remove, ids_to_add: ids_to_add }
91
+ end
92
+
93
+ def find_edges_with_node(edges, node)
94
+ edges.select do |edge|
95
+ parent, child = edge.split('/')
96
+ parent == node || child == node
97
+ end
98
+ end
99
+
100
+ def find_orphans
101
+ edges = $redis_jason.smembers("jason:subscriptions:#{id}:graph")
102
+ graph = build_graph_from_edges(edges)
103
+ find_orphans_in_graph(graph)
104
+ end
105
+
106
+ def find_orphans_in_graph(graph)
107
+ reachable_nodes = get_reachable_nodes(graph)
108
+ all_nodes = (graph.values + graph.keys).flatten.uniq - ['root']
109
+ all_nodes - reachable_nodes
110
+ end
111
+
112
+ def get_reachable_nodes(graph, parent = 'root')
113
+ reached_nodes = graph[parent] || []
114
+ reached_nodes.each do |child|
115
+ reached_nodes += get_reachable_nodes(graph, child)
116
+ end
117
+ reached_nodes
118
+ end
119
+
120
+ def build_graph_from_edges(edges)
121
+ graph = {}
122
+ edges.each do |edge|
123
+ parent, child = edge.split('/')
124
+ graph[parent] ||= []
125
+ graph[parent].push(child)
126
+ end
127
+ graph
128
+ end
129
+
130
+ private
131
+
132
+ def build_edges(all_models, instance_ids, include_root: true)
133
+ # Build the tree
134
+ # Find parent -> child model relationships
135
+ edges = []
136
+
137
+ all_models.each_with_index do |parent_model, parent_idx|
138
+ all_models.each_with_index do |child_model, child_idx|
139
+ next if parent_model == child_model
140
+ next if !includes_helper.in_sub(parent_model, child_model)
141
+
142
+ pairs = instance_ids.map { |row| [row[parent_idx], row[child_idx]] }
143
+ .uniq
144
+ .reject{ |pair| pair[0].blank? || pair[1].blank? }
145
+
146
+ edges += pairs.map.each do |pair|
147
+ "#{parent_model}:#{pair[0]}/#{child_model}:#{pair[1]}"
148
+ end
149
+ end
150
+ end
151
+
152
+ root_model = includes_helper.root_model
153
+
154
+ if include_root && all_models.include?(root_model)
155
+ root_idx = all_models.find_index(root_model)
156
+ root_ids = instance_ids.map { |row| row[root_idx] }.uniq.compact
157
+
158
+ edges += root_ids.map do |id|
159
+ "root/#{root_model}:#{id}"
160
+ end
161
+ end
162
+
163
+ edges
164
+ end
165
+ end