Skip to content

Commit

Permalink
Merge pull request #134 from digitalsadhu/has_many_through_upsert
Browse files Browse the repository at this point in the history
hasManyThrough upsert
  • Loading branch information
digitalsadhu authored Jun 11, 2017
2 parents 78c91a8 + fb53937 commit 12f513c
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 7 deletions.
12 changes: 6 additions & 6 deletions lib/deserializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ function defaultAfterDeserialize (options, cb) {
module.exports = function deserializer (options, cb) {
var model = options.model

var beforeDeserialize = (typeof model.beforeJsonApiDeserialize === 'function') ?
model.beforeJsonApiDeserialize : defaultBeforeDeserialize
var beforeDeserialize = (typeof model.beforeJsonApiDeserialize === 'function')
? model.beforeJsonApiDeserialize : defaultBeforeDeserialize

var deserialize = (typeof model.jsonApiDeserialize === 'function') ?
model.jsonApiDeserialize : defaultDeserialize
var deserialize = (typeof model.jsonApiDeserialize === 'function')
? model.jsonApiDeserialize : defaultDeserialize

var afterDeserialize = (typeof model.afterJsonApiDeserialize === 'function') ?
model.afterJsonApiDeserialize : defaultAfterDeserialize
var afterDeserialize = (typeof model.afterJsonApiDeserialize === 'function')
? model.afterJsonApiDeserialize : defaultAfterDeserialize

var deserializeOptions = _.cloneDeep(options)

Expand Down
49 changes: 49 additions & 0 deletions lib/relationships.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

var _ = require('lodash')
var utils = require('./utils')
var debug = require('debug')('loopback-component-jsonapi')

module.exports = function (app, options) {
// get remote methods.
Expand Down Expand Up @@ -100,6 +101,40 @@ function relationships (id, data, model) {

updateRelation(modelTo, idToFind, where)
})

if (serverRelation.modelThrough) {
var modelThrough = serverRelation.modelThrough
var key = keyByModel(modelThrough, modelTo)
var data = {}
data[fkName] = id
var stringIds = false

var payloadIds = _.map(relationship.data, function (item) {
if (typeof item.id === 'string') {
stringIds = true
}
return item.id
})

modelThrough.find({where: data, fields: key}, function(err, instances) {
if (err) return
var serverIds = _.map(instances, function(instance) {
return stringIds ? instance[key].toString() : instance[key]
})
// to delete
var toDelete = _.difference(serverIds, payloadIds)
_.each(toDelete, function (id) {
data[key] = id
modelThrough.destroyAll(data)
})
// new
var newAssocs = _.difference(payloadIds, serverIds)
_.each(newAssocs, function (id) {
data[key] = id
modelThrough.create(data)
})
})
}
} else {
if (relationship.data === null) {
where[fkName] = null
Expand Down Expand Up @@ -132,3 +167,17 @@ function updateRelation (model, id, data) {
}
})
}

