jason-rails 0.6.8 → 0.7.5
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/CHANGELOG.md +34 -0
- data/Gemfile.lock +5 -3
- data/README.md +20 -14
- data/app/controllers/jason/jason_controller.rb +26 -4
- data/app/workers/jason/outbound_message_queue_worker.rb +1 -1
- data/client/lib/JasonProvider.d.ts +3 -1
- data/client/lib/JasonProvider.js +2 -2
- data/client/lib/addRelations.d.ts +1 -0
- data/client/lib/addRelations.js +39 -0
- data/client/lib/createJasonReducers.js +4 -2
- data/client/lib/createOptDis.d.ts +1 -1
- data/client/lib/createOptDis.js +9 -8
- data/client/lib/createServerActionQueue.d.ts +3 -2
- data/client/lib/createServerActionQueue.js +32 -6
- data/client/lib/createServerActionQueue.test.js +61 -6
- data/client/lib/createThenable.d.ts +1 -0
- data/client/lib/createThenable.js +5 -0
- data/client/lib/createTransportAdapter.d.ts +1 -1
- data/client/lib/createTransportAdapter.js +2 -2
- data/client/lib/index.d.ts +8 -1
- data/client/lib/index.js +3 -1
- data/client/lib/transportAdapters/actionCableAdapter.d.ts +1 -1
- data/client/lib/transportAdapters/actionCableAdapter.js +27 -6
- data/client/lib/transportAdapters/pusherAdapter.js +1 -1
- data/client/lib/useDraft.d.ts +1 -0
- data/client/lib/useDraft.js +13 -0
- data/client/lib/useEager.d.ts +1 -1
- data/client/lib/useEager.js +10 -5
- data/client/lib/useJason.d.ts +3 -1
- data/client/lib/useJason.js +4 -7
- data/client/package.json +1 -1
- data/client/src/JasonProvider.tsx +2 -2
- data/client/src/addRelations.ts +33 -0
- data/client/src/createJasonReducers.ts +4 -2
- data/client/src/createOptDis.ts +10 -8
- data/client/src/createServerActionQueue.test.ts +60 -6
- data/client/src/createServerActionQueue.ts +41 -6
- data/client/src/createTransportAdapter.ts +2 -2
- data/client/src/index.ts +2 -0
- data/client/src/transportAdapters/actionCableAdapter.ts +28 -7
- data/client/src/transportAdapters/pusherAdapter.ts +1 -2
- data/client/src/useDraft.ts +17 -0
- data/client/src/useEager.ts +9 -6
- data/client/src/useJason.ts +10 -7
- data/lib/jason.rb +9 -2
- data/lib/jason/api_model.rb +0 -4
- data/lib/jason/channel.rb +0 -7
- data/lib/jason/conditions_matcher.rb +88 -0
- data/lib/jason/consistency_checker.rb +65 -0
- data/lib/jason/graph_helper.rb +4 -0
- data/lib/jason/publisher.rb +36 -36
- data/lib/jason/subscription.rb +51 -15
- data/lib/jason/version.rb +1 -1
- metadata +12 -5
- data/client/src/makeEager.ts +0 -46
- data/lib/jason/publisher_old.rb +0 -112
- data/lib/jason/subscription_old.rb +0 -171
| @@ -1,31 +1,65 @@ | |
| 1 1 | 
             
            // A FIFO queue with deduping of actions whose effect will be cancelled by later actions
         | 
| 2 | 
            -
             | 
| 2 | 
            +
            import { v4 as uuidv4 } from 'uuid'
         | 
| 3 3 | 
             
            import _ from 'lodash'
         | 
| 4 4 |  | 
| 5 | 
            +
            class Deferred {
         | 
| 6 | 
            +
              promise: Promise<any>;
         | 
| 7 | 
            +
              resolve: any;
         | 
| 8 | 
            +
              reject: any;
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              constructor() {
         | 
| 11 | 
            +
                this.promise = new Promise((resolve, reject)=> {
         | 
| 12 | 
            +
                  this.reject = reject
         | 
| 13 | 
            +
                  this.resolve = resolve
         | 
| 14 | 
            +
                })
         | 
| 15 | 
            +
              }
         | 
| 16 | 
            +
            }
         | 
| 17 | 
            +
             | 
| 5 18 | 
             
            export default function createServerActionQueue() {
         | 
| 6 19 | 
             
              const queue: any[] = []
         | 
| 20 | 
            +
              const deferreds = {}
         | 
| 21 | 
            +
             | 
| 7 22 | 
             
              let inFlight = false
         | 
| 8 23 |  | 
| 9 | 
            -
              function addItem( | 
| 24 | 
            +
              function addItem(action) {
         | 
| 10 25 | 
             
                // Check if there are any items ahead in the queue that this item would effectively overwrite.
         | 
| 11 26 | 
             
                // In that case we can remove them
         | 
| 12 27 | 
             
                // If this is an upsert && item ID is the same && current item attributes are a superset of the earlier item attributes
         | 
| 13 | 
            -
                const { type, payload } =  | 
| 28 | 
            +
                const { type, payload } = action
         | 
| 29 | 
            +
                const id = uuidv4()
         | 
| 30 | 
            +
                const dfd = new Deferred()
         | 
| 31 | 
            +
                deferreds[id] = [dfd]
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                const item = { id, action }
         | 
| 34 | 
            +
             | 
| 14 35 | 
             
                if (type.split('/')[1] !== 'upsert') {
         | 
| 15 36 | 
             
                  queue.push(item)
         | 
| 16 | 
            -
                  return
         | 
| 37 | 
            +
                  return dfd.promise
         | 
| 17 38 | 
             
                }
         | 
| 18 39 |  | 
| 19 40 | 
             
                _.remove(queue, item => {
         | 
| 20 | 
            -
                  const { type: itemType, payload: itemPayload } = item
         | 
| 41 | 
            +
                  const { type: itemType, payload: itemPayload } = item.action
         | 
| 21 42 | 
             
                  if (type !== itemType) return false
         | 
| 22 43 | 
             
                  if (itemPayload.id !== payload.id) return false
         | 
| 23 44 |  | 
| 24 45 | 
             
                  // Check that all keys of itemPayload are in payload.
         | 
| 46 | 
            +
                  deferreds[id].push(...deferreds[item.id])
         | 
| 25 47 | 
             
                  return _.difference(_.keys(itemPayload),_.keys(payload)).length === 0
         | 
| 26 48 | 
             
                })
         | 
| 27 49 |  | 
| 28 50 | 
             
                queue.push(item)
         | 
| 51 | 
            +
                return dfd.promise
         | 
| 52 | 
            +
              }
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              function itemProcessed(id, data?: any) {
         | 
| 55 | 
            +
                inFlight = false
         | 
| 56 | 
            +
                deferreds[id].forEach(dfd => dfd.resolve(data))
         | 
| 57 | 
            +
              }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              function itemFailed(id, error?: any) {
         | 
| 60 | 
            +
                queue.length = 0
         | 
| 61 | 
            +
                deferreds[id].forEach(dfd => dfd.reject(error))
         | 
| 62 | 
            +
                inFlight = false
         | 
| 29 63 | 
             
              }
         | 
| 30 64 |  | 
| 31 65 | 
             
              return {
         | 
| @@ -40,7 +74,8 @@ export default function createServerActionQueue() { | |
| 40 74 | 
             
                  }
         | 
| 41 75 | 
             
                  return false
         | 
| 42 76 | 
             
                },
         | 
| 43 | 
            -
                itemProcessed | 
| 77 | 
            +
                itemProcessed,
         | 
| 78 | 
            +
                itemFailed,
         | 
| 44 79 | 
             
                fullySynced: () => queue.length === 0 && !inFlight,
         | 
| 45 80 | 
             
                getData: () => ({ queue, inFlight })
         | 
| 46 81 | 
             
              }
         | 
| @@ -1,10 +1,10 @@ | |
| 1 1 | 
             
            import actionCableAdapter from './transportAdapters/actionCableAdapter'
         | 
| 2 2 | 
             
            import pusherAdapter from './transportAdapters/pusherAdapter'
         | 
| 3 3 |  | 
| 4 | 
            -
            export default function createTransportAdapter(jasonConfig, handlePayload, dispatch, onConnect) {
         | 
| 4 | 
            +
            export default function createTransportAdapter(jasonConfig, handlePayload, dispatch, onConnect, transportOptions) {
         | 
| 5 5 | 
             
              const { transportService } = jasonConfig
         | 
| 6 6 | 
             
              if (transportService === 'action_cable') {
         | 
| 7 | 
            -
                return actionCableAdapter(jasonConfig, handlePayload, dispatch, onConnect)
         | 
| 7 | 
            +
                return actionCableAdapter(jasonConfig, handlePayload, dispatch, onConnect, transportOptions)
         | 
| 8 8 | 
             
              } else if (transportService === 'pusher') {
         | 
| 9 9 | 
             
                return pusherAdapter(jasonConfig, handlePayload, dispatch)
         | 
| 10 10 | 
             
              } else {
         | 
    
        data/client/src/index.ts
    CHANGED
    
    | @@ -1,8 +1,10 @@ | |
| 1 | 
            +
            import _JasonContext from './JasonContext'
         | 
| 1 2 | 
             
            import _JasonProvider from './JasonProvider'
         | 
| 2 3 | 
             
            import _useAct from './useAct'
         | 
| 3 4 | 
             
            import _useSub from './useSub'
         | 
| 4 5 | 
             
            import _useEager from './useEager'
         | 
| 5 6 |  | 
| 7 | 
            +
            export const JasonContext = _JasonContext
         | 
| 6 8 | 
             
            export const JasonProvider = _JasonProvider
         | 
| 7 9 | 
             
            export const useAct = _useAct
         | 
| 8 10 | 
             
            export const useSub = _useSub
         | 
| @@ -1,7 +1,14 @@ | |
| 1 1 | 
             
            import { createConsumer } from "@rails/actioncable"
         | 
| 2 | 
            +
            import restClient from '../restClient'
         | 
| 3 | 
            +
            import { v4 as uuidv4 } from 'uuid'
         | 
| 4 | 
            +
            import _ from 'lodash'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            export default function actionCableAdapter(jasonConfig, handlePayload, dispatch, onConnected, transportOptions) {
         | 
| 7 | 
            +
              const consumerId = uuidv4()
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              const { cableUrl } = transportOptions
         | 
| 10 | 
            +
              const consumer = cableUrl ? createConsumer(cableUrl) : createConsumer()
         | 
| 2 11 |  | 
| 3 | 
            -
            export default function actionCableAdapter(jasonConfig, handlePayload, dispatch, onConnected) {
         | 
| 4 | 
            -
              const consumer = createConsumer()
         | 
| 5 12 | 
             
              const subscription = (consumer.subscriptions.create({
         | 
| 6 13 | 
             
                channel: 'Jason::Channel'
         | 
| 7 14 | 
             
              }, {
         | 
| @@ -22,16 +29,30 @@ export default function actionCableAdapter(jasonConfig, handlePayload, dispatch, | |
| 22 29 | 
             
                }
         | 
| 23 30 | 
             
              }));
         | 
| 24 31 |  | 
| 25 | 
            -
              function getPayload(config, options) {
         | 
| 26 | 
            -
                subscription.send({ getPayload: config, ...options })
         | 
| 27 | 
            -
              }
         | 
| 28 | 
            -
             | 
| 29 32 | 
             
              function createSubscription(config) {
         | 
| 30 33 | 
             
                subscription.send({ createSubscription: config })
         | 
| 31 34 | 
             
              }
         | 
| 32 35 |  | 
| 33 36 | 
             
              function removeSubscription(config) {
         | 
| 34 | 
            -
                 | 
| 37 | 
            +
                restClient.post('/jason/api/remove_subscription', { config, consumerId })
         | 
| 38 | 
            +
                .catch(e => console.error(e))
         | 
| 39 | 
            +
              }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              function getPayload(config, options) {
         | 
| 42 | 
            +
                restClient.post('/jason/api/get_payload', {
         | 
| 43 | 
            +
                  config,
         | 
| 44 | 
            +
                  options
         | 
| 45 | 
            +
                })
         | 
| 46 | 
            +
                .then(({ data }) => {
         | 
| 47 | 
            +
                  _.map(data, (payload, modelName) => {
         | 
| 48 | 
            +
                    handlePayload(payload)
         | 
| 49 | 
            +
                  })
         | 
| 50 | 
            +
                })
         | 
| 51 | 
            +
                .catch(e => console.error(e))
         | 
| 52 | 
            +
              }
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              function fullChannelName(channelName) {
         | 
| 55 | 
            +
                return channelName
         | 
| 35 56 | 
             
              }
         | 
| 36 57 |  | 
| 37 58 | 
             
              return { getPayload, createSubscription, removeSubscription }
         | 
| @@ -1,11 +1,10 @@ | |
| 1 1 | 
             
            import Pusher from 'pusher-js'
         | 
| 2 | 
            -
            import { createConsumer } from "@rails/actioncable"
         | 
| 3 2 | 
             
            import restClient from '../restClient'
         | 
| 4 3 | 
             
            import { v4 as uuidv4 } from 'uuid'
         | 
| 5 4 | 
             
            import _ from 'lodash'
         | 
| 6 5 |  | 
| 7 6 | 
             
            export default function pusherAdapter(jasonConfig, handlePayload, dispatch) {
         | 
| 8 | 
            -
               | 
| 7 | 
            +
              const consumerId = uuidv4()
         | 
| 9 8 |  | 
| 10 9 | 
             
              const { pusherKey, pusherRegion, pusherChannelPrefix } = jasonConfig
         | 
| 11 10 | 
             
              const pusher = new Pusher(pusherKey, {
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            import _ from 'lodash'
         | 
| 2 | 
            +
            import { useSelector } from 'react-redux'
         | 
| 3 | 
            +
            import addRelations from './addRelations'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            /* Can be called as
         | 
| 6 | 
            +
            useDraft() => draft object for making updates
         | 
| 7 | 
            +
            useDraft('entity', id) => returns [draft, object]
         | 
| 8 | 
            +
            useDraft('entity', id, relations) => returns [draft, objectWithEmbeddedRelations]
         | 
| 9 | 
            +
            */
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            export default function useDraft(entity, id, relations = []) {
         | 
| 12 | 
            +
              // const entityDraft =`${entity}Draft`
         | 
| 13 | 
            +
              // const object = { ...s[entityDraft].entities[String(id)] }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              // return useSelector(s => addRelations(s, object, entity, relations, 'Draft'), _.isEqual)
         | 
| 16 | 
            +
            }
         | 
| 17 | 
            +
             | 
    
        data/client/src/useEager.ts
    CHANGED
    
    | @@ -1,9 +1,12 @@ | |
| 1 | 
            -
            import  | 
| 2 | 
            -
            import {  | 
| 1 | 
            +
            import _ from 'lodash'
         | 
| 2 | 
            +
            import { useSelector } from 'react-redux'
         | 
| 3 | 
            +
            import addRelations from './addRelations'
         | 
| 3 4 |  | 
| 4 | 
            -
            export default function useEager(entity, id =  | 
| 5 | 
            -
               | 
| 6 | 
            -
             | 
| 7 | 
            -
               | 
| 5 | 
            +
            export default function useEager(entity: string, id = '', relations = [] as any) {
         | 
| 6 | 
            +
              if (id) {
         | 
| 7 | 
            +
                return useSelector(s => addRelations(s, { ...s[entity].entities[String(id)] }, entity, relations), _.isEqual)
         | 
| 8 | 
            +
              } else {
         | 
| 9 | 
            +
                return useSelector(s => addRelations(s, _.values(s[entity].entities), entity, relations), _.isEqual)
         | 
| 10 | 
            +
              }
         | 
| 8 11 | 
             
            }
         | 
| 9 12 |  | 
    
        data/client/src/useJason.ts
    CHANGED
    
    | @@ -9,13 +9,12 @@ import createTransportAdapater from './createTransportAdapter' | |
| 9 9 |  | 
| 10 10 | 
             
            import { createEntityAdapter, createSlice, createReducer, configureStore } from '@reduxjs/toolkit'
         | 
| 11 11 |  | 
| 12 | 
            -
            import makeEager from './makeEager'
         | 
| 13 12 | 
             
            import { camelizeKeys } from 'humps'
         | 
| 14 13 | 
             
            import md5 from 'blueimp-md5'
         | 
| 15 14 | 
             
            import _ from 'lodash'
         | 
| 16 15 | 
             
            import React, { useState, useEffect } from 'react'
         | 
| 17 16 |  | 
| 18 | 
            -
            export default function useJason({ reducers, middleware = [], extraActions }: { reducers?: any, middleware?: any[], extraActions?: any }) {
         | 
| 17 | 
            +
            export default function useJason({ reducers, middleware = [], enhancers = [], transportOptions = {}, extraActions }: { reducers?: any, middleware?: any[], enhancers?: any[], extraActions?: any, transportOptions?: any }) {
         | 
| 19 18 | 
             
              const [store, setStore] = useState(null as any)
         | 
| 20 19 | 
             
              const [value, setValue] = useState(null as any)
         | 
| 21 20 |  | 
| @@ -36,12 +35,11 @@ export default function useJason({ reducers, middleware = [], extraActions }: { | |
| 36 35 |  | 
| 37 36 | 
             
                  console.debug({ allReducers })
         | 
| 38 37 |  | 
| 39 | 
            -
                  const store = configureStore({ reducer: allReducers, middleware: [...middleware, pruneIdsMiddleware(schema)] })
         | 
| 38 | 
            +
                  const store = configureStore({ reducer: allReducers, middleware: [...middleware, pruneIdsMiddleware(schema)], enhancers })
         | 
| 40 39 | 
             
                  const dispatch = store.dispatch
         | 
| 41 40 |  | 
| 42 41 | 
             
                  const optDis = createOptDis(schema, dispatch, restClient, serverActionQueue)
         | 
| 43 42 | 
             
                  const actions = createActions(schema, store, restClient, optDis, extraActions)
         | 
| 44 | 
            -
                  const eager = makeEager(schema)
         | 
| 45 43 |  | 
| 46 44 | 
             
                  let payloadHandlers = {}
         | 
| 47 45 | 
             
                  let configs = {}
         | 
| @@ -50,7 +48,7 @@ export default function useJason({ reducers, middleware = [], extraActions }: { | |
| 50 48 | 
             
                  function handlePayload(payload) {
         | 
| 51 49 | 
             
                    const { md5Hash } = payload
         | 
| 52 50 |  | 
| 53 | 
            -
                    const { handlePayload } = payloadHandlers[md5Hash]
         | 
| 51 | 
            +
                    const { handlePayload } = payloadHandlers[md5Hash] || {}
         | 
| 54 52 | 
             
                    if (handlePayload) {
         | 
| 55 53 | 
             
                      handlePayload(payload)
         | 
| 56 54 | 
             
                    } else {
         | 
| @@ -58,7 +56,13 @@ export default function useJason({ reducers, middleware = [], extraActions }: { | |
| 58 56 | 
             
                    }
         | 
| 59 57 | 
             
                  }
         | 
| 60 58 |  | 
| 61 | 
            -
                  const transportAdapter = createTransportAdapater( | 
| 59 | 
            +
                  const transportAdapter = createTransportAdapater(
         | 
| 60 | 
            +
                    jasonConfig,
         | 
| 61 | 
            +
                    handlePayload,
         | 
| 62 | 
            +
                    dispatch,
         | 
| 63 | 
            +
                    () => _.keys(configs).forEach(md5Hash => createSubscription(configs[md5Hash], subOptions[md5Hash])),
         | 
| 64 | 
            +
                    transportOptions
         | 
| 65 | 
            +
                  )
         | 
| 62 66 |  | 
| 63 67 | 
             
                  function createSubscription(config, options = {}) {
         | 
| 64 68 | 
             
                    // We need the hash to be consistent in Ruby / Javascript
         | 
| @@ -99,7 +103,6 @@ export default function useJason({ reducers, middleware = [], extraActions }: { | |
| 99 103 | 
             
                  setValue({
         | 
| 100 104 | 
             
                    actions: actions,
         | 
| 101 105 | 
             
                    subscribe: createSubscription,
         | 
| 102 | 
            -
                    eager,
         | 
| 103 106 | 
             
                    handlePayload
         | 
| 104 107 | 
             
                  })
         | 
| 105 108 | 
             
                  setStore(store)
         | 
    
        data/lib/jason.rb
    CHANGED
    
    | @@ -12,6 +12,8 @@ require 'jason/engine' | |
| 12 12 | 
             
            require 'jason/lua_generator'
         | 
| 13 13 | 
             
            require 'jason/includes_helper'
         | 
| 14 14 | 
             
            require 'jason/graph_helper'
         | 
| 15 | 
            +
            require 'jason/conditions_matcher'
         | 
| 16 | 
            +
            require 'jason/consistency_checker'
         | 
| 15 17 |  | 
| 16 18 | 
             
            module Jason
         | 
| 17 19 | 
             
              class Error < StandardError; end
         | 
| @@ -23,7 +25,8 @@ module Jason | |
| 23 25 | 
             
              self.mattr_accessor :pusher_key
         | 
| 24 26 | 
             
              self.mattr_accessor :pusher_region
         | 
| 25 27 | 
             
              self.mattr_accessor :pusher_channel_prefix
         | 
| 26 | 
            -
              self.mattr_accessor : | 
| 28 | 
            +
              self.mattr_accessor :subscription_authorization_service
         | 
| 29 | 
            +
              self.mattr_accessor :update_authorization_service
         | 
| 27 30 | 
             
              self.mattr_accessor :sidekiq_queue
         | 
| 28 31 |  | 
| 29 32 | 
             
              self.schema = {}
         | 
| @@ -48,7 +51,11 @@ module Jason | |
| 48 51 | 
             
                    puts "Old config was #{previous_schema[model]}"
         | 
| 49 52 | 
             
                    puts "New config is #{config}"
         | 
| 50 53 | 
             
                    puts "Rebuilding cache for #{model}"
         | 
| 51 | 
            -
             | 
| 54 | 
            +
             | 
| 55 | 
            +
                    # This is necessary to ensure all Rails methods have been added to model before we attempt to cache.
         | 
| 56 | 
            +
                    Rails.configuration.after_initialize do
         | 
| 57 | 
            +
                       model.classify.constantize.cache_all
         | 
| 58 | 
            +
                    end
         | 
| 52 59 | 
             
                    puts "Done"
         | 
| 53 60 | 
             
                  end
         | 
| 54 61 | 
             
                end
         | 
    
        data/lib/jason/api_model.rb
    CHANGED
    
    | @@ -36,11 +36,7 @@ class Jason::ApiModel | |
| 36 36 | 
             
              end
         | 
| 37 37 |  | 
| 38 38 | 
             
              def permit(params)
         | 
| 39 | 
            -
                pp self
         | 
| 40 | 
            -
                pp params
         | 
| 41 39 | 
             
                params = params.require(:payload).permit(allowed_params).tap do |allowed|
         | 
| 42 | 
            -
                  pp "ALLOWED"
         | 
| 43 | 
            -
                  pp allowed
         | 
| 44 40 | 
             
                  allowed_object_params.each do |key|
         | 
| 45 41 | 
             
                    allowed[key] = params[:payload][key].to_unsafe_h if params[:payload][key]
         | 
| 46 42 | 
             
                  end
         | 
    
        data/lib/jason/channel.rb
    CHANGED
    
    | @@ -12,7 +12,6 @@ class Jason::Channel < ActionCable::Channel::Base | |
| 12 12 | 
             
              private
         | 
| 13 13 |  | 
| 14 14 | 
             
              def handle_message(message)
         | 
| 15 | 
            -
                pp message['createSubscription']
         | 
| 16 15 | 
             
                @subscriptions ||= []
         | 
| 17 16 |  | 
| 18 17 | 
             
                begin # ActionCable swallows errors in this message - ensure they're output to logs.
         | 
| @@ -38,18 +37,12 @@ class Jason::Channel < ActionCable::Channel::Base | |
| 38 37 |  | 
| 39 38 | 
             
                subscriptions.push(subscription)
         | 
| 40 39 | 
             
                subscription.add_consumer(identifier)
         | 
| 41 | 
            -
                subscription.get.each do |payload|
         | 
| 42 | 
            -
                  pp payload
         | 
| 43 | 
            -
                  transmit(payload) if payload.present?
         | 
| 44 | 
            -
                end
         | 
| 45 40 | 
             
              end
         | 
| 46 41 |  | 
| 47 42 | 
             
              def remove_subscription(config)
         | 
| 48 43 | 
             
                subscription = Jason::Subscription.upsert_by_config(config['model'], conditions: config['conditions'], includes: config['includes'])
         | 
| 49 44 | 
             
                subscriptions.reject! { |s| s.id == subscription.id }
         | 
| 50 45 | 
             
                subscription.remove_consumer(identifier)
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                # TODO Stop streams
         | 
| 53 46 | 
             
              end
         | 
| 54 47 |  | 
| 55 48 | 
             
              def get_payload(config, force_refresh = false)
         | 
| @@ -0,0 +1,88 @@ | |
| 1 | 
            +
            class Jason::ConditionsMatcher
         | 
| 2 | 
            +
              attr_reader :klass
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              def initialize(klass)
         | 
| 5 | 
            +
                @klass = klass
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              # key, rules = 'post_id', 123
         | 
| 9 | 
            +
              # key, rules = 'post_id', { 'value': [123,C456], 'type': 'between' }
         | 
| 10 | 
            +
              # key, rules = 'post_id', { 'value': [123,456], 'type': 'between', 'not': true }
         | 
| 11 | 
            +
              # key, rules = 'post_id', { 'value': 123, 'type': 'equals', 'not': true }
         | 
| 12 | 
            +
              def test_match(key, rules, previous_changes)
         | 
| 13 | 
            +
                return nil if !previous_changes.keys.include?(key)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                if rules.is_a?(Hash)
         | 
| 16 | 
            +
                  matches = false
         | 
| 17 | 
            +
                  value = convert_to_datatype(key, rules['value'])
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  if rules['type'] == 'equals'
         | 
| 20 | 
            +
                    matches = previous_changes[key][1] == value
         | 
| 21 | 
            +
                  elsif rules['type'] == 'between'
         | 
| 22 | 
            +
                    matches = (value[0]..value[1]).cover?(previous_changes[key][1])
         | 
| 23 | 
            +
                  else
         | 
| 24 | 
            +
                    raise "Unrecognized rule type #{rules['type']}"
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  if rules['not']
         | 
| 28 | 
            +
                    return !matches
         | 
| 29 | 
            +
                  else
         | 
| 30 | 
            +
                    return matches
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                elsif rules.is_a?(Array)
         | 
| 34 | 
            +
                  value = convert_to_datatype(key, rules)
         | 
| 35 | 
            +
                  return previous_changes[key][1].includes?(value)
         | 
| 36 | 
            +
                else
         | 
| 37 | 
            +
                  value = convert_to_datatype(key, rules)
         | 
| 38 | 
            +
                  return previous_changes[key][1] == value
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              # conditions = { 'post_id' => 123, 'created_at' => { 'type' => 'between', 'value' => ['2020-01-01', '2020-01-02'] } }
         | 
| 43 | 
            +
              def apply_conditions(relation, conditions)
         | 
| 44 | 
            +
                conditions.each do |key, rules|
         | 
| 45 | 
            +
                  relation = apply_condition(relation, key, rules)
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                relation
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              private
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              def apply_condition(relation, key, rules)
         | 
| 54 | 
            +
                if rules.is_a?(Hash)
         | 
| 55 | 
            +
                  value = convert_to_datatype(key, rules['value'])
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  if rules['type'] == 'equals'
         | 
| 58 | 
            +
                    arg = { key => value }
         | 
| 59 | 
            +
                  elsif rules['type'] == 'between'
         | 
| 60 | 
            +
                    arg = { key => value[0]..value[1] }
         | 
| 61 | 
            +
                  else
         | 
| 62 | 
            +
                    raise "Unrecognized rule type #{rules['type']}"
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  if rules['not']
         | 
| 66 | 
            +
                    return relation.where.not(arg)
         | 
| 67 | 
            +
                  else
         | 
| 68 | 
            +
                    return relation.where(arg)
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                else
         | 
| 71 | 
            +
                  value = convert_to_datatype(key, rules)
         | 
| 72 | 
            +
                  return relation.where({ key => value })
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              def convert_to_datatype(key, value)
         | 
| 77 | 
            +
                datatype = klass.type_for_attribute(key).type
         | 
| 78 | 
            +
                if datatype == :datetime || datatype == :date
         | 
| 79 | 
            +
                  if value.is_a?(Array)
         | 
| 80 | 
            +
                    value.map { |v| v&.to_datetime }
         | 
| 81 | 
            +
                  else
         | 
| 82 | 
            +
                    value&.to_datetime
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                else
         | 
| 85 | 
            +
                  value
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
            end
         | 
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            class Jason::ConsistencyChecker
         | 
| 2 | 
            +
              attr_reader :subscription
         | 
| 3 | 
            +
              attr_reader :inconsistent
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              def self.check_all(fix: false)
         | 
| 6 | 
            +
                Jason::Subscription.all.each do |sub|
         | 
| 7 | 
            +
                  next if sub.consumer_count == 0
         | 
| 8 | 
            +
                  checker = Jason::ConsistencyChecker.new(sub)
         | 
| 9 | 
            +
                  result = checker.check
         | 
| 10 | 
            +
                  if checker.inconsistent?
         | 
| 11 | 
            +
                    pp sub.config
         | 
| 12 | 
            +
                    pp result
         | 
| 13 | 
            +
                    if fix
         | 
| 14 | 
            +
                      sub.reset!(hard: true)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              def self.fix_all
         | 
| 21 | 
            +
                check_all(fix: true)
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def wipe_all_subs
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              def initialize(subscription)
         | 
| 29 | 
            +
                @subscription = subscription
         | 
| 30 | 
            +
                @inconsistent = false
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def inconsistent?
         | 
| 34 | 
            +
                inconsistent
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              # Take a subscription, get the current cached payload, and compare it to the data retrieved from the database
         | 
| 38 | 
            +
              def check
         | 
| 39 | 
            +
                cached_payload = subscription.get
         | 
| 40 | 
            +
                edge_set = subscription.load_ids_for_sub_models(subscription.model, nil)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                result = cached_payload.map do |model_name, data|
         | 
| 43 | 
            +
                  cached_payload_instance_ids = data[:payload].map { |row| row['id'] }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  model_idx = edge_set[:model_names].index(model_name)
         | 
| 46 | 
            +
                  if model_idx.present?
         | 
| 47 | 
            +
                    edge_set_instance_ids = edge_set[:instance_ids].map { |row| row[model_idx] }
         | 
| 48 | 
            +
                  else
         | 
| 49 | 
            +
                    next
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  missing = edge_set_instance_ids - cached_payload_instance_ids
         | 
| 53 | 
            +
                  intruding = cached_payload_instance_ids - edge_set_instance_ids
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  if missing.present? || intruding.present?
         | 
| 56 | 
            +
                    @inconsistent = true
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  [model_name, {
         | 
| 60 | 
            +
                    'missing' => missing,
         | 
| 61 | 
            +
                    'intruding' => intruding
         | 
| 62 | 
            +
                  }]
         | 
| 63 | 
            +
                end.compact.to_h
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         |