fix(json): serialize blob data

Summary:
A fix to reserialize JSON blob data properly for complex object types.
We should investigate overriding all of JSON.parse and JSON.stringify.
This coming in a future diff.

Also we were using old Electron APIs that were throwing backend errors

Test Plan: todo

Reviewers: juan, bengotow

Reviewed By: bengotow

Differential Revision: https://phab.nylas.com/D2326
This commit is contained in:
Evan Morikawa 2015-12-08 16:11:22 -05:00
parent 17e9cc8921
commit e275f353c1
12 changed files with 92 additions and 47 deletions

View file

@ -83,7 +83,7 @@ class N1SpecReporter extends View
@on 'click', '.stack-trace', ->
$(this).toggleClass('expanded')
@reloadButton.on 'click', -> require('electron').ipcRenderer.send('call-window-method', 'restart')
@reloadButton.on 'click', -> require('electron').ipcRenderer.send('call-webcontents-method', 'reload')
reportRunnerResults: (runner) ->
@updateSpecCounts()

View file

@ -364,6 +364,11 @@ class Application
ipcMain.on 'call-webcontents-method', (event, method, args...) ->
event.sender[method](args...)
ipcMain.on 'call-devtools-webcontents-method', (event, method, args...) ->
# If devtools aren't open the `webContents::devToolsWebContents`
# will be null
event.sender.devToolsWebContents?[method](args...)
ipcMain.on 'action-bridge-rebroadcast-to-all', (event, args...) =>
win = BrowserWindow.fromWebContents(event.sender)
@windowManager.windows().forEach (nylasWindow) ->

View file

@ -9,6 +9,7 @@ AttributeDateTime = require './attributes/attribute-datetime'
AttributeCollection = require './attributes/attribute-collection'
AttributeJoinedData = require './attributes/attribute-joined-data'
AttributeServerId = require './attributes/attribute-serverid'
AttributeSerializedObjects = require './attributes/attribute-serialized-objects'
module.exports =
Matcher: Matcher
@ -22,6 +23,7 @@ module.exports =
Collection: -> new AttributeCollection(arguments...)
JoinedData: -> new AttributeJoinedData(arguments...)
ServerId: -> new AttributeServerId(arguments...)
SerializedObjects: -> new AttributeSerializedObjects(arguments...)
AttributeNumber: AttributeNumber
AttributeString: AttributeString
@ -31,3 +33,4 @@ module.exports =
AttributeCollection: AttributeCollection
AttributeJoinedData: AttributeJoinedData
AttributeServerId: AttributeServerId
AttributeSerializedObjects: AttributeSerializedObjects

View file

@ -3,6 +3,10 @@ Matcher = require './matcher'
###
Public: An object that can be cast to `itemClass`
If you don't know the `itemClass` ahead of time and are storing complex,
typed, nested objects, use `AttributeSerializedObject` instead.
Section: Database
###
class AttributeObject extends Attribute
@ -21,8 +25,9 @@ class AttributeObject extends Attribute
if @itemClass
obj = new @itemClass(val)
# Important: if no ids are in the JSON, don't make them up randomly.
# This causes an object to be "different" each time it's de-serialized
# even if it's actually the same, makes React components re-render!
# This causes an object to be "different" each time it's
# de-serialized even if it's actually the same, makes React
# components re-render!
obj.clientId = undefined
# Warning: typeof(null) is object
if obj.fromJSON and val and typeof(val) is 'object'

View file

@ -0,0 +1,21 @@
Utils = require '../models/utils'
Attribute = require './attribute'
###
Public: An object that is a composite of several types of objects. We
inflate and deflate them using `Utils.deserializeRegisteredObjects` and
`Utils.serializeRegisteredObjects`.
If you're storing an object of a single type, use `AttributeObject` with
the `itemClass` option
Section: Database
###
class AttributeSerializedObjects extends Attribute
toJSON: (val) ->
return Utils.serializeRegisteredObjects(val)
fromJSON: (val) ->
return Utils.deserializeRegisteredObjects(val)
module.exports = AttributeSerializedObjects

View file

@ -0,0 +1,8 @@
Utils = require './utils'
ModelQuery = require './query'
class JSONBlobQuery extends ModelQuery
formatResultObjects: (objects) =>
return objects[0]?.json || null
module.exports = JSONBlobQuery

View file

@ -17,7 +17,7 @@ class JSONBlob extends Model
modelKey: 'serverId'
jsonKey: 'server_id'
'json': Attributes.Object
'json': Attributes.SerializedObjects
modelKey: 'json'
jsonKey: 'json'

View file