function keyByModel (assocModel, model) {
var key = null
_.each(assocModel.relations, function (relation) {
if (relation.modelTo.modelName === model.modelName) {
key = relation.keyFrom
}
})

if (key === null) {
debug('Can not find relation for ' + model.modelName)
}
return key
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"test": "npm run lint && istanbul cover _mocha --report lcovonly --reporter=spec ./test/**/*.test.js && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage",
"tester": "mocha --reporter=spec ./test/**/*.test.js",
"coverage": "istanbul cover _mocha ./test/**/*.test.js",
"lint": "standard .",
"lint": "standard ./test/**/*.js ./lib/**/*.js --verbose | snazzy",
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
},
"repository": {
Expand All @@ -32,6 +32,7 @@
"http-status-codes": "^1.0.5",
"inflection": "^1.7.2",
"lodash": "^4.17.1",
"snazzy": "^7.0.0",
"type-is": "^1.6.14"
},
"devDependencies": {
Expand Down
205 changes: 205 additions & 0 deletions test/hasManyThroughUpsert.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/* global describe, beforeEach, it */

var request = require('supertest')
var loopback = require('loopback')
var expect = require('chai').expect
var JSONAPIComponent = require('../')
var RSVP = require('rsvp')

var app
var Movie, Category, MovieCategoryAssoc

describe('hasManyThrough upsert', function () {
beforeEach(function (done) {
app = loopback()
app.set('legacyExplorer', false)
var ds = loopback.createDataSource('memory')
// create models
Movie = ds.createModel('movie', {
id: { type: Number, id: true },
name: String
})

Category = ds.createModel('category', {
id: { type: Number, id: true },
name: String
})

MovieCategoryAssoc = ds.createModel('movieCategoryAssoc', {
id: { type: Number, id: true }
})

// add models
app.model(Movie)
app.model(Category)
app.model(MovieCategoryAssoc)

// set up relationships
Movie.hasMany(Category, { through: MovieCategoryAssoc })
Category.hasMany(Movie, { through: MovieCategoryAssoc })

MovieCategoryAssoc.belongsTo(Movie)
MovieCategoryAssoc.belongsTo(Category)
makeData()
.then(function () {
done()
})
.catch(function (err) {
done(err)
})
app.use(loopback.rest())
JSONAPIComponent(app, { restApiRoot: '' })
})

it('should make initial data', function (done) {
request(app).get('/movies/1/categories').end(function (err, res) {
expect(err).to.equal(null)
expect(res.body.data.length).to.equal(2)
expect(res.body.data[0].attributes.name).to.equal('Crime')
done(err)
})
})

it('should handle POST', function (done) {
var agent = request(app)
agent
.post('/movies')
.send({
data: {
type: 'movies',
attributes: {
name: 'Ace Ventura: Pet Detective'
},
relationships: {
categories: {
data: [{ type: 'categories', id: 4 }]
}
}
}
})
.end(function () {
agent.get('/movieCategoryAssocs').end(function (err, res) {
expect(err).to.equal(null)
expect(res.body.data.length).to.equal(3)
done()
})
})
})

it('should handle PATCH', function (done) {
var agent = request(app)
agent
.patch('/movies/1')
.send({
data: {
id: 1,
type: 'movies',
attributes: {
name: 'The Shawshank Redemption'
},
relationships: {
categories: {
data: [
{ type: 'categories', id: 1 },
{ type: 'categories', id: 2 },
{ type: 'categories', id: 3 }
]
}
}
}
})
.end(function () {
agent.get('/movieCategoryAssocs').end(function (err, res) {
expect(err).to.equal(null)
expect(res.body.data.length).to.equal(3)
done()
})
})
})

it('should handle string IDs', function (done) {
var agent = request(app)
agent
.patch('/movies/1')
.send({
data: {
id: '1',
type: 'movies',
attributes: {
name: 'The Shawshank Redemption'
},
relationships: {
categories: {
data: [{ type: 'categories', id: '1' }, { type: 'categories', id: '4' }]
}
}
}
})
.end(function () {
agent.get('/movieCategoryAssocs/1').end(function (err, res) {
expect(err).to.equal(null)
expect(res.body.data.id).to.equal('1')
done()
})
})
})

it('should handle PATCH with less assocs', function (done) {
var agent = request(app)
agent
.patch('/movies/1')
.send({
data: {
id: 1,
type: 'movies',
attributes: {
name: 'The Shawshank Redemption'
},
relationships: {
categories: {
data: [{ type: 'categories', id: 1 }, { type: 'categories', id: 4 }]
}
}
}
})
.end(function (err, res) {
expect(err).to.equal(null)
agent.get('/movieCategoryAssocs').end(function (err, res) {
expect(err).to.equal(null)
expect(res.body.data.length).to.equal(2)

agent.get('/movies/1/categories').end(function (err, res) {
expect(err).to.equal(null)
expect(res.body.data.length).to.equal(2)
expect(res.body.data[1].attributes.name).to.equal('Comedy')
done()
})
})
})
})
})

function makeData () {
var createMovie = denodeifyCreate(Movie)
var createCategory = denodeifyCreate(Category)
var createAssoc = denodeifyCreate(MovieCategoryAssoc)

return RSVP.hash({
movie: createMovie({ name: 'The Shawshank Redemption' }),
categories: RSVP.all([
createCategory({ name: 'Crime' }),
createCategory({ name: 'Drama' }),
createCategory({ name: 'History' }),
createCategory({ name: 'Comedy' })
])
}).then(function (models) {
return RSVP.all([
createAssoc({ movieId: models.movie.id, categoryId: models.categories[0].id }),
createAssoc({ movieId: models.movie.id, categoryId: models.categories[2].id })
])
})

function denodeifyCreate (Model) {
return RSVP.denodeify(Model.create.bind(Model))
}
}

0 comments on commit 12f513c

Please sign in to comment.