Skip to content

Commit

Permalink
Make hasMany associations on new records properly reflect their child…
Browse files Browse the repository at this point in the history
…ren after save.
  • Loading branch information
airhorns committed Dec 21, 2011
1 parent 1533034 commit 1693100
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 36 deletions.
42 changes: 24 additions & 18 deletions lib/batman.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 23 additions & 13 deletions src/batman.coffee
Expand Up @@ -2640,13 +2640,15 @@ class Batman.HasOneProxy extends Batman.AssociationProxy
@set('loaded', true)
callback undefined, loadedRecords[0]

class Batman.AssociationSet extends Batman.Set
constructor: (@key, @association) -> super()
class Batman.AssociationSet extends Batman.SetSort
constructor: (@foreignKeyValue, @association) ->
base = new Batman.Set
super(base, 'hashKey')
loaded: false
load: (callback) ->
return callback(undefined, @) unless @key?
return callback(undefined, @) unless @foreignKeyValue?
loadOptions = {}
loadOptions[@association.foreignKey] = @key
loadOptions[@association.foreignKey] = @foreignKeyValue
@association.getRelatedModel().load loadOptions, (err, records) =>
@loaded = true unless err
callback(err, @)
Expand Down Expand Up @@ -2860,7 +2862,7 @@ class Batman.HasOneAssociation extends Batman.SingularAssociation
@foreignKey = @options.foreignKey or "#{helpers.underscore($functionName(@model))}_id"

apply: (baseSaveError, base) ->
if relation = base.constructor.defaultAccessor.get.call(base, @label)
if relation = @getFromAttributes(base)
relation.set @foreignKey, base.get(@localKey)

encoder: ->
Expand Down Expand Up @@ -2889,9 +2891,11 @@ class Batman.HasManyAssociation extends Batman.PluralAssociation
@foreignKey = @options.foreignKey or "#{helpers.underscore($functionName(@model))}_id"

apply: (baseSaveError, base) ->
if relations = base.constructor.defaultAccessor.get.call(base, @label)
relations.forEach (model) =>
model.set @foreignKey, base.get(@localKey)
unless baseSaveError
if relations = @getFromAttributes(base)
relations.forEach (model) =>
model.set @foreignKey, base.get(@localKey)
base.set @label, @setForRecord(base)

encoder: ->
association = @
Expand All @@ -2912,20 +2916,26 @@ class Batman.HasManyAssociation extends Batman.PluralAssociation
jsonArray

decode: (data, key, _, __, parentRecord) ->
relations = association.setForRecord(parentRecord)
if relatedModel = association.getRelatedModel()
for jsonObject in data
record = new relatedModel
existingRelations = association.getFromAttributes(parentRecord) || association.setForRecord(parentRecord)
existingArray = existingRelations?.toArray()
for jsonObject, i in data
record = if existingArray && existingArray[i]
existing = true
existingArray[i]
else
existing = false
new relatedModel()
record.fromJSON jsonObject

if association.options.inverseOf
record.set association.options.inverseOf, parentRecord

record = relatedModel._mapIdentity(record)
relations.add record
existingRelations.add record
else
developer.error "Can't decode model #{association.options.name} because it hasn't been loaded yet!"
relations
existingRelations
}

# ### Model Associations API
Expand Down
54 changes: 49 additions & 5 deletions tests/batman/model/associations/has_many_test.coffee
Expand Up @@ -44,7 +44,7 @@ QUnit.module "Batman.Model hasMany Associations"
}]

namespace.ProductVariant = class @ProductVariant extends Batman.Model
@encode 'price'
@encode 'id', 'price'
@belongsTo 'product', namespace: namespace

@variantsAdapter = createStorageAdapter @ProductVariant, AsyncTestStorageAdapter,
Expand Down Expand Up @@ -161,7 +161,7 @@ asyncTest "hasMany association can be loaded from JSON data", 14, ->
@Product.find 3, (err, product) =>
throw err if err
variants = product.get('productVariants')
ok variants instanceof Batman.Set
ok variants instanceof Batman.AssociationSet
equal variants.length, 2

variant5 = variants.toArray()[0]
Expand Down Expand Up @@ -225,6 +225,23 @@ asyncTest "unsaved hasMany models should save their associated children", 4, ->
variants = product.get('productVariants')
variant = new @ProductVariant(price: 100)
variants.add variant

# Mock out what a realbackend would do: assign ids to the child records
# The TestStorageAdapter is smart enough to do this for the parent, but not the children.
@productAdapter.create = (record, options, callback) ->
id = record.set('id', @counter++)
if id
@storage[@storageKey(record) + id] = record.toJSON()
record.fromJSON
id: id
productVariants: [{
price: 100
id: 11
}]
callback(undefined, record)
else
callback(new Error("Couldn't get record primary key."))

product.save (err, product) =>
throw err if err
storedJSON = @productAdapter.storage["products#{product.get('id')}"]
Expand All @@ -236,12 +253,39 @@ asyncTest "unsaved hasMany models should save their associated children", 4, ->
]

ok !product.isNew()
ok !variant.isNew()
equal variant.get('product_id'), product.get('id')
QUnit.start()

asyncTest "unsaved hasMany models should reflect their associated children after save", 3, ->
product = new @Product(name: "Hello!")
variants = product.get('productVariants')
variant = new @ProductVariant(price: 100)
variants.add variant

# Mock out what a realbackend would do: assign ids to the child records
# The TestStorageAdapter is smart enough to do this for the parent, but not the children.
@productAdapter.create = (record, options, callback) ->
id = record.set('id', @counter++)
if id
@storage[@storageKey(record) + id] = record.toJSON()
record.fromJSON
id: id
productVariants: [{
price: 100
id: 11
}]
callback(undefined, record)
else
callback(new Error("Couldn't get record primary key."))

product.save (err, product) =>
throw err if err
# Mock out what a realbackend would do: assign ids to the child records
# The TestStorageAdapter is smart enough to do this for the parent, but not the children.
variant.fromJSON {price:100, product_id: product.get('id'), id: 11}
equal variant.get('product_id'), product.get('id')
ok !variant.isNew()
equal product.get('productVariants.length'), 1
ok product.get('productVariants').has(variant)
equal variants.get('length'), 1
QUnit.start()

asyncTest "hasMany associations render", 4, ->
Expand Down

0 comments on commit 1693100

Please sign in to comment.