@ -176,18 +176,20 @@ class ModelQuery
return result[0]['count'] / 1
else
try
objects = result.map (row) =>
json = JSON.parse(row['data'])
object = (new @_klass).fromJSON(json)
for attr in @_includeJoinedData
value = row[attr.jsonKey]
value = null if value is AttributeJoinedData.NullPlaceholder
object[attr.modelKey] = value
object
objects = result.map(@inflateRawRow)
catch jsonError
throw new Error("Query could not parse the database result. Query: #{@sql()}, Error: #{jsonError.toString()}")
return objects
inflateRawRow: (row) =>
json = JSON.parse(row['data'])
object = (new @_klass).fromJSON(json)
for attr in @_includeJoinedData
value = row[attr.jsonKey]
value = null if value is AttributeJoinedData.NullPlaceholder
object[attr.modelKey] = value
object
formatResultObjects: (objects) ->
return objects[0] if @_returnOne
return objects

View file

@ -10,35 +10,39 @@ DefaultResourcePath = null
module.exports =
Utils =
# To deserialize a string serialized with the `Utils.serializeRegisteredObjects`
# method. This will convert anything that has a known class to its
# appropriate object type.
JSONReviver: (k,v) ->
TaskRegistry ?= require '../../task-registry'
DatabaseObjectRegistry ?= require '../../database-object-registry'
type = v?.__constructorName
return v unless type
if DatabaseObjectRegistry.isInRegistry(type)
return DatabaseObjectRegistry.deserialize(type, v)
if TaskRegistry.isInRegistry(type)
return TaskRegistry.deserialize(type, v)
return v
JSONReplacer: (k,v) ->
TaskRegistry ?= require '../../task-registry'
DatabaseObjectRegistry ?= require '../../database-object-registry'
if _.isObject(v)
type = this[k].constructor.name
if DatabaseObjectRegistry.isInRegistry(type) or TaskRegistry.isInRegistry(type)
v.__constructorName = type
return v
# To deserialize a string serialized with the
# `Utils.serializeRegisteredObjects` method. This will convert anything
# that has a known class to its appropriate object type.
## TODO: Globally override JSON.parse???
deserializeRegisteredObjects: (json) ->
TaskRegistry ?= require '../../task-registry'
DatabaseObjectRegistry ?= require '../../database-object-registry'
JSON.parse json, (k,v) ->
type = v?.__constructorName
return v unless type
if DatabaseObjectRegistry.isInRegistry(type)
return DatabaseObjectRegistry.deserialize(type, v)
if TaskRegistry.isInRegistry(type)
return TaskRegistry.deserialize(type, v)
return v
return JSON.parse(json, Utils.JSONReviver)
## TODO: Globally override JSON.stringify???
serializeRegisteredObjects: (object) ->
TaskRegistry ?= require '../../task-registry'
DatabaseObjectRegistry ?= require '../../database-object-registry'
JSON.stringify object, (k, v) ->
if _.isObject(v)
type = this[k].constructor.name
if DatabaseObjectRegistry.isInRegistry(type) or TaskRegistry.isInRegistry(type)
v.__constructorName = type
return v
return JSON.stringify(object, Utils.JSONReplacer)
timeZone: tz

View file

@ -6,6 +6,7 @@ Model = require '../models/model'
Utils = require '../models/utils'
Actions = require '../actions'
ModelQuery = require '../models/query'
JSONBlobQuery = require '../models/json-blob-query'
NylasStore = require '../../global/nylas-store'
PromiseQueue = require 'promise-queue'
DatabaseSetupQueryBuilder = require './database-setup-query-builder'
@ -34,10 +35,6 @@ DEBUG_MISSING_ACCOUNT_ID = false
BEGIN_TRANSACTION = 'BEGIN TRANSACTION'
COMMIT = 'COMMIT'
class JSONBlobQuery extends ModelQuery
formatResultObjects: (objects) =>
return objects[0]?.json || null
###
Public: N1 is built on top of a custom database layer modeled after
ActiveRecord. For many parts of the application, the database is the source

View file

@ -521,7 +521,7 @@ class NylasEnvConstructor extends Model
# Extended: Reload the current window.
reload: ->
ipcRenderer.send('call-window-method', 'restart')
ipcRenderer.send('call-webcontents-method', 'reload')
# Updates the window load settings - called when the app is ready to display
# a hot-loaded window. Causes listeners registered with `onWindowPropsReceived`
@ -789,15 +789,15 @@ class NylasEnvConstructor extends Model
# Extended: Open the dev tools for the current window.
openDevTools: ->
ipcRenderer.send('call-window-method', 'openDevTools')
ipcRenderer.send('call-webcontents-method', 'openDevTools')
# Extended: Toggle the visibility of the dev tools for the current window.
toggleDevTools: ->
ipcRenderer.send('call-window-method', 'toggleDevTools')
ipcRenderer.send('call-webcontents-method', 'toggleDevTools')
# Extended: Execute code in dev tools.
executeJavaScriptInDevTools: (code) ->
ipcRenderer.send('call-webcontents-method', 'executeJavaScriptInDevTools', code)
ipcRenderer.send('call-devtools-webcontents-method', 'executeJavaScript', code)
###
Section: Private

View file

@ -34,4 +34,4 @@ prefs.activate()
ipc.on 'command', (command, args) ->
if command is 'window:toggle-dev-tools'
ipc.send('call-window-method', 'toggleDevTools')
ipc.send('call-webcontents-method', 'toggleDevTools')