Skip to content

Commit

Permalink
Merge pull request #28 from mcchrish/feature-graph-upsert
Browse files Browse the repository at this point in the history
Graph upsert and insert
  • Loading branch information
dekelev authored Aug 26, 2018
2 parents d6f683f + 3e81d0f commit 7fff580
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 23 deletions.
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,53 @@ app.service('/user-todos').get([1, 2]);
app.service('/user-todos').get({ userId: 1, todoId: 2 });
```
### Graph upsert
Arbitrary relation graphs can be upserted (insert + update + delete) using the upsertGraph method.
See [`examples`](https://vincit.github.io/objection.js/#graph-upserts) for a better explanation.
Runs on the `.update(id, data, params)` service method.
*The relation being upserted must also be present in `allowedEager` option and included in `$eager` query.*
#### Options
* **`allowedUpsert`** - relation expression to allow relations to be upserted along with update.
Defaults to `null`, meaning relations will not be automatically upserted unless specified here.
See [`allowUpsert`](https://vincit.github.io/objection.js/#allowupsert) documentation.
* **`upsertGraphOptions`** - See [`upsertGraphOptions`](https://vincit.github.io/objection.js/#upsertgraphoptions) documentation.
```js
app.use('/companies', service({
model: Company,
allowedEager: 'clients',
allowedUpsert: 'clients'
})

app.service('/companies').update(1, {
name: 'New Name',
clients: [{
id: 100,
name: 'Existing Client'
}, {
name: 'New Client'
}]
})
```
In the example above, we are updating the name of an existing company, along with adding a new client which is a relationship for companies. The client without the ID would be inserted and related. The client with the ID will just be updated (if there are any changes at all).
### Graph insert
Arbitrary relation graphs can be inserted using the insertGraph method.
Provides the ability to relate the inserted object with its associations.
Runs on the `.create(data, params)` service method.
*The relation being created must also be present in `allowedEager` option and included in `$eager` query.*
#### Options
* **`allowedInsert`** - relation expression to allow relations to be created along with insert.
Defaults to `null`, meaning relations will not be automatically created unless specified here.
See [`allowInsert`](https://vincit.github.io/objection.js/#allowinsert) documentation.
* **`insertGraphOptions`** - See [`insertGraphOptions`](https://vincit.github.io/objection.js/#insertgraphoptions) documentation.
## Complete Example
Expand Down
57 changes: 36 additions & 21 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ class Service {
this.allowedEager = options.allowedEager || '[]'
this.namedEagerFilters = options.namedEagerFilters
this.eagerFilters = options.eagerFilters
this.allowedInsert = options.allowedInsert
this.insertGraphOptions = options.insertGraphOptions
this.allowedUpsert = options.allowedUpsert
this.upsertGraphOptions = options.upsertGraphOptions
}

extend (obj) {
Expand Down Expand Up @@ -338,8 +342,15 @@ class Service {
}

_create (data, params) {
return this._createQuery(params)
.insert(data, this.id)
let q = this._createQuery(params)

if (this.allowedInsert) {
q.allowInsert(this.allowedInsert)
q.insertGraph(data, this.insertGraphOptions)
} else {
q.insert(data, this.id)
}
return q
.then(row => {
let id = null

Expand Down Expand Up @@ -399,28 +410,32 @@ class Service {
}
}

// NOTE (EK): Delete id field so we don't update it
if (Array.isArray(this.id)) {
for (const idKey of this.id) {
delete newObject[idKey]
if (!this.allowedUpsert) {
// NOTE (EK): Delete id field so we don't update it
if (Array.isArray(this.id)) {
for (const idKey of this.id) {
delete newObject[idKey]
}
} else {
delete newObject[this.id]
}
return this._createQuery(params)
.where(this.getIdsQuery(id))
.update(newObject)
.then(() => {
// NOTE (EK): Restore the id field so we can return it to the client
if (Array.isArray(this.id)) {
newObject = Object.assign({}, newObject, this.getIdsQuery(id))
} else {
newObject[this.id] = id
}

return newObject
})
} else {
delete newObject[this.id]
return this._createQuery(params)
.upsertGraphAndFetch(newObject, this.upsertGraphOptions)
}

return this._createQuery(params)
.where(this.getIdsQuery(id))
.update(newObject)
.then(() => {
// NOTE (EK): Restore the id field so we can return it to the client
if (Array.isArray(this.id)) {
newObject = Object.assign({}, newObject, this.getIdsQuery(id))
} else {
newObject[this.id] = id
}

return newObject
})
})
.catch(errorHandler)
}
Expand Down
28 changes: 28 additions & 0 deletions test/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Model } from 'objection'

export default class Client extends Model {
static tableName = 'clients'
static jsonSchema = {
type: 'object',
required: ['name'],

properties: {
id: { type: 'integer' },
companyId: { type: 'integer' },
name: { type: 'string' }
}
}

static get relationMappings () {
return {
company: {
relation: Model.BelongsToOneRelation,
modelClass: require('./company'),
join: {
from: 'clients.companyId',
to: 'companies.id'
}
}
}
}
}
8 changes: 8 additions & 0 deletions test/company.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ export default class Company extends Model {
from: 'companies.id',
to: 'employees.companyId'
}
},
clients: {
relation: Model.HasManyRelation,
modelClass: path.join(__dirname, '/client'),
join: {
from: 'companies.id',
to: 'clients.companyId'
}
}
}
}
93 changes: 91 additions & 2 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import PeopleRoomsCustomIdSeparator from './people-rooms-custom-id-separator'
import Company from './company'
import { Model } from 'objection'
import Employee from './employee'
import Client from './client'

const db = knex({
client: 'sqlite3',
Expand Down Expand Up @@ -68,7 +69,7 @@ const app = feathers()
model: Company,
id: 'id',
events: ['testing'],
allowedEager: 'ceos',
allowedEager: '[ceos, clients]',
namedEagerFilters: {
notSnoop (builder) {
return builder.whereNot('name', 'Snoop')
Expand All @@ -81,7 +82,9 @@ const app = feathers()
return builder.where('age', '<', '25')
}
}
]
],
allowedInsert: 'clients',
allowedUpsert: 'clients'
})
)
.use(
Expand All @@ -91,6 +94,13 @@ const app = feathers()
allowedEager: 'company'
})
)
.use(
'/clients',
service({
model: Client,
allowedEager: 'company'
})
)

let people = app.service('people')
let peopleRooms = app.service('people-rooms')
Expand Down Expand Up @@ -158,6 +168,16 @@ function clean (done) {
table.integer('companyId').references('companies.id')
table.string('name')
})
})
})
.then(() => {
return db.schema.dropTableIfExists('clients').then(() => {
return db.schema
.createTable('clients', table => {
table.increments('id')
table.integer('companyId').references('companies.id')
table.string('name')
})
.then(() => done())
})
})
Expand Down Expand Up @@ -457,6 +477,75 @@ describe('Feathers Objection Service', () => {
})
})

describe('Graph Insert Queries', () => {
before(async () => {
await companies.remove(null)
await companies
.create([
{
name: 'Google',
clients: [
{
name: 'Dan Davis'
},
{
name: 'Ken Patrick'
}
]
},
{
name: 'Apple'
}
])
})

it('allows insertGraph queries', () => {
return companies.find({ query: { $eager: 'clients' } }).then(data => {
expect(data[0].clients).to.be.an('array')
expect(data[0].clients).to.have.lengthOf(2)
})
})
})

describe('Graph Upsert Queries', () => {
before(async () => {
await companies.remove(null)
const [google] = await companies
.create([
{
name: 'Google',
clients: [
{
name: 'Dan Davis'
}
]
},
{
name: 'Apple'
}
], { query: { $eager: 'clients' } })

const newClients = (google.clients) ? google.clients.concat([{
name: 'Ken Patrick'
}]) : []

await companies
.update(google.id, {
id: google.id,
name: 'Alphabet',
clients: newClients
}, { query: { $eager: 'clients' } })
})

it('allows upsertGraph queries on update', () => {
return companies.find({ query: { $eager: 'clients' } }).then(data => {
expect(data[0].name).equal('Alphabet')
expect(data[0].clients).to.be.an('array')
expect(data[0].clients).to.have.lengthOf(2)
})
})
})

describe('$like method', () => {
beforeEach(async () => {
await people
Expand Down

0 comments on commit 7fff580

Please sign in to comment